universal-plugin 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/.cursor-plugin/plugin.json +1 -1
  4. package/bin/upx.mjs +12 -5
  5. package/com.github.copilot/agents/agentskills-specialist.agent.md +132 -0
  6. package/dist/cli.mjs +5806 -261
  7. package/governances/plugin-design.md +14 -0
  8. package/package.json +4 -1
  9. package/plugin.json +1 -1
  10. package/readme.md +12 -5
  11. package/schema/README.md +10 -0
  12. package/schema/claude-code-marketplace.json +1939 -0
  13. package/schema/extension.schema.json +842 -0
  14. package/skills/adopt-upx/README.md +4 -4
  15. package/skills/adopt-upx/SKILL.md +4 -4
  16. package/skills/doctor/README.md +3 -4
  17. package/skills/doctor/SKILL.md +44 -14
  18. package/skills/doctor/scripts/doctor.mjs +145 -37
  19. package/skills/{init → init-universal-plugin}/README.md +5 -2
  20. package/skills/{init → init-universal-plugin}/SKILL.md +38 -13
  21. package/skills/init-universal-plugin/references/adopt.md +206 -0
  22. package/skills/{init → init-universal-plugin}/references/detection.md +8 -2
  23. package/skills/{init → init-universal-plugin}/references/standard.md +5 -1
  24. package/skills/{init → init-universal-plugin}/references/vendors/claude-code.md +1 -1
  25. package/skills/init-universal-plugin/references/vendors/copilot-cli.md +201 -0
  26. package/skills/marketplace/SKILL.md +1 -1
  27. package/skills/migrate-plugin/SKILL.md +140 -26
  28. package/skills/migrate-plugin/evals/evals.json +6 -0
  29. package/skills/migrate-plugin/evals/trigger-queries.json +18 -0
  30. package/skills/publish-plugin/README.md +35 -0
  31. package/skills/publish-plugin/SKILL.md +118 -19
  32. package/skills/publish-plugin/evals/evals.json +12 -0
  33. package/skills/remove-plugin/README.md +1 -1
  34. package/skills/remove-plugin/SKILL.md +2 -2
  35. package/skills/version/SKILL.md +2 -2
  36. package/dist/run.mjs +0 -271
  37. package/skills/init/references/adopt.md +0 -118
  38. package/skills/init/references/vendors/copilot-cli.md +0 -53
  39. /package/skills/{init → init-universal-plugin}/assets/templates/agent.md +0 -0
  40. /package/skills/{init → init-universal-plugin}/assets/templates/command.md +0 -0
  41. /package/skills/{init → init-universal-plugin}/assets/templates/hooks.json +0 -0
  42. /package/skills/{init → init-universal-plugin}/assets/templates/plugin.json +0 -0
  43. /package/skills/{init → init-universal-plugin}/assets/templates/setup-command.md +0 -0
  44. /package/skills/{init → init-universal-plugin}/assets/templates/skill.md +0 -0
  45. /package/skills/{init → init-universal-plugin}/references/create.md +0 -0
  46. /package/skills/{init → init-universal-plugin}/references/frontmatter.md +0 -0
  47. /package/skills/{init → init-universal-plugin}/references/update.md +0 -0
  48. /package/skills/{init → init-universal-plugin}/references/vendors/codex.md +0 -0
  49. /package/skills/{init → init-universal-plugin}/references/vendors/cursor.md +0 -0
  50. /package/skills/{init → init-universal-plugin}/scripts/init.mjs +0 -0
@@ -1,15 +1,17 @@
1
1
  ---
2
2
  name: migrate-plugin
3
- description: "Move a repository-root universal agent plugin into its npm package so the package distributes its manifest, vendor manifests, skills, and agents. Use this skill when packaging an existing plugin for npm, relocating a top-level plugin into a package, or fixing a package that omits plugin assets, even if the user says only 'ship this plugin through npm' or 'move the plugin into the package.'"
3
+ description: "Move a repository-root or sibling-workspace universal agent plugin into its npm package so the package distributes its manifest, vendor manifests, skills, and agents, and bundle the package's CLI with tsdown so it runs from an installed plugin directory with no node_modules. Use this skill when packaging an existing plugin for npm, relocating a top-level or plugins/<name> plugin into a package, fixing a package that omits plugin assets, or making a plugin's bundled CLI self-contained, even if the user says only 'ship this plugin through npm', 'move the plugin into the package', or 'the CLI can't find its dependencies when installed as a plugin.'"
4
4
  ---
5
5
 
6
6
  # Migrate Universal Plugin to npm
7
7
 
8
8
  ## Scope
9
9
 
10
- Move one existing universal plugin from a repository root into its owning npm
11
- package. The package becomes the plugin root and must contain every runtime
12
- artifact it needs after installation.
10
+ Move one existing universal plugin into its owning npm package. The plugin may
11
+ sit at the repository root or in its own workspace member such as
12
+ `plugins/<name>/`. The package becomes the plugin root and must contain every
13
+ runtime artifact it needs after installation — including a CLI that runs
14
+ without `node_modules`.
13
15
 
14
16
  This skill does not create a plugin from scratch, publish a package, or move
15
17
  project-local agent configuration unrelated to the plugin.
@@ -22,34 +24,44 @@ Before changing manifest fields or component compatibility behavior, consult the
22
24
  and its versioned specification: it is the canonical reference for the current
23
25
  standard. Keep an existing schema version pinned unless the user explicitly
24
26
  requests a standards upgrade.
25
- Inventory these root-level plugin assets when they exist:
27
+ Inventory these plugin assets in the source root when they exist:
26
28
 
27
29
  - `plugin.json`
28
30
  - vendor manifest directories for Claude Code, Cursor, Codex, and Copilot CLI
31
+ - `.plugin/` (for example a `pins.json` that skills read at runtime)
29
32
  - `skills/`, `agents/`, `commands/`, `hooks/`, `rules/`, `output-styles/`
30
33
  - `.mcp.json`, `.lsp.json`, and plugin-owned `assets/`
31
34
 
35
+ Also inventory what exists only to host the plugin in its old place: a
36
+ workspace-member `package.json` (for example a private `@scope/<name>-plugin`),
37
+ and the `pnpm-workspace.yaml` or `workspaces` glob that includes it.
38
+
32
39
  Leave project configuration in place unless it is required solely to maintain
33
40
  the moved plugin. In particular, do not move `.agents/skills/`, plans, or a
34
- marketplace catalog such as `.claude/marketplace.json`.
41
+ marketplace catalog such as `.claude-plugin/marketplace.json`.
35
42
 
36
43
  Before modifying files, report the exact source-to-destination mapping and any
37
- destination collisions. Ask for confirmation if a destination contains a
38
- different file that would be overwritten.
44
+ destination collisions. A `readme.md` on both sides is the usual one: merge the
45
+ plugin readme into the package readme as its own section rather than
46
+ overwriting either. Ask for confirmation if a destination contains a different
47
+ file that would be overwritten.
39
48
 
40
49
  ## 2. Move the plugin root
41
50
 
42
- Move each inventoried asset under the package directory, preserving its path.
43
- A package receives these relative paths:
51
+ Move each inventoried asset under the package directory with `git mv`,
52
+ preserving its relative path, so history follows the files:
44
53
 
45
54
  ```text
46
55
  the canonical manifest
47
56
  vendor manifests
57
+ .plugin/
48
58
  skills
49
59
  agents
50
60
  ```
51
61
 
52
62
  The source root must no longer retain duplicate distributable plugin assets.
63
+ When the source was its own workspace member, delete its `package.json`, drop
64
+ the now-empty workspace glob, and reinstall so the lockfile loses the member.
53
65
  Do not move a project-local skill merely because it is under `.agents/skills/`.
54
66
 
55
67
  ## 3. Configure npm packaging
@@ -68,6 +80,7 @@ configuration is:
68
80
  ".claude-plugin",
69
81
  ".cursor-plugin",
70
82
  ".codex-plugin",
83
+ ".plugin",
71
84
  "skills",
72
85
  "agents"
73
86
  ]
@@ -77,30 +90,131 @@ configuration is:
77
90
  Retain existing package entries such as `bin`, `dist`, and `governances`.
78
91
  Never replace the allowlist wholesale.
79
92
 
80
- ## 4. Preserve release synchronization
81
-
82
- If the repository runs `universal-plugin publish sync-version`, move its
83
- plugin-specific configuration beside the new `plugin.json` and change
84
- `packagePath` to `"."`. Update the root release script to run the command from
85
- the destination package, for example:
86
-
87
- ```sh
88
- pnpm exec universal-plugin publish sync-version --root .
93
+ ## 4. Bundle the CLI with tsdown
94
+
95
+ An installed agent plugin is a copy of a source checkout, not an npm install,
96
+ so its directory has no reliable `node_modules`. A CLI whose build leaves
97
+ dependencies external fails there. When the package ships a CLI, build it so
98
+ every runtime dependency is inlined into the CLI entry, while any library entry
99
+ keeps its dependencies external.
100
+
101
+ Split `tsdown.config.ts` into two configs that share an `outDir`:
102
+
103
+ ```ts
104
+ import { defineConfig } from 'tsdown'
105
+
106
+ // Two configs, because the entries want opposite dependency treatment. They share an
107
+ // `outDir`, which is safe: tsdown hoists `clean` and runs it once across every config
108
+ // before any build writes, so neither wipes the other.
109
+ const shared = {
110
+ outDir: 'dist',
111
+ format: 'esm',
112
+ platform: 'node',
113
+ clean: true,
114
+ } as const
115
+
116
+ export default defineConfig([
117
+ {
118
+ // Library entry. Dependencies stay EXTERNAL: their types surface in the public
119
+ // `.d.ts`, and a consumer that also uses them must share one copy.
120
+ ...shared,
121
+ entry: { index: 'src/index.ts' },
122
+ dts: true,
123
+ },
124
+ {
125
+ // CLI entry. Every runtime dependency is inlined so it runs with no node_modules.
126
+ ...shared,
127
+ entry: { cli: 'src/cli.ts' },
128
+ dts: false,
129
+ deps: {
130
+ alwaysBundle: [/^commander(\/|$)/, /^some-dep(\/|$)/],
131
+ onlyBundle: false,
132
+ },
133
+ },
134
+ ])
89
135
  ```
90
136
 
137
+ Rules for the CLI config:
138
+
139
+ - **`alwaysBundle` lists exactly the package's own `dependencies`.** Those are
140
+ the only ones tsdown externalizes by default; their transitive dependencies
141
+ are inlined automatically. Use `^name(\/|$)` regexes so subpath imports
142
+ (`some-dep/worktree`) are caught too.
143
+ - **Omit a dependency consumed only through `import type`.** TypeScript strips
144
+ it before bundling, and a package whose entry is raw `.ts` cannot be bundled.
145
+ - **`onlyBundle: false`** silences the "bundled a dependency" warnings that are
146
+ the point of this config.
147
+ - **Keep the existing output extensions.** If `bin` and `exports` already name
148
+ `.mjs`, leave tsdown's default; if they name `.js`, add
149
+ `outExtensions: () => ({ js: '.js' })` to `shared`. Never move published
150
+ paths as a side effect.
151
+ - **Keep the entry's invocation contract.** When `bin` points straight at the
152
+ CLI output, `src/cli.ts` must keep its `#!/usr/bin/env node` shebang (tsdown
153
+ preserves it and sets the execute bit). When `bin` is a wrapper that imports
154
+ the output and calls an exported function, leave the wrapper as is.
155
+ - **Resolve runtime files relative to the output.** A CLI that reads
156
+ `../package.json` through `import.meta.url` still works only if `src/` and
157
+ `dist/` sit at the same depth and `package.json` ships — confirm both.
158
+
159
+ Verify the bundle, not just the build:
160
+
161
+ 1. `grep -En "from ['\"](dep-a|dep-b)" dist/cli.*` prints nothing, while the
162
+ library output still imports them.
163
+ 2. Pack into a scratch directory, extract, confirm the extracted `package/` has
164
+ no `node_modules`, then run the CLI through its `bin` path: `--version` and
165
+ one read-only command. Do not run it from the workspace, where hoisted
166
+ `node_modules` hides a missing inline.
167
+
168
+ Skills that invoke the CLI through `npx <cli>@<version>` can then prefer the
169
+ shipped CLI. That changes skill behavior and often a frozen spec, so offer it
170
+ as a follow-up instead of doing it inside this migration.
171
+
172
+ ## 5. Preserve release synchronization
173
+
174
+ Point every version and build step at the new plugin root:
175
+
176
+ - Set the manifest extension's `packagePath` to `"."`.
177
+ - `universal-plugin publish sync-version --root <pkg>` reads `packagePath` from
178
+ `<pkg>/.agents/universal-plugin.json`, not from the manifest extension. To use
179
+ it, create that file with `{ "packagePath": "." }`. If the repository instead
180
+ runs its own sync script, repoint that script's manifest path and leave it.
181
+ - Change every `universal-plugin plugin build --root <old>` in `package.json`
182
+ scripts to the package directory, then run it and confirm it reports the
183
+ vendor manifests as built and the catalog as unchanged or refolded.
184
+ - Repoint a local-directory `source` in the repository's marketplace catalog to
185
+ the package directory.
186
+
91
187
  Update any checked-in skill lockfile that records an absolute source path for a
92
188
  moved skill. Do not modify unrelated agent configuration.
93
189
 
94
- ## 5. Verify the result
190
+ ## 6. Repoint tooling and prose
191
+
192
+ Search the repository for the old plugin path and update live references:
95
193
 
96
- 1. Run the package's focused tests and typecheck/lint commands.
194
+ - lint and format excludes for generated vendor manifests (for example
195
+ `biome.json` `files.includes` negations), knip workspaces, turbo filters
196
+ - `AGENTS.md`, `CONTRIBUTING.md`, readmes, docs pages, and spec frontmatter
197
+ such as `project-path`
198
+ - guards scoped to the package directory, such as a vocabulary or lint check —
199
+ confirm moved skills did not enter a scope that rejects their wording
200
+
201
+ Leave historical records as written: changelogs, ADRs, research notes, and
202
+ decision or gate ledgers describe the layout at the time.
203
+
204
+ ## 7. Verify the result
205
+
206
+ 1. Run the repository's full verification (lint, build, typecheck, test).
97
207
  2. Run `npm pack --dry-run` from the destination package. Confirm it lists
98
- `plugin.json`, every required vendor manifest, `skills/`, and `agents/`.
99
- 3. If the manifest declares build targets, run `universal-plugin plugin build`
100
- from the destination package and confirm generated paths stay inside it.
101
- 4. Search the repository for stale references to the old top-level asset paths.
208
+ `plugin.json`, every required vendor manifest, `.plugin/` when present,
209
+ `skills/`, `agents/`, and the bundled CLI.
210
+ 3. Confirm step 4's no-`node_modules` run passed.
211
+ 4. Search the repository again for stale references to the old asset paths.
102
212
  5. Review the diff to confirm no project-local `.agents` content or marketplace
103
- catalog was included in the package.
213
+ catalog was moved into the package.
214
+ 6. Add a changeset for the package when the repository uses changesets.
215
+
216
+ Commit the move and the CLI bundling as separate commits: each is revertable on
217
+ its own.
104
218
 
105
219
  Report the package path, files added to the npm tarball, checks run, and any
106
220
  intentionally retained root-local files.
@@ -6,6 +6,12 @@
6
6
  "prompt": "Move the top-level universal plugin into packages/universal-plugin so npm distributes it.",
7
7
  "expected_output": "Inventories and relocates only distributable plugin assets, adds them to package.json files, updates version synchronization, verifies npm pack --dry-run, and preserves project-local .agents content.",
8
8
  "files": []
9
+ },
10
+ {
11
+ "id": 2,
12
+ "prompt": "Convert plugins/my-tool into an npm-distributed plugin inside packages/my-tool. The package already has a CLI built with tsdown that depends on commander.",
13
+ "expected_output": "Moves the plugin assets with git mv, merges colliding readmes instead of overwriting, removes the old workspace member and its glob, extends package.json files, sets packagePath to '.', repoints plugin build and the marketplace source, splits tsdown.config.ts so the CLI entry inlines commander via alwaysBundle while the library entry keeps it external, and proves the CLI runs from an extracted tarball with no node_modules.",
14
+ "files": []
9
15
  }
10
16
  ]
11
17
  }
@@ -19,6 +19,24 @@
19
19
  "should_trigger": true,
20
20
  "split": "val"
21
21
  },
22
+ {
23
+ "id": 6,
24
+ "query": "Convert plugins/cyberlegion to an npm distributed plugin in packages/cyberlegion.",
25
+ "should_trigger": true,
26
+ "split": "train"
27
+ },
28
+ {
29
+ "id": 7,
30
+ "query": "Our plugin's CLI crashes with 'Cannot find package commander' when Claude Code installs the plugin. Bundle it so it runs without node_modules.",
31
+ "should_trigger": true,
32
+ "split": "val"
33
+ },
34
+ {
35
+ "id": 8,
36
+ "query": "Switch this library's build from tsup to tsdown.",
37
+ "should_trigger": false,
38
+ "split": "train"
39
+ },
22
40
  {
23
41
  "id": 4,
24
42
  "query": "Upgrade all npx universal-plugin pins to version 0.3.0.",
@@ -0,0 +1,35 @@
1
+ # publish-plugin skill
2
+
3
+ List an already-packaged plugin in a shared marketplace repository by opening a pull request against
4
+ its vendor catalogs.
5
+
6
+ ## What it does
7
+
8
+ Four steps: locate the plugin and the marketplace, validate the plugin is ready, prepare the entry
9
+ for each vendor catalog that exists, then open one PR that updates all of them together.
10
+
11
+ ## Two repos, either one could be the cwd
12
+
13
+ The plugin being published and the marketplace it is going into are different repos, and the current
14
+ working directory could be either. The skill checks the cwd's remote against the target marketplace
15
+ repo (`cyberuni/marketplace` by default) before doing anything else:
16
+
17
+ - cwd is the plugin → the marketplace repo gets cloned or forked, as it always did.
18
+ - cwd is already the marketplace → work happens in place; the plugin's metadata is read from wherever
19
+ the user points to instead (a local path, or a URL cloned to a scratch directory).
20
+
21
+ Every check and every entry field in Steps 1–2 reads from the plugin location this step finds, never
22
+ from the cwd by assumption. Getting this wrong means the marketplace entry ends up pointing at the
23
+ marketplace repo's own URL instead of the plugin's.
24
+
25
+ ## Why this is not the `marketplace` skill
26
+
27
+ `marketplace` generates a repository's own local catalog — no submission, no shared listing, no PR.
28
+ This skill is the opposite case: putting a plugin into someone else's shared catalog, which only a
29
+ pull request can do. They compose rather than overlap: a plugin can have both a local catalog for
30
+ `git clone`-and-go installs and a listing here.
31
+
32
+ ## References
33
+
34
+ - [Spec](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/spec.md)
35
+ - `references/vendor-requirements.md` — required fields and hook casing rules per runtime
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: publish-plugin
3
- description: Use this skill whenever the user wants to publish, release, submit, or share a plugin to the universal plugin marketplace so it works across Claude Code, Cursor, Codex, and GitHub Copilot CLI. Trigger on phrases like "publish my plugin", "submit to marketplace", "release plugin", "list my plugin", "share my plugin", or any mention of getting a plugin into the universal registry. The plugin should already be packaged before using this skill.
3
+ description: Use this skill whenever the user wants to publish, release, submit, or share a plugin to the universal plugin marketplace so it works across Claude Code, Cursor, Codex, and GitHub Copilot CLI. Trigger on phrases like "publish my plugin", "submit to marketplace", "release plugin", "list my plugin", "share my plugin", or any mention of getting a plugin into the universal registry. Works whether the current repo is the plugin being published or the marketplace it is going into. The plugin should already be packaged before using this skill.
4
4
  ---
5
5
 
6
6
  # Publish Universal Plugin
@@ -15,8 +15,9 @@ two compose, and neither replaces the other.
15
15
 
16
16
  ## Overview
17
17
 
18
- Publishing has three steps:
18
+ Publishing has four steps:
19
19
 
20
+ 0. **Locate** — work out which repo is which, and where the plugin and the marketplace each live
20
21
  1. **Pre-flight** — validate the plugin is ready
21
22
  2. **Prepare entries** — build the entry for each vendor marketplace file that exists
22
23
  3. **Submit PR** — one PR that updates all relevant marketplace files
@@ -25,9 +26,48 @@ Work through each step in order. Do not skip pre-flight even if the user says th
25
26
 
26
27
  ---
27
28
 
29
+ ## Step 0: Locate the plugin and the marketplace
30
+
31
+ Two repos are in play — the plugin being published and the marketplace it is going into — and the
32
+ current working directory could be either one. Do not assume it is the plugin repo just because that
33
+ is the common case.
34
+
35
+ ### 0a. Identify the marketplace repo
36
+
37
+ Default to `cyberuni/marketplace` unless the user names another one.
38
+
39
+ ### 0b. Identify which repo the cwd is
40
+
41
+ ```bash
42
+ gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null
43
+ ```
44
+
45
+ Compare the result against the marketplace repo from 0a.
46
+
47
+ - **Matches the marketplace repo** → cwd *is* the marketplace. The plugin lives elsewhere: ask the
48
+ user for its location if they have not given one — a local path, or a git URL to clone. Resolve a
49
+ git URL with `gh repo clone <url> /tmp/<plugin-name>` (or a scratch directory) so pre-flight has a
50
+ checkout to read; do not guess metadata from the URL alone.
51
+ - **Does not match, or `gh repo view` fails** (no `origin`, not a GitHub repo, detached checkout) →
52
+ cwd is the plugin repo. This is the default path described in Steps 1–3 below.
53
+ - **User states their role explicitly** ("I'm already in the marketplace repo", "publish the plugin
54
+ at ../foo") — trust that over the remote check; it exists to catch the unstated case, not to
55
+ override what the user already told you.
56
+
57
+ Name two locations going forward and keep them distinct in your own reasoning and in what you tell
58
+ the user:
59
+
60
+ - **plugin location** — the directory holding the plugin's root `plugin.json`, wherever it is
61
+ - **marketplace location** — the directory holding the marketplace's vendor catalog files
62
+
63
+ Every pre-flight check and entry field in Steps 1–2 reads from the plugin location, not the cwd.
64
+ Step 3 branches on whether the marketplace location is already the cwd.
65
+
66
+ ---
67
+
28
68
  ## Step 1: Pre-flight validation
29
69
 
30
- Run these checks against the plugin directory. Stop and fix any failures before continuing.
70
+ Run these checks against the **plugin location** from Step 0. Stop and fix any failures before continuing.
31
71
 
32
72
  ### 1a. Required metadata
33
73
 
@@ -74,7 +114,9 @@ List what passed and what failed. Do not proceed to Step 2 until all checks pass
74
114
 
75
115
  ### 2a. Detect which vendor marketplace files exist
76
116
 
77
- Clone or check out the marketplace repo locally, then look for vendor marketplace files:
117
+ Get the **marketplace location** locally if it is not already the cwd — see Step 3a, which this step
118
+ can run ahead of when you need the files present before drafting entries. Then look for vendor
119
+ marketplace files:
78
120
 
79
121
  | File | Runtime |
80
122
  |---|---|
@@ -83,17 +125,39 @@ Clone or check out the marketplace repo locally, then look for vendor marketplac
83
125
 
84
126
  Only prepare entries for files that actually exist — do not create new vendor marketplace files.
85
127
 
86
- ### 2b. Claude Code entry (`.claude-plugin/marketplace.json`)
128
+ ### 2b. Determine the source/distribution type
129
+
130
+ Before writing any entry, decide how the plugin is actually distributed — do not default to a
131
+ root-clone `url`. Read the signals from the plugin's own repository, in this order:
132
+
133
+ 1. **`npm`** — the CLI reports a `packagePath`, and `<packagePath>/package.json` is not
134
+ `"private": true`. The plugin ships as an npm package. Ask the CLI rather than reading
135
+ `.agents/universal-plugin.json` yourself; the path it prints is relative to the plugin root:
87
136
 
88
- The richer format. Use this shape:
137
+ ```bash
138
+ npx universal-plugin config get --key packagePath --format json --root <plugin-root>
139
+ ```
140
+
141
+ `null` means no npm package is declared.
142
+ 2. **`git-subdir`** — the plugin's `plugin.json` (or its vendor manifests) live below the
143
+ repository root, e.g. `packages/<name>/plugin.json`, and there is no `packagePath`/npm
144
+ distribution. This is the common monorepo case: the repository root holds no plugin manifest at
145
+ all, only a `marketplace.json` catalog or unrelated packages, so a root-clone `url` source
146
+ resolves nothing.
147
+ 3. **`url` / `github`** — `plugin.json` sits at the repository root. `url` for a plain clone,
148
+ `github` when a sparse/shallow checkout is preferred.
149
+
150
+ Only ask the user when these signals genuinely conflict or are missing (e.g. no `plugin.json` found
151
+ at any candidate path). Otherwise decide from the signals and state which one you picked and why.
152
+
153
+ ### 2c. Claude Code entry (`.claude-plugin/marketplace.json`)
154
+
155
+ The richer format. Use this shape, filling `source` per the type decided in 2b:
89
156
 
90
157
  ```json
91
158
  {
92
159
  "name": "<plugin-name>",
93
- "source": {
94
- "source": "url",
95
- "url": "<git-clone-url>.git"
96
- },
160
+ "source": { "source": "url", "url": "<git-clone-url>.git" },
97
161
  "description": "<one-line description>",
98
162
  "author": { "name": "<author>" },
99
163
  "homepage": "<URL>",
@@ -103,7 +167,17 @@ The richer format. Use this shape:
103
167
  }
104
168
  ```
105
169
 
106
- **`source`** — use `"source": "url"` with the `.git` clone URL for public repos. Other options: `github` (sparse checkout), `git` (with ref/path), `file`, `directory`.
170
+ **`<git-clone-url>`** — the plugin's own repo URL, read from the **plugin location** (`gh repo view --json url -q .url`, or the `repository` field in its `plugin.json`) — never the marketplace repo's URL.
171
+
172
+ **`source` shapes** — pick the one matching 2b's decision:
173
+
174
+ | Type | Shape | When |
175
+ |---|---|---|
176
+ | `url` | `{ "source": "url", "url": "<git-clone-url>.git" }` | `plugin.json` at repo root |
177
+ | `github` | `{ "source": "github", "repo": "<owner>/<repo>" }` | repo root, sparse checkout |
178
+ | `git-subdir` | `{ "source": "git-subdir", "url": "<git-clone-url>.git", "path": "<repo-relative-path-to-plugin-dir>" }` | plugin lives in a monorepo subdirectory, e.g. `packages/<name>` |
179
+ | `npm` | `{ "source": "npm", "package": "<npm-package-name>" }` | plugin ships as an npm package (`packagePath` present, not private) |
180
+ | `file` / `directory` | see the marketplace schema | local-only entries |
107
181
 
108
182
  **`category`** — choose closest: `research`, `skills`, `setup`, `productivity`, `plugin-authoring`.
109
183
 
@@ -116,29 +190,40 @@ The richer format. Use this shape:
116
190
 
117
191
  Omit optional fields rather than leaving them empty.
118
192
 
119
- ### 2c. Cursor entry (`.cursor-plugin/marketplace.json`)
193
+ ### 2d. Cursor entry (`.cursor-plugin/marketplace.json`)
120
194
 
121
195
  The simpler format — only three required fields:
122
196
 
123
197
  ```json
124
198
  {
125
199
  "name": "<plugin-name>",
126
- "source": {
127
- "source": "url",
128
- "url": "<git-clone-url>.git"
129
- },
200
+ "source": { "source": "url", "url": "<git-clone-url>.git" },
130
201
  "description": "<one-line description>"
131
202
  }
132
203
  ```
133
204
 
134
- The `source` field supports the same options as Claude Code (`url`, `github`, `git`, `file`, `directory`).
205
+ The `source` field supports the same shapes as Claude Code (`url`, `github`, `git-subdir`, `npm`,
206
+ `file`, `directory`) — pick per 2b, same as the Claude Code entry.
135
207
 
136
- ### 2d. Update scenario
208
+ ### 2e. Update scenario
137
209
 
138
210
  If the plugin is already listed (this is an update), find its existing entry, update changed fields, and preserve any curator-added fields not in the standard shape. Do not overwrite `publishedAt` if present — add `"updatedAt": "<today>"` instead.
139
211
 
140
212
  Show all prepared entries to the user and ask them to confirm before proceeding.
141
213
 
214
+ ### 2f. Validate the catalogs
215
+
216
+ Before moving to Step 3, run the validator against the marketplace repo's working copy (the same
217
+ checkout the entries were just written into):
218
+
219
+ ```bash
220
+ npx universal-plugin marketplace validate --root .
221
+ ```
222
+
223
+ This is the same check `marketplace init`/`build` rely on — it catches an entry shape no runtime can
224
+ load (a bad `source`, a missing required key) before it reaches a PR. Fix any reported issue and
225
+ re-run before continuing. Do not open the PR while validation fails.
226
+
142
227
  ---
143
228
 
144
229
  ## Step 3: Submit PR
@@ -147,7 +232,15 @@ Use the `gh` CLI. Confirm commands that affect shared state before running.
147
232
 
148
233
  ### 3a. Get the marketplace repo locally
149
234
 
150
- If the user has write access (org member), work directly:
235
+ Skip this whole step when Step 0 already found the cwd to be the marketplace location — work
236
+ directly in it, but bring it up to date first:
237
+
238
+ ```bash
239
+ git fetch origin && git merge origin/main
240
+ ```
241
+
242
+ Otherwise, the marketplace repo is elsewhere and needs a working copy. If the user has write access
243
+ (org member), work directly:
151
244
 
152
245
  ```bash
153
246
  gh repo clone cyberuni/marketplace
@@ -162,6 +255,9 @@ cd marketplace
162
255
  git fetch upstream && git merge upstream/main
163
256
  ```
164
257
 
258
+ From here on, "the marketplace repo" means whichever of these is now the working directory —
259
+ including the original cwd, when Step 0 found it already was the marketplace.
260
+
165
261
  ### 3b. Create a branch
166
262
 
167
263
  ```bash
@@ -240,6 +336,9 @@ Return the PR URL to the user when done.
240
336
  | PR rejected: missing source link | Add `homepage` or `repository` to plugin.json |
241
337
  | Name conflict in marketplace | Check existing entries in each marketplace file first |
242
338
  | Plugin loads but skills missing | Add `skills` array to the Claude Code marketplace entry |
339
+ | Entry installs nothing for a monorepo plugin | `source` was `url`/root-clone instead of `git-subdir` — redo 2b/2c with the plugin's actual repo-relative path |
340
+ | `marketplace validate` fails after 2f | Fix the reported shape before opening the PR — do not open it while validation fails |
341
+ | Entry's `repository`/`source.url` points at the marketplace repo | Step 0 role detection was skipped or overridden wrongly — re-check which repo the cwd is |
243
342
 
244
343
  ---
245
344
 
@@ -18,6 +18,18 @@
18
18
  "prompt": "My plugin 'ai-commit' is already published in the universal marketplace at version 1.0.0. I've just released v1.1.0 with some new hooks. How do I update the marketplace listing?",
19
19
  "expected_output": "Recognizes this as an update scenario, reads existing registry entry, runs pre-flight on new version, prepares updated entry preserving existing fields (especially curator_note and original publishedAt), adds updatedAt, submits an update PR.",
20
20
  "files": []
21
+ },
22
+ {
23
+ "id": 4,
24
+ "prompt": "My plugin 'foo' lives at packages/foo/plugin.json inside a monorepo (the repo root only has a marketplace.json catalog, no plugin.json). Publish it to cyberuni/marketplace.",
25
+ "expected_output": "Determines the plugin is not at the repo root and has no npm packagePath, so decides source type git-subdir rather than a root-clone url. Writes { \"source\": \"git-subdir\", \"url\": \"<clone-url>.git\", \"path\": \"packages/foo\" } in the marketplace entry. Runs marketplace validate against the marketplace working copy before opening the PR, and does not proceed if it fails.",
26
+ "files": []
27
+ },
28
+ {
29
+ "id": 5,
30
+ "prompt": "I'm a maintainer of cyberuni/marketplace and I already have it checked out here. I want to add a plugin called 'db-migrate' that lives at github.com/someuser/db-migrate. Add it.",
31
+ "expected_output": "Detects the cwd is the marketplace repo itself, not the plugin, so it reads the plugin's metadata from the given URL (cloning it to a scratch location) instead of the cwd, then works directly in the existing checkout (fetch/merge, branch, edit, commit, push, PR) rather than cloning or forking cyberuni/marketplace again. The prepared entry's source/repository URL points at db-migrate's repo, not at cyberuni/marketplace.",
32
+ "files": []
21
33
  }
22
34
  ]
23
35
  }
@@ -22,7 +22,7 @@ Root `plugin.json` is never deleted as cleanup. It is the canonical source of tr
22
22
  manifest GitHub Copilot CLI reads, so removing it takes out the source and a live target at once.
23
23
 
24
24
  Dropping a vendor is a manifest edit first: deleting only the file leaves the vendor declared, and
25
- the next build writes it straight back. That edit routes to `init`.
25
+ the next build writes it straight back. That edit routes to `init-universal-plugin`.
26
26
 
27
27
  Deleting a published plugin's source does not unpublish it. The skill says so rather than implying
28
28
  the removal reached consumers.
@@ -49,7 +49,7 @@ Copilot CLI's search order is `.plugin/plugin.json` → `plugin.json` → `.gith
49
49
 
50
50
  Removing a vendor is a manifest edit first and a deletion second — deleting only the file leaves the
51
51
  vendor declared, and the next build writes it straight back. That edit belongs to
52
- `/universal-plugin:init`'s update route; come back here for the file.
52
+ `/universal-plugin:init-universal-plugin`'s update route; come back here for the file.
53
53
 
54
54
  ## Remove the whole plugin
55
55
 
@@ -82,6 +82,6 @@ this skill does.
82
82
 
83
83
  | Task | Skill |
84
84
  |------|-------|
85
- | Remove a vendor from what the plugin declares | `init`, update route |
85
+ | Remove a vendor from what the plugin declares | `init-universal-plugin`, update route |
86
86
  | Confirm what is stale, shadowing, or unbuilt before deleting | `doctor` |
87
87
  | Take a published plugin out of a marketplace listing | `publish-plugin` |
@@ -90,7 +90,7 @@ Every guard resolves before the first write, so a failed run leaves the tree unt
90
90
 
91
91
  | Message names | What it means | Do this |
92
92
  |---|---|---|
93
- | a missing `plugin.json` | not at a plugin root, or the plugin was never scaffolded | `cd` to the plugin root, or run `/universal-plugin:init` |
93
+ | a missing `plugin.json` | not at a plugin root, or the plugin was never scaffolded | `cd` to the plugin root, or run `/universal-plugin:init-universal-plugin` |
94
94
  | no version to bump from | the manifest has never carried a `version` | pass an explicit version (`plugin version 0.1.0`) to set the first one |
95
95
  | an unknown version or release type | the argument is neither a release type nor valid semver | use one of the values in the table above |
96
96
  | a target that does not advance | the requested version is not greater than the current one | pick a higher version, or pass `--force` if the user genuinely wants to move backward |
@@ -108,7 +108,7 @@ Every guard resolves before the first write, so a failed run leaves the tree unt
108
108
 
109
109
  | Task | Skill |
110
110
  |------|-------|
111
- | Create, adopt, or change what the plugin declares | `init` |
111
+ | Create, adopt, or change what the plugin declares | `init-universal-plugin` |
112
112
  | Check whether the two authored versions agree | `doctor` |
113
113
  | Add a changeset for the change being released | `add-changeset` |
114
114
  | Refresh the repository's own marketplace catalogs after a bump | `marketplace` |