universal-plugin 0.3.1 → 0.4.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/LICENSE +21 -0
- package/dist/cli.mjs +76 -134
- package/package.json +3 -1
- package/plugin.json +1 -1
- package/readme.md +73 -42
- package/skills/doctor/README.md +42 -0
- package/skills/doctor/SKILL.md +123 -0
- package/skills/doctor/scripts/doctor.mjs +196 -0
- package/skills/init/README.md +57 -0
- package/skills/init/SKILL.md +200 -0
- package/skills/{plugin → init}/references/adopt.md +8 -4
- package/skills/init/references/create.md +111 -0
- package/skills/init/references/detection.md +62 -0
- package/skills/init/references/frontmatter.md +65 -0
- package/skills/init/references/standard.md +92 -0
- package/skills/init/references/update.md +31 -0
- package/skills/init/references/vendors/claude-code.md +44 -0
- package/skills/init/references/vendors/codex.md +48 -0
- package/skills/init/references/vendors/copilot-cli.md +45 -0
- package/skills/init/references/vendors/cursor.md +45 -0
- package/skills/init/scripts/init.mjs +11 -0
- package/skills/remove-plugin/README.md +38 -0
- package/skills/remove-plugin/SKILL.md +87 -0
- package/skills/version/README.md +36 -0
- package/skills/{plugin/references/version.md → version/SKILL.md} +26 -5
- package/skills/version/scripts/version.mjs +11 -0
- package/skills/plugin/README.md +0 -37
- package/skills/plugin/SKILL.md +0 -105
- package/skills/plugin/references/create.md +0 -163
- package/skills/plugin/references/delete.md +0 -23
- package/skills/plugin/references/inspect.md +0 -21
- package/skills/plugin/references/update.md +0 -26
- /package/skills/{plugin → init}/assets/templates/agent.md +0 -0
- /package/skills/{plugin → init}/assets/templates/command.md +0 -0
- /package/skills/{plugin → init}/assets/templates/hooks.json +0 -0
- /package/skills/{plugin → init}/assets/templates/plugin.json +0 -0
- /package/skills/{plugin → init}/assets/templates/setup-command.md +0 -0
- /package/skills/{plugin → init}/assets/templates/skill.md +0 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doctor
|
|
3
|
+
description: Use this skill to diagnose a universal agent plugin — when a runtime loads none of the plugin's skills, when a vendor manifest is missing or looks out of date after a pull, when a build prints warnings nobody has read, or when checking whether what the canonical plugin.json declares still matches what is on disk for Claude Code, Cursor, Codex, and GitHub Copilot CLI. Trigger on "is my plugin set up right", "why isn't my plugin loading", "check the plugin", "are the vendor manifests current", or "what does this plugin declare".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Plugin Doctor
|
|
7
|
+
|
|
8
|
+
Root `plugin.json` is the canonical manifest. Every other manifest a runtime reads is derived from
|
|
9
|
+
it, and a derived manifest that is missing, stale, or hand-edited fails silently: the runtime loads
|
|
10
|
+
what it finds, or loads nothing, and says nothing either way.
|
|
11
|
+
|
|
12
|
+
This skill is **read-only**. It never repairs. Every finding names the skill that owns its repair —
|
|
13
|
+
hand it over rather than fixing it here, because a repair can rewrite a manifest the user maintains
|
|
14
|
+
and that judgment belongs to the skill that owns the write.
|
|
15
|
+
|
|
16
|
+
## Diagnose
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
node scripts/doctor.mjs
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Resolve that path against this skill's own directory. It runs the CLI that shipped beside it against
|
|
23
|
+
the current working directory, so nothing is downloaded; add `--root <path>` to diagnose elsewhere.
|
|
24
|
+
It never prompts and never writes, so it is safe to run unattended.
|
|
25
|
+
|
|
26
|
+
Stdout is one JSON object — that is the contract to read, not the CLI's own terminal output:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"root": "…",
|
|
31
|
+
"manifest": { "name": "my-plugin", "version": "1.0.0" },
|
|
32
|
+
"vendors": [{ "vendor": "claude-code", "path": ".claude-plugin/plugin.json", "status": "built", "exists": true, "stale": false }],
|
|
33
|
+
"findings": [{ "code": "unbuilt", "severity": "high", "detail": "…", "repair": "…" }],
|
|
34
|
+
"ok": false
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`findings` is empty and `ok` is `true` when everything resolves — say so outright rather than
|
|
39
|
+
reporting an empty list. Exit status is `0` whether or not findings exist; a finding is a result, not
|
|
40
|
+
a failure. Add `--verbose` for a human-readable summary on stderr.
|
|
41
|
+
|
|
42
|
+
Read `vendors[].status` literally:
|
|
43
|
+
|
|
44
|
+
| Status | Means |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `built` | the build writes this vendor's manifest |
|
|
47
|
+
| `canonical` | the vendor reads root `plugin.json`; **no file is written, and that is correct** |
|
|
48
|
+
| `skipped` | an unknown vendor id — a typo in `vendors` |
|
|
49
|
+
| `failed` | the write itself failed; the finding names why |
|
|
50
|
+
|
|
51
|
+
`copilot-cli` reporting `canonical` with `exists: false` is a healthy plugin, not a missing build.
|
|
52
|
+
Never report it as a fault.
|
|
53
|
+
|
|
54
|
+
If `node` is unavailable, read `scripts/doctor.mjs` and apply the same checks by hand: it composes
|
|
55
|
+
`universal-plugin plugin build --dry-run --format json` with filesystem facts that build cannot see.
|
|
56
|
+
|
|
57
|
+
## Findings and their repairs
|
|
58
|
+
|
|
59
|
+
Each `code` below is what the script emits.
|
|
60
|
+
|
|
61
|
+
| Finding | What it means | Repair |
|
|
62
|
+
| --- | --- | --- |
|
|
63
|
+
| `no-manifest` | no root `plugin.json` — this is not a plugin yet | `/universal-plugin:init` |
|
|
64
|
+
| `legacy-manifest` | root `plugin.json` with neither `$schema` nor `extensions` — a single-vendor manifest on the canonical path | `/universal-plugin:init`, adopt route |
|
|
65
|
+
| `vendor-only` | a vendor manifest with no canonical manifest above it | `/universal-plugin:init`, adopt route |
|
|
66
|
+
| `unbuilt` | a declared vendor whose output path holds no file — that runtime sees no plugin | `universal-plugin plugin build` |
|
|
67
|
+
| `stale` | a derived manifest older than `plugin.json` | `universal-plugin plugin build` |
|
|
68
|
+
| `hand-edited` | a derived manifest that `build` would rewrite — the edit is already lost, it just has not been overwritten yet | move the field to the canonical manifest or to `harnesses.<vendor>`, then rebuild |
|
|
69
|
+
| `unknown-vendor` | a `vendors` entry no build target matches; reported as `skipped` plus a warning | fix the id in `plugin.json` |
|
|
70
|
+
| `undeliverable-override` | `harnesses["copilot-cli"]` sets fields that reach nothing | `/universal-plugin:init`, update route — move them to a vendor that has a derived manifest, or drop them |
|
|
71
|
+
| `codex-fields-missing` | Codex is targeted without `version` or `description`; the build fails and writes **nothing at all**, including for the other vendors | add both to the canonical top level |
|
|
72
|
+
| `version-drift` | the `packagePath` `package.json` and the canonical manifest carry different versions | `/universal-plugin:version` |
|
|
73
|
+
| `stale-github-plugin` | a leftover `.github/plugin/plugin.json` from an older build — shadowed by root and no longer generated | `/universal-plugin:remove-plugin` |
|
|
74
|
+
| `shadowing-manifest` | a `.plugin/plugin.json` exists — it outranks root in Copilot CLI's search order and silently shadows the canonical manifest | `/universal-plugin:remove-plugin` |
|
|
75
|
+
| `no-vendors` | no vendor is declared, so the build writes nothing and no runtime reads the plugin | `/universal-plugin:init`, update route |
|
|
76
|
+
| `package-path-missing` | `packagePath` names a directory with no readable `package.json` | fix `packagePath`, or create the package |
|
|
77
|
+
| `unparsable-manifest` | root `plugin.json` is not valid JSON | fix the syntax error |
|
|
78
|
+
|
|
79
|
+
## Checking staleness properly
|
|
80
|
+
|
|
81
|
+
The `stale` finding is an mtime comparison, which catches the common case and nothing more. It cannot
|
|
82
|
+
see a hand-edit made after the last build. The definitive check is to rebuild on a clean tree and read
|
|
83
|
+
the diff:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
git status --short # must be clean first, or the diff proves nothing
|
|
87
|
+
npx universal-plugin plugin build
|
|
88
|
+
git diff -- .claude-plugin .cursor-plugin .codex-plugin
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
An empty diff means the derived manifests match what the canonical manifest says. Any hunk is drift —
|
|
92
|
+
either a stale build or a hand-edit that the rebuild has now discarded.
|
|
93
|
+
|
|
94
|
+
That rebuild is a **write**, so it is not part of the diagnosis. Report the check as a repair the
|
|
95
|
+
user can run, or ask before running it yourself.
|
|
96
|
+
|
|
97
|
+
## Version drift
|
|
98
|
+
|
|
99
|
+
Two files carry an authored version: the canonical `plugin.json`, and the `package.json` at
|
|
100
|
+
`extensions["org.cyberuni.universal-plugin"].packagePath` when one is declared. The script compares
|
|
101
|
+
them and emits `version-drift`.
|
|
102
|
+
|
|
103
|
+
They diverge when someone ran `npm version`, or when changesets released a number that never flowed
|
|
104
|
+
back. Both are `/universal-plugin:version`'s to fix — never patch one file by hand to match the
|
|
105
|
+
other.
|
|
106
|
+
|
|
107
|
+
## Rules
|
|
108
|
+
|
|
109
|
+
- **Never repair.** Report the finding and name the skill that owns it.
|
|
110
|
+
- **Never hand-edit a derived manifest to make a finding go away.** The next build overwrites it and
|
|
111
|
+
the finding comes back.
|
|
112
|
+
- Do not report `copilot-cli` writing no file as a fault. It reads the canonical manifest directly.
|
|
113
|
+
- Do not treat repo-private agent configuration (`.claude/skills/`, `.agents/skills/`) as part of the
|
|
114
|
+
plugin. Diagnosing a repository's own skill wiring is `buddy-agent-harness:doctor`.
|
|
115
|
+
|
|
116
|
+
## Related skills
|
|
117
|
+
|
|
118
|
+
| Task | Skill |
|
|
119
|
+
|------|-------|
|
|
120
|
+
| Create, adopt, or change what the plugin declares | `init` |
|
|
121
|
+
| Move the plugin's version | `version` |
|
|
122
|
+
| Remove derived manifests, or the plugin itself | `remove-plugin` |
|
|
123
|
+
| Publish it to a marketplace | `publish-plugin` |
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Read-only diagnosis of a universal plugin. Emits one JSON object on stdout; never writes.
|
|
3
|
+
// Derivation itself is not re-implemented here — the shipped CLI's own build resolver is the source
|
|
4
|
+
// of truth for what each vendor gets, and this script only adds the filesystem facts it cannot see.
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
6
|
+
import * as fs from 'node:fs'
|
|
7
|
+
import * as path from 'node:path'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
|
|
10
|
+
const UP_NAMESPACE = 'org.cyberuni.universal-plugin'
|
|
11
|
+
// <package>/skills/<skill>/scripts/doctor.mjs: four levels up is the package root.
|
|
12
|
+
const packageRoot = path.dirname(path.dirname(path.dirname(path.dirname(fileURLToPath(import.meta.url)))))
|
|
13
|
+
|
|
14
|
+
const argv = process.argv.slice(2)
|
|
15
|
+
const verbose = argv.includes('--verbose')
|
|
16
|
+
const rootFlag = argv.indexOf('--root')
|
|
17
|
+
const root = path.resolve(rootFlag === -1 ? process.cwd() : (argv[rootFlag + 1] ?? process.cwd()))
|
|
18
|
+
|
|
19
|
+
const findings = []
|
|
20
|
+
const add = (code, severity, detail, repair) => findings.push({ code, severity, detail, repair })
|
|
21
|
+
|
|
22
|
+
const readJson = (file) => {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
25
|
+
} catch {
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const mtime = (file) => (fs.existsSync(file) ? fs.statSync(file).mtimeMs : null)
|
|
30
|
+
|
|
31
|
+
const VENDOR_MANIFESTS = ['.claude-plugin/plugin.json', '.cursor-plugin/plugin.json', '.codex-plugin/plugin.json']
|
|
32
|
+
|
|
33
|
+
const manifestPath = path.join(root, 'plugin.json')
|
|
34
|
+
const manifest = fs.existsSync(manifestPath) ? readJson(manifestPath) : null
|
|
35
|
+
|
|
36
|
+
if (manifest === null) {
|
|
37
|
+
const orphans = VENDOR_MANIFESTS.filter((rel) => fs.existsSync(path.join(root, rel)))
|
|
38
|
+
if (fs.existsSync(manifestPath)) {
|
|
39
|
+
add('unparsable-manifest', 'critical', 'plugin.json is not valid JSON', 'fix the syntax error')
|
|
40
|
+
} else if (orphans.length > 0) {
|
|
41
|
+
add(
|
|
42
|
+
'vendor-only',
|
|
43
|
+
'high',
|
|
44
|
+
`vendor manifests with no canonical manifest: ${orphans.join(', ')}`,
|
|
45
|
+
'/universal-plugin:init, adopt route',
|
|
46
|
+
)
|
|
47
|
+
} else {
|
|
48
|
+
add('no-manifest', 'high', 'no root plugin.json — this is not a plugin yet', '/universal-plugin:init')
|
|
49
|
+
}
|
|
50
|
+
report({ vendors: [] })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const ext = manifest.extensions?.[UP_NAMESPACE] ?? null
|
|
54
|
+
if (!manifest.$schema?.includes('agent-plugins.org') || ext === null) {
|
|
55
|
+
add(
|
|
56
|
+
'legacy-manifest',
|
|
57
|
+
'high',
|
|
58
|
+
'root plugin.json carries no $schema on agent-plugins.org or no extensions block',
|
|
59
|
+
'/universal-plugin:init, adopt route',
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Shadowing and leftovers. `.plugin/plugin.json` outranks root in Copilot CLI's search order.
|
|
64
|
+
if (fs.existsSync(path.join(root, '.plugin/plugin.json'))) {
|
|
65
|
+
add(
|
|
66
|
+
'shadowing-manifest',
|
|
67
|
+
'high',
|
|
68
|
+
'.plugin/plugin.json outranks root and is read instead of the canonical manifest',
|
|
69
|
+
'/universal-plugin:remove-plugin',
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
if (fs.existsSync(path.join(root, '.github/plugin/plugin.json'))) {
|
|
73
|
+
add(
|
|
74
|
+
'stale-github-plugin',
|
|
75
|
+
'low',
|
|
76
|
+
'.github/plugin/plugin.json is a leftover from an older build and is no longer generated',
|
|
77
|
+
'/universal-plugin:remove-plugin',
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Ask the shipped CLI what it would write, without writing it.
|
|
82
|
+
const bin = path.join(packageRoot, 'bin', 'universal-plugin.mjs')
|
|
83
|
+
const cli = fs.existsSync(bin)
|
|
84
|
+
? spawnSync(process.execPath, [bin, 'plugin', 'build', '--dry-run', '--format', 'json', '--root', root], {
|
|
85
|
+
encoding: 'utf8',
|
|
86
|
+
})
|
|
87
|
+
: spawnSync('npx', ['universal-plugin', 'plugin', 'build', '--dry-run', '--format', 'json', '--root', root], {
|
|
88
|
+
encoding: 'utf8',
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const vendors = []
|
|
92
|
+
const build = readJson_stdout(cli.stdout)
|
|
93
|
+
|
|
94
|
+
if (build === null) {
|
|
95
|
+
const stderr = (cli.stderr ?? '').trim()
|
|
96
|
+
if (/required when targeting codex/.test(stderr)) {
|
|
97
|
+
add(
|
|
98
|
+
'codex-fields-missing',
|
|
99
|
+
'high',
|
|
100
|
+
'codex is targeted without version or description — the build writes nothing at all, for any vendor',
|
|
101
|
+
'add both to the canonical top level',
|
|
102
|
+
)
|
|
103
|
+
} else {
|
|
104
|
+
add(
|
|
105
|
+
'build-failed',
|
|
106
|
+
'high',
|
|
107
|
+
stderr.split('\n')[0] || 'plugin build could not resolve the manifest',
|
|
108
|
+
'run plugin build to see the full error',
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
const manifestMtime = mtime(manifestPath)
|
|
113
|
+
for (const row of [...build.built, ...build.canonical, ...build.skipped, ...build.failed]) {
|
|
114
|
+
const abs = path.join(root, row.path)
|
|
115
|
+
const exists = fs.existsSync(abs)
|
|
116
|
+
// `canonical` means the vendor reads root plugin.json; no derived file is expected.
|
|
117
|
+
const stale = row.status === 'built' && exists && manifestMtime !== null && mtime(abs) < manifestMtime
|
|
118
|
+
vendors.push({ vendor: row.vendor, path: row.path, status: row.status, exists, stale })
|
|
119
|
+
|
|
120
|
+
if (row.status === 'built' && !exists) {
|
|
121
|
+
add(
|
|
122
|
+
'unbuilt',
|
|
123
|
+
'high',
|
|
124
|
+
`${row.vendor} is declared but ${row.path} does not exist — that runtime sees no plugin`,
|
|
125
|
+
'universal-plugin plugin build',
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
if (stale) {
|
|
129
|
+
add('stale', 'medium', `${row.path} is older than plugin.json`, 'universal-plugin plugin build')
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const warning of build.warnings ?? []) {
|
|
133
|
+
if (/not delivered/.test(warning)) {
|
|
134
|
+
add('undeliverable-override', 'medium', warning, '/universal-plugin:init, update route')
|
|
135
|
+
} else if (/No vendors declared/.test(warning)) {
|
|
136
|
+
add(
|
|
137
|
+
'no-vendors',
|
|
138
|
+
'medium',
|
|
139
|
+
'no vendor is declared — the build writes nothing, so no runtime reads this plugin',
|
|
140
|
+
'/universal-plugin:init, update route',
|
|
141
|
+
)
|
|
142
|
+
} else if (/^Unknown vendor/.test(warning)) {
|
|
143
|
+
add('unknown-vendor', 'medium', warning, 'fix the vendor id in plugin.json')
|
|
144
|
+
} else {
|
|
145
|
+
add('build-warning', 'low', warning, 'read the warning and decide')
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Version drift between the two authored numbers.
|
|
151
|
+
if (ext?.packagePath) {
|
|
152
|
+
const pkgPath = path.join(root, ext.packagePath, 'package.json')
|
|
153
|
+
const pkg = readJson(pkgPath)
|
|
154
|
+
if (pkg === null) {
|
|
155
|
+
add(
|
|
156
|
+
'package-path-missing',
|
|
157
|
+
'medium',
|
|
158
|
+
`packagePath names ${ext.packagePath}, which holds no readable package.json`,
|
|
159
|
+
'fix packagePath, or create the package',
|
|
160
|
+
)
|
|
161
|
+
} else if (manifest.version !== undefined && pkg.version !== manifest.version) {
|
|
162
|
+
add(
|
|
163
|
+
'version-drift',
|
|
164
|
+
'high',
|
|
165
|
+
`plugin.json is ${manifest.version}, ${ext.packagePath}/package.json is ${pkg.version}`,
|
|
166
|
+
'/universal-plugin:version',
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
report({ vendors })
|
|
172
|
+
|
|
173
|
+
function readJson_stdout(stdout) {
|
|
174
|
+
if (!stdout) return null
|
|
175
|
+
try {
|
|
176
|
+
return JSON.parse(stdout)
|
|
177
|
+
} catch {
|
|
178
|
+
return null
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function report({ vendors }) {
|
|
183
|
+
const result = {
|
|
184
|
+
root,
|
|
185
|
+
manifest: manifest === null ? null : { name: manifest.name ?? null, version: manifest.version ?? null },
|
|
186
|
+
vendors,
|
|
187
|
+
findings,
|
|
188
|
+
ok: findings.length === 0,
|
|
189
|
+
}
|
|
190
|
+
process.stdout.write(`${JSON.stringify(result)}\n`)
|
|
191
|
+
if (verbose) {
|
|
192
|
+
process.stderr.write(result.ok ? 'no findings\n' : `${findings.length} finding(s)\n`)
|
|
193
|
+
for (const f of findings) process.stderr.write(` [${f.severity}] ${f.code}: ${f.detail}\n`)
|
|
194
|
+
}
|
|
195
|
+
process.exit(0)
|
|
196
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# init skill
|
|
2
|
+
|
|
3
|
+
Give a project one canonical `plugin.json` on the [Agent Plugins
|
|
4
|
+
Specification](https://agent-plugins.org), then derive the manifest each runtime expects — Claude
|
|
5
|
+
Code, Cursor, Codex, and GitHub Copilot CLI.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
The skill runs a five-phase workflow: survey what the project already has, classify each finding,
|
|
10
|
+
confirm the plan, apply it, then verify and report.
|
|
11
|
+
|
|
12
|
+
It detects what is already there — a canonical manifest, hand-written vendor manifests, a legacy root
|
|
13
|
+
manifest, publicly-shipped skills with no manifest at all — and either adopts it onto the standard,
|
|
14
|
+
rebuilds it, or reports it as something to leave alone. It never rewrites a file the user authored
|
|
15
|
+
without asking first.
|
|
16
|
+
|
|
17
|
+
## Why the derivation step is small
|
|
18
|
+
|
|
19
|
+
Copilot CLI reads the canonical `plugin.json` directly, so nothing is generated for it. Only Claude
|
|
20
|
+
Code, Cursor, and Codex get a derived manifest, and the difference between them is a handful of
|
|
21
|
+
vendor-specific fields under `harnesses.<vendor>`.
|
|
22
|
+
|
|
23
|
+
`references/standard.md` defines the baseline every plugin gets. `references/vendors/<vendor>.md`
|
|
24
|
+
covers what one runtime needs on top of it, and `SKILL.md` routes to them directly rather than
|
|
25
|
+
loading all four.
|
|
26
|
+
|
|
27
|
+
## Routes
|
|
28
|
+
|
|
29
|
+
Phase 4 routes to exactly one reference:
|
|
30
|
+
|
|
31
|
+
| Route | Reference | Covers |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| Create | [`references/create.md`](./references/create.md) | scaffold a new plugin with chosen vendors and components |
|
|
34
|
+
| Adopt | [`references/adopt.md`](./references/adopt.md) | put an existing vendor-specific plugin, or already-shipped skills, onto the open standard |
|
|
35
|
+
| Update | [`references/update.md`](./references/update.md) | add or remove a vendor or a component |
|
|
36
|
+
|
|
37
|
+
Three neighbours own the rest of the plugin's life, each named for what it does: `doctor` diagnoses,
|
|
38
|
+
`version` moves the number, `remove-plugin` deletes. This skill is the one that writes the manifest.
|
|
39
|
+
|
|
40
|
+
## The part that needs care
|
|
41
|
+
|
|
42
|
+
Deriving a manifest is easy; keeping a skill's *behavior* identical across runtimes is not. Each
|
|
43
|
+
runtime parses the frontmatter fields it knows and silently drops the rest, so anything that must
|
|
44
|
+
hold everywhere belongs in the Markdown body. See `references/frontmatter.md`, which also documents
|
|
45
|
+
`invocation-policy` — the one field this build acts on, by rewriting the authored `SKILL.md`.
|
|
46
|
+
|
|
47
|
+
## Boundaries
|
|
48
|
+
|
|
49
|
+
Plugin authoring only. Setting a repository up to *consume* skills — `AGENTS.md`, the
|
|
50
|
+
`.agents/skills/` layout, per-harness bridges — is `buddy-agent-harness:init`. This skill does not
|
|
51
|
+
touch CI, repository settings, or unrelated project files.
|
|
52
|
+
|
|
53
|
+
## References
|
|
54
|
+
|
|
55
|
+
- [Spec](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/spec.md)
|
|
56
|
+
- [Schema](https://raw.githubusercontent.com/cyberuni/universal-plugin/refs/heads/main/schema/v1.json)
|
|
57
|
+
- [Examples](https://github.com/cyberuni/universal-plugin/tree/main/examples)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: init
|
|
3
|
+
description: Use this skill to create or change a universal agent plugin — scaffold a new one, adopt an existing vendor-specific plugin or already-shipped skills onto the open Agent Plugins Specification, or add and remove vendors and components on the canonical plugin.json that drives Claude Code, Cursor, Codex, and GitHub Copilot CLI. Trigger on "init a plugin here", "make my Claude Code plugin work in Cursor", "convert this to the open plugin standard", "turn these skills into a plugin", "add Codex support", or "add a hooks component".
|
|
4
|
+
argument-hint: '[--name <name>] [--vendor <id>] [--scaffold] [--npm] [--force]'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Plugin Init
|
|
8
|
+
|
|
9
|
+
Give a project one canonical plugin manifest — a root `plugin.json` on the Agent Plugins
|
|
10
|
+
Specification — and derive from it the manifest each runtime expects.
|
|
11
|
+
|
|
12
|
+
Most of the manifest is shared. The divergence is small and asymmetric: Copilot CLI reads the
|
|
13
|
+
canonical `plugin.json` directly and gets no derived file at all, while Claude Code, Cursor, and
|
|
14
|
+
Codex each read their own path. Keep that asymmetry in mind — this is a consolidation job, not a
|
|
15
|
+
copy-everywhere job.
|
|
16
|
+
|
|
17
|
+
`references/standard.md` defines the baseline every plugin gets. Read the vendor file for each
|
|
18
|
+
runtime you are enabling, and only those.
|
|
19
|
+
|
|
20
|
+
| Vendor | Derived manifest | Extra requirements | Read |
|
|
21
|
+
| --- | --- | --- | --- |
|
|
22
|
+
| Claude Code | `.claude-plugin/plugin.json` | none | `references/vendors/claude-code.md` |
|
|
23
|
+
| Cursor | `.cursor-plugin/plugin.json` | none | `references/vendors/cursor.md` |
|
|
24
|
+
| Codex | `.codex-plugin/plugin.json` | `version`, `description` | `references/vendors/codex.md` |
|
|
25
|
+
| GitHub Copilot CLI | none — reads root `plugin.json` | none | `references/vendors/copilot-cli.md` |
|
|
26
|
+
|
|
27
|
+
This skill owns the **authoring** side: the plugin a project ships. Setting a repository up to
|
|
28
|
+
*consume* skills — the `.agents/skills/` layout, `AGENTS.md`, per-harness bridges — is
|
|
29
|
+
`buddy-agent-harness:init`, not this.
|
|
30
|
+
|
|
31
|
+
## Prerequisites
|
|
32
|
+
|
|
33
|
+
Load the component-selection governance before any work that adds or removes components:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx universal-plugin governance show plugin-design
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
It is the authoritative source for which component to reach for and which anti-patterns to avoid.
|
|
40
|
+
|
|
41
|
+
## Arguments
|
|
42
|
+
|
|
43
|
+
An invocation may carry the CLI's own flags: `/universal-plugin:init --name my-plugin --scaffold --npm`.
|
|
44
|
+
|
|
45
|
+
Read them from the invocation itself rather than from a placeholder. Claude Code appends what the
|
|
46
|
+
caller typed as `ARGUMENTS: <value>`, and Codex substitutes nothing at all, so on every runtime the
|
|
47
|
+
flags arrive as text you can read. Writing `$ARGUMENTS` into this body would resolve on Claude Code
|
|
48
|
+
and stay literal everywhere else.
|
|
49
|
+
|
|
50
|
+
- `--name`, `--vendor`, `--scaffold`, `--force`, `--npm`, and `--root` pass through to
|
|
51
|
+
`universal-plugin plugin init` in Phase 4.
|
|
52
|
+
- Prose carries the same weight: "ship it on npm" means `--npm`, "overwrite what's there" means
|
|
53
|
+
`--force`.
|
|
54
|
+
- An argument never skips a phase. `--force` still needs the Phase 3 approval, and the survey still
|
|
55
|
+
runs first.
|
|
56
|
+
- Say what you did not recognize and carry on. Never guess at a flag.
|
|
57
|
+
|
|
58
|
+
Work in five phases. Do not skip Phase 3.
|
|
59
|
+
|
|
60
|
+
## 1. Survey
|
|
61
|
+
|
|
62
|
+
Locate the plugin root — the directory that holds, or will hold, the canonical `plugin.json`. In a
|
|
63
|
+
monorepo that is usually the package that ships the plugin, not the repository root; `migrate-plugin`
|
|
64
|
+
covers moving one that sits in the wrong place.
|
|
65
|
+
|
|
66
|
+
Inventory both the canonical surface (root `plugin.json`, its `extensions` block, the component
|
|
67
|
+
directories it names) and every pre-existing vendor artifact listed in `references/detection.md`.
|
|
68
|
+
Read what you find. Write nothing yet.
|
|
69
|
+
|
|
70
|
+
## 2. Classify
|
|
71
|
+
|
|
72
|
+
Sort each finding into exactly one bucket:
|
|
73
|
+
|
|
74
|
+
- **canonical** — a root `plugin.json` with `$schema` on `agent-plugins.org` and an `extensions`
|
|
75
|
+
object. This is the source of truth; every edit lands here.
|
|
76
|
+
- **derived** — `.claude-plugin/`, `.cursor-plugin/`, `.codex-plugin/` manifests that `plugin build`
|
|
77
|
+
regenerates. Rebuild them; never hand-edit them.
|
|
78
|
+
- **adoptable** — a hand-written vendor manifest with no canonical manifest above it, a legacy root
|
|
79
|
+
`plugin.json` carrying neither `$schema` nor `extensions`, or publicly-shipped skills with no
|
|
80
|
+
manifest at all. These become canonical content in Phase 4.
|
|
81
|
+
- **undeliverable** — a vendor-specific field with no delivery path, most often a
|
|
82
|
+
`harnesses["copilot-cli"]` override: the canonical schema is closed, so the field cannot ride
|
|
83
|
+
along in root and the build warns about it. Report; do not invent a home for it.
|
|
84
|
+
- **not a plugin** — repo-private agent configuration (`.claude/skills/`, `.agents/skills/`,
|
|
85
|
+
`.cursor/rules/`). It is this project's own tooling, not something it distributes. Say nothing
|
|
86
|
+
about packaging it.
|
|
87
|
+
|
|
88
|
+
`references/detection.md` maps each signal to its bucket, and draws the public-versus-private line
|
|
89
|
+
that decides the last two.
|
|
90
|
+
|
|
91
|
+
## 3. Confirm
|
|
92
|
+
|
|
93
|
+
Present the plan before touching anything the user wrote: which files will be created, which
|
|
94
|
+
hand-written vendor manifests become generated artifacts, which vendors will be enabled, what the
|
|
95
|
+
canonical metadata will say (show `name`, `version`, and `description` verbatim), and what is being
|
|
96
|
+
left alone and why.
|
|
97
|
+
|
|
98
|
+
Get explicit approval before any step that deletes, replaces, or rewrites a user-authored file —
|
|
99
|
+
adoption always crosses that line, because it turns manifests the user maintains into build output.
|
|
100
|
+
Creating a missing `plugin.json`, a missing component directory, or a missing skill scaffold needs
|
|
101
|
+
no approval; report those rather than asking.
|
|
102
|
+
|
|
103
|
+
One case needs asking even when nothing is overwritten: two vendor manifests that disagree on a
|
|
104
|
+
shared field. A silent pick there is a silent behavior change for one runtime.
|
|
105
|
+
|
|
106
|
+
## 4. Apply
|
|
107
|
+
|
|
108
|
+
Route by what Phase 2 found. Read exactly the reference for the work at hand — do not load all six.
|
|
109
|
+
|
|
110
|
+
| What Phase 2 found | Do this | Reference |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| nothing — greenfield | scaffold a canonical manifest and its components | [`references/create.md`](./references/create.md) |
|
|
113
|
+
| adoptable | carry it onto the canonical manifest, losslessly | [`references/adopt.md`](./references/adopt.md) |
|
|
114
|
+
| canonical, and a vendor or component changes | edit `extensions`, then rebuild | [`references/update.md`](./references/update.md) |
|
|
115
|
+
|
|
116
|
+
Three asks leave this skill rather than routing inside it. Hand them over instead of improvising a
|
|
117
|
+
route: **`doctor`** reports what is declared, built, stale, or drifting; **`version`** moves the
|
|
118
|
+
number; **`remove-plugin`** deletes derived manifests or the plugin itself.
|
|
119
|
+
|
|
120
|
+
Every route ends in the same two commands. Scaffold with the CLI that shipped beside this skill:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
node scripts/init.mjs --name <plugin-name> --vendor claude-code --vendor cursor
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Resolve that path against this skill's own directory. The launcher runs the bundled CLI, so nothing
|
|
127
|
+
is downloaded; fall back to `npx universal-plugin plugin init` when the path cannot be resolved.
|
|
128
|
+
`plugin init` never prompts, so it is safe to run unattended (`--yes` is accepted as a no-op).
|
|
129
|
+
|
|
130
|
+
Then derive the vendor manifests:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npx universal-plugin plugin build
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`plugin init` writes a **minimal** manifest — `$schema`, `name`, and the vendor list. It never reads
|
|
137
|
+
an existing vendor manifest, so shared metadata and per-vendor overrides are carried in by hand
|
|
138
|
+
afterwards, per the reference you routed to.
|
|
139
|
+
|
|
140
|
+
If the request spans more than one route ("add Codex, then cut a release"), take this skill's part
|
|
141
|
+
first and hand the rest to the skill that owns it. If the route is unclear, ask which the user means
|
|
142
|
+
rather than guessing.
|
|
143
|
+
|
|
144
|
+
## 5. Verify and report
|
|
145
|
+
|
|
146
|
+
Confirm the build wrote what the manifest declares: one file per derived vendor, `canonical` status
|
|
147
|
+
and no file for Copilot CLI. Read the build's warnings — an unknown vendor id and an undeliverable
|
|
148
|
+
override both surface there and both are silent capability loss if ignored.
|
|
149
|
+
|
|
150
|
+
When the work was an adoption, the proof is the diff:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
git diff -- .claude-plugin .cursor-plugin .codex-plugin .github/plugin
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Expect only formatting and key-order churn. Any field that disappeared is a regression, not a
|
|
157
|
+
cleanup — trace it back to the shared metadata or to that vendor's `harnesses` entry before shipping.
|
|
158
|
+
|
|
159
|
+
Audit each skill the plugin ships, per [`references/create.md`](./references/create.md) Step 6.
|
|
160
|
+
Report what was created, adopted, derived, and left alone. For a fuller read of the plugin's state
|
|
161
|
+
than this phase gives — drift, version skew, shadowing manifests — hand off to `doctor`.
|
|
162
|
+
|
|
163
|
+
This skill is not a formatter. If the project has one, run it over the written files and say so.
|
|
164
|
+
|
|
165
|
+
## Rules
|
|
166
|
+
|
|
167
|
+
- **Edit the canonical manifest, never a derived one.** `.claude-plugin/plugin.json` and its
|
|
168
|
+
siblings belong to `plugin build`; a hand-edit there is overwritten on the next build.
|
|
169
|
+
- **Never hand-edit a `version` field.** Two authored files and every derived artifact fall out of
|
|
170
|
+
sync. The `version` skill owns that move.
|
|
171
|
+
- **Adoption is lossless by contract.** Every vendor that worked before must still work after, and
|
|
172
|
+
the Phase 5 diff is the check that proves it.
|
|
173
|
+
- **Do not package repo-private agent configuration.** A `.claude/skills/` directory is the project's
|
|
174
|
+
own tooling; offering to publish it is wrong.
|
|
175
|
+
- **Do not convert vendor settings without a documented mapping.** Hook event names diverge by case
|
|
176
|
+
across runtimes and the build does not translate them today — see
|
|
177
|
+
`references/vendors/claude-code.md`. Unmapped settings stay where they are, reported.
|
|
178
|
+
- **Offer adoption once.** If the user declines, or asked for something unrelated, drop it and do
|
|
179
|
+
what they asked.
|
|
180
|
+
- Plugin authoring only. Do not change CI workflows, repository settings, or unrelated project files.
|
|
181
|
+
|
|
182
|
+
## Related skills
|
|
183
|
+
|
|
184
|
+
| Task | Skill |
|
|
185
|
+
|------|-------|
|
|
186
|
+
| Diagnose a plugin — what is declared, built, stale, or drifting | `doctor` |
|
|
187
|
+
| Move the plugin's version, or reconcile one that drifted | `version` |
|
|
188
|
+
| Delete derived manifests, or the whole plugin | `remove-plugin` |
|
|
189
|
+
| Move a repo-root plugin into its npm package | `migrate-plugin` |
|
|
190
|
+
| Publish a packaged plugin to the marketplace | `publish-plugin` |
|
|
191
|
+
| Bump the pinned `universal-plugin@<version>` the project *calls* (not the plugin's own version) | `upgrade-plugin` |
|
|
192
|
+
| Rewrite `npx` pins to the `upx` runner | `adopt-upx` |
|
|
193
|
+
| Set a repository up to *consume* skills (`AGENTS.md`, `.agents/skills/`) | `buddy-agent-harness:init` |
|
|
194
|
+
|
|
195
|
+
## References
|
|
196
|
+
|
|
197
|
+
- Governance: `npx universal-plugin governance show plugin-design`
|
|
198
|
+
- Spec: https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/spec.md
|
|
199
|
+
- Schema: https://raw.githubusercontent.com/cyberuni/universal-plugin/refs/heads/main/schema/v1.json
|
|
200
|
+
- Examples: https://github.com/cyberuni/universal-plugin/tree/main/examples
|
|
@@ -16,8 +16,9 @@ is the check that proves it — do not skip it.
|
|
|
16
16
|
|
|
17
17
|
## Step 0 — Confirm the user wants this
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
This is the skill's Phase 3 gate, and adoption always needs it: adoption rewrites the project's
|
|
20
|
+
manifest layout and turns hand-written vendor manifests into generated artifacts. Say that plainly
|
|
21
|
+
and get agreement before touching files. If the user declines,
|
|
21
22
|
route back to whatever they originally asked for.
|
|
22
23
|
|
|
23
24
|
Also confirm the working tree is clean (`git status`). The Step 6 diff is worthless if uncommitted
|
|
@@ -37,7 +38,7 @@ reproduce all of it.
|
|
|
37
38
|
|
|
38
39
|
**If a root `plugin.json` already exists**, read it before assuming anything. It is either the
|
|
39
40
|
canonical manifest (has `$schema` pointing at `agent-plugins.org` and an `extensions` object — in
|
|
40
|
-
which case there is nothing to adopt; route to `update.md
|
|
41
|
+
which case there is nothing to adopt; route to `update.md`, or hand off to `doctor`, instead), or a legacy
|
|
41
42
|
Copilot CLI manifest that now collides with the canonical path and must be folded in.
|
|
42
43
|
|
|
43
44
|
## Step 2 — Sort every field into shared vs vendor-specific
|
|
@@ -58,9 +59,12 @@ rather than picking one. A silent choice here is a silent behavior change for on
|
|
|
58
59
|
Scaffold it, naming exactly the vendors you found in Step 1:
|
|
59
60
|
|
|
60
61
|
```bash
|
|
61
|
-
|
|
62
|
+
node scripts/init.mjs --name <name> --vendor claude-code --vendor cursor
|
|
62
63
|
```
|
|
63
64
|
|
|
65
|
+
Resolve `scripts/init.mjs` against this skill's directory; `npx universal-plugin plugin init` is the
|
|
66
|
+
fallback.
|
|
67
|
+
|
|
64
68
|
> `plugin init` writes a **minimal** manifest — `$schema`, `name`, and the `vendors` list. It does
|
|
65
69
|
> not read your existing vendor manifests. Carry the Step 2 buckets in by hand afterwards.
|
|
66
70
|
|