You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: add install-info command and buildInstallSnippet API (#18)
* refactor: migrate cursor to the target registry (no behavior change)
Re-checking the originally planned Cursor "fixes" against the vendored
plugin.schema.json / marketplace.schema.json (this repo's own
conformance oracle, already passing against the current shape) showed
they were wrong:
- displayName/category/tags ARE valid plugin.json fields per the
schema (additionalProperties: false, and they're explicitly listed)
— not marketplace-entry-only fields as previously assumed.
- Marketplace `owner` is genuinely optional (required: ["name",
"plugins"] does not include it) — not required as previously
assumed. Moving category/tags to the marketplace entry would have
been actively wrong: entries only allow name/source/description
(additionalProperties: false).
None of that is changed here. What this commit actually does:
- Migrates cursor onto PluginTargetDefinition, preserving every
existing field and behavior (verified by the vendored-schema
conformance test staying green).
- Fixes one genuine, low-risk issue: the manifest builder's own
hardcoded default-components list could diverge from this target's
actual defaultComponents (components.ts). Replaced both with one
list of schema-valid pointer fields, checked directly against the
plugin's real resolved componentDirs — eliminates the divergence
risk with no observable behavior change (confirmed via a new test
exercising the one case that could have differed: an explicit
`components: [...]` override).
- Ports update-check's hook-injection into the new shared engine
(src/targets/engine.ts), which previously only existed in the
legacy emitCursor/emitClaude path — migrating cursor without this
would have silently dropped update-check support.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: apply the per-plugin version override in cursor's manifest
buildPluginManifest used the target-level `version` param directly
instead of `pluginConfig.version ?? version`, silently dropping a
per-plugin version override — a real regression from the pre-migration
behavior, caught by porting the equivalent test from the claude
migration (no test previously covered this for cursor specifically).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: migrate claude to the target registry (no behavior change)
Ports emitClaude/validateClaude into src/targets/claude.ts as a
PluginTargetDefinition, verified directly against `claude plugin
validate --strict`: a minimal manifest with only `name` fails that
check, confirming the existing version/description/author.name
requirements already match the real CLI rather than over-constraining
it.
Applies the per-plugin version override in buildPluginManifest
(pluginConfig.version ?? version) up front, matching the identical fix
just made to cursor's copy of this pattern.
* fix: correct Codex plugin output for the target registry
Ports emitCodex/validateCodex into src/targets/codex.ts as a
PluginTargetDefinition, correcting the plugin format's real shape —
re-verified directly against developers.openai.com/codex/plugins/build
(fetched twice, independently, for consistency) since Codex has no
CLI validator or vendored schema to check against:
- plugin.json requires only "name"; version/description/author etc.
are optional. The previous validator wrongly required version and
description.
- Every marketplace entry needs policy.installation,
policy.authentication, and category — previously unvalidated, so an
incomplete entry shipped silently. pluginpack can't infer these, so
the base entry stays guess-free and validateOutput now errors
clearly when an author never supplies them via the per-plugin
`entry` passthrough (already how the existing conformance fixture
supplies them).
- A marketplace entry's source is a bare string only for local
plugins (the only shape pluginpack itself ever emits); url/git-subdir/npm
sources are structured objects with an inner "source" discriminator.
validateMarketplaceEntry now accepts either shape instead of the
previously shared, string-only validator.
- plugin.json now declares a `hooks` pointer when hooks/ is present,
matching skills/mcpServers (previously only skills/mcpServers were
declared, so hooks were emitted but never referenced).
Updates CONFORMANCE.md's Codex section, which had pinned a stale,
bare-string-only shape from an earlier doc retrieval.
* fix: restore deeper hooks.json validation dropped during target migration
Every migrated target's validateOutput calls validateHooksShape (added
in the registry scaffold), which only checked that hooks.json has a
"hooks" object — narrower than the legacy per-target validateHooks it
replaced, which also required each event's entries to be an array,
rejected empty "command" strings, and errored if a command referenced
the generated update-check script without that script actually being
present. None of that depth had a regression test, so the narrowing
was silent.
Ports the full check into validateHooksShape once, so every target
that already calls it (all 5, post-migration) regains it for free
instead of needing the fix repeated per target file.
* refactor: delete legacy per-target emitters/validators now that all targets are migrated
All 5 targets (copilot, antigravity, cursor, claude, codex) now have a
PluginTargetDefinition in src/targets/registry.ts, so the legacy
fallback path adapters.ts existed for is dead:
- Deletes src/targets.ts and src/validate.ts entirely (their only
consumer was adapters.ts's legacyAdapters map).
- Moves withRootFiles into src/targets/engine.ts, next to the artifact
helper it depends on.
- Tightens the registry's type from Partial<Record<TargetName, ...>>
to Record<TargetName, ...> now that every target has an entry — a
new TargetName won't build until it has a registry entry, the same
exhaustiveness guarantee the deleted legacyAdapters map used to
provide.
- Simplifies adapters.ts to a thin emitTarget/validateOutput wrapper
around the registry + engine, dropping the now-pointless
TargetAdapter/adapters indirection that existed only to switch
between legacy and registry per target.
- Removes targetDefaultComponents/resolveTargetComponents from
components.ts (superseded by each target's own defaultComponents).
- Updates CLAUDE.md's Architecture/Targets sections and a couple of
stale doc-comment references to match.
* docs: note the shared hooks validation depth in CONFORMANCE.md
Cross-references the validateHooksShape fix from the prior commit —
what it checks and where it's shared from, for the conformance doc's
own "what's actually verified and how" mandate.
* feat: add install-info command and buildInstallSnippet API
Adds the install-snippet feature designed alongside the target
registry migration: every PluginTargetDefinition already carries an
installSnippet (populated as each target migrated), so this wires it
up to a public API and CLI command rather than introducing new
per-target data.
- src/install-snippet.ts: buildInstallSnippet, getInstallSnippetCitation,
getSupportedInstallTargets, getUnsupportedInstallTargets — thin
wrappers around each target's registry entry.
- New `pluginpack install-info [--target <t>] [--json]` CLI command,
defaulting to every configured target (matching `build`'s pattern).
Resolves each plugin's path via that target's own resolvePluginPath
for accuracy (antigravity's snippet is the one that consumes it).
- targets.<name>.repository config field (falls back to
metadata.repository, mirroring updateCheck.repository's existing
fallback) — "which repo does this target's output live in."
- Exported from the package entry for programmatic use.
- README: new "Install Snippet" section + Configuration Reference row
+ Programmatic API table rows. CONFORMANCE.md: "Install-snippet
facts" table with per-target doc citations.
Tests: tests/install-snippet.test.ts locks in the exact snippet text
per target; tests/conformance.test.ts adds CLI end-to-end coverage
(default/--target/--json/missing-repository error) via the real built
binary.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|`cursor`| No CLI equivalent exists. Prints the repo URL, pasted into Dashboard → Plugins → Team Marketplaces → "Import from Repo." |<https://cursor.com/docs/plugins>| 2026-07-25 |
114
+
115
+
`claude`'s two-step sequence is not symmetric with `codex`/`copilot`'s shell
116
+
commands: `/plugin marketplace add` is slash-only inside an active Claude Code
117
+
session, with no shell equivalent — but once a marketplace is already added,
118
+
`claude plugin install <name>@<marketplace>` does work as a standalone shell
119
+
command, surfaced as a secondary `note`.
120
+
121
+
Every target resolves to `userConfigurable: true` today;
122
+
`getUnsupportedInstallTargets()` returns `[]`. The `false` branch of the
123
+
`InstallSnippet` union exists for forward-compatibility, not because any
124
+
target needs it now.
125
+
99
126
## Refreshing vendored schemas
100
127
101
128
The Cursor schemas are pinned copies. To update them, re-fetch from the source
Copy file name to clipboardExpand all lines: README.md
+62-20Lines changed: 62 additions & 20 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -317,6 +317,17 @@ Disable for a single plugin with `updateCheck: false` on that plugin. Configurin
317
317
318
318
Like MCP config, the generated hook is wired in regardless of a plugin's `components` selection: on `cursor`, the manifest's `hooks` field is set even if `components` doesn't include `"hooks"`, since the check itself is a separate opt-in from which source-authored component dirs get emitted.
319
319
320
+
## Install Snippet
321
+
322
+
Once a target's output is pushed to a repo, `pluginpack install-info` prints the real, doc-verified command or URL a user needs to add that marketplace — one per configured target:
323
+
324
+
```bash
325
+
pluginpack install-info
326
+
pluginpack install-info --target claude
327
+
```
328
+
329
+
The repo comes from `targets.<name>.repository`, defaulting to `metadata.repository` (an error if neither is set) — the same fallback `updateCheck.repository` uses. Every target today resolves to a real command except `cursor`, which has no CLI equivalent: it prints the repo URL and a note to paste it into Cursor's Dashboard under Team Marketplaces. See `CONFORMANCE.md`'s "Install-snippet facts" section for the doc citation behind each target's snippet.
330
+
320
331
## Target Overrides
321
332
322
333
Skill files are not always perfectly portable. When one app needs different frontmatter or content, add a target override next to the base file:
@@ -395,17 +406,18 @@ To publish a repo-root file (for example a README authored once in the source re
395
406
396
407
**`targets.<name>`** — `<name>` is one of `cursor`, `claude`, `antigravity`, `copilot`, `codex`.
0 commit comments