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.
Files changed (41) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/.cursor-plugin/plugin.json +1 -1
  4. package/LICENSE +21 -0
  5. package/dist/cli.mjs +76 -134
  6. package/package.json +3 -1
  7. package/plugin.json +1 -1
  8. package/readme.md +73 -42
  9. package/skills/doctor/README.md +42 -0
  10. package/skills/doctor/SKILL.md +123 -0
  11. package/skills/doctor/scripts/doctor.mjs +196 -0
  12. package/skills/init/README.md +57 -0
  13. package/skills/init/SKILL.md +200 -0
  14. package/skills/{plugin → init}/references/adopt.md +8 -4
  15. package/skills/init/references/create.md +111 -0
  16. package/skills/init/references/detection.md +62 -0
  17. package/skills/init/references/frontmatter.md +65 -0
  18. package/skills/init/references/standard.md +92 -0
  19. package/skills/init/references/update.md +31 -0
  20. package/skills/init/references/vendors/claude-code.md +44 -0
  21. package/skills/init/references/vendors/codex.md +48 -0
  22. package/skills/init/references/vendors/copilot-cli.md +45 -0
  23. package/skills/init/references/vendors/cursor.md +45 -0
  24. package/skills/init/scripts/init.mjs +11 -0
  25. package/skills/remove-plugin/README.md +38 -0
  26. package/skills/remove-plugin/SKILL.md +87 -0
  27. package/skills/version/README.md +36 -0
  28. package/skills/{plugin/references/version.md → version/SKILL.md} +26 -5
  29. package/skills/version/scripts/version.mjs +11 -0
  30. package/skills/plugin/README.md +0 -37
  31. package/skills/plugin/SKILL.md +0 -105
  32. package/skills/plugin/references/create.md +0 -163
  33. package/skills/plugin/references/delete.md +0 -23
  34. package/skills/plugin/references/inspect.md +0 -21
  35. package/skills/plugin/references/update.md +0 -26
  36. /package/skills/{plugin → init}/assets/templates/agent.md +0 -0
  37. /package/skills/{plugin → init}/assets/templates/command.md +0 -0
  38. /package/skills/{plugin → init}/assets/templates/hooks.json +0 -0
  39. /package/skills/{plugin → init}/assets/templates/plugin.json +0 -0
  40. /package/skills/{plugin → init}/assets/templates/setup-command.md +0 -0
  41. /package/skills/{plugin → init}/assets/templates/skill.md +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-plugin",
3
- "version": "0.3.1",
3
+ "version": "0.4.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.1",
3
+ "version": "0.4.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.1",
3
+ "version": "0.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 unional
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/cli.mjs CHANGED
@@ -3,6 +3,7 @@ import { Command, Option } from "commander";
3
3
  import * as fsNode from "node:fs";
4
4
  import * as os from "node:os";
5
5
  import * as path from "node:path";
6
+ import { encode } from "@toon-format/toon";
6
7
  import { fileURLToPath } from "node:url";
7
8
  import * as childProcess from "node:child_process";
8
9
  import * as semver from "semver";
@@ -158,21 +159,6 @@ function cleanCommand() {
158
159
  function printJson(data) {
159
160
  console.log(JSON.stringify(data, null, 2));
160
161
  }
161
- function printFields(fields) {
162
- const entries = Object.entries(fields).filter(([, v]) => v != null);
163
- const width = Math.max(...entries.map(([k]) => k.length));
164
- for (const [key, val] of entries) console.log(`${key.padEnd(width)} ${val}`);
165
- }
166
- function printTable(items, cols) {
167
- if (items.length === 0) {
168
- console.log("(none)");
169
- return;
170
- }
171
- const widths = cols.map((c) => Math.max(c.label.length, ...items.map((i) => c.get(i).length)));
172
- console.log(cols.map((c, i) => c.label.toUpperCase().padEnd(widths[i])).join(" "));
173
- console.log(widths.map((w) => "-".repeat(w)).join(" "));
174
- for (const item of items) console.log(cols.map((c, i) => c.get(item).padEnd(widths[i])).join(" "));
175
- }
176
162
  function getFormat() {
177
163
  const argv = process.argv;
178
164
  const fmtIdx = argv.indexOf("--format");
@@ -182,9 +168,20 @@ function getFormat() {
182
168
  function isJsonOutput() {
183
169
  return getFormat() === "json";
184
170
  }
185
- function output(data, readable) {
171
+ /** Print the machine result on stdout: JSON when asked for, TOON otherwise (AXI #1).
172
+ *
173
+ * `view` is the default format's payload, carrying the minimal row schema and the
174
+ * pre-computed aggregates a reader needs (AXI #2, #4). It defaults to `data`, which
175
+ * suits a command whose full result is already minimal. */
176
+ function output(data, view) {
177
+ if (isJsonOutput()) printJson(data);
178
+ else console.log(encode(view ?? data));
179
+ }
180
+ /** Print a result whose default rendering is a document body rather than a record.
181
+ * `--format json` still emits the structured payload. */
182
+ function outputText(data, text) {
186
183
  if (isJsonOutput()) printJson(data);
187
- else readable();
184
+ else text();
188
185
  }
189
186
  //#endregion
190
187
  //#region src/json.ts
@@ -419,30 +416,22 @@ function buildCommand() {
419
416
  });
420
417
  for (const warning of result.warnings) process.stderr.write(`warn: ${warning}\n`);
421
418
  const { built, skipped, failed, canonical } = result.summary;
422
- output({
419
+ const jsonResult = {
423
420
  built: result.rows.filter((r) => r.status === "built"),
424
421
  skipped: result.rows.filter((r) => r.status === "skipped"),
425
422
  failed: result.rows.filter((r) => r.status === "failed"),
426
423
  canonical: result.rows.filter((r) => r.status === "canonical"),
427
424
  summary: result.summary,
428
425
  warnings: result.warnings
429
- }, () => {
430
- if (result.rows.length > 0) printTable(result.rows, [
431
- {
432
- label: "vendor",
433
- get: (r) => r.vendor
434
- },
435
- {
436
- label: "path",
437
- get: (r) => r.path
438
- },
439
- {
440
- label: "status",
441
- get: (r) => r.status
442
- }
443
- ]);
444
- const counts = `built ${built}, skipped ${skipped}, failed ${failed}`;
445
- console.log(canonical > 0 ? `${counts}, served by plugin.json ${canonical}` : counts);
426
+ };
427
+ const counts = `built ${built}, skipped ${skipped}, failed ${failed}`;
428
+ output(jsonResult, {
429
+ vendors: result.rows.map((r) => ({
430
+ vendor: r.vendor,
431
+ path: r.path,
432
+ status: r.status
433
+ })),
434
+ summary: canonical > 0 ? `${counts}, served by plugin.json ${canonical}` : counts
446
435
  });
447
436
  process.stderr.write(NEXT_STEP$2);
448
437
  if (failed > 0) process.exitCode = 1;
@@ -730,29 +719,17 @@ function bundleCommand() {
730
719
  const pinned = result.pins.filter((p) => p.status === "pinned").length;
731
720
  const unchanged = result.pins.filter((p) => p.status === "unchanged").length;
732
721
  const skipped = result.pins.filter((p) => p.status === "skipped").length;
733
- output({ pins: result.pins }, () => {
734
- const truncated = !opts.full && result.pins.length > TRUNCATE_THRESHOLD;
735
- const rows = truncated ? result.pins.slice(0, TRUNCATE_THRESHOLD) : result.pins;
736
- if (rows.length > 0) printTable(rows, [
737
- {
738
- label: "package",
739
- get: (r) => r.package
740
- },
741
- {
742
- label: "current",
743
- get: (r) => r.current
744
- },
745
- {
746
- label: "resolved",
747
- get: (r) => r.resolved
748
- },
749
- {
750
- label: "status",
751
- get: (r) => r.status
752
- }
753
- ]);
754
- if (truncated) console.log(`… +${result.pins.length - TRUNCATE_THRESHOLD} more — rerun with --full`);
755
- console.log(`pinned ${pinned}, unchanged ${unchanged}, skipped ${skipped}`);
722
+ const truncated = !opts.full && result.pins.length > TRUNCATE_THRESHOLD;
723
+ const shown = truncated ? result.pins.slice(0, TRUNCATE_THRESHOLD) : result.pins;
724
+ output({ pins: result.pins }, {
725
+ pins: shown.map((r) => ({
726
+ package: r.package,
727
+ current: r.current,
728
+ resolved: r.resolved,
729
+ status: r.status
730
+ })),
731
+ ...truncated ? { truncated: `… +${result.pins.length - TRUNCATE_THRESHOLD} more — rerun with --full` } : {},
732
+ summary: `pinned ${pinned}, unchanged ${unchanged}, skipped ${skipped}`
756
733
  });
757
734
  if (result.pins.length === 0) process.stderr.write("nothing to bundle\n");
758
735
  process.stderr.write(NEXT_STEP$1);
@@ -853,22 +830,11 @@ function addCommand(fs) {
853
830
  key: opts.key,
854
831
  name: result.name,
855
832
  action: result.action
856
- }, () => {
857
- printTable([result], [
858
- {
859
- label: "key",
860
- get: () => opts.key
861
- },
862
- {
863
- label: "name",
864
- get: (r) => r.name
865
- },
866
- {
867
- label: "action",
868
- get: (r) => r.action
869
- }
870
- ]);
871
- console.log(`${opts.key}: ${count} entries`);
833
+ }, {
834
+ key: opts.key,
835
+ name: result.name,
836
+ action: result.action,
837
+ summary: `${opts.key}: ${count} entries`
872
838
  });
873
839
  process.stderr.write(`→ universal-plugin config get --key ${opts.key}\n`);
874
840
  } catch (err) {
@@ -883,12 +849,9 @@ function getCommand(fs) {
883
849
  assertNotReserved(opts.key);
884
850
  const root = resolveRoot(opts.root);
885
851
  const entries = getEntries(fs.read(root), opts.key);
886
- output(entries, () => {
887
- printTable(entries, [{
888
- label: "name",
889
- get: (e) => String(e.name ?? "")
890
- }]);
891
- console.log(`${opts.key}: ${entries.length} entries`);
852
+ output(entries, {
853
+ names: entries.map((e) => String(e.name ?? "")),
854
+ summary: `${opts.key}: ${entries.length} entries`
892
855
  });
893
856
  process.stderr.write(`→ universal-plugin config add --key ${opts.key} --entry <json>\n`);
894
857
  } catch (err) {
@@ -1026,20 +989,18 @@ function governanceCommand() {
1026
989
  process.stderr.write(`Governance "${name}" not found\n`);
1027
990
  process.exit(1);
1028
991
  }
1029
- output(result, () => {
992
+ outputText(result, () => {
1030
993
  process.stdout.write(result.content);
1031
994
  });
1032
995
  });
1033
996
  cmd.command("list").description("List available governances").option("--format <format>", "Output format: json or text (default: text)").addOption(ROOT_OPTION).addOption(new Option("--json").hideHelp()).action((opts) => {
1034
997
  const entries = listGovernances(resolveRoot(opts.root), realGovernanceFs);
1035
- output(entries, () => {
1036
- printTable(entries, [{
1037
- label: "name",
1038
- get: (e) => e.name
1039
- }, {
1040
- label: "scope",
1041
- get: (e) => e.scope
1042
- }]);
998
+ output(entries, {
999
+ governances: entries.map((e) => ({
1000
+ name: e.name,
1001
+ scope: e.scope
1002
+ })),
1003
+ summary: `${entries.length} governances across ${new Set(entries.map((e) => e.scope)).size} scopes`
1043
1004
  });
1044
1005
  });
1045
1006
  return cmd;
@@ -1172,15 +1133,12 @@ function initCommand$1(deps = { fs: realInitFs }) {
1172
1133
  created: plan.rows.filter((r) => r.action === "created").map((r) => r.path),
1173
1134
  updated: plan.rows.filter((r) => r.action === "updated").map((r) => r.path),
1174
1135
  summary: plan.summary
1175
- }, () => {
1176
- printTable(plan.rows, [{
1177
- label: "path",
1178
- get: (r) => r.path
1179
- }, {
1180
- label: "action",
1181
- get: (r) => r.action
1182
- }]);
1183
- console.log(`created ${plan.summary.created}, updated ${plan.summary.updated}`);
1136
+ }, {
1137
+ files: plan.rows.map((r) => ({
1138
+ path: r.path,
1139
+ action: r.action
1140
+ })),
1141
+ summary: `created ${plan.summary.created}, updated ${plan.summary.updated}`
1184
1142
  });
1185
1143
  process.stderr.write(NEXT_STEP);
1186
1144
  } catch (err) {
@@ -1496,24 +1454,15 @@ function initCommand() {
1496
1454
  dryRun: opts.dryRun,
1497
1455
  force: opts.force
1498
1456
  });
1499
- output(results, () => printTable(results, [
1500
- {
1501
- label: "target",
1502
- get: (row) => row.target
1503
- },
1504
- {
1505
- label: "status",
1506
- get: (row) => row.status
1507
- },
1508
- {
1509
- label: "paths",
1510
- get: (row) => row.paths.join(", ") || "-"
1511
- },
1512
- {
1513
- label: "plugins",
1514
- get: (row) => row.plugins.join(", ") || "-"
1515
- }
1516
- ]));
1457
+ output(results, {
1458
+ targets: results.map((row) => ({
1459
+ target: row.target,
1460
+ status: row.status,
1461
+ paths: row.paths.join(" ") || "-",
1462
+ plugins: row.plugins.join(" ") || "-"
1463
+ })),
1464
+ summary: `${results.length} targets`
1465
+ });
1517
1466
  process.stderr.write("Generated repository metadata only; no marketplace publication, registration, installation, authentication, or provisioning occurred.\n");
1518
1467
  } catch (err) {
1519
1468
  process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
@@ -1849,11 +1798,9 @@ function publishCommand() {
1849
1798
  cmd.command("sync-version").description("Sync version from packagePath/package.json into plugin.json").addOption(ROOT_OPTION).action((opts) => {
1850
1799
  try {
1851
1800
  const result = syncVersion(resolveRoot(opts.root), realSyncVersionFs);
1852
- output(result, () => {
1853
- printFields({
1854
- version: result.version,
1855
- manifest: result.manifestPath
1856
- });
1801
+ output(result, {
1802
+ version: result.version,
1803
+ manifest: result.manifestPath
1857
1804
  });
1858
1805
  } catch (err) {
1859
1806
  process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
@@ -2129,19 +2076,14 @@ function versionCommand(deps = { fs: realVersionFs }) {
2129
2076
  derived: rows.filter((r) => r.action === "derived").length
2130
2077
  }
2131
2078
  };
2132
- output(jsonResult, () => {
2133
- printFields({
2134
- from: plan.from ?? "(none)",
2135
- to: plan.to
2136
- });
2137
- printTable(rows, [{
2138
- label: "path",
2139
- get: (r) => r.path
2140
- }, {
2141
- label: "action",
2142
- get: (r) => r.action
2143
- }]);
2144
- console.log(dryRun ? `planned ${plan.summary.updated}, updated 0 (dry run)` : `updated ${plan.summary.updated}, derived ${jsonResult.summary.derived}`);
2079
+ output(jsonResult, {
2080
+ from: plan.from ?? "(none)",
2081
+ to: plan.to,
2082
+ files: rows.map((r) => ({
2083
+ path: r.path,
2084
+ action: r.action
2085
+ })),
2086
+ summary: dryRun ? `planned ${plan.summary.updated}, updated 0 (dry run)` : `updated ${plan.summary.updated}, derived ${jsonResult.summary.derived}`
2145
2087
  });
2146
2088
  process.stderr.write(nextStep(dryRun, opts.build !== false));
2147
2089
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-plugin",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Universal AI agent plugin build tool",
5
5
  "keywords": [
6
6
  "agent-plugin",
@@ -25,6 +25,7 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "files": [
28
+ "LICENSE",
28
29
  "bin",
29
30
  "dist",
30
31
  "governances",
@@ -36,6 +37,7 @@
36
37
  "agents"
37
38
  ],
38
39
  "dependencies": {
40
+ "@toon-format/toon": "^4.1.1",
39
41
  "commander": "^14.0.3",
40
42
  "semver": "^7.8.1"
41
43
  },
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.3.1",
4
+ "version": "0.4.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
@@ -1,87 +1,95 @@
1
1
  # universal-plugin
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/universal-plugin.svg)](https://www.npmjs.com/package/universal-plugin)
4
- [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
+ [![node](https://img.shields.io/node/v/universal-plugin.svg)](https://www.npmjs.com/package/universal-plugin)
5
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/cyberuni/universal-plugin/blob/main/LICENSE)
5
6
 
6
- Universal AI agent plugin build tool. Author one canonical plugin manifest (root `plugin.json`) and generate vendor-specific manifests for Claude Code, Cursor, Codex, and GitHub Copilot CLI.
7
-
8
- ## Specification
9
-
10
- This package follows the [Agent Plugins Specification](https://github.com/agentplugins/agent-plugins-spec).
11
- Consult that repository's versioned specification and releases before changing manifest
12
- or component compatibility behavior; it is the canonical reference for the current standard.
7
+ Write one canonical plugin manifest (root `plugin.json`). Generate the vendor manifests for Claude
8
+ Code, Cursor, Codex, and GitHub Copilot CLI.
13
9
 
14
10
  ## Usage
15
11
 
16
- No install required — run with `npx`:
12
+ No install required:
17
13
 
18
14
  ```sh
19
15
  npx universal-plugin <command>
20
16
  ```
21
17
 
22
- Or pin to an exact version for reproducible builds:
18
+ Pin an exact version for reproducible builds:
23
19
 
24
20
  ```sh
25
- npx universal-plugin@0.2.0 <command>
21
+ npx universal-plugin@0.3.1 <command>
26
22
  ```
27
23
 
28
- ## upx — the fast package runner
29
-
30
- `npm i -g universal-plugin` also puts a second bin, `upx`, on PATH. `upx <pkg>@^<major>` finds an
31
- already-installed version satisfying the range (local `node_modules` first, then global) and runs
32
- it directly — about 10× faster than `npx`'s ~1s per-call resolve+spawn cost — falling back to
33
- `npx` when nothing installed matches:
34
-
35
- ```sh
36
- npm i -g universal-plugin
37
- upx cyber-skills@^2 audit validate
38
- ```
24
+ ## Specification
39
25
 
40
- Use a caret range on the major, not an exact pin, so one global install serves every caller. `upx`
41
- needs to be installed to be on PATH; `npx` always ships with npm, so `npx` remains the safe default
42
- where `universal-plugin` isn't installed globally.
26
+ This package follows the [Agent Plugins Specification](https://github.com/agentplugins/agent-plugins-spec).
27
+ That repository is the canonical reference. Consult its versioned specification and releases before
28
+ you change manifest or component compatibility behavior.
43
29
 
44
30
  ## Commands
45
31
 
46
- ### plugin — author the canonical manifest
32
+ ### plugin
33
+
34
+ Author the canonical manifest and derive everything from it.
47
35
 
48
36
  ```sh
49
- # Generate vendor manifests from root plugin.json
50
- npx universal-plugin plugin build
37
+ npx universal-plugin plugin init # scaffold plugin.json
38
+ npx universal-plugin plugin init --npm # also wire an npm package to ship it
39
+ npx universal-plugin plugin build # generate vendor manifests
40
+ npx universal-plugin plugin version <bump> # move the version across every file carrying one
41
+ npx universal-plugin plugin bundle # pin skill npx references to workspace versions
51
42
  ```
52
43
 
53
- `validate` and `init` are specified but implementation is deferred.
44
+ `build` writes `.claude-plugin/plugin.json`, `.cursor-plugin/plugin.json`, and
45
+ `.codex-plugin/plugin.json`. Copilot CLI reads the canonical root `plugin.json` directly, so no
46
+ fourth file is derived.
47
+
48
+ Each command writes JSON with `JSON.stringify`. Your repository decides how JSON looks, so run your
49
+ formatter after any command that writes a manifest.
54
50
 
55
- ### sync — cross-vendor plugin sync
51
+ ### sync
52
+
53
+ Move an installed plugin from the runtime that installed it to the others.
56
54
 
57
55
  ```sh
58
- # Detect cross-vendor sync actions from a vendor's manifest
59
56
  npx universal-plugin prepare <vendor-id> # e.g. claude-code
60
57
  npx universal-plugin prepare <vendor-id> --scope project --root <path>
61
- npx universal-plugin prepare <vendor-id> --dry-run # print action count without writing state
62
-
63
- # Apply a pending sync action
58
+ npx universal-plugin prepare <vendor-id> --dry-run # print the action count without writing state
64
59
  npx universal-plugin sync apply <action-id>
65
60
  ```
66
61
 
67
62
  ### publish
68
63
 
69
64
  ```sh
70
- # Sync version from packagePath/package.json into root plugin.json
71
- npx universal-plugin publish sync-version
65
+ npx universal-plugin publish sync-version # copy packagePath/package.json version into plugin.json
72
66
  ```
73
67
 
68
+ This writes the canonical `plugin.json` only. Run `plugin build` afterwards, or the vendor manifests
69
+ keep their previous version.
70
+
74
71
  ### marketplace
75
72
 
76
73
  ```sh
77
- # Generate a local Codex catalog from canonical plugin manifests.
78
74
  npx universal-plugin marketplace init --codex --root .
79
75
  ```
80
76
 
81
- Codex caches a local plugin install by its marketplace entry version. After changing packaged plugin
82
- files, update the canonical `plugin.json` version, regenerate the Codex catalog (use `--force` when
83
- replacing an existing catalog), reinstall the plugin, and start a new Codex session. This ensures the
84
- installed copy and its generated marketplace entry use the same version.
77
+ Codex caches a local plugin install by its marketplace entry version. After you change packaged
78
+ plugin files: update the canonical `plugin.json` version, regenerate the catalog (add `--force` to
79
+ replace an existing one), reinstall the plugin, then start a new Codex session. The installed copy
80
+ and its marketplace entry then carry the same version.
81
+
82
+ ### config
83
+
84
+ Read and write plugin-registered config in `.agents/universal-plugin.json`.
85
+
86
+ ```sh
87
+ npx universal-plugin config get --key sdd-plugins
88
+ npx universal-plugin config add --key sdd-plugins --entry '{"name":"aces","handles":["agent evaluation"]}'
89
+ ```
90
+
91
+ `add` appends the entry, or replaces the existing entry with the same `name`. Both commands print
92
+ TOON by default; pass `--format json` for JSON.
85
93
 
86
94
  ### governance
87
95
 
@@ -99,6 +107,29 @@ npx universal-plugin clean # remove the asset store
99
107
  npx universal-plugin self-update <version> # update the version pin in hook files
100
108
  ```
101
109
 
110
+ ## upx, the fast package runner
111
+
112
+ `npm i -g universal-plugin` puts a second bin, `upx`, on PATH.
113
+
114
+ `upx <pkg>@^<major>` looks for an already-installed version satisfying the range, checking local
115
+ `node_modules` first and then global. It spawns that binary directly, skipping the resolve step that
116
+ costs `npx` roughly 1s per call. When nothing installed matches, it falls back to `npx`.
117
+
118
+ ```sh
119
+ npm i -g universal-plugin
120
+ upx cyber-skills@^2 audit validate
121
+ ```
122
+
123
+ Use a caret range on the major rather than an exact pin, so one global install serves every caller.
124
+
125
+ `upx` only works once `universal-plugin` is installed globally. `npx` ships with npm, so keep `npx`
126
+ as the default anywhere you cannot guarantee that install.
127
+
128
+ ## Related
129
+
130
+ This package publishes a plugin. To set up the agent configuration of a repository you work in, use
131
+ [`buddy-agent-harness`](https://github.com/repobuddy/buddy-agent-harness).
132
+
102
133
  ## License
103
134
 
104
- MIT
135
+ [MIT](https://github.com/cyberuni/universal-plugin/blob/main/LICENSE)
@@ -0,0 +1,42 @@
1
+ # doctor skill
2
+
3
+ Diagnose a universal agent plugin: what the canonical `plugin.json` declares, and whether what is on
4
+ disk still matches it for Claude Code, Cursor, Codex, and GitHub Copilot CLI.
5
+
6
+ ## What it does
7
+
8
+ `scripts/doctor.mjs` composes the shipped CLI's `plugin build --dry-run --format json` with the
9
+ filesystem facts that build cannot see — whether each derived manifest exists, whether it predates
10
+ the canonical manifest, whether a stale or shadowing manifest is lying around, and whether the two
11
+ authored version numbers still agree. It emits one JSON object: `vendors`, `findings`, `ok`.
12
+
13
+ The skill supplies the judgment around it: which finding matters, and which skill owns its repair.
14
+
15
+ ## It never repairs
16
+
17
+ Every finding names the skill that fixes it — `init` for anything that rewrites the manifest,
18
+ `version` for the release number, `remove-plugin` for artifacts. A repair can overwrite a manifest
19
+ the user maintains, and that judgment belongs to the skill that owns the write.
20
+
21
+ The script is read-only and exits `0` whether or not it finds anything, so it is safe to run
22
+ unattended, including from a session-start hook.
23
+
24
+ ## Why a script rather than a checklist
25
+
26
+ The checks are deterministic: same tree, same findings. The one check that is not scriptable is the
27
+ definitive staleness test — rebuild on a clean tree and read the diff — because it writes. The skill
28
+ reports that one as a repair for the user to run.
29
+
30
+ Manifest validation is chartered as a CLI capability (`plugin validate`, specified but not yet
31
+ shipped). This script stays a thin composition on purpose, so it folds into that command rather than
32
+ competing with it.
33
+
34
+ ## Boundaries
35
+
36
+ Diagnoses the plugin a project ships. A repository's own agent wiring — `.agents/skills/`,
37
+ `AGENTS.md`, per-harness bridges — is `buddy-agent-harness:doctor`.
38
+
39
+ ## References
40
+
41
+ - [Spec](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/spec.md)
42
+ - [`plugin build`](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/plugin/build/README.md)