universal-plugin 0.9.0 → 0.10.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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/dist/cli.mjs +718 -450
- package/package.json +3 -4
- package/plugin.json +1 -1
- package/readme.md +12 -8
- package/{governances → references}/plugin-design.md +1 -5
- package/references/universal-plugin.md +4 -0
- package/skills/build-plugin/README.md +29 -0
- package/skills/build-plugin/SKILL.md +137 -0
- package/skills/build-plugin/scripts/build.mjs +11 -0
- package/skills/doctor-universal-plugin/SKILL.md +1 -1
- package/skills/doctor-universal-plugin/scripts/doctor.mjs +1 -1
- package/skills/init-universal-plugin/SKILL.md +3 -6
- package/skills/init-universal-plugin/references/create.md +1 -1
- package/skills/init-universal-plugin/references/standard.md +1 -1
- package/skills/init-universal-plugin/references/vendors/cursor.md +1 -1
- package/skills/marketplace/README.md +23 -7
- package/skills/marketplace/SKILL.md +54 -165
- package/skills/marketplace/references/add.md +138 -0
- package/skills/marketplace/references/init.md +131 -0
- package/skills/marketplace/references/validate.md +46 -0
- package/skills/marketplace/scripts/add.mjs +11 -0
- package/skills/migrate-plugin/SKILL.md +4 -3
- /package/{governances → references}/slash-invocation.md +0 -0
|
@@ -1,193 +1,82 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: marketplace
|
|
3
|
-
description: Use this skill to
|
|
4
|
-
argument-hint: '[--claude] [--codex] [--copilot] [--cursor] [--dry-run] [--force]'
|
|
3
|
+
description: Use this skill to run a repository's own plugin marketplace — generate the catalogs Claude Code, Codex, GitHub Copilot CLI, and Cursor read, list a plugin that lives elsewhere (an npm package, a GitHub repo, or `<plugin>@<marketplace>`), check every catalog against the schema its runtime loads, and write the README install section. Trigger on "set up a local marketplace", "let users install this from my repo", "generate marketplace catalogs", "add a plugin to the marketplace", "list this npm package in my marketplace", "make this repo installable", "add install instructions to the README", or "check my marketplace catalogs".
|
|
4
|
+
argument-hint: '[init|add|validate] [--claude] [--codex] [--copilot] [--cursor] [--dry-run] [--force]'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# marketplace
|
|
8
8
|
|
|
9
9
|
A repository can carry its own catalog, so a user adds the repository as a marketplace and installs
|
|
10
10
|
from it. No service, no submission, no account.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
This skill is the front door to that catalog. It routes; the procedure lives in the reference the
|
|
13
|
+
route names. Read only the one you need.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
| --- | --- | --- |
|
|
17
|
-
| Claude Code | `.claude-plugin/marketplace.json` | `/plugin marketplace add`, then `/plugin install` |
|
|
18
|
-
| Codex | `.agents/plugins/marketplace.json`, or the Claude path | `codex plugin marketplace add`, then `codex plugin add` |
|
|
19
|
-
| GitHub Copilot CLI | `.github/plugin/marketplace.json`, or the Claude path | `copilot plugin marketplace add`, then `copilot plugin install` |
|
|
20
|
-
| Cursor | `.cursor-plugin/marketplace.json` | nothing; an admin imports the repository as a team marketplace |
|
|
21
|
-
|
|
22
|
-
Read `references/runtimes.md` before writing any command into a README, and treat that file as the
|
|
23
|
-
only source of install commands. The trap that lives there: Codex installs with `plugin add` where
|
|
24
|
-
Copilot CLI uses `plugin install`.
|
|
25
|
-
|
|
26
|
-
## Workflow
|
|
27
|
-
|
|
28
|
-
### 1. Find what there is to list
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
ls plugins/*/plugin.json 2>/dev/null
|
|
32
|
-
test -f plugin.json && cat plugin.json
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
`plugin init --vendor <id>` already registers the plugin it scaffolds in these catalogs, so a
|
|
36
|
-
repository that has run it carries an entry before this skill starts. Read what is there first: this
|
|
37
|
-
command regenerates a catalog from what it discovers, which is the whole repository rather than one
|
|
38
|
-
plugin.
|
|
39
|
-
|
|
40
|
-
A catalog lists plugins found at `<scan-root>/<plugin-dir>/plugin.json`, which defaults to
|
|
41
|
-
`plugins/`. Pass `--plugin-scan-dir <dir>` when the repository keeps them elsewhere. A repository
|
|
42
|
-
whose only plugin sits at its root has nothing to discover; say so rather than generating an empty
|
|
43
|
-
catalog.
|
|
44
|
-
|
|
45
|
-
Discovery reads a plugin's `name` and nothing else. A missing or malformed `name` stops the command
|
|
46
|
-
before any write.
|
|
47
|
-
|
|
48
|
-
### 2. Choose targets with the user
|
|
49
|
-
|
|
50
|
-
Name the runtimes and what each one gets, using the table above. With no target flags the command
|
|
51
|
-
selects all four.
|
|
52
|
-
|
|
53
|
-
One catalog can serve two runtimes when the user wants fewer files: Codex reads the Claude catalog
|
|
54
|
-
too, so `--claude` alone covers both. The reverse does not hold, because Claude Code rejects the
|
|
55
|
-
Codex catalog for its missing `owner`. Generating both is the default, so each is idiomatic for its
|
|
56
|
-
runtime.
|
|
57
|
-
|
|
58
|
-
### 3. Generate
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
node scripts/marketplace.mjs --claude --copilot --dry-run
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Resolve that path against this skill's own directory; `npx universal-plugin marketplace init` is the
|
|
65
|
-
fallback. Run `--dry-run` first and show the plan. The command never prompts.
|
|
66
|
-
|
|
67
|
-
Then generate for real. Selected targets compose as a union, so name every target you want each run.
|
|
68
|
-
|
|
69
|
-
| Status | Means |
|
|
70
|
-
| --- | --- |
|
|
71
|
-
| `generated` | written |
|
|
72
|
-
| `unchanged` | already correct, byte differences in key order and whitespace ignored |
|
|
73
|
-
| `planned` | `--dry-run` only |
|
|
74
|
-
| `empty` | nothing discovered for this target |
|
|
75
|
-
|
|
76
|
-
A selected artifact that differs from what would be generated stops the whole run. That is the
|
|
77
|
-
command protecting a hand-edited catalog. Read the difference, then re-run with `--force` only once
|
|
78
|
-
you know what it discards.
|
|
79
|
-
|
|
80
|
-
### 4. Validate every catalog
|
|
81
|
-
|
|
82
|
-
A catalog the runtime rejects is worse than no catalog: it is found, read, and refused at install
|
|
83
|
-
time, far from here. Never conclude this skill without running the check.
|
|
15
|
+
## Routes
|
|
84
16
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
the fallback. It reads each catalog and checks it against the schema its runtime loads. Exit status is
|
|
91
|
-
1 when any selected catalog is invalid, and every issue names the key and the value to write instead,
|
|
92
|
-
on stderr:
|
|
93
|
-
|
|
94
|
-
```
|
|
95
|
-
error: catalog ".claude-plugin/marketplace.json" does not match the marketplace schema:
|
|
96
|
-
owner must be an object with a name, not string — write { "name": "Ari Vance" }
|
|
97
|
-
plugins[0].repository must be a string, not object — write "https://github.com/o/r.git"
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
| Status | Means |
|
|
101
|
-
| --- | --- |
|
|
102
|
-
| `valid` | loads in that runtime |
|
|
103
|
-
| `invalid` | the runtime would refuse it; the issues say why |
|
|
104
|
-
| `missing` | no catalog at that path, which is only a failure under `--required` |
|
|
105
|
-
|
|
106
|
-
Fix an issue in the source it comes from, then regenerate: an entry's fields are derived from the
|
|
107
|
-
plugin's `plugin.json`, so an npm-style `repository` object belongs fixed there. The top-level `name`
|
|
108
|
-
and `owner` live in the catalog itself, and `owner` must be an object — `{ "name": "…" }`, never the
|
|
109
|
-
`"Name <email>"` string `package.json` uses.
|
|
110
|
-
|
|
111
|
-
The two checks the schema cannot make: `--required` for a target the user asked for, and sources on
|
|
112
|
-
disk. Every `./` source is checked for existence by `validate`; sources resolve against the directory
|
|
113
|
-
containing `.claude-plugin/`, and they do not resolve at all for a user who adds the marketplace by
|
|
114
|
-
direct URL to the JSON file.
|
|
115
|
-
|
|
116
|
-
### 5. Offer the README section
|
|
117
|
-
|
|
118
|
-
Ask before writing. A README is the user's document, and this is an edit to it, not a new file.
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
node scripts/install-docs.mjs
|
|
122
|
-
```
|
|
17
|
+
| Route | Use it when | Procedure |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| `init` | The repository **holds** the plugins. Derive a catalog from what is on disk, then offer the README install section. | `references/init.md` |
|
|
20
|
+
| `add` | The plugin **lives elsewhere** — an npm package, a GitHub repository, or an entry another marketplace already publishes. | `references/add.md` |
|
|
21
|
+
| `validate` | Check the catalogs the repository already carries against the schema each runtime loads. | `references/validate.md` |
|
|
123
22
|
|
|
124
|
-
|
|
125
|
-
|
|
23
|
+
`init` and `add` are not alternatives. One repository can be both a plugin's home and a curated
|
|
24
|
+
list, and the two commands compose: a regeneration keeps the entries `add` wrote, because discovery
|
|
25
|
+
could never have produced them.
|
|
126
26
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
rather than leaving a placeholder in their README.
|
|
27
|
+
Both routes end in `validate`. A catalog the runtime rejects is worse than no catalog — it is found,
|
|
28
|
+
read, and refused at install time, in someone else's terminal.
|
|
130
29
|
|
|
131
|
-
|
|
132
|
-
append a second one.
|
|
30
|
+
## Intake
|
|
133
31
|
|
|
134
|
-
|
|
32
|
+
**Name the route and go.** "Set up a local marketplace", "make this repo installable", "generate the
|
|
33
|
+
catalogs" → `init`. "Add repobuddy to the marketplace", "list this npm package" → `add`. "Check the
|
|
34
|
+
catalogs" → `validate`. Load that reference and follow it; do not ask.
|
|
135
35
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
user adds it as a marketplace.
|
|
36
|
+
**Ask only when the invocation is bare.** One question: does the repository hold the plugins it is
|
|
37
|
+
listing, or is it listing plugins that live somewhere else? Offer `init`, `add`, and `validate`.
|
|
139
38
|
|
|
140
|
-
|
|
39
|
+
Discovery settles the question faster than the user can:
|
|
141
40
|
|
|
142
41
|
```bash
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
codex plugin marketplace remove <marketplace-name>
|
|
42
|
+
ls plugins/*/plugin.json 2>/dev/null
|
|
43
|
+
ls .claude-plugin/marketplace.json .agents/plugins/marketplace.json 2>/dev/null
|
|
146
44
|
```
|
|
147
45
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
Codex config. Remove what you added.
|
|
151
|
-
|
|
152
|
-
## Local development against Codex
|
|
46
|
+
Plugins under `plugins/` and no catalog yet is `init`. A catalog already there and nothing new on
|
|
47
|
+
disk is usually `add`.
|
|
153
48
|
|
|
154
|
-
|
|
155
|
-
`~/.codex/plugins/cache/<marketplace>/<plugin>/<version>`, where the version is the one the plugin's
|
|
156
|
-
own manifest carries. Editing the plugin's files does not reach that copy.
|
|
49
|
+
## Where each runtime looks
|
|
157
50
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
codex plugin add
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
That one command is the whole refresh: re-running it at the same version overwrites the cached copy
|
|
165
|
-
with the current source, so neither `codex plugin remove` nor a version bump is needed. Codex reads
|
|
166
|
-
the cache when a session starts, so the session you are in keeps the old copy — start a new one.
|
|
51
|
+
| Runtime | Catalog it reads | What the user types |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| Claude Code | `.claude-plugin/marketplace.json` | `/plugin marketplace add`, then `/plugin install` |
|
|
54
|
+
| Codex | `.agents/plugins/marketplace.json`, or the Claude path | `codex plugin marketplace add`, then `codex plugin add` |
|
|
55
|
+
| GitHub Copilot CLI | `.github/plugin/marketplace.json`, or the Claude path | `copilot plugin marketplace add`, then `copilot plugin install` |
|
|
56
|
+
| Cursor | `.cursor-plugin/marketplace.json` | nothing; an admin imports the repository as a team marketplace |
|
|
167
57
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
a version the plugin does not have.
|
|
58
|
+
Three of the four read `.claude-plugin/marketplace.json`, so one file covers Claude Code, Codex, and
|
|
59
|
+
Copilot CLI. The reverse does not hold: Claude Code rejects the Codex catalog for its missing
|
|
60
|
+
`owner`.
|
|
172
61
|
|
|
173
|
-
## Rules
|
|
62
|
+
## Rules for every route
|
|
174
63
|
|
|
175
64
|
- **Never publish a command that is not in `references/runtimes.md`.** An install command that fails
|
|
176
|
-
is worse than no install section. Widely-copied README
|
|
65
|
+
is worse than no install section, and that file is the only source of them. Widely-copied README
|
|
66
|
+
snippets are not sources. The trap that lives there: Codex installs with `plugin add` where
|
|
67
|
+
Copilot CLI uses `plugin install`.
|
|
68
|
+
- **Never hand-author or hand-patch a catalog.** Generate it, then validate it. A catalog written by
|
|
69
|
+
copying fields out of `package.json` carries `owner` as a string and `repository` as an object,
|
|
70
|
+
and Claude Code refuses it for either one.
|
|
177
71
|
- **Name every catalog `marketplace.json`.** Codex discovers a catalog by that filename inside a
|
|
178
72
|
supported directory. A file named anything else is invisible to it, whatever directory holds it.
|
|
179
|
-
- **Do not write a Cursor install command.** Cursor has no command that adds a repository catalog
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
- **
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
and validated none has not been verified.
|
|
187
|
-
- **Ask before editing the README**, and before `--force` replaces a catalog the user may have
|
|
188
|
-
hand-edited.
|
|
189
|
-
- This command publishes nothing and registers nothing. Say so in the report; a user who believes
|
|
190
|
-
they have published will not understand why nobody can install.
|
|
73
|
+
- **Do not write a Cursor install command.** Cursor has no command that adds a repository catalog.
|
|
74
|
+
- **Report the validation result, not just the write.** A run that wrote four files and validated
|
|
75
|
+
none has not been verified.
|
|
76
|
+
- **Nothing here publishes or registers anything.** Say so in the report; a user who believes they
|
|
77
|
+
have published will not understand why nobody can install.
|
|
78
|
+
- **Ask before editing the README**, and before `--force` replaces anything a user may have written
|
|
79
|
+
by hand.
|
|
191
80
|
- Listing a plugin in the shared `cyberuni/marketplace` repository is a different job: use
|
|
192
81
|
`publish-plugin`.
|
|
193
82
|
|
|
@@ -202,8 +91,8 @@ a version the plugin does not have.
|
|
|
202
91
|
|
|
203
92
|
## References
|
|
204
93
|
|
|
94
|
+
- `references/init.md` — derive the catalogs from the plugins this repository holds
|
|
95
|
+
- `references/add.md` — list a plugin that lives elsewhere
|
|
96
|
+
- `references/validate.md` — check the catalogs against the schema each runtime loads
|
|
205
97
|
- `references/runtimes.md` — per-runtime install commands and their sources
|
|
206
|
-
- The official Claude Code marketplace schema, which `validate` checks against:
|
|
207
|
-
<https://json.schemastore.org/claude-code-marketplace.json>
|
|
208
98
|
- [Research conclusion](https://github.com/cyberuni/universal-plugin/blob/main/.research/local-marketplaces/conclusion.md)
|
|
209
|
-
- [`marketplace init` spec](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/marketplace/init/README.md)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Route: add
|
|
2
|
+
|
|
3
|
+
List a plugin that lives somewhere else — an npm package, a GitHub repository, or an entry another
|
|
4
|
+
marketplace already publishes. This is what makes a repository a curated marketplace rather than
|
|
5
|
+
only its own plugins' home.
|
|
6
|
+
|
|
7
|
+
`init` covers the other case and the two compose; see `references/init.md`.
|
|
8
|
+
|
|
9
|
+
## 1. Work out what the user is naming
|
|
10
|
+
|
|
11
|
+
One positional argument says where the plugin lives. The command disambiguates by shape:
|
|
12
|
+
|
|
13
|
+
| Form | Example | Source written |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| path in this repository | `./plugins/alpha` | `"./plugins/alpha"` |
|
|
16
|
+
| GitHub repository | `cyberuni/universal-plugin` | `{ "source": "github", "repo": "…" }` |
|
|
17
|
+
| git or https URL | `https://example.com/o/r.git` | `{ "source": "url", "url": "…" }` |
|
|
18
|
+
| npm package | `npm:repobuddy`, `@cyberuni/upx` | `{ "source": "npm", "package": "…" }` |
|
|
19
|
+
| another marketplace | `repobuddy@cyberplace` | whatever source that marketplace publishes |
|
|
20
|
+
|
|
21
|
+
Two shapes collide, and both have a flag that settles them:
|
|
22
|
+
|
|
23
|
+
- **`plugins/alpha` reads as a GitHub repository**, not a directory — `owner/repo` and a relative
|
|
24
|
+
path are the same string. Write `./plugins/alpha`, or pass `--path`.
|
|
25
|
+
- **A leading `@` is a scope, not a marketplace.** `@cyberuni/upx` is a package; `upx@cyberplace` is
|
|
26
|
+
a marketplace entry.
|
|
27
|
+
|
|
28
|
+
`--path`, `--npm`, `--github`, `--url`, and `--from-marketplace` each force the reading. Pass one.
|
|
29
|
+
|
|
30
|
+
### One plugin out of a monorepo
|
|
31
|
+
|
|
32
|
+
`owner/repo` and a git URL both name a whole repository, and neither source form can say which
|
|
33
|
+
directory inside it is the plugin. `--subdir` says it, which turns the source into `git-subdir`:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
node scripts/add.mjs cyberuni/cyber-sdd --subdir plugins/aced
|
|
37
|
+
# { "source": "git-subdir", "url": "https://github.com/cyberuni/cyber-sdd.git", "path": "plugins/aced" }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The entry is named for that directory — `aced` — not for the repository. Ask for `--subdir` whenever
|
|
41
|
+
the user names a repository you know publishes more than one plugin; without it the entry points at
|
|
42
|
+
the repository root, which installs the wrong thing rather than failing.
|
|
43
|
+
|
|
44
|
+
`--ref <branch-or-tag>` and `--sha <commit>` pin a git source. Both apply to `url`, `github`, and
|
|
45
|
+
`git-subdir` only: an npm package is pinned by version and a path is whatever is on disk. A `--sha`
|
|
46
|
+
must be the full 40-character hash, which the schema requires.
|
|
47
|
+
|
|
48
|
+
## 2. Choose targets, and know what will be skipped
|
|
49
|
+
|
|
50
|
+
Not every runtime installs from every source. Only a repository path reaches all four:
|
|
51
|
+
|
|
52
|
+
| Source | Claude Code | Codex | Copilot CLI | Cursor |
|
|
53
|
+
| --- | --- | --- | --- | --- |
|
|
54
|
+
| path | yes | yes | yes | yes |
|
|
55
|
+
| npm | yes | yes | — | — |
|
|
56
|
+
| github, url, git-subdir | yes | — | — | — |
|
|
57
|
+
|
|
58
|
+
A target that cannot resolve the source is reported `skipped` with the reason, and no file is
|
|
59
|
+
written for it. That is deliberate: a source a runtime refuses is an install failure in someone
|
|
60
|
+
else's terminal. Say which targets were skipped and why — do not report "added" and leave the user
|
|
61
|
+
to discover that two catalogs have no entry.
|
|
62
|
+
|
|
63
|
+
With no target flags all four are selected, so the skips are the normal case for an npm package.
|
|
64
|
+
|
|
65
|
+
## 3. Supply the metadata
|
|
66
|
+
|
|
67
|
+
Nothing is fetched. The command reads what is already on this machine and takes the rest from flags:
|
|
68
|
+
|
|
69
|
+
- a **path** source reads the plugin's own `plugin.json`
|
|
70
|
+
- an **npm** source reads `node_modules/<pkg>/package.json` when the package happens to be installed
|
|
71
|
+
- a **marketplace** entry brings the metadata that marketplace already publishes
|
|
72
|
+
|
|
73
|
+
Flags fill or override the gaps: `--description`, `--version`, `--homepage`, `--repository`,
|
|
74
|
+
`--license`, `--keywords` (comma-separated). Ask the user for a description at minimum; an entry
|
|
75
|
+
with only a name tells a browsing user nothing.
|
|
76
|
+
|
|
77
|
+
`--name` overrides the entry name, which otherwise comes from the spec: the package name without its
|
|
78
|
+
scope, the repository name, or the directory name.
|
|
79
|
+
|
|
80
|
+
## 4. Preview, then write
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
node scripts/add.mjs npm:repobuddy --description "Repo automation" --dry-run
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Resolve that path against this skill's own directory; `npx universal-plugin marketplace add` is the
|
|
87
|
+
fallback. Run `--dry-run` first and show the plan, including the skips. The command never prompts.
|
|
88
|
+
|
|
89
|
+
| Status | Means |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `added` | the entry was not listed; it is now |
|
|
92
|
+
| `updated` | an entry of that name was listed and has been replaced |
|
|
93
|
+
| `unchanged` | already says exactly this |
|
|
94
|
+
| `planned` | `--dry-run` only |
|
|
95
|
+
| `skipped` | that runtime cannot resolve this source; the reason says which |
|
|
96
|
+
|
|
97
|
+
An entry already listed **differently** stops the run and asks for `--force`. Read what would change
|
|
98
|
+
before passing it: someone wrote that entry, and `--force` is what discards their version.
|
|
99
|
+
|
|
100
|
+
The catalog is created if the repository has none, named and owned the same way `init` names it —
|
|
101
|
+
from the root `plugin.json` author, with `--marketplace-name` and `--owner` to override. A
|
|
102
|
+
repository curating other people's plugins usually has no root manifest, so `--owner` is the flag it
|
|
103
|
+
needs.
|
|
104
|
+
|
|
105
|
+
## 5. Resolving `<plugin>@<marketplace>`
|
|
106
|
+
|
|
107
|
+
The catalog schema has no "from another marketplace" source, so the entry is **resolved and copied**
|
|
108
|
+
rather than referenced. The marketplace has to be readable:
|
|
109
|
+
|
|
110
|
+
- installed in the runtime already — read from `~/.claude/plugins`, no network
|
|
111
|
+
- otherwise, `--from <dir>` naming a checkout of it
|
|
112
|
+
|
|
113
|
+
A marketplace that is not installed stops the run and says so. Ask the user to add it in their
|
|
114
|
+
runtime, or to point `--from` at a clone; do not guess a URL.
|
|
115
|
+
|
|
116
|
+
An entry whose source is a `./` path is **rewritten**, not copied. That path resolves against the
|
|
117
|
+
other marketplace's root, which this repository is not — but it is a location inside a repository
|
|
118
|
+
whose URL is known, so it becomes an absolute source:
|
|
119
|
+
|
|
120
|
+
| Entry in the other marketplace | Written here |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `./plugins/aced` in `cyberuni/cyberplace` | `{ "source": "git-subdir", "url": "https://github.com/cyberuni/cyberplace.git", "path": "plugins/aced" }` |
|
|
123
|
+
| `./` in `unional/skills` | `{ "source": "github", "repo": "unional/skills" }` |
|
|
124
|
+
|
|
125
|
+
The origin comes from what the runtime recorded for that marketplace, falling back to the clone's
|
|
126
|
+
own git remote — which is also how `--from` resolves one. A marketplace with neither stops the run:
|
|
127
|
+
there is no URL to rewrite against, and a bare path would name a directory this repository does not
|
|
128
|
+
have.
|
|
129
|
+
|
|
130
|
+
Both rewritten forms are Claude Code source types, so expect Codex, Copilot CLI, and Cursor to be
|
|
131
|
+
skipped for a subdirectory plugin.
|
|
132
|
+
|
|
133
|
+
## 6. Validate
|
|
134
|
+
|
|
135
|
+
Follow `references/validate.md`. The entry has to load in every runtime that got one.
|
|
136
|
+
|
|
137
|
+
Then state plainly that nothing was published: the entry sits in the repository until a user adds it
|
|
138
|
+
as a marketplace.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Route: init
|
|
2
|
+
|
|
3
|
+
Derive a catalog from the plugins this repository holds, then offer the README section that tells
|
|
4
|
+
users what to type.
|
|
5
|
+
|
|
6
|
+
For a plugin that lives elsewhere, see `references/add.md`. The two compose on one repository.
|
|
7
|
+
|
|
8
|
+
## 1. Find what there is to list
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
ls plugins/*/plugin.json 2>/dev/null
|
|
12
|
+
test -f plugin.json && cat plugin.json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`plugin init --vendor <id>` already registers the plugin it scaffolds in these catalogs, so a
|
|
16
|
+
repository that has run it carries an entry before this route starts. Read what is there first: this
|
|
17
|
+
command regenerates a catalog from what it discovers, which is the whole repository rather than one
|
|
18
|
+
plugin.
|
|
19
|
+
|
|
20
|
+
A catalog lists plugins found at `<scan-root>/<plugin-dir>/plugin.json`, which defaults to
|
|
21
|
+
`plugins/`. Pass `--plugin-scan-dir <dir>` when the repository keeps them elsewhere. A repository
|
|
22
|
+
whose only plugin sits at its root has nothing to discover; say so rather than generating an empty
|
|
23
|
+
catalog.
|
|
24
|
+
|
|
25
|
+
Discovery reads a plugin's `name` and nothing else. A missing or malformed `name` stops the command
|
|
26
|
+
before any write.
|
|
27
|
+
|
|
28
|
+
## 2. Choose targets with the user
|
|
29
|
+
|
|
30
|
+
Name the runtimes and what each one gets, using the table in `SKILL.md`. With no target flags the
|
|
31
|
+
command selects all four.
|
|
32
|
+
|
|
33
|
+
One catalog can serve two runtimes when the user wants fewer files: Codex reads the Claude catalog
|
|
34
|
+
too, so `--claude` alone covers both. The reverse does not hold, because Claude Code rejects the
|
|
35
|
+
Codex catalog for its missing `owner`. Generating both is the default, so each is idiomatic for its
|
|
36
|
+
runtime.
|
|
37
|
+
|
|
38
|
+
## 3. Generate
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
node scripts/marketplace.mjs --claude --copilot --dry-run
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Resolve that path against this skill's own directory; `npx universal-plugin marketplace init` is the
|
|
45
|
+
fallback. Run `--dry-run` first and show the plan. The command never prompts.
|
|
46
|
+
|
|
47
|
+
Then generate for real. Selected targets compose as a union, so name every target you want each run.
|
|
48
|
+
|
|
49
|
+
| Status | Means |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `generated` | written |
|
|
52
|
+
| `unchanged` | already correct, byte differences in key order and whitespace ignored |
|
|
53
|
+
| `planned` | `--dry-run` only |
|
|
54
|
+
| `empty` | nothing discovered for this target |
|
|
55
|
+
|
|
56
|
+
A selected artifact that differs from what would be generated stops the whole run. That is the
|
|
57
|
+
command protecting a hand-edited catalog. Read the difference, then re-run with `--force` only once
|
|
58
|
+
you know what it discards.
|
|
59
|
+
|
|
60
|
+
### What a regeneration keeps
|
|
61
|
+
|
|
62
|
+
Discovery walks directories, so it can only speak for plugins that are in them. Two things survive a
|
|
63
|
+
regeneration untouched:
|
|
64
|
+
|
|
65
|
+
- an entry whose source is **not** a local path — an npm package, a GitHub repository — which only
|
|
66
|
+
`add` puts there
|
|
67
|
+
- the source of a **discovered** plugin whose entry names a non-local source, with its derived
|
|
68
|
+
metadata refreshed around it
|
|
69
|
+
|
|
70
|
+
So a plugin shipped through npm, whose repository path holds gitignored build output, keeps pointing
|
|
71
|
+
at npm. What discovery owns it still owns: a local-path entry it no longer finds is dropped.
|
|
72
|
+
|
|
73
|
+
## 4. Validate every catalog
|
|
74
|
+
|
|
75
|
+
Follow `references/validate.md`. Never conclude without it.
|
|
76
|
+
|
|
77
|
+
## 5. Offer the README section
|
|
78
|
+
|
|
79
|
+
Ask before writing. A README is the user's document, and this is an edit to it, not a new file.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
node scripts/install-docs.mjs
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Stdout is one JSON object: `targets`, `repo`, and `markdown`. Insert `markdown` verbatim. It carries
|
|
86
|
+
a section per generated catalog, built from the marketplace name and plugin names actually on disk.
|
|
87
|
+
|
|
88
|
+
Check `repoResolved` first. When it is `false` the repository slug could not be found and the
|
|
89
|
+
snippet contains `<owner>/<repo>`; ask the user for the slug and re-run with `--repo <owner>/<repo>`
|
|
90
|
+
rather than leaving a placeholder in their README.
|
|
91
|
+
|
|
92
|
+
If the README already has an install section, show the difference and let the user choose. Do not
|
|
93
|
+
append a second one.
|
|
94
|
+
|
|
95
|
+
## 6. Verify
|
|
96
|
+
|
|
97
|
+
Re-run the generator and confirm every selected target reports `unchanged`. Confirm each catalog
|
|
98
|
+
path exists. State plainly that nothing was published: these files sit in the repository until a
|
|
99
|
+
user adds it as a marketplace.
|
|
100
|
+
|
|
101
|
+
A local path is the cheapest end-to-end proof:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
codex plugin marketplace add "$(pwd)"
|
|
105
|
+
codex plugin list
|
|
106
|
+
codex plugin marketplace remove <marketplace-name>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`codex plugin list` prints the manifest path it read, which is what tells you the catalog was found
|
|
110
|
+
rather than merely present. Offer this check rather than running it unasked: it writes to the user's
|
|
111
|
+
Codex config. Remove what you added.
|
|
112
|
+
|
|
113
|
+
## Local development against Codex
|
|
114
|
+
|
|
115
|
+
Codex installs a **copy** of the plugin at
|
|
116
|
+
`~/.codex/plugins/cache/<marketplace>/<plugin>/<version>`, where the version is the one the plugin's
|
|
117
|
+
own manifest carries. Editing the plugin's files does not reach that copy.
|
|
118
|
+
|
|
119
|
+
After changing packaged files, install again and start a new session:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
codex plugin add <plugin>@<marketplace>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
That one command is the whole refresh: re-running it at the same version overwrites the cached copy
|
|
126
|
+
with the current source, so neither `codex plugin remove` nor a version bump is needed. Codex reads
|
|
127
|
+
the cache when a session starts, so the session you are in keeps the old copy — start a new one.
|
|
128
|
+
|
|
129
|
+
The catalog entry's version is derived from the canonical manifest, so a version move updates both
|
|
130
|
+
(`/universal-plugin:version` owns that). Codex itself does not read the entry's version; keeping it
|
|
131
|
+
true is this project's policy, so the catalog never states a version the plugin does not have.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Route: validate
|
|
2
|
+
|
|
3
|
+
Check the catalogs the repository carries against the schema each runtime loads.
|
|
4
|
+
|
|
5
|
+
Every route ends here. A catalog the runtime rejects is worse than no catalog: it is found, read,
|
|
6
|
+
and refused at install time, far from the repository that wrote it. Never conclude the skill without
|
|
7
|
+
running this.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
node scripts/validate.mjs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Resolve that path against this skill's own directory; `npx universal-plugin marketplace validate` is
|
|
14
|
+
the fallback. Exit status is 1 when any selected catalog is invalid, and every issue names the key
|
|
15
|
+
and the value to write instead, on stderr:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
error: catalog ".claude-plugin/marketplace.json" does not match the marketplace schema:
|
|
19
|
+
owner must be an object with a name, not string — write { "name": "Ari Vance" }
|
|
20
|
+
plugins[0].repository must be a string, not object — write "https://github.com/o/r.git"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
| Status | Means |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `valid` | loads in that runtime |
|
|
26
|
+
| `invalid` | the runtime would refuse it; the issues say why |
|
|
27
|
+
| `missing` | no catalog at that path, which is only a failure under `--required` |
|
|
28
|
+
|
|
29
|
+
## Fixing what it reports
|
|
30
|
+
|
|
31
|
+
Fix an issue in the source it comes from, then regenerate. An entry's fields are derived, so an
|
|
32
|
+
npm-style `repository` object belongs fixed in the plugin's `plugin.json` (for a discovered plugin)
|
|
33
|
+
or passed as `--repository <url>` (for an added one).
|
|
34
|
+
|
|
35
|
+
The top-level `name` and `owner` live in the catalog itself, and `owner` must be an object —
|
|
36
|
+
`{ "name": "…" }`, never the `"Name <email>"` string `package.json` uses.
|
|
37
|
+
|
|
38
|
+
Nothing is repaired automatically. A catalog edited by hand is the user's to correct.
|
|
39
|
+
|
|
40
|
+
## The two checks the schema cannot make
|
|
41
|
+
|
|
42
|
+
- `--required`, for a target the user asked for and did not get.
|
|
43
|
+
- Sources on disk. Every `./` source is checked for existence. They resolve against the directory
|
|
44
|
+
holding `.claude-plugin/`, and they do not resolve at all for a user who adds the marketplace by
|
|
45
|
+
direct URL to the JSON file — which is the argument for a non-local source on a plugin
|
|
46
|
+
distributed that way (`references/add.md`).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runs `universal-plugin marketplace add` from the CLI that ships beside this skill, so listing a
|
|
3
|
+
// plugin never depends on a network fetch or on which version `npx` happens to resolve.
|
|
4
|
+
import { dirname, join } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
|
|
7
|
+
// <package>/skills/<skill>/scripts/add.mjs: four levels up is the package root.
|
|
8
|
+
const packageRoot = dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))))
|
|
9
|
+
|
|
10
|
+
process.argv.splice(2, 0, 'marketplace', 'add')
|
|
11
|
+
await import(join(packageRoot, 'bin', 'universal-plugin.mjs'))
|
|
@@ -175,9 +175,10 @@ Point every version and build step at the new plugin root:
|
|
|
175
175
|
|
|
176
176
|
- Set the manifest extension's `packagePath` to `"."`.
|
|
177
177
|
- `universal-plugin publish sync-version --root <pkg>` reads `packagePath` from
|
|
178
|
-
`<pkg>/.agents/universal-plugin.json`, not from the manifest extension.
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
`<pkg>/.agents/universal-plugin.json`, not from the manifest extension. With
|
|
179
|
+
no `packagePath` it reads `<pkg>/package.json`, so a package that holds its
|
|
180
|
+
own plugin needs no config file for it. If the repository instead runs its
|
|
181
|
+
own sync script, repoint that script's manifest path and leave it.
|
|
181
182
|
- Change every `universal-plugin plugin build --root <old>` in `package.json`
|
|
182
183
|
scripts to the package directory, then run it and confirm it reports the
|
|
183
184
|
vendor manifests as built and the catalog as unchanged or refolded.
|
|
File without changes
|