universal-plugin 0.4.0 → 0.5.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 +1188 -243
- package/dist/data/vendors.json +12 -0
- package/dist/run.mjs +4 -4
- package/governances/plugin-design.md +42 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/readme.md +7 -0
- package/skills/doctor/README.md +2 -2
- package/skills/doctor/SKILL.md +22 -1
- package/skills/doctor/scripts/doctor.mjs +81 -4
- package/skills/init/SKILL.md +23 -4
- package/skills/init/references/create.md +13 -2
- package/skills/init/references/standard.md +2 -1
- package/skills/init/references/vendors/claude-code.md +40 -8
- package/skills/init/references/vendors/codex.md +10 -2
- package/skills/init/references/vendors/copilot-cli.md +10 -2
- package/skills/init/references/vendors/cursor.md +9 -2
- package/skills/marketplace/README.md +38 -0
- package/skills/marketplace/SKILL.md +170 -0
- package/skills/marketplace/references/runtimes.md +104 -0
- package/skills/marketplace/scripts/install-docs.mjs +115 -0
- package/skills/marketplace/scripts/marketplace.mjs +11 -0
- package/skills/publish-plugin/SKILL.md +10 -8
- package/skills/publish-plugin/references/vendor-requirements.md +13 -10
- package/skills/version/SKILL.md +2 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: marketplace
|
|
3
|
+
description: Use this skill to let people install a plugin straight from its own repository — generate the local marketplace catalogs Claude Code, Codex, GitHub Copilot CLI, and Cursor read, and write the README install section that tells users what to type. Trigger on "set up a local marketplace", "let users install this from my repo", "generate marketplace catalogs", "add install instructions to the README", "how do people install this plugin", or "make this repo installable".
|
|
4
|
+
argument-hint: '[--claude] [--codex] [--copilot] [--cursor] [--dry-run] [--force]'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Local marketplace
|
|
8
|
+
|
|
9
|
+
A repository can carry its own catalog, so a user adds the repository as a marketplace and installs
|
|
10
|
+
from it. No service, no submission, no account.
|
|
11
|
+
|
|
12
|
+
All four runtimes read such a catalog. Three of them let a user add it; Cursor's reaches users when
|
|
13
|
+
an admin imports the repository as a team marketplace.
|
|
14
|
+
|
|
15
|
+
| Runtime | Catalog it reads | What the user types |
|
|
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. Offer the README section
|
|
81
|
+
|
|
82
|
+
Ask before writing. A README is the user's document, and this is an edit to it, not a new file.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
node scripts/install-docs.mjs
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Stdout is one JSON object: `targets`, `repo`, and `markdown`. Insert `markdown` verbatim. It carries
|
|
89
|
+
a section per generated catalog, built from the marketplace name and plugin names actually on disk.
|
|
90
|
+
|
|
91
|
+
Check `repoResolved` first. When it is `false` the repository slug could not be found and the
|
|
92
|
+
snippet contains `<owner>/<repo>`; ask the user for the slug and re-run with `--repo <owner>/<repo>`
|
|
93
|
+
rather than leaving a placeholder in their README.
|
|
94
|
+
|
|
95
|
+
If the README already has an install section, show the difference and let the user choose. Do not
|
|
96
|
+
append a second one.
|
|
97
|
+
|
|
98
|
+
### 5. Verify
|
|
99
|
+
|
|
100
|
+
Re-run the generator and confirm every selected target reports `unchanged`. Confirm each catalog
|
|
101
|
+
path exists. State plainly that nothing was published: these files sit in the repository until a
|
|
102
|
+
user adds it as a marketplace.
|
|
103
|
+
|
|
104
|
+
For Claude Code, check that each plugin `source` is a `./`-prefixed path that exists. Sources resolve
|
|
105
|
+
against the directory containing `.claude-plugin/`, and they do not resolve at all for a user who
|
|
106
|
+
adds the marketplace by direct URL to the JSON file.
|
|
107
|
+
|
|
108
|
+
A local path is the cheapest end-to-end proof:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
codex plugin marketplace add "$(pwd)"
|
|
112
|
+
codex plugin list
|
|
113
|
+
codex plugin marketplace remove <marketplace-name>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`codex plugin list` prints the manifest path it read, which is what tells you the catalog was found
|
|
117
|
+
rather than merely present. Offer this check rather than running it unasked: it writes to the user's
|
|
118
|
+
Codex config. Remove what you added.
|
|
119
|
+
|
|
120
|
+
## Local development against Codex
|
|
121
|
+
|
|
122
|
+
Codex installs a **copy** of the plugin at
|
|
123
|
+
`~/.codex/plugins/cache/<marketplace>/<plugin>/<version>`, where the version is the one the plugin's
|
|
124
|
+
own manifest carries. Editing the plugin's files does not reach that copy.
|
|
125
|
+
|
|
126
|
+
After changing packaged files, install again and start a new session:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
codex plugin add <plugin>@<marketplace>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
That one command is the whole refresh: re-running it at the same version overwrites the cached copy
|
|
133
|
+
with the current source, so neither `codex plugin remove` nor a version bump is needed. Codex reads
|
|
134
|
+
the cache when a session starts, so the session you are in keeps the old copy — start a new one.
|
|
135
|
+
|
|
136
|
+
The catalog entry's version is derived from the canonical manifest, so a version move updates both
|
|
137
|
+
(`/universal-plugin:version` owns that). Codex itself does
|
|
138
|
+
not read the entry's version; keeping it true is this project's policy, so the catalog never states
|
|
139
|
+
a version the plugin does not have.
|
|
140
|
+
|
|
141
|
+
## Rules
|
|
142
|
+
|
|
143
|
+
- **Never publish a command that is not in `references/runtimes.md`.** An install command that fails
|
|
144
|
+
is worse than no install section. Widely-copied README snippets are not sources.
|
|
145
|
+
- **Name every catalog `marketplace.json`.** Codex discovers a catalog by that filename inside a
|
|
146
|
+
supported directory. A file named anything else is invisible to it, whatever directory holds it.
|
|
147
|
+
- **Do not write a Cursor install command.** Cursor has no command that adds a repository catalog;
|
|
148
|
+
a developer tests through `~/.cursor/plugins/local/<name>` and users get the plugin through a team
|
|
149
|
+
marketplace an admin imports.
|
|
150
|
+
- **Ask before editing the README**, and before `--force` replaces a catalog the user may have
|
|
151
|
+
hand-edited.
|
|
152
|
+
- This command publishes nothing and registers nothing. Say so in the report; a user who believes
|
|
153
|
+
they have published will not understand why nobody can install.
|
|
154
|
+
- Listing a plugin in the shared `cyberuni/marketplace` repository is a different job: use
|
|
155
|
+
`publish-plugin`.
|
|
156
|
+
|
|
157
|
+
## Related skills
|
|
158
|
+
|
|
159
|
+
| Task | Skill |
|
|
160
|
+
|------|-------|
|
|
161
|
+
| Create or change the plugin being listed | `init` |
|
|
162
|
+
| Check that the plugin's own manifests are current | `doctor` |
|
|
163
|
+
| Move the version users will install | `version` |
|
|
164
|
+
| Submit to the shared marketplace repository instead | `publish-plugin` |
|
|
165
|
+
|
|
166
|
+
## References
|
|
167
|
+
|
|
168
|
+
- `references/runtimes.md` — per-runtime install commands and their sources
|
|
169
|
+
- [Research conclusion](https://github.com/cyberuni/universal-plugin/blob/main/.research/local-marketplaces/conclusion.md)
|
|
170
|
+
- [`marketplace init` spec](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/marketplace/init/README.md)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Install commands per runtime
|
|
2
|
+
|
|
3
|
+
The only source of install commands for this skill. Every entry traces to vendor documentation
|
|
4
|
+
reviewed on 2026-08-18 and recorded in `.research/local-marketplaces/evidence.md`. A command not
|
|
5
|
+
listed here does not go into a README.
|
|
6
|
+
|
|
7
|
+
## Claude Code — documented, complete
|
|
8
|
+
|
|
9
|
+
Catalog: `.claude-plugin/marketplace.json` at the repository root (E-CC-M1).
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
/plugin marketplace add <owner>/<repo>
|
|
13
|
+
/plugin install <plugin>@<marketplace-name>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`<marketplace-name>` is the `name` field inside `marketplace.json`, not the repository name (E-CC-M2).
|
|
17
|
+
|
|
18
|
+
Both verbs also exist outside the session as `claude plugin marketplace add` and
|
|
19
|
+
`claude plugin install`. In a non-interactive shell, `--yes` accepts the command string an install
|
|
20
|
+
prints (E-CC-M3). Users refresh a catalog with `/plugin marketplace update`.
|
|
21
|
+
|
|
22
|
+
Plugin `source` paths resolve against the marketplace root, the directory holding `.claude-plugin/`.
|
|
23
|
+
They do not resolve when a user adds the marketplace by direct URL to the JSON file, so a repository
|
|
24
|
+
distributing by URL must use a non-relative plugin source (E-CC-M4).
|
|
25
|
+
|
|
26
|
+
Auto-update is off by default for third-party marketplaces, so a user who wants background refresh
|
|
27
|
+
enables it once from `/plugin` → Marketplaces. That is a user-side setting; this skill does not
|
|
28
|
+
change it.
|
|
29
|
+
|
|
30
|
+
## GitHub Copilot CLI — documented, complete
|
|
31
|
+
|
|
32
|
+
Catalog: `.github/plugin/marketplace.json`. Copilot CLI also accepts `marketplace.json` at the root,
|
|
33
|
+
in `.plugin/`, or in `.claude-plugin/` (E-COPILOT-M3).
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
copilot plugin marketplace add <owner>/<repo>
|
|
37
|
+
copilot plugin install <plugin>@<marketplace-name>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The marketplace's own `name` becomes its registration key, and a user cannot choose a local alias
|
|
41
|
+
(E-COPILOT-M1). `copilot plugin install` also accepts `OWNER/REPO`, `OWNER/REPO:PATH`, a Git URL, or
|
|
42
|
+
a local path, so a plugin is installable from a repository even without a catalog (E-COPILOT-M2).
|
|
43
|
+
|
|
44
|
+
## Codex — works, and reads two paths
|
|
45
|
+
|
|
46
|
+
Catalog: `.agents/plugins/marketplace.json`, which `marketplace init --codex` writes. Codex also
|
|
47
|
+
reads `.claude-plugin/marketplace.json`, so a repository that generates only the Claude catalog is
|
|
48
|
+
installable from Codex too (E-CODEX-M10, E-CODEX-M11).
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
codex plugin marketplace add <owner>/<repo>
|
|
52
|
+
codex plugin add <plugin>@<marketplace-name>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`marketplace add` also accepts a local path, `owner/repo@ref`, an HTTPS Git URL, or an SSH Git URL,
|
|
56
|
+
with `list`, `upgrade`, and `remove` beside it (E-CODEX-M3).
|
|
57
|
+
|
|
58
|
+
**Codex installs with `plugin add`, not `plugin install`.** Copilot CLI is the opposite. Do not
|
|
59
|
+
carry one runtime's verb over to the other.
|
|
60
|
+
|
|
61
|
+
Neither verb appears in Codex's published documentation. Both are in the shipped CLI, verified on
|
|
62
|
+
codex-cli 0.147.0.
|
|
63
|
+
|
|
64
|
+
**Discovery is by filename.** Codex looks for `marketplace.json` inside a supported directory. These
|
|
65
|
+
are read: `.claude-plugin/`, `.agents/plugins/`. These are not, with the correct filename in each:
|
|
66
|
+
`.codex-plugin/`, `.plugin/`, `.github/plugin/`, the repository root (E-CODEX-M10).
|
|
67
|
+
|
|
68
|
+
The two catalogs are not interchangeable in content. The Codex one carries `interface`, `policy`,
|
|
69
|
+
`category`, and an object `source`; Codex accepts the Claude shape as well, but Claude Code rejects
|
|
70
|
+
the Codex shape for its missing `owner` (E-CC-M5, E-CODEX-M9). Where a repository wants one file
|
|
71
|
+
instead of two, that file is the Claude one.
|
|
72
|
+
|
|
73
|
+
Codex copies an install to `~/.codex/plugins/cache/<marketplace>/<plugin>/<version>`, keyed by the
|
|
74
|
+
version the **plugin's own manifest** carries rather than the catalog entry's (E-CODEX-M13,
|
|
75
|
+
E-CODEX-M15). A source edit is invisible until the plugin is installed again and a new session
|
|
76
|
+
starts; `codex plugin add` on its own is the reinstall, because re-running it at the same version
|
|
77
|
+
overwrites the cached copy (E-CODEX-M14). A catalog entry that declares no version installs normally
|
|
78
|
+
(E-CODEX-M16).
|
|
79
|
+
|
|
80
|
+
## Cursor — a catalog it reads, but no command to install from one
|
|
81
|
+
|
|
82
|
+
Cursor reads a repository-local catalog: `.cursor-plugin/marketplace.json` or
|
|
83
|
+
`.claude-plugin/marketplace.json` (E-CUR-M5), so the Claude catalog covers it. The documented shape
|
|
84
|
+
is close to Claude Code's — `name`, an object `owner` carrying `name` and an optional `email`, and
|
|
85
|
+
`plugins` whose `source` is a path inside the repository (E-CUR-M6). `marketplace init --cursor`
|
|
86
|
+
writes it.
|
|
87
|
+
|
|
88
|
+
Nothing on the command line consumes one. `cursor-agent` has no plugin subcommand (E-CUR-M2), and
|
|
89
|
+
users install from the Customize sidebar, sourced from Cursor's reviewed marketplace,
|
|
90
|
+
cursor.directory, or a team marketplace (E-CUR-M1). A repository catalog reaches users when an admin
|
|
91
|
+
imports it: Dashboard → Plugins → Team Marketplaces → Add Marketplace → Import from Repo, after
|
|
92
|
+
which Auto Refresh tracks the branch the marketplace is configured against (E-CUR-M7). So generate
|
|
93
|
+
the file and write no Cursor install command.
|
|
94
|
+
|
|
95
|
+
For local development, `universal-plugin plugin install` copies the plugin into
|
|
96
|
+
`~/.cursor/plugins/local/<name>`; reload the window afterwards. It copies rather than symlinks
|
|
97
|
+
because Cursor's scan resolves each symlink and rejects a target outside that directory (E-CUR-M4),
|
|
98
|
+
which is what the `ln -sfn "$(pwd)"` recipe that used to sit here ran into.
|
|
99
|
+
|
|
100
|
+
## When these decay
|
|
101
|
+
|
|
102
|
+
A vendor command is a claim with a shelf life. Re-verify against the vendor page before relying on
|
|
103
|
+
an entry that looks stale, and update the evidence file in the same change. The recheck triggers are
|
|
104
|
+
listed in `.research/local-marketplaces/conclusion.md`.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Derives README install instructions from the marketplace catalogs a repository already carries.
|
|
3
|
+
// Every command emitted here is traceable to vendor documentation; see
|
|
4
|
+
// .research/local-marketplaces/evidence.md. Do not add a command without an evidence row.
|
|
5
|
+
import { execFileSync } from 'node:child_process'
|
|
6
|
+
import * as fs from 'node:fs'
|
|
7
|
+
import * as path from 'node:path'
|
|
8
|
+
|
|
9
|
+
const argv = process.argv.slice(2)
|
|
10
|
+
const rootFlag = argv.indexOf('--root')
|
|
11
|
+
const root = path.resolve(rootFlag === -1 ? process.cwd() : (argv[rootFlag + 1] ?? process.cwd()))
|
|
12
|
+
const slugFlag = argv.indexOf('--repo')
|
|
13
|
+
|
|
14
|
+
const readJson = (file) => {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
17
|
+
} catch {
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** owner/repo, from the flag, the manifest's repository field, or the git remote. */
|
|
23
|
+
function resolveSlug() {
|
|
24
|
+
if (slugFlag !== -1 && argv[slugFlag + 1]) return argv[slugFlag + 1]
|
|
25
|
+
const manifest = readJson(path.join(root, 'plugin.json'))
|
|
26
|
+
const repo = typeof manifest?.repository === 'string' ? manifest.repository : manifest?.repository?.url
|
|
27
|
+
const fromManifest = repo?.match(/github\.com[/:]([^/]+\/[^/.]+)/)?.[1]
|
|
28
|
+
if (fromManifest) return fromManifest
|
|
29
|
+
try {
|
|
30
|
+
const remote = execFileSync('git', ['-C', root, 'remote', 'get-url', 'origin'], {
|
|
31
|
+
encoding: 'utf8',
|
|
32
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
33
|
+
})
|
|
34
|
+
return remote.match(/github\.com[/:]([^/]+\/[^/.\s]+)/)?.[1] ?? null
|
|
35
|
+
} catch {
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const slug = resolveSlug()
|
|
41
|
+
const repoRef = slug ?? '<owner>/<repo>'
|
|
42
|
+
|
|
43
|
+
const claude = readJson(path.join(root, '.claude-plugin/marketplace.json'))
|
|
44
|
+
const copilot = readJson(path.join(root, '.github/plugin/marketplace.json'))
|
|
45
|
+
// Codex reads its own catalog and the Claude one. Either makes a repository installable from Codex;
|
|
46
|
+
// prefer its own when both exist. See references/runtimes.md (E-CODEX-M10).
|
|
47
|
+
const codexOwn = readJson(path.join(root, '.agents/plugins/marketplace.json'))
|
|
48
|
+
const codex = codexOwn ?? claude
|
|
49
|
+
const cursor = readJson(path.join(root, '.cursor-plugin/marketplace.json'))
|
|
50
|
+
|
|
51
|
+
const targets = []
|
|
52
|
+
const sections = []
|
|
53
|
+
|
|
54
|
+
const pluginNames = (catalog) => (catalog?.plugins ?? []).map((p) => p.name).filter(Boolean)
|
|
55
|
+
|
|
56
|
+
if (claude) {
|
|
57
|
+
const names = pluginNames(claude)
|
|
58
|
+
const installs = (names.length > 0 ? names : ['<plugin>']).map((n) => `/plugin install ${n}@${claude.name}`)
|
|
59
|
+
targets.push('claude-code')
|
|
60
|
+
sections.push(['**Claude Code**', '', '```', `/plugin marketplace add ${repoRef}`, ...installs, '```'].join('\n'))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (copilot) {
|
|
64
|
+
const names = pluginNames(copilot)
|
|
65
|
+
const installs = (names.length > 0 ? names : ['<plugin>']).map((n) => `copilot plugin install ${n}@${copilot.name}`)
|
|
66
|
+
targets.push('copilot-cli')
|
|
67
|
+
sections.push(
|
|
68
|
+
['**GitHub Copilot CLI**', '', '```', `copilot plugin marketplace add ${repoRef}`, ...installs, '```'].join('\n'),
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (codex) {
|
|
73
|
+
const names = pluginNames(codex)
|
|
74
|
+
// Codex installs with `plugin add`; Copilot CLI installs with `plugin install`. Not interchangeable.
|
|
75
|
+
const installs = (names.length > 0 ? names : ['<plugin>']).map((n) => `codex plugin add ${n}@${codex.name}`)
|
|
76
|
+
targets.push('codex')
|
|
77
|
+
sections.push(
|
|
78
|
+
[
|
|
79
|
+
'**Codex**',
|
|
80
|
+
'',
|
|
81
|
+
'```',
|
|
82
|
+
`codex plugin marketplace add ${repoRef}`,
|
|
83
|
+
...installs,
|
|
84
|
+
'```',
|
|
85
|
+
'',
|
|
86
|
+
'Start a new session before using the plugin.',
|
|
87
|
+
].join('\n'),
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (cursor) {
|
|
92
|
+
targets.push('cursor')
|
|
93
|
+
sections.push(
|
|
94
|
+
[
|
|
95
|
+
'**Cursor**',
|
|
96
|
+
'',
|
|
97
|
+
'This repository carries a Cursor catalog, but Cursor has no command that adds it from a',
|
|
98
|
+
'local path: a team admin imports the repository from the Cursor dashboard. To try the plugin',
|
|
99
|
+
'before then, run `npx universal-plugin plugin install --vendor cursor` in a clone, then',
|
|
100
|
+
'reload the window.',
|
|
101
|
+
].join('\n'),
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const markdown = sections.length === 0 ? '' : ['## Install', '', sections.join('\n\n'), ''].join('\n')
|
|
106
|
+
|
|
107
|
+
process.stdout.write(
|
|
108
|
+
`${JSON.stringify({
|
|
109
|
+
root,
|
|
110
|
+
repo: slug,
|
|
111
|
+
repoResolved: slug !== null,
|
|
112
|
+
targets,
|
|
113
|
+
markdown,
|
|
114
|
+
})}\n`,
|
|
115
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runs `universal-plugin marketplace init` from the CLI that ships beside this skill, so catalog
|
|
3
|
+
// generation 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/marketplace.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', 'init')
|
|
11
|
+
await import(join(packageRoot, 'bin', 'universal-plugin.mjs'))
|
|
@@ -9,6 +9,10 @@ Guides adding an already-packaged plugin to a marketplace repo by opening a pull
|
|
|
9
9
|
|
|
10
10
|
**Default marketplace repo:** `cyberuni/marketplace` (adjust if the user targets a different one)
|
|
11
11
|
|
|
12
|
+
Listing a plugin in a shared repository is not the only way to distribute it. A repository can carry
|
|
13
|
+
its own catalog and let users add it directly as a marketplace. That is the `marketplace` skill; the
|
|
14
|
+
two compose, and neither replaces the other.
|
|
15
|
+
|
|
12
16
|
## Overview
|
|
13
17
|
|
|
14
18
|
Publishing has three steps:
|
|
@@ -51,12 +55,10 @@ See `references/vendor-requirements.md` for required fields and hook casing rule
|
|
|
51
55
|
|
|
52
56
|
### 1c. Hook casing check
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Mixed casing causes silent hook failures at runtime.
|
|
58
|
+
`plugin build` owns hook casing: the canonical file is authored in PascalCase, and Cursor's
|
|
59
|
+
`.cursor-plugin/hooks.json` is derived from it. Verify the build ran rather than hand-checking the
|
|
60
|
+
casing, and read its warnings — a handler type the vendor cannot run is dropped, so a hook can be
|
|
61
|
+
absent from a vendor's file by design.
|
|
60
62
|
|
|
61
63
|
### 1d. Skills check (if present)
|
|
62
64
|
|
|
@@ -217,7 +219,7 @@ gh pr create \
|
|
|
217
219
|
## Checklist
|
|
218
220
|
|
|
219
221
|
- [ ] All targeted vendor manifests present and valid
|
|
220
|
-
- [ ]
|
|
222
|
+
- [ ] `plugin build` ran, and its dropped-handler warnings were read
|
|
221
223
|
- [ ] Semver version string
|
|
222
224
|
- [ ] SPDX license identifier
|
|
223
225
|
- [ ] Entry appended to each detected marketplace file
|
|
@@ -233,7 +235,7 @@ Return the PR URL to the user when done.
|
|
|
233
235
|
|
|
234
236
|
| Problem | Fix |
|
|
235
237
|
|---|---|
|
|
236
|
-
| Hook events silently don't fire |
|
|
238
|
+
| Hook events silently don't fire | Re-run `plugin build`; check its warnings for a handler type that vendor cannot run |
|
|
237
239
|
| Codex rejects manifest | `version` and `description` are required by Codex |
|
|
238
240
|
| PR rejected: missing source link | Add `homepage` or `repository` to plugin.json |
|
|
239
241
|
| Name conflict in marketplace | Check existing entries in each marketplace file first |
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
**Manifest path:** `.claude-plugin/plugin.json`
|
|
6
6
|
**Required fields:** `name`
|
|
7
|
-
**Hook casing:** PascalCase (`SessionStart`, `
|
|
7
|
+
**Hook casing:** PascalCase (`SessionStart`, `PreToolUse`, `PostToolUse`, `Stop`) — canonical form
|
|
8
8
|
**Schema:** https://json.schemastore.org/claude-code-plugin-manifest.json
|
|
9
9
|
|
|
10
10
|
## Cursor
|
|
11
11
|
|
|
12
12
|
**Manifest path:** `.cursor-plugin/plugin.json`
|
|
13
13
|
**Required fields:** `name`
|
|
14
|
-
**Hook casing:** camelCase (`sessionStart`, `
|
|
14
|
+
**Hook casing:** camelCase (`sessionStart`, `preToolUse`, `postToolUse`) — derived by `plugin build`
|
|
15
15
|
**Schema:** https://raw.githubusercontent.com/cursor/plugins/main/schemas/plugin.schema.json
|
|
16
16
|
|
|
17
17
|
## Codex
|
|
@@ -24,15 +24,18 @@
|
|
|
24
24
|
|
|
25
25
|
**Manifest path:** `plugin.json` (root of plugin directory)
|
|
26
26
|
**Required fields:** `name`
|
|
27
|
-
**Hook casing:**
|
|
27
|
+
**Hook casing:** either — PascalCase selects its Claude-compatible payload format, so the canonical file serves it
|
|
28
28
|
**Notes:** Also searches `.plugin/plugin.json` as a fallback path
|
|
29
29
|
|
|
30
30
|
## Hook event name reference
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
|
36
|
-
|
|
37
|
-
|
|
|
38
|
-
|
|
|
32
|
+
Author the canonical name; `plugin build` derives Cursor's. Claude Code, Codex, and Copilot CLI read
|
|
33
|
+
the canonical name as authored.
|
|
34
|
+
|
|
35
|
+
| Canonical (Claude Code, Codex, Copilot CLI) | Cursor |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `SessionStart` | `sessionStart` |
|
|
38
|
+
| `SessionEnd` | `sessionEnd` |
|
|
39
|
+
| `PreToolUse` | `preToolUse` |
|
|
40
|
+
| `PostToolUse` | `postToolUse` |
|
|
41
|
+
| `Stop` | `stop` |
|
package/skills/version/SKILL.md
CHANGED
|
@@ -111,7 +111,8 @@ Every guard resolves before the first write, so a failed run leaves the tree unt
|
|
|
111
111
|
| Create, adopt, or change what the plugin declares | `init` |
|
|
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` |
|
|
115
|
+
| List the released plugin in the shared marketplace | `publish-plugin` |
|
|
115
116
|
|
|
116
117
|
## References
|
|
117
118
|
|