universal-plugin 0.17.0 → 0.18.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/CHANGELOG.md +28 -0
- package/agents/agentskills-specialist.md +6 -11
- package/com.github.copilot/agents/agentskills-specialist.agent.md +6 -11
- package/dist/cli.mjs +164 -12
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/readme.md +1 -1
- package/references/plugin-design.md +19 -33
- package/references/slash-invocation.md +2 -2
- package/skills/adopt-upx/scripts/rewrite-upx.mjs +1 -1
- package/skills/build-plugin/SKILL.md +2 -3
- package/skills/build-plugin/scripts/build.mjs +1 -1
- package/skills/doctor-universal-plugin/README.md +1 -1
- package/skills/doctor-universal-plugin/SKILL.md +18 -6
- package/skills/doctor-universal-plugin/scripts/doctor.mjs +225 -2
- package/skills/init-universal-plugin/README.md +1 -1
- package/skills/init-universal-plugin/SKILL.md +62 -3
- package/skills/init-universal-plugin/references/create.md +10 -7
- package/skills/init-universal-plugin/references/detection.md +1 -1
- package/skills/init-universal-plugin/references/frontmatter.md +1 -1
- package/skills/init-universal-plugin/references/release-tools.md +272 -0
- package/skills/init-universal-plugin/references/standard.md +3 -3
- package/skills/init-universal-plugin/references/vendors/copilot-cli.md +3 -0
- package/skills/init-universal-plugin/scripts/init.mjs +1 -1
- package/skills/marketplace/scripts/add.mjs +1 -1
- package/skills/marketplace/scripts/install-docs.mjs +1 -1
- package/skills/marketplace/scripts/marketplace.mjs +1 -1
- package/skills/marketplace/scripts/update.mjs +1 -1
- package/skills/marketplace/scripts/validate.mjs +1 -1
- package/skills/migrate-plugin/SKILL.md +1 -2
- package/skills/publish-plugin/SKILL.md +7 -6
- package/skills/version/README.md +7 -3
- package/skills/version/SKILL.md +129 -10
- package/skills/version/scripts/version.mjs +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Research and design toolkit for building universal AI coding agent plugins that work across Claude Code, Cursor, Codex, and GitHub Copilot CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "unional"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Research and design toolkit for building universal AI coding agent plugins that work across Claude Code, Cursor, Codex, and GitHub Copilot CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "unional"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Research and design toolkit for building universal AI coding agent plugins that work across Claude Code, Cursor, Codex, and GitHub Copilot CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "unional"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# universal-plugin
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 760b3d7: `doctor` checks semantic-release wiring. It reports `semantic-release-no-stamp` when the semantic-release config has no `@semantic-release/exec` `prepareCmd` running `plugin version`, and `semantic-release-npm-unshipped` when a plugin that ships to no npm package still runs `@semantic-release/npm`, listed or through the default plugins list.
|
|
8
|
+
- c7739a2: The `init-universal-plugin` skill now wires release-it and release-please when the survey finds them or the user picks them, once the user approves. For release-it it adds an `after:bump` hook running `plugin version ${version}`, sets `"npm": false` for a plugin that ships to no npm package, and offers to tag the current commit with the plugin's version when the repository has no tag, since release-it then reads the current version from the tag. For release-please it writes `release-please-config.json` (`release-type: simple` with an `extra-files` entry for `plugin.json`, or `release-type: node` for an npm package) and seeds `.release-please-manifest.json` with the current version, and it hands the user the workflow step that restamps the release PR. `plugin init` gets no new flag.
|
|
9
|
+
- 9f5f59a: The `init-universal-plugin` skill now wires semantic-release when the survey finds it or the user picks it. Once the user approves, it adds `@semantic-release/exec` running `plugin version ${nextRelease.version}` in `prepare`, and `@semantic-release/git` to commit the stamped files back. For a plugin that ships to no npm package it drops `@semantic-release/npm`. For release-it and release-please, the skill names the stamp each tool must call. The wiring stays in the skill, and `plugin init` gets no new flag.
|
|
10
|
+
- 11c42a6: A plugin that ships to no npm package can now be released with changesets. In a changesets repository with no `package.json` at the plugin root, `plugin init` writes a minimal private one (`name`, `"private": true`, `version`), wires the root `version` script, and adds `"release": "changeset publish"`; `--no-changesets` opts out. `plugin version --from-package` with no package in a changesets repository names that `package.json` and the `privatePackages` setting. `doctor` reports `changesets-no-package`, `changesets-private-untagged`, and `changesets-no-publish-script`.
|
|
11
|
+
- fa31d36: release-it is now a documented release model. With `"npm": false` release-it reads the version from the latest git tag and needs no `package.json`; an `after:bump` hook running `universal-plugin plugin version ${version}` stamps it before the release commit and tag. `doctor` reports `release-it-no-stamp` for a release-it config with no hook running `plugin version`.
|
|
12
|
+
- bea7278: `doctor` reports `release-please-restamp-no-ci` when a workflow pushes the `--from-manifest` restamp with the default `GITHUB_TOKEN`. That push starts no workflow run, so the release PR's checks never run on its head commit and a required check blocks the merge. The `version` skill's release-please workflow now mints a GitHub App token with `actions/create-github-app-token` and passes it to release-please and to `actions/checkout`.
|
|
13
|
+
- 8e20a28: A plugin that ships to no npm package can now be released with release-please. `plugin version --from-manifest` restamps every place at the version `plugin.json` already holds, for a CI step on the release PR after `extra-files` bumps `plugin.json`; it is refused where `packagePath` is declared. `doctor` reports `release-please-no-restamp` when a release-please config bumps `plugin.json` and no workflow runs `plugin version`. The `version` skill gives the config and the workflow.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 7e8b2e1: `doctor` no longer reports `unreleased-content` for a non-npm plugin that a release tool versions. A plugin released with changesets (through a private `package.json`), release-please (its config bumps `plugin.json`), semantic-release, or release-it is skipped like a plugin that declares `packagePath`, because the tool picks the next number at release time.
|
|
18
|
+
|
|
19
|
+
## 0.17.1
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- 7c951cc: `plugin bundle` and `publish sync-version` no longer warn that they are removed "in the next minor release", a release that has already shipped. The warning now says they have been deprecated since 0.14.0 and will be removed in a later release.
|
|
24
|
+
- 621d36d: The `publish-plugin` skill's clone, fork and pull request commands now use the marketplace repo the user chose instead of always naming `cyberuni/cyberplace`.
|
|
25
|
+
- 621d36d: The skill references no longer contradict each other on how the plugin is built and shipped: Copilot CLI reads the canonical `plugin.json` directly, a local install goes through `plugin install` rather than a hand-written symlink, `anthropics/claude-plugins-official` takes no pull requests, the build writes no Cursor command or Codex prompt, `packagePath` lives in `.agents/universal-plugin.json`, and `plugin build` has a `--check` flag.
|
|
26
|
+
- 621d36d: The plugin references and the `agentskills-specialist` agent now say Copilot CLI hooks keep the canonical PascalCase event names, which is what the build writes, instead of camelCase.
|
|
27
|
+
- 621d36d: The skills now name skills that exist: `buddy-agent-harness:init-buddy-agent-harness`, `buddy-agent-harness:doctor-buddy-agent-harness`, `buddy-changesets:changesets`, `aced:improve-skill` and its `validate.mjs` script, and the `marketplace` skill for a repository-local catalog.
|
|
28
|
+
- 621d36d: The plugin references now name `mcp.json` as a plugin's MCP config, the fixed location the build reads, and drop the rule that it must be a symlink to `.mcp.json`.
|
|
29
|
+
- 621d36d: The plugin references and the `agentskills-specialist` agent drop claims nothing backs: a fixed `SKILL.md` section template, a 6,000-character limit for every runtime, a `skill.json`, support for Zed, Continue.dev and Cline, `$ARGUMENTS` expanding everywhere, and dated "next minor release" wording. They also call references references, not governances.
|
|
30
|
+
|
|
3
31
|
## 0.17.0
|
|
4
32
|
|
|
5
33
|
### Minor Changes
|
|
@@ -14,7 +14,7 @@ model: opus
|
|
|
14
14
|
# AgentSkills Specialist
|
|
15
15
|
|
|
16
16
|
## Role
|
|
17
|
-
You are an expert AgentSkills engineer who designs, audits, and improves skills across Claude Code, Cursor, Codex, GitHub Copilot CLI, Windsurf, Zed, Continue.dev,
|
|
17
|
+
You are an expert AgentSkills engineer who designs, audits, and improves skills across Claude Code, Cursor, Codex, and GitHub Copilot CLI, the runtimes `plugin build` targets. Flag a skill meant for Windsurf as needing conversion, and Zed, Aider, Continue.dev, or Cline as incompatible.
|
|
18
18
|
|
|
19
19
|
## Core Responsibilities
|
|
20
20
|
|
|
@@ -32,14 +32,9 @@ skills/<name>/SKILL.md # Universal skill definition (primary)
|
|
|
32
32
|
.agents/skills/<name>/SKILL.md # Project-level extension (if present)
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
A `SKILL.md`
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- **Inputs**: What context or parameters the skill consumes.
|
|
39
|
-
- **Outputs / Side Effects**: What the skill produces or changes.
|
|
40
|
-
- **Steps**: Numbered, concrete procedural steps.
|
|
41
|
-
- **Runtime Notes**: Any known incompatibilities or runtime-specific behavior.
|
|
42
|
-
- **Examples**: At least one concrete invocation example.
|
|
35
|
+
A `SKILL.md` should make clear what the skill does, when it is invoked, what it consumes and
|
|
36
|
+
produces, and the steps it follows, plus runtime limitations and an example where they help. These
|
|
37
|
+
are questions the body answers, not required section names.
|
|
43
38
|
|
|
44
39
|
## Auditing Workflow
|
|
45
40
|
|
|
@@ -75,7 +70,7 @@ For skill creation or edits, write the full `SKILL.md` content directly — no p
|
|
|
75
70
|
## Key Research Context
|
|
76
71
|
|
|
77
72
|
- A root `plugin.json` on Agent Plugins Spec 1.0.0 is the universal manifest; `skills/<name>/SKILL.md` is the universal skill entry point.
|
|
78
|
-
- Hook event naming is the sharpest incompatibility: PascalCase (Claude Code, Codex), camelCase (Cursor
|
|
73
|
+
- Hook event naming is the sharpest incompatibility: PascalCase (Claude Code, Codex, Copilot CLI), camelCase (Cursor), snake_case (Windsurf).
|
|
79
74
|
- Only Claude Code and Cursor publish machine-readable JSON Schemas.
|
|
80
75
|
|
|
81
76
|
## Out of Scope
|
|
@@ -87,7 +82,7 @@ For skill creation or edits, write the full `SKILL.md` content directly — no p
|
|
|
87
82
|
## Quality Gates
|
|
88
83
|
|
|
89
84
|
Before declaring any skill complete:
|
|
90
|
-
- [ ] SKILL.md is present and
|
|
85
|
+
- [ ] SKILL.md is present and answers the questions above.
|
|
91
86
|
- [ ] No runtime-specific assumptions are baked into the universal layer.
|
|
92
87
|
- [ ] Project-level `.agents/skills/<name>/SKILL.md` extension is merged, not duplicated.
|
|
93
88
|
- [ ] Examples are concrete and runnable.
|
|
@@ -14,7 +14,7 @@ model: opus
|
|
|
14
14
|
# AgentSkills Specialist
|
|
15
15
|
|
|
16
16
|
## Role
|
|
17
|
-
You are an expert AgentSkills engineer who designs, audits, and improves skills across Claude Code, Cursor, Codex, GitHub Copilot CLI, Windsurf, Zed, Continue.dev,
|
|
17
|
+
You are an expert AgentSkills engineer who designs, audits, and improves skills across Claude Code, Cursor, Codex, and GitHub Copilot CLI, the runtimes `plugin build` targets. Flag a skill meant for Windsurf as needing conversion, and Zed, Aider, Continue.dev, or Cline as incompatible.
|
|
18
18
|
|
|
19
19
|
## Core Responsibilities
|
|
20
20
|
|
|
@@ -32,14 +32,9 @@ skills/<name>/SKILL.md # Universal skill definition (primary)
|
|
|
32
32
|
.agents/skills/<name>/SKILL.md # Project-level extension (if present)
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
A `SKILL.md`
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- **Inputs**: What context or parameters the skill consumes.
|
|
39
|
-
- **Outputs / Side Effects**: What the skill produces or changes.
|
|
40
|
-
- **Steps**: Numbered, concrete procedural steps.
|
|
41
|
-
- **Runtime Notes**: Any known incompatibilities or runtime-specific behavior.
|
|
42
|
-
- **Examples**: At least one concrete invocation example.
|
|
35
|
+
A `SKILL.md` should make clear what the skill does, when it is invoked, what it consumes and
|
|
36
|
+
produces, and the steps it follows, plus runtime limitations and an example where they help. These
|
|
37
|
+
are questions the body answers, not required section names.
|
|
43
38
|
|
|
44
39
|
## Auditing Workflow
|
|
45
40
|
|
|
@@ -75,7 +70,7 @@ For skill creation or edits, write the full `SKILL.md` content directly — no p
|
|
|
75
70
|
## Key Research Context
|
|
76
71
|
|
|
77
72
|
- A root `plugin.json` on Agent Plugins Spec 1.0.0 is the universal manifest; `skills/<name>/SKILL.md` is the universal skill entry point.
|
|
78
|
-
- Hook event naming is the sharpest incompatibility: PascalCase (Claude Code, Codex), camelCase (Cursor
|
|
73
|
+
- Hook event naming is the sharpest incompatibility: PascalCase (Claude Code, Codex, Copilot CLI), camelCase (Cursor), snake_case (Windsurf).
|
|
79
74
|
- Only Claude Code and Cursor publish machine-readable JSON Schemas.
|
|
80
75
|
|
|
81
76
|
## Out of Scope
|
|
@@ -87,7 +82,7 @@ For skill creation or edits, write the full `SKILL.md` content directly — no p
|
|
|
87
82
|
## Quality Gates
|
|
88
83
|
|
|
89
84
|
Before declaring any skill complete:
|
|
90
|
-
- [ ] SKILL.md is present and
|
|
85
|
+
- [ ] SKILL.md is present and answers the questions above.
|
|
91
86
|
- [ ] No runtime-specific assumptions are baked into the universal layer.
|
|
92
87
|
- [ ] Project-level `.agents/skills/<name>/SKILL.md` extension is merged, not duplicated.
|
|
93
88
|
- [ ] Examples are concrete and runnable.
|
package/dist/cli.mjs
CHANGED
|
@@ -8608,10 +8608,31 @@ function ownPackage(state) {
|
|
|
8608
8608
|
json: state.rootPackageJson ?? null
|
|
8609
8609
|
};
|
|
8610
8610
|
}
|
|
8611
|
+
/** Changesets releases only what has a `package.json`, so a plugin that ships to no npm package
|
|
8612
|
+
* needs a private one to carry its version (#234). Names the file and the config that makes
|
|
8613
|
+
* `changeset publish` tag it. */
|
|
8614
|
+
function changesetsPackageHint(state) {
|
|
8615
|
+
const name = typeof state.manifest?.name === "string" ? state.manifest.name : "<plugin-name>";
|
|
8616
|
+
const version = typeof state.manifest?.version === "string" ? state.manifest.version : "0.0.0";
|
|
8617
|
+
return [
|
|
8618
|
+
"changesets releases only a package, so a plugin that is not published to npm needs a private package.json at the plugin root:",
|
|
8619
|
+
` ${JSON.stringify({
|
|
8620
|
+
name,
|
|
8621
|
+
private: true,
|
|
8622
|
+
version
|
|
8623
|
+
})}`,
|
|
8624
|
+
"and .changeset/config.json needs \"privatePackages\": { \"version\": true, \"tag\": true } so changeset publish tags the release.",
|
|
8625
|
+
"plugin init writes the package.json when it finds .changeset/."
|
|
8626
|
+
].join("\n");
|
|
8627
|
+
}
|
|
8611
8628
|
/** Reads the version a release tool already wrote into the package. */
|
|
8612
8629
|
function versionFromPackage(state) {
|
|
8613
8630
|
const pkg = ownPackage(state);
|
|
8614
|
-
if (pkg.json === null)
|
|
8631
|
+
if (pkg.json === null) {
|
|
8632
|
+
if (state.packagePath !== null) throw new Error(`No package.json found at packagePath "${state.packagePath}"`);
|
|
8633
|
+
const message = "No package.json to read the version from: packagePath is not set in .agents/universal-plugin.json and the plugin root has no package.json";
|
|
8634
|
+
throw new Error(state.changesets ? `${message}\n${changesetsPackageHint(state)}` : message);
|
|
8635
|
+
}
|
|
8615
8636
|
const version = pkg.json["version"];
|
|
8616
8637
|
if (typeof version !== "string" || version.length === 0) throw new Error(`No version found in ${pkg.path}`);
|
|
8617
8638
|
if (version === "0.0.0-development") throw new Error(`${pkg.path} holds the placeholder ${UNSTAMPED_VERSION} — pass the release's version explicitly (plugin version \${nextRelease.version})`);
|
|
@@ -8619,15 +8640,33 @@ function versionFromPackage(state) {
|
|
|
8619
8640
|
if (valid === null) throw new Error(`Version "${version}" in ${pkg.path} is not valid semver`);
|
|
8620
8641
|
return valid;
|
|
8621
8642
|
}
|
|
8622
|
-
/**
|
|
8643
|
+
/** Reads the version a release tool already wrote into the canonical manifest (#238). Under a
|
|
8644
|
+
* declared `packagePath` the package owns the number, so the manifest is not a source there. */
|
|
8645
|
+
function versionFromManifest(state, from) {
|
|
8646
|
+
if (state.packagePath !== null) {
|
|
8647
|
+
const pkg = joinRelative(state.packagePath, "package.json");
|
|
8648
|
+
throw new Error(`packagePath is declared, so ${pkg} owns the number, not plugin.json — run plugin version --from-package`);
|
|
8649
|
+
}
|
|
8650
|
+
if (from === null) throw new Error("plugin.json has no version to restamp — pass an explicit version (e.g. 0.1.0) to set the first one");
|
|
8651
|
+
if (from === "0.0.0-development") throw new Error(`plugin.json holds the placeholder ${UNSTAMPED_VERSION} — pass the release's version explicitly`);
|
|
8652
|
+
const valid = import_semver.valid(from);
|
|
8653
|
+
if (valid === null) throw new Error(`Version "${from}" in plugin.json is not valid semver`);
|
|
8654
|
+
return valid;
|
|
8655
|
+
}
|
|
8656
|
+
/** Picks the number. Exactly one source: a bump argument, `--from-package`, or `--from-manifest`. Under ADR-0010 §2 a
|
|
8623
8657
|
* plugin with `packagePath` gets its number from the package, so a release type there is refused —
|
|
8624
8658
|
* the explicit and `--from-package` forms stay, because release tools call them. */
|
|
8625
8659
|
function resolveVersion(state, from, opts) {
|
|
8660
|
+
if (opts.fromPackage && opts.fromManifest) throw new Error("Pass --from-package or --from-manifest, not both");
|
|
8661
|
+
if (opts.fromManifest) {
|
|
8662
|
+
if (opts.bump !== void 0) throw new Error(`Pass a version or --from-manifest, not both (got "${opts.bump}")`);
|
|
8663
|
+
return versionFromManifest(state, from);
|
|
8664
|
+
}
|
|
8626
8665
|
if (opts.fromPackage) {
|
|
8627
8666
|
if (opts.bump !== void 0) throw new Error(`Pass a version or --from-package, not both (got "${opts.bump}")`);
|
|
8628
8667
|
return versionFromPackage(state);
|
|
8629
8668
|
}
|
|
8630
|
-
if (opts.bump === void 0) throw new Error(`Pass a release type (${RELEASE_TYPES.join(", ")}), an explicit version, or --from-
|
|
8669
|
+
if (opts.bump === void 0) throw new Error(`Pass a release type (${RELEASE_TYPES.join(", ")}), an explicit version, --from-package, or --from-manifest`);
|
|
8631
8670
|
if (state.packagePath !== null && isReleaseType(opts.bump)) {
|
|
8632
8671
|
const pkg = joinRelative(state.packagePath, "package.json");
|
|
8633
8672
|
throw new Error(`packagePath is declared, so the release picks the number — bump ${pkg} with your release tool (e.g. changeset version), then run plugin version --from-package`);
|
|
@@ -8896,6 +8935,10 @@ function gatherMcpFiles(root, declaration) {
|
|
|
8896
8935
|
}
|
|
8897
8936
|
return files;
|
|
8898
8937
|
}
|
|
8938
|
+
/** Whether `.changeset/` sits at the plugin root or at the repository root above it. */
|
|
8939
|
+
function hasChangesets(root) {
|
|
8940
|
+
return [root, gitToplevel(root)].some((dir) => dir !== void 0 && fs$1.existsSync(path$1.join(dir, ".changeset")));
|
|
8941
|
+
}
|
|
8899
8942
|
function toRelative(root, abs) {
|
|
8900
8943
|
return path$1.relative(root, abs).split(path$1.sep).join("/");
|
|
8901
8944
|
}
|
|
@@ -8921,7 +8964,8 @@ const realVersionFs = {
|
|
|
8921
8964
|
})),
|
|
8922
8965
|
workspace: Object.fromEntries(discoverWorkspace(root)),
|
|
8923
8966
|
mcpFiles: gatherMcpFiles(root, uext["mcpServers"]),
|
|
8924
|
-
pinsMapExists: fs$1.existsSync(path$1.join(root, PINS_MAP_PATH))
|
|
8967
|
+
pinsMapExists: fs$1.existsSync(path$1.join(root, PINS_MAP_PATH)),
|
|
8968
|
+
changesets: hasChangesets(root)
|
|
8925
8969
|
};
|
|
8926
8970
|
},
|
|
8927
8971
|
apply(root, plan) {
|
|
@@ -8987,7 +9031,7 @@ const NEXT_STEP$1 = "→ review and commit the pinned skills\n";
|
|
|
8987
9031
|
function bundleCommand() {
|
|
8988
9032
|
const cmd = new Command("bundle").description("Deprecated: use `plugin version`, which stamps the skill pins");
|
|
8989
9033
|
cmd.option("--dry-run", "Resolve and report pins without writing them").option("--full", "Show every pins row without truncation").option("--format <format>", "Output format: json or toon (default: toon)").option("--runner <runner>", "Runner word to emit on rewritten refs: npx or upx (default: preserve each ref's runner)").addOption(ROOT_OPTION).addHelpText("after", "\nExample:\n $ universal-plugin plugin bundle --dry-run --full\n").action((opts) => {
|
|
8990
|
-
process.stderr.write("warn: plugin bundle is deprecated and will be removed in
|
|
9034
|
+
process.stderr.write("warn: plugin bundle is deprecated since 0.14.0 and will be removed in a later release — use plugin version, which stamps the skill pins\n");
|
|
8991
9035
|
try {
|
|
8992
9036
|
const runner = opts.runner;
|
|
8993
9037
|
if (runner !== void 0 && runner !== "npx" && runner !== "upx") throw new Error(`error: unknown --runner value "${runner}" (expected npx or upx)`);
|
|
@@ -9164,9 +9208,22 @@ function gatherChangesets(root, repo) {
|
|
|
9164
9208
|
if (!fs$1.existsSync(path$1.join(workspace, ".changeset"))) return void 0;
|
|
9165
9209
|
return {
|
|
9166
9210
|
pluginPath,
|
|
9167
|
-
packageJson: readJson$1(packageJsonPath(workspace))
|
|
9211
|
+
packageJson: readJson$1(packageJsonPath(workspace)),
|
|
9212
|
+
config: readJsonSafe(path$1.join(workspace, ".changeset", "config.json"))
|
|
9168
9213
|
};
|
|
9169
9214
|
}
|
|
9215
|
+
function readJsonSafe(file) {
|
|
9216
|
+
try {
|
|
9217
|
+
return readJson$1(file);
|
|
9218
|
+
} catch {
|
|
9219
|
+
return null;
|
|
9220
|
+
}
|
|
9221
|
+
}
|
|
9222
|
+
/** The version an existing `plugin.json` carries, which a new private package.json is seeded from. */
|
|
9223
|
+
function readManifestVersion(root) {
|
|
9224
|
+
const version = readJsonSafe(manifestPath(root))?.version;
|
|
9225
|
+
return typeof version === "string" && version !== "" ? version : void 0;
|
|
9226
|
+
}
|
|
9170
9227
|
function writeJson(file, value) {
|
|
9171
9228
|
const existing = fs$1.existsSync(file) ? fs$1.readFileSync(file, "utf8") : void 0;
|
|
9172
9229
|
fs$1.writeFileSync(file, formatJson(value, {
|
|
@@ -9179,6 +9236,7 @@ const realInitFs = {
|
|
|
9179
9236
|
const repo = gatherRepo(root);
|
|
9180
9237
|
return {
|
|
9181
9238
|
manifestExists: fs$1.existsSync(manifestPath(root)),
|
|
9239
|
+
manifestVersion: readManifestVersion(root),
|
|
9182
9240
|
packageJson: readJson$1(packageJsonPath(root)),
|
|
9183
9241
|
repo,
|
|
9184
9242
|
changesets: gatherChangesets(root, repo)
|
|
@@ -9284,6 +9342,89 @@ function wireChangesets(pkg, pluginPath, cliVersion) {
|
|
|
9284
9342
|
};
|
|
9285
9343
|
return { content: next };
|
|
9286
9344
|
}
|
|
9345
|
+
const CHANGESET_PUBLISH = "changeset publish";
|
|
9346
|
+
/** The version a private package starts at when the manifest carries none to seed it from. */
|
|
9347
|
+
const FIRST_VERSION = "0.0.0";
|
|
9348
|
+
/** The package.json a plugin that ships to no npm package needs before changesets can release it
|
|
9349
|
+
* (#234). Changesets finds only packages, and `@changesets/apply-release-plan` always writes
|
|
9350
|
+
* `<dir>/package.json`, so this file is what carries the version. `private` keeps
|
|
9351
|
+
* `changeset publish` to a git tag; no `files`, `main`, or `exports`, because nothing is packed. */
|
|
9352
|
+
function privatePackage(name, version) {
|
|
9353
|
+
return {
|
|
9354
|
+
name,
|
|
9355
|
+
private: true,
|
|
9356
|
+
version
|
|
9357
|
+
};
|
|
9358
|
+
}
|
|
9359
|
+
/** Adds `"release": "changeset publish"`, the script changesets/action runs as its publish step.
|
|
9360
|
+
* For a private package it creates the git tag the GitHub release hangs on. A custom `release`
|
|
9361
|
+
* script is left alone with a note. Returns `null` content when nothing changes. */
|
|
9362
|
+
function wireReleaseScript(pkg) {
|
|
9363
|
+
const scripts = typeof pkg.scripts === "object" && pkg.scripts !== null ? pkg.scripts : {};
|
|
9364
|
+
const script = typeof scripts.release === "string" ? scripts.release : void 0;
|
|
9365
|
+
if (script?.includes(CHANGESET_PUBLISH)) return { content: null };
|
|
9366
|
+
if (script !== void 0) return {
|
|
9367
|
+
content: null,
|
|
9368
|
+
note: `changesets: the root "release" script is custom; make sure it runs "${CHANGESET_PUBLISH}" so a release is tagged`
|
|
9369
|
+
};
|
|
9370
|
+
return { content: {
|
|
9371
|
+
...pkg,
|
|
9372
|
+
scripts: {
|
|
9373
|
+
...scripts,
|
|
9374
|
+
release: CHANGESET_PUBLISH
|
|
9375
|
+
}
|
|
9376
|
+
} };
|
|
9377
|
+
}
|
|
9378
|
+
/** Whether `.changeset/config.json` has changesets bump and tag private packages. Changesets bumps
|
|
9379
|
+
* them by default but tags them only when `privatePackages.tag` is `true`. */
|
|
9380
|
+
function tagsPrivatePackages(config) {
|
|
9381
|
+
const setting = config?.privatePackages;
|
|
9382
|
+
if (typeof setting !== "object" || setting === null) return false;
|
|
9383
|
+
const { version, tag } = setting;
|
|
9384
|
+
return version !== false && tag === true;
|
|
9385
|
+
}
|
|
9386
|
+
/** Wires a plugin that ships to no npm package for a changesets release: a private package.json at
|
|
9387
|
+
* the plugin root, and the `version` and `release` scripts at the workspace root. Appends to `rows`
|
|
9388
|
+
* and `notes` in place. */
|
|
9389
|
+
function planPrivateRelease(opts, name, version, changesets, rows, notes) {
|
|
9390
|
+
const sameFile = changesets.pluginPath === "";
|
|
9391
|
+
let packageJson = privatePackage(name, version);
|
|
9392
|
+
rows.push({
|
|
9393
|
+
path: "package.json",
|
|
9394
|
+
action: "created"
|
|
9395
|
+
});
|
|
9396
|
+
if (sameFile) packageJson = {
|
|
9397
|
+
...packageJson,
|
|
9398
|
+
scripts: { version: CHANGESET_VERSION }
|
|
9399
|
+
};
|
|
9400
|
+
else notes.push(`changesets: ${changesets.pluginPath}/package.json must be a workspace package (pnpm-workspace.yaml or "workspaces") for changesets to release it`);
|
|
9401
|
+
let workspacePackageJson = null;
|
|
9402
|
+
const rootPkg = sameFile ? packageJson : changesets.packageJson;
|
|
9403
|
+
if (rootPkg === null) notes.push(`changesets: no package.json at the workspace root to wire; add a "version" script running "${CHANGESET_VERSION} && ${syncVersionCommand(changesets.pluginPath)}" and a "release" script running "${CHANGESET_PUBLISH}"`);
|
|
9404
|
+
else {
|
|
9405
|
+
const versioned = wireChangesets(rootPkg, changesets.pluginPath, opts.cliVersion);
|
|
9406
|
+
if (versioned.note) notes.push(versioned.note);
|
|
9407
|
+
const released = wireReleaseScript(versioned.content ?? rootPkg);
|
|
9408
|
+
if (released.note) notes.push(released.note);
|
|
9409
|
+
const wired = released.content ?? versioned.content;
|
|
9410
|
+
if (wired && sameFile) packageJson = wired;
|
|
9411
|
+
else if (wired) {
|
|
9412
|
+
workspacePackageJson = {
|
|
9413
|
+
path: `${changesets.pluginPath.split("/").map(() => "..").join("/")}/package.json`,
|
|
9414
|
+
content: wired
|
|
9415
|
+
};
|
|
9416
|
+
rows.push({
|
|
9417
|
+
path: workspacePackageJson.path,
|
|
9418
|
+
action: "updated"
|
|
9419
|
+
});
|
|
9420
|
+
}
|
|
9421
|
+
}
|
|
9422
|
+
if (!tagsPrivatePackages(changesets.config)) notes.push("changesets: set \"privatePackages\": { \"version\": true, \"tag\": true } in .changeset/config.json so changeset publish tags each release");
|
|
9423
|
+
return {
|
|
9424
|
+
packageJson,
|
|
9425
|
+
workspacePackageJson
|
|
9426
|
+
};
|
|
9427
|
+
}
|
|
9287
9428
|
/** The local marketplace a repository carries is named after the repository, not after the plugin:
|
|
9288
9429
|
* the catalog sits at the repository root and lists every plugin the repository develops. `-local`
|
|
9289
9430
|
* separates it from a published marketplace of the same plugins. */
|
|
@@ -9356,9 +9497,12 @@ function planCatalogs(state, opts, manifest, notes) {
|
|
|
9356
9497
|
function planInit(state, opts, rootDirName, resolveShippedPaths) {
|
|
9357
9498
|
if (opts.npm && state.packageJson === null) throw new Error("error: --npm requires a package.json at the project root");
|
|
9358
9499
|
if (state.manifestExists && !opts.force) throw new Error("plugin.json already exists — pass --force to overwrite");
|
|
9359
|
-
const
|
|
9500
|
+
const name = opts.name ?? rootDirName;
|
|
9501
|
+
const manifest = buildManifest(name, opts.vendors);
|
|
9360
9502
|
const packageVersion = state.packageJson?.version;
|
|
9361
9503
|
if (opts.npm && typeof packageVersion === "string" && packageVersion !== "") manifest.version = packageVersion;
|
|
9504
|
+
const privateVersion = !opts.npm && opts.changesets !== false && state.changesets && state.packageJson === null ? state.manifestVersion ?? FIRST_VERSION : void 0;
|
|
9505
|
+
if (privateVersion !== void 0) manifest.version = privateVersion;
|
|
9362
9506
|
const dirs = opts.scaffold ? [...SCAFFOLD_DIRS] : [];
|
|
9363
9507
|
const rows = [{
|
|
9364
9508
|
path: "plugin.json",
|
|
@@ -9384,7 +9528,7 @@ function planInit(state, opts, rootDirName, resolveShippedPaths) {
|
|
|
9384
9528
|
});
|
|
9385
9529
|
}
|
|
9386
9530
|
let workspacePackageJson = null;
|
|
9387
|
-
const changesets = state.changesets;
|
|
9531
|
+
const changesets = opts.changesets === false ? void 0 : state.changesets;
|
|
9388
9532
|
if (opts.npm && changesets) {
|
|
9389
9533
|
const sameFile = changesets.pluginPath === "";
|
|
9390
9534
|
const rootPkg = sameFile ? packageJson : changesets.packageJson;
|
|
@@ -9405,6 +9549,11 @@ function planInit(state, opts, rootDirName, resolveShippedPaths) {
|
|
|
9405
9549
|
}
|
|
9406
9550
|
}
|
|
9407
9551
|
}
|
|
9552
|
+
if (privateVersion !== void 0 && changesets) {
|
|
9553
|
+
const wired = planPrivateRelease(opts, name, privateVersion, changesets, rows, notes);
|
|
9554
|
+
packageJson = wired.packageJson;
|
|
9555
|
+
workspacePackageJson = wired.workspacePackageJson;
|
|
9556
|
+
}
|
|
9408
9557
|
const created = rows.filter((r) => r.action === "created").length;
|
|
9409
9558
|
const updated = rows.filter((r) => r.action === "updated").length;
|
|
9410
9559
|
const unchanged = rows.filter((r) => r.action === "unchanged").length;
|
|
@@ -9431,7 +9580,7 @@ function collect$1(value, previous) {
|
|
|
9431
9580
|
}
|
|
9432
9581
|
function initCommand$1(deps = { fs: realInitFs }) {
|
|
9433
9582
|
const cmd = new Command("init").description("Scaffold the canonical plugin.json (with --npm, wire an npm package to ship it)");
|
|
9434
|
-
cmd.option("--name <name>", "Plugin name (default: the root directory name)").option("--vendor <id>", "Target vendor; repeatable", collect$1, []).option("--scaffold", "Create the standard skills/ agents/ references/ commands/ directories").option("--force", "Overwrite an existing plugin.json").option("--yes", "Non-interactive (compatibility no-op; init never prompts)").option("--npm", "Wire package.json to ship the plugin (files, version; changesets version sync)").option("--no-marketplace", "Skip the repository's local marketplace catalogs").option("--format <format>", "Output format: json or toon (default: toon)").addOption(new Option("--json").hideHelp()).addOption(ROOT_OPTION).addHelpText("after", "\nExample:\n $ universal-plugin plugin init --name my-plugin --scaffold\n").action((opts) => {
|
|
9583
|
+
cmd.option("--name <name>", "Plugin name (default: the root directory name)").option("--vendor <id>", "Target vendor; repeatable", collect$1, []).option("--scaffold", "Create the standard skills/ agents/ references/ commands/ directories").option("--force", "Overwrite an existing plugin.json").option("--yes", "Non-interactive (compatibility no-op; init never prompts)").option("--npm", "Wire package.json to ship the plugin (files, version; changesets version sync)").option("--no-marketplace", "Skip the repository's local marketplace catalogs").option("--no-changesets", "Skip the changesets release wiring (private package.json, version and release scripts)").option("--format <format>", "Output format: json or toon (default: toon)").addOption(new Option("--json").hideHelp()).addOption(ROOT_OPTION).addHelpText("after", "\nExample:\n $ universal-plugin plugin init --name my-plugin --scaffold\n").action((opts) => {
|
|
9435
9584
|
try {
|
|
9436
9585
|
const root = resolveRoot(opts.root);
|
|
9437
9586
|
const plan = planInit(deps.fs.gather(root), {
|
|
@@ -9441,6 +9590,7 @@ function initCommand$1(deps = { fs: realInitFs }) {
|
|
|
9441
9590
|
force: Boolean(opts.force),
|
|
9442
9591
|
npm: Boolean(opts.npm),
|
|
9443
9592
|
marketplace: opts.marketplace !== false,
|
|
9593
|
+
changesets: opts.changesets !== false,
|
|
9444
9594
|
cliVersion: deps.version
|
|
9445
9595
|
}, path$1.basename(root), (vendor) => VENDOR_SHIPPED_PATHS[vendor] ?? []);
|
|
9446
9596
|
deps.fs.apply(root, plan);
|
|
@@ -18114,12 +18264,13 @@ function prepareCommand() {
|
|
|
18114
18264
|
//#region src/version/cli.ts
|
|
18115
18265
|
function versionCommand(deps = { fs: realVersionFs }) {
|
|
18116
18266
|
const cmd = new Command("version").description("Stamp the plugin version into every file that carries one").argument("[bump]", `Release type (${RELEASE_TYPES.join(", ")}) or an explicit version (1.4.0)`);
|
|
18117
|
-
cmd.option("--from-package", "Take the version the package's package.json already holds (changesets, release-please)").option("--preid <id>", "Prerelease identifier for the pre* release types").option("--force", "Allow a version that does not advance on the current one").option("--no-build", "Skip re-deriving the vendor manifests").option("--dry-run", "Print what would be written without writing").option("--format <format>", "Output format: json or toon (default: toon)").addOption(new Option("--json").hideHelp()).addOption(ROOT_OPTION).addHelpText("after", [
|
|
18267
|
+
cmd.option("--from-package", "Take the version the package's package.json already holds (changesets, release-please)").option("--from-manifest", "Restamp every place with the version plugin.json already holds (release-please extra-files)").option("--preid <id>", "Prerelease identifier for the pre* release types").option("--force", "Allow a version that does not advance on the current one").option("--no-build", "Skip re-deriving the vendor manifests").option("--dry-run", "Print what would be written without writing").option("--format <format>", "Output format: json or toon (default: toon)").addOption(new Option("--json").hideHelp()).addOption(ROOT_OPTION).addHelpText("after", [
|
|
18118
18268
|
"",
|
|
18119
18269
|
"Examples:",
|
|
18120
18270
|
" $ universal-plugin plugin version minor # plain repository: the author picks",
|
|
18121
18271
|
" $ universal-plugin plugin version 1.4.0 # semantic-release: prepareCmd",
|
|
18122
|
-
" $ universal-plugin plugin version --from-package # changesets / release-please",
|
|
18272
|
+
" $ universal-plugin plugin version --from-package # changesets / release-please, npm package",
|
|
18273
|
+
" $ universal-plugin plugin version --from-manifest # release-please, plugin.json bumped by extra-files",
|
|
18123
18274
|
""
|
|
18124
18275
|
].join("\n")).action((bump, opts) => runVersion(bump, opts, deps.fs));
|
|
18125
18276
|
return cmd;
|
|
@@ -18132,6 +18283,7 @@ function runVersion(bump, opts, versionFs = realVersionFs) {
|
|
|
18132
18283
|
const plan = planVersion(versionFs.gather(root), {
|
|
18133
18284
|
...bump === void 0 ? {} : { bump },
|
|
18134
18285
|
fromPackage: Boolean(opts.fromPackage),
|
|
18286
|
+
fromManifest: Boolean(opts.fromManifest),
|
|
18135
18287
|
...opts.preid === void 0 ? {} : { preid: opts.preid },
|
|
18136
18288
|
force: Boolean(opts.force)
|
|
18137
18289
|
});
|
|
@@ -18303,7 +18455,7 @@ function stringField(value, key) {
|
|
|
18303
18455
|
function publishCommand() {
|
|
18304
18456
|
const cmd = new Command("publish").description("Prepare plugin for publishing").helpCommand(false);
|
|
18305
18457
|
cmd.command("sync-version").description("Deprecated: use `plugin version --from-package`").option("--no-build", "Skip re-deriving the vendor manifests").option("--format <format>", "Output format: json or toon (default: toon)").addOption(ROOT_OPTION).action((opts) => {
|
|
18306
|
-
process.stderr.write("warn: publish sync-version is deprecated and will be removed in
|
|
18458
|
+
process.stderr.write("warn: publish sync-version is deprecated since 0.14.0 and will be removed in a later release — use plugin version --from-package\n");
|
|
18307
18459
|
runVersion(void 0, {
|
|
18308
18460
|
...opts,
|
|
18309
18461
|
fromPackage: true
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "universal-plugin",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"description": "Research and design toolkit for building universal AI coding agent plugins that work across Claude Code, Cursor, Codex, and GitHub Copilot CLI.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "unional"
|
package/readme.md
CHANGED
|
@@ -74,7 +74,7 @@ npx universal-plugin publish check-version # fail when any place carr
|
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
`publish sync-version` and `plugin bundle` are deprecated aliases of `plugin version --from-package`
|
|
77
|
-
and `plugin version`. They print a warning and
|
|
77
|
+
and `plugin version`. They print a warning and will be removed in a later release.
|
|
78
78
|
|
|
79
79
|
### marketplace
|
|
80
80
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Plugin Design
|
|
2
2
|
|
|
3
|
-
Authoritative rules for creating, validating, and transforming cross-vendor agent plugins. Apply when creating, auditing, or distributing a plugin; see **skill-design**
|
|
3
|
+
Authoritative rules for creating, validating, and transforming cross-vendor agent plugins. Apply when creating, auditing, or distributing a plugin; see the **skill-design** reference for standalone skill authoring rules.
|
|
4
4
|
|
|
5
5
|
A **plugin** is the distribution unit — it bundles skills, MCP servers, hooks, commands, agents, and other extensions into a single installable package. A **skill** is the capability unit inside a plugin. Install plugins; invoke skills.
|
|
6
6
|
|
|
7
7
|
## Source of Truth: root `plugin.json`
|
|
8
8
|
|
|
9
|
-
Author `plugin.json` at the plugin root as the canonical manifest — the Agent Plugins Specification v1.0.0 shape (closed field set, `additionalProperties: false`). All vendor manifests are derived from it via `build`.
|
|
9
|
+
Author `plugin.json` at the plugin root as the canonical manifest — the Agent Plugins Specification v1.0.0 shape (closed field set, `additionalProperties: false`). All vendor manifests are derived from it via `build`. GitHub Copilot CLI reads this file directly at runtime; every other vendor reads the manifest the build layer derives from it.
|
|
10
10
|
|
|
11
11
|
Schema declaration (first field, fixed by the spec):
|
|
12
12
|
|
|
@@ -98,8 +98,7 @@ Vendor-specific extension fields:
|
|
|
98
98
|
│
|
|
99
99
|
├── hooks/hooks.json ← canonical hooks (PascalCase, ${PLUGIN_ROOT})
|
|
100
100
|
│
|
|
101
|
-
├──
|
|
102
|
-
├── mcp.json -> .mcp.json ← symlink (Cursor + open-plugin-spec)
|
|
101
|
+
├── mcp.json ← MCP config (fixed location, all vendors)
|
|
103
102
|
│
|
|
104
103
|
└── README.md
|
|
105
104
|
```
|
|
@@ -141,11 +140,11 @@ Build reads root `plugin.json`, applies the rules below, writes each vendor's ou
|
|
|
141
140
|
| Canonical field | Claude Code | Cursor | Codex | Copilot CLI |
|
|
142
141
|
| --- | --- | --- | --- | --- |
|
|
143
142
|
| `skills` | ✓ | ✓ | ✓ | ✓ |
|
|
144
|
-
| `mcpServers` | ✓ →
|
|
143
|
+
| `mcpServers` | ✓ → `./mcp.json` | ✓ → `./mcp.json` | ✓ → `./mcp.json` | ✓ |
|
|
145
144
|
| `commands` | ✓ | ✓ | **omit** | ✓ |
|
|
146
145
|
| `agents` | ✓ | ✓ | **omit** | ✓ |
|
|
147
146
|
| `rules` | **omit** | ✓ | **omit** | **omit** |
|
|
148
|
-
| `hooks` | adapt → PascalCase, `${CLAUDE_PLUGIN_ROOT}` | adapt → camelCase, pass-through env | ✓ → PascalCase, `${PLUGIN_ROOT}` native |
|
|
147
|
+
| `hooks` | adapt → PascalCase, `${CLAUDE_PLUGIN_ROOT}` | adapt → camelCase, pass-through env | ✓ → PascalCase, `${PLUGIN_ROOT}` native | ✓ → PascalCase (selects the Claude-compatible payload) |
|
|
149
148
|
| `lspServers` | ✓ | **omit** | **omit** | **omit** |
|
|
150
149
|
| `outputStyles` | ✓ | **omit** | **omit** | **omit** |
|
|
151
150
|
| `dependencies` | ✓ | **drop + warn** | **drop + warn** | **drop + warn** |
|
|
@@ -193,15 +192,15 @@ Canonical hooks file uses **PascalCase** event names. Build translates per vendo
|
|
|
193
192
|
|
|
194
193
|
| Canonical (PascalCase) | Claude Code | Cursor | Codex | Copilot CLI |
|
|
195
194
|
| --- | --- | --- | --- | --- |
|
|
196
|
-
| `PreToolUse` | `PreToolUse` | `preToolUse` | `PreToolUse` | `
|
|
197
|
-
| `PostToolUse` | `PostToolUse` | `postToolUse` | `PostToolUse` | `
|
|
195
|
+
| `PreToolUse` | `PreToolUse` | `preToolUse` | `PreToolUse` | `PreToolUse` |
|
|
196
|
+
| `PostToolUse` | `PostToolUse` | `postToolUse` | `PostToolUse` | `PostToolUse` |
|
|
198
197
|
| `PostToolUseFailure` | `PostToolUseFailure` | — (drop+warn) | — (drop+warn) | — (drop+warn) |
|
|
199
|
-
| `SessionStart` | `SessionStart` | `sessionStart` | `SessionStart` | `
|
|
200
|
-
| `SessionEnd` | `SessionEnd` | `sessionEnd` | `SessionEnd` | `
|
|
201
|
-
| `Stop` | `Stop` | — (drop+warn) | — (drop+warn) | `
|
|
198
|
+
| `SessionStart` | `SessionStart` | `sessionStart` | `SessionStart` | `SessionStart` |
|
|
199
|
+
| `SessionEnd` | `SessionEnd` | `sessionEnd` | `SessionEnd` | `SessionEnd` |
|
|
200
|
+
| `Stop` | `Stop` | — (drop+warn) | — (drop+warn) | `Stop` |
|
|
202
201
|
| `UserPromptSubmit` | `UserPromptSubmit` | `beforeSubmitPrompt` | — (drop+warn) | — (drop+warn) |
|
|
203
|
-
| `Notification` | `Notification` | — (drop+warn) | — (drop+warn) | `
|
|
204
|
-
| `PermissionRequest` | `PermissionRequest` | — (drop+warn) | — (drop+warn) | `
|
|
202
|
+
| `Notification` | `Notification` | — (drop+warn) | — (drop+warn) | `Notification` |
|
|
203
|
+
| `PermissionRequest` | `PermissionRequest` | — (drop+warn) | — (drop+warn) | `PermissionRequest` |
|
|
205
204
|
| `SubagentStart` | — | `subagentStart` | — | — |
|
|
206
205
|
| `SubagentStop` | — | `subagentStop` | — | — |
|
|
207
206
|
| `PreCompact` | `PreCompact` | `preCompact` | — | — |
|
|
@@ -230,20 +229,9 @@ Events not in this table: pass through for `claude-code`; drop + warn for all ot
|
|
|
230
229
|
|
|
231
230
|
Build generates vendor-specific versions. Do not hand-author the generated hook files.
|
|
232
231
|
|
|
233
|
-
## MCP:
|
|
232
|
+
## MCP: `mcp.json`
|
|
234
233
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
```bash
|
|
238
|
-
ln -sf .mcp.json mcp.json
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
| Runtime | Reads |
|
|
242
|
-
| --- | --- |
|
|
243
|
-
| Claude Code, Codex | `.mcp.json` |
|
|
244
|
-
| Cursor, open-plugin-spec | `mcp.json` (via symlink) |
|
|
245
|
-
|
|
246
|
-
If the repo needs explicit symlink tracking: `mcp.json symlink` in `.gitattributes`. MCP server startup failures are non-fatal.
|
|
234
|
+
`mcp.json` at the plugin root is the Agent Plugins Specification's fixed MCP location, Author it as a regular file. `build` points each derived manifest at it, unless it has to change an entry for that vendor; then it writes the changed copy beside the vendor manifest. MCP server startup failures are non-fatal.
|
|
247
235
|
|
|
248
236
|
### Pinning a self-published server to the plugin version
|
|
249
237
|
|
|
@@ -271,9 +259,9 @@ A plugin whose MCP server is its own npm package writes an unpinned invocation
|
|
|
271
259
|
|
|
272
260
|
## Component Authoring Rules
|
|
273
261
|
|
|
274
|
-
**Skills:** Author `skills/<name>/SKILL.md` following the **skill-design**
|
|
262
|
+
**Skills:** Author `skills/<name>/SKILL.md` following the **skill-design** reference. Within a plugin, reference MCP tools by fully qualified name: `{plugin-name}:{server-name}__{tool-name}`. Use the **slash-invocation** reference when a skill needs a user-only, model-only, or explicit both-invocation policy.
|
|
275
263
|
|
|
276
|
-
**Commands:** One `.md` file per command in `commands/`. Filename (minus extension) is the command identifier. Optional frontmatter: `description`, `argument-hint`, `allowed-tools`, `disable-model-invocation`. `$ARGUMENTS` expands to user input.
|
|
264
|
+
**Commands:** One `.md` file per command in `commands/`. Filename (minus extension) is the command identifier. Optional frontmatter: `description`, `argument-hint`, `allowed-tools`, `disable-model-invocation`. `$ARGUMENTS` expands to user input on Claude Code and stays literal on other runtimes.
|
|
277
265
|
|
|
278
266
|
**Agents:** One `.md` file per agent in `agents/`. Required frontmatter: `name` (1–64 lowercase alphanumeric + hyphens), `description` (≤ 1024 chars). Body is the agent system prompt.
|
|
279
267
|
|
|
@@ -306,9 +294,9 @@ A plugin whose MCP server is its own npm package writes an unpinned invocation
|
|
|
306
294
|
|
|
307
295
|
| Scope | Claude Code | Cursor | Codex |
|
|
308
296
|
| --- | --- | --- | --- |
|
|
309
|
-
| **Personal** |
|
|
297
|
+
| **Personal** | `universal-plugin plugin install`, then restart | `universal-plugin plugin install` (copies), then reload the window | `~/.agents/plugins/marketplace.json` |
|
|
310
298
|
| **Team** | npm private package | Cursor Teams admin import | `.agents/plugins/marketplace.json` in repo |
|
|
311
|
-
| **Public** |
|
|
299
|
+
| **Public** | `anthropics/claude-plugins-official` is curated and takes no pull requests; check its submission route live (`publish-plugin`, step 0c) | `cursor.com/marketplace/publish` | `codex plugin marketplace add` |
|
|
312
300
|
|
|
313
301
|
Default scope: **team**.
|
|
314
302
|
|
|
@@ -318,13 +306,11 @@ Default scope: **team**.
|
|
|
318
306
|
|
|
319
307
|
- Using `../` in any manifest-declared path
|
|
320
308
|
- Hardcoding absolute paths instead of `${PLUGIN_ROOT}` or `${PLUGIN_DATA}`
|
|
321
|
-
- Committing `mcp.json` as a regular file — must always be a symlink to `.mcp.json`
|
|
322
309
|
- Hand-authoring vendor hook files — use canonical `hooks/hooks.json` and let build generate vendor versions
|
|
323
310
|
- Relying on extended component types (commands, rules, agents, hooks) for core functionality — silently ignored on non-supporting hosts
|
|
324
311
|
- Duplicating SKILL.md content in `plugin.json` — the manifest is a path index, never a content mirror
|
|
325
312
|
- Using `rules/` for cross-agent always-on guidance — use AGENTS.md instead
|
|
326
313
|
- Putting role- or task-scoped guidance in always-on context (`rules/` or AGENTS.md) — make it a reference loaded on demand
|
|
327
|
-
- Putting install-time metadata in `plugin.json` instead of `skill.json`
|
|
328
314
|
|
|
329
315
|
## Cross-Platform Portability
|
|
330
316
|
|
|
@@ -338,7 +324,7 @@ Default scope: **team**.
|
|
|
338
324
|
| Windsurf | Needs conversion | 6,000 char/file hard limit; 12,000 chars total |
|
|
339
325
|
| Zed, Aider, Continue.dev, Cline | No | Incompatible formats |
|
|
340
326
|
|
|
341
|
-
Portability rules for skill bodies: keep
|
|
327
|
+
Portability rules for skill bodies: for a skill that must load in Windsurf, keep the `SKILL.md` body under its 6,000-char limit and move detail into `references/`; use forward slashes in path references; declare environment requirements in `compatibility` frontmatter; do not embed vendor-specific syntax in skill bodies.
|
|
342
328
|
|
|
343
329
|
## References
|
|
344
330
|
|