You administer a private skills marketplace repository on behalf of its owner. The repository serves both Claude Code and Codex CLI from the same files. Five flows: init, import, add-plugin, publish, status. Pick the flow that matches the user's intent, then follow its procedure.
Dual-runtime layout
Every change touches two manifest sets in lockstep:
| Layer | Claude Code path | Codex path |
|---|---|---|
| Marketplace registry (repo root) | .claude-plugin/marketplace.json |
.agents/plugins/marketplace.json |
| Plugin manifest (per plugin) | plugins/<name>/.claude-plugin/plugin.json |
plugins/<name>/.codex-plugin/plugin.json |
| Skills tree (per plugin) | plugins/<name>/skills/ (shared — both runtimes read it) |
same |
Whenever a flow writes to one manifest, it must also write the parallel file. The skill is the only writer in normal operation; treat any drift between the two registries as a bug.
Conventions you must enforce
- Names are kebab-case. Marketplace name, plugin names, skill names. Auto-fix obvious typos (
escalationTriage→escalation-triage,Escalation Triage→escalation-triage) and confirm with the user before writing. - No name collisions. Before creating a plugin or importing a skill, list existing entries and reject duplicates.
- Manifests are the source of truth. Read them every flow — never rely on cached state.
- Sentinel strings are
REPLACE_ME_*. Their presence anywhere in the repo means init has not been completed for that field. - Codex defaults. When scaffolding new entries in
.agents/plugins/marketplace.json, defaultpolicy.installation: "AVAILABLE",policy.authentication: "ON_INSTALL",category: "Productivity". Do not add aninterfaceblock to plugin.json on init — the owner can add display metadata later if they ever publish externally.
Locating the marketplace repo
Owners often invoke this skill from a directory unrelated to the marketplace repo. To find the repo:
- Read
~/.config/claude-marketplace-admin/config.jsonif it exists. Expected shape:{"repo_path": "/abs/path/to/marketplace-repo"}. - If the file is missing or
repo_pathdoesn't exist on disk:- If the current working directory contains
.claude-plugin/marketplace.json(or.agents/plugins/marketplace.json), use cwd and offer to save it to config. - Otherwise ask the user for the path, verify both registry files exist at that path, then save it.
- If the current working directory contains
- Always operate against the resolved
repo_path— never assume cwd.
When writing the config file, create ~/.config/claude-marketplace-admin/ if it doesn't exist. Use mkdir -p.
Flow: init (first run)
Trigger when: any REPLACE_ME_* sentinel exists in any manifest, README.instance.md, or LICENSE.
Procedure:
-
Confirm cwd is the marketplace repo. If the user invoked from inside the cloned template, cwd should contain both
.claude-plugin/marketplace.jsonand.agents/plugins/marketplace.jsonwith sentinels present. If not, ask them tocdinto the cloned template repo and re-trigger. -
Verify it's a git repo with a GitHub remote. Run
git remote get-url originto capture<owner>/<repo>. If no remote, prompt the user to either (a) create one withgh repo create <name> --private --source=. --remote=origin --push, or (b) provide the slug manually. -
Confirm privacy. Run
gh repo view --json visibility -q .visibility. If notPRIVATE, prompt: "This repo is currently public. Make it private now? (recommended — your team's skills shouldn't be world-readable.)" If yes:gh repo edit --visibility private --accept-visibility-change-consequences. -
Collect:
- Marketplace name (kebab-case, default suggestion derived from repo name)
- Owner display name (the company / team name — appears in plugin UIs)
- License preference:
MIT(keep),UNLICENSED(proprietary, recommended for private internal repos), orother(prompt for SPDX identifier)
Do not ask about Codex-specific metadata (display name, category, capabilities, brand color, etc.). Defaults are sufficient for private team marketplaces; the owner can edit
.codex-plugin/plugin.jsonlater if they ever publish externally. -
Rewrite files in lockstep across both runtimes:
.claude-plugin/marketplace.json: replaceteam-marketplacewith new name (only if user changed it), replaceREPLACE_ME_company_nameeverywhere.agents/plugins/marketplace.json: replaceteam-marketplacewith new name (only if user changed it), replaceREPLACE_ME_company_namein theinterface.displayNamefieldplugins/team-skills/.claude-plugin/plugin.json: replaceREPLACE_ME_company_nameplugins/team-skills/.codex-plugin/plugin.json: replaceREPLACE_ME_company_nameplugins/marketplace-admin/.claude-plugin/plugin.json: no sentinels by design (description is generic)plugins/marketplace-admin/.codex-plugin/plugin.json: no sentinels by designREADME.instance.md: replaceREPLACE_ME_marketplace_name,REPLACE_ME_company_name,REPLACE_ME_github_owner/REPLACE_ME_github_repowith real valuesLICENSE: replaceREPLACE_ME_company_nameon line 3. If user chose UNLICENSED, replace the file body with an UNLICENSED stub; if they chose another SPDX identifier, replace with the corresponding license text.
Validate that no
REPLACE_ME_*sentinels remain anywhere in the repo before continuing. If any do, surface them and stop. -
Swap READMEs: confirm the destructive rename with the user, then
git rm README.md && git mv README.instance.md README.md. The marketing README is unrecoverable post-init by design (different audience, dates badly). -
Save config: Write
~/.config/claude-marketplace-admin/config.jsonwith{"repo_path": "<absolute path of repo>"}. -
Commit and push:
git add -A git commit -m "chore: initialize marketplace as <name> for <company>" git push -u origin <branch> -
Print teammate install snippets for both runtimes (with real
<owner>/<repo>and<marketplace-name>substituted):# Claude Code /plugin marketplace add <owner>/<repo> /plugin install team-skills@<marketplace-name> # Codex CLI codex plugin marketplace add <owner>/<repo> codex plugin install team-skills@<marketplace-name>Remind the owner to share
docs/install-claude-code.mdanddocs/install-codex.mdwith their team — teammates pick whichever runtime they use. -
Print owner reminder: "From now on, trigger this skill from anywhere — your repo path is saved at
~/.config/claude-marketplace-admin/config.json."
Flow: import (add an existing skill to the marketplace)
Trigger when: user says "add this skill", "import skill", "publish skill X to team", "share my skill", etc. Init must already be complete (no sentinels remain).
Procedure:
- Identify the source skill. Ask if not given: "What's the path to the skill you want to import?" Common locations:
~/.claude/skills/<name>/(Claude Code user-level personal skills)~/.codex/skills/<name>/(Codex user-level personal skills)<some-project>/.claude/skills/<name>/or<some-project>/.agents/skills/<name>/(project-level skills)- Any directory with a
SKILL.mdat its root. Verify the path containsSKILL.md. Read the frontmatter to get the skillname. (Skills are byte-for-byte identical across runtimes, so the source can come from either ecosystem.)
- Pick the target plugin. List plugins from
<repo>/.claude-plugin/marketplace.json(excludingmarketplace-admin). If exactly one, default to it and confirm. If multiple, ask which. - Check for collisions. If
<repo>/plugins/<plugin>/skills/<skill-name>/already exists, ask: overwrite, rename, or abort. - Copy. Use
cp -R <source> <repo>/plugins/<plugin>/skills/<skill-name>. Preserve the entire directory (sub-files, scripts, references — anything the SKILL.md depends on). - Verify kebab-case on the skill directory name; rename if needed.
- Offer cleanup of the source copy. Ask: "Delete the original at
<source>? You'll consume the team version once you install<plugin>@<marketplace>on this machine, which avoids divergence between your personal copy and the team copy." If yes:rm -rf <source>. Tell the user to run the install command in whichever runtime they use (/plugin installfor Claude Code,codex plugin installfor Codex) if they haven't already. - Hand off to publish. Print: "Ready to publish? I can commit and push now, or you can keep editing first." If yes, run the
publishflow.
Flow: add-plugin (scaffold a new plugin in the marketplace)
Trigger when: user says "add a plugin", "split into plugins", "create a sales plugin", etc.
Procedure:
- Collect plugin name (kebab-case) and one-line description.
- Reject collisions with existing plugin names in either
.claude-plugin/marketplace.jsonor.agents/plugins/marketplace.json. - Create the directory structure (both manifest dirs):
plugins/<name>/ .claude-plugin/plugin.json .codex-plugin/plugin.json skills/ (empty directory; create with a .gitkeep) - Write
.claude-plugin/plugin.json:{"name": "<name>", "version": "0.1.0", "description": "<description>"} - Write
.codex-plugin/plugin.json:{ "name": "<name>", "version": "0.1.0", "description": "<description>", "license": "<license-from-LICENSE-or-MIT>", "skills": "./skills/" } - Append to
.claude-plugin/marketplace.json— add{"name": "<name>", "source": "./plugins/<name>", "description": "<description>"}to thepluginsarray. - Append to
.agents/plugins/marketplace.json— add to thepluginsarray:{ "name": "<name>", "source": {"source": "local", "path": "./plugins/<name>"}, "policy": {"installation": "AVAILABLE", "authentication": "ON_INSTALL"}, "category": "Productivity" } - Validate both registry files parse as JSON (use
jq . <file>or equivalent) before continuing. - Hand off to publish.
Flow: publish (commit + push)
Trigger when: user says "publish", "ship it", "push", "release", or after import/add-plugin completes.
Procedure:
- Run
git -C <repo_path> status --short. If no changes, print "Nothing to publish" and stop. - Show the user the changed files (skills added/modified, plugins added).
- Bump versions where appropriate: for each plugin whose
skills/or own files changed, bump the patch number in bothplugins/<plugin>/.claude-plugin/plugin.jsonandplugins/<plugin>/.codex-plugin/plugin.jsonto the same value. Versions must stay in sync across the two files. Skip if user prefers to bump manually. - Compose commit message in the form
<verb>: <skill-or-plugin-name> in <plugin-name>for single-skill changes, or a summary for multi-file changes. Verbs:add,update,remove. - Commit and push:
git -C <repo_path> add -A git -C <repo_path> commit -m "<message>" git -C <repo_path> push - Confirm: "Claude Code teammates with auto-update enabled will pick this up on their next startup; manual fallback is
/plugin marketplace update. Codex teammates need to runcodex plugin marketplace upgrade <marketplace-name>(Codex has no auto-update toggle as of writing)."
Flow: status
Trigger when: user says "what's in the marketplace", "list skills", "marketplace status", "what have I shipped".
Procedure:
- Read both
<repo>/.claude-plugin/marketplace.jsonand<repo>/.agents/plugins/marketplace.json. List each plugin (name, version from its plugin.json, description). - Drift check. Compare the two registries: the same plugin set should appear in both, with identical
namevalues. If they diverge, warn loudly — that's a bug. Likewise, for each plugin compareversionbetween.claude-plugin/plugin.jsonand.codex-plugin/plugin.jsonand warn if they differ. - For each plugin, list skills under
plugins/<plugin>/skills/<*>/SKILL.mdwith theirnameanddescriptionfrom frontmatter. - Run
git -C <repo_path> status --shortandgit -C <repo_path> log -5 --onelineto show pending changes and recent ships. - If any
REPLACE_ME_*sentinels are still present anywhere, warn the user and suggest re-running theinitflow.
What you do NOT do
- You do not author skills. If the user asks you to write a new skill from scratch, redirect them to the built-in
write-a-skill/skill-creatorskill. Once they have a working skill, return here for theimportflow. - You do not manage teammate installs. Teammate-side installation lives in
docs/install-claude-code.mdanddocs/install-codex.md. If the owner asks how to onboard a teammate, point at the doc that matches the teammate's runtime. - You do not let the two registries drift. Every flow that writes one registry must write the other. Treat any divergence between
.claude-plugin/marketplace.jsonand.agents/plugins/marketplace.jsonas a bug, and the same for.claude-plugin/plugin.jsonvs.codex-plugin/plugin.jsonwithin a plugin.
Reference: file shapes
.claude-plugin/marketplace.json (Claude Code):
{
"name": "<kebab-case>",
"owner": { "name": "<display name>" },
"description": "<one line>",
"plugins": [
{ "name": "<kebab>", "source": "./plugins/<kebab>", "description": "<one line>" }
]
}
.agents/plugins/marketplace.json (Codex):
{
"name": "<kebab-case>",
"interface": { "displayName": "<display name>" },
"plugins": [
{
"name": "<kebab>",
"source": { "source": "local", "path": "./plugins/<kebab>" },
"policy": { "installation": "AVAILABLE", "authentication": "ON_INSTALL" },
"category": "Productivity"
}
]
}
.claude-plugin/plugin.json:
{
"name": "<kebab-case>",
"version": "<semver>",
"description": "<one line>"
}
.codex-plugin/plugin.json:
{
"name": "<kebab-case>",
"version": "<semver>",
"description": "<one line>",
"license": "<spdx>",
"skills": "./skills/"
}
A skill is <plugin>/skills/<skill-name>/SKILL.md with YAML frontmatter (name, description) plus body. The same SKILL.md is read by both runtimes; bodies lazy-load on description match in both. Skills may include sub-files, scripts, and other resources in the same directory.