universal-plugin 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/dist/cli.mjs +517 -141
- package/dist/data/vendors.json +0 -9
- package/package.json +3 -2
- package/plugin.json +1 -1
- package/readme.md +4 -6
- package/references/plugin-design.md +1 -1
- package/references/universal-plugin.md +1 -1
- package/skills/build-plugin/SKILL.md +1 -1
- package/skills/init-universal-plugin/SKILL.md +2 -2
- package/skills/init-universal-plugin/references/standard.md +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
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.11.1",
|
|
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.11.1",
|
|
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/dist/cli.mjs
CHANGED
|
@@ -4,8 +4,10 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import * as childProcess from "node:child_process";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
6
|
import * as path from "node:path";
|
|
7
|
+
import { join, win32 } from "node:path";
|
|
7
8
|
import * as fs from "node:fs";
|
|
8
9
|
import * as os from "node:os";
|
|
10
|
+
import { homedir } from "node:os";
|
|
9
11
|
//#region \0rolldown/runtime.js
|
|
10
12
|
var __create = Object.create;
|
|
11
13
|
var __defProp = Object.defineProperty;
|
|
@@ -24,11 +26,11 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
24
26
|
}
|
|
25
27
|
return to;
|
|
26
28
|
};
|
|
27
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
28
30
|
value: mod,
|
|
29
31
|
enumerable: true
|
|
30
32
|
}) : target, mod));
|
|
31
|
-
var __require = /*
|
|
33
|
+
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
32
34
|
//#endregion
|
|
33
35
|
//#region ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/error.js
|
|
34
36
|
var require_error = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
@@ -100,7 +102,6 @@ var require_argument = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
100
102
|
default:
|
|
101
103
|
this.required = true;
|
|
102
104
|
this._name = name;
|
|
103
|
-
break;
|
|
104
105
|
}
|
|
105
106
|
if (this._name.endsWith("...")) {
|
|
106
107
|
this.variadic = true;
|
|
@@ -1595,16 +1596,20 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1595
1596
|
const oldValue = this.getOptionValue(name);
|
|
1596
1597
|
if (val !== null && option.parseArg) val = this._callParseArg(option, val, oldValue, invalidValueMessage);
|
|
1597
1598
|
else if (val !== null && option.variadic) val = option._collectValue(val, oldValue);
|
|
1598
|
-
if (val == null)
|
|
1599
|
-
|
|
1600
|
-
|
|
1599
|
+
if (val == null) {
|
|
1600
|
+
if (option.negate) val = false;
|
|
1601
|
+
else if (option.isBoolean() || option.optional) val = true;
|
|
1602
|
+
else val = "";
|
|
1603
|
+
}
|
|
1601
1604
|
this.setOptionValueWithSource(name, val, valueSource);
|
|
1602
1605
|
};
|
|
1603
1606
|
this.on("option:" + oname, (val) => {
|
|
1604
|
-
|
|
1607
|
+
const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`;
|
|
1608
|
+
handleOptionValue(val, invalidValueMessage, "cli");
|
|
1605
1609
|
});
|
|
1606
1610
|
if (option.envVar) this.on("optionEnv:" + oname, (val) => {
|
|
1607
|
-
|
|
1611
|
+
const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`;
|
|
1612
|
+
handleOptionValue(val, invalidValueMessage, "env");
|
|
1608
1613
|
});
|
|
1609
1614
|
return this;
|
|
1610
1615
|
}
|
|
@@ -1995,12 +2000,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1995
2000
|
}
|
|
1996
2001
|
launchWithNode = sourceExt.includes(path$1.extname(executableFile));
|
|
1997
2002
|
let proc;
|
|
1998
|
-
if (process$1.platform !== "win32")
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2003
|
+
if (process$1.platform !== "win32") {
|
|
2004
|
+
if (launchWithNode) {
|
|
2005
|
+
args.unshift(executableFile);
|
|
2006
|
+
args = incrementNodeInspectorPort(process$1.execArgv).concat(args);
|
|
2007
|
+
proc = childProcess$1.spawn(process$1.argv[0], args, { stdio: "inherit" });
|
|
2008
|
+
} else proc = childProcess$1.spawn(executableFile, args, { stdio: "inherit" });
|
|
2009
|
+
} else {
|
|
2004
2010
|
this._checkForMissingExecutable(executableFile, executableDir, subcommand._name);
|
|
2005
2011
|
args.unshift(executableFile);
|
|
2006
2012
|
args = incrementNodeInspectorPort(process$1.execArgv).concat(args);
|
|
@@ -2444,8 +2450,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2444
2450
|
"default",
|
|
2445
2451
|
"config",
|
|
2446
2452
|
"env"
|
|
2447
|
-
].includes(this.getOptionValueSource(optionKey)))
|
|
2448
|
-
|
|
2453
|
+
].includes(this.getOptionValueSource(optionKey))) {
|
|
2454
|
+
if (option.required || option.optional) this.emit(`optionEnv:${option.name()}`, process$1.env[option.envVar]);
|
|
2455
|
+
else this.emit(`optionEnv:${option.name()}`);
|
|
2456
|
+
}
|
|
2449
2457
|
}
|
|
2450
2458
|
});
|
|
2451
2459
|
}
|
|
@@ -3069,7 +3077,7 @@ function resolveOwnVersion(packageJsonPath, deps = {}) {
|
|
|
3069
3077
|
}
|
|
3070
3078
|
//#endregion
|
|
3071
3079
|
//#region src/state/state.ts
|
|
3072
|
-
const KNOWN_ACTION_TYPES = new Set([
|
|
3080
|
+
const KNOWN_ACTION_TYPES = /* @__PURE__ */ new Set([
|
|
3073
3081
|
"install",
|
|
3074
3082
|
"upgrade",
|
|
3075
3083
|
"remove"
|
|
@@ -3782,14 +3790,12 @@ function output(data, view) {
|
|
|
3782
3790
|
//#endregion
|
|
3783
3791
|
//#region ../../node_modules/.pnpm/semver@7.8.1/node_modules/semver/internal/constants.js
|
|
3784
3792
|
var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
3785
|
-
const SEMVER_SPEC_VERSION = "2.0.0";
|
|
3786
|
-
const MAX_LENGTH = 256;
|
|
3787
|
-
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
|
|
3788
3793
|
module.exports = {
|
|
3789
|
-
MAX_LENGTH,
|
|
3794
|
+
MAX_LENGTH: 256,
|
|
3790
3795
|
MAX_SAFE_COMPONENT_LENGTH: 16,
|
|
3791
|
-
MAX_SAFE_BUILD_LENGTH:
|
|
3792
|
-
MAX_SAFE_INTEGER
|
|
3796
|
+
MAX_SAFE_BUILD_LENGTH: 250,
|
|
3797
|
+
MAX_SAFE_INTEGER: Number.MAX_SAFE_INTEGER ||
|
|
3798
|
+
/* istanbul ignore next */ 9007199254740991,
|
|
3793
3799
|
RELEASE_TYPES: [
|
|
3794
3800
|
"major",
|
|
3795
3801
|
"premajor",
|
|
@@ -3799,7 +3805,7 @@ var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
3799
3805
|
"prepatch",
|
|
3800
3806
|
"prerelease"
|
|
3801
3807
|
],
|
|
3802
|
-
SEMVER_SPEC_VERSION,
|
|
3808
|
+
SEMVER_SPEC_VERSION: "2.0.0",
|
|
3803
3809
|
FLAG_INCLUDE_PRERELEASE: 1,
|
|
3804
3810
|
FLAG_LOOSE: 2
|
|
3805
3811
|
};
|
|
@@ -3931,9 +3937,10 @@ var require_semver$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
3931
3937
|
module.exports = class SemVer {
|
|
3932
3938
|
constructor(version, options) {
|
|
3933
3939
|
options = parseOptions(options);
|
|
3934
|
-
if (version instanceof SemVer)
|
|
3935
|
-
|
|
3936
|
-
|
|
3940
|
+
if (version instanceof SemVer) {
|
|
3941
|
+
if (version.loose === !!options.loose && version.includePrerelease === !!options.includePrerelease) return version;
|
|
3942
|
+
else version = version.version;
|
|
3943
|
+
} else if (typeof version !== "string") throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`);
|
|
3937
3944
|
if (version.length > MAX_LENGTH) throw new TypeError(`version is longer than ${MAX_LENGTH} characters`);
|
|
3938
3945
|
debug("SemVer", version, options);
|
|
3939
3946
|
this.options = options;
|
|
@@ -4354,7 +4361,11 @@ var require_coerce = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4354
4361
|
}
|
|
4355
4362
|
if (match === null) return null;
|
|
4356
4363
|
const major = match[2];
|
|
4357
|
-
|
|
4364
|
+
const minor = match[3] || "0";
|
|
4365
|
+
const patch = match[4] || "0";
|
|
4366
|
+
const prerelease = options.includePrerelease && match[5] ? `-${match[5]}` : "";
|
|
4367
|
+
const build = options.includePrerelease && match[6] ? `+${match[6]}` : "";
|
|
4368
|
+
return parse(`${major}.${minor}.${patch}${prerelease}${build}`, options);
|
|
4358
4369
|
};
|
|
4359
4370
|
module.exports = coerce;
|
|
4360
4371
|
}));
|
|
@@ -4370,7 +4381,8 @@ var require_truncate = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4370
4381
|
return clonedVersion && doTruncation(clonedVersion, truncation);
|
|
4371
4382
|
};
|
|
4372
4383
|
const cloneInputVersion = (version, options) => {
|
|
4373
|
-
|
|
4384
|
+
const versionStringToParse = version instanceof SemVer ? version.version : version;
|
|
4385
|
+
return parse(versionStringToParse, options);
|
|
4374
4386
|
};
|
|
4375
4387
|
const doTruncation = (version, truncation) => {
|
|
4376
4388
|
if (isPrerelease(truncation)) return version.version;
|
|
@@ -4380,9 +4392,7 @@ var require_truncate = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4380
4392
|
version.minor = 0;
|
|
4381
4393
|
version.patch = 0;
|
|
4382
4394
|
break;
|
|
4383
|
-
case "minor":
|
|
4384
|
-
version.patch = 0;
|
|
4385
|
-
break;
|
|
4395
|
+
case "minor": version.patch = 0;
|
|
4386
4396
|
}
|
|
4387
4397
|
return version.format();
|
|
4388
4398
|
};
|
|
@@ -4431,8 +4441,10 @@ var require_range = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4431
4441
|
module.exports = class Range {
|
|
4432
4442
|
constructor(range, options) {
|
|
4433
4443
|
options = parseOptions(options);
|
|
4434
|
-
if (range instanceof Range)
|
|
4435
|
-
|
|
4444
|
+
if (range instanceof Range) {
|
|
4445
|
+
if (range.loose === !!options.loose && range.includePrerelease === !!options.includePrerelease) return range;
|
|
4446
|
+
else return new Range(range.raw, options);
|
|
4447
|
+
}
|
|
4436
4448
|
if (range instanceof Comparator) {
|
|
4437
4449
|
this.raw = range.value;
|
|
4438
4450
|
this.set = [[range]];
|
|
@@ -4600,18 +4612,21 @@ var require_range = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4600
4612
|
let ret;
|
|
4601
4613
|
if (isX(M)) ret = "";
|
|
4602
4614
|
else if (isX(m)) ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`;
|
|
4603
|
-
else if (isX(p))
|
|
4604
|
-
|
|
4605
|
-
|
|
4615
|
+
else if (isX(p)) {
|
|
4616
|
+
if (M === "0") ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`;
|
|
4617
|
+
else ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0`;
|
|
4618
|
+
} else if (pr) {
|
|
4606
4619
|
debug("replaceCaret pr", pr);
|
|
4607
|
-
if (M === "0")
|
|
4608
|
-
|
|
4609
|
-
|
|
4620
|
+
if (M === "0") {
|
|
4621
|
+
if (m === "0") ret = `>=${M}.${m}.${p}-${pr} <${M}.${m}.${+p + 1}-0`;
|
|
4622
|
+
else ret = `>=${M}.${m}.${p}-${pr} <${M}.${+m + 1}.0-0`;
|
|
4623
|
+
} else ret = `>=${M}.${m}.${p}-${pr} <${+M + 1}.0.0-0`;
|
|
4610
4624
|
} else {
|
|
4611
4625
|
debug("no pr");
|
|
4612
|
-
if (M === "0")
|
|
4613
|
-
|
|
4614
|
-
|
|
4626
|
+
if (M === "0") {
|
|
4627
|
+
if (m === "0") ret = `>=${M}.${m}.${p}${z} <${M}.${m}.${+p + 1}-0`;
|
|
4628
|
+
else ret = `>=${M}.${m}.${p}${z} <${M}.${+m + 1}.0-0`;
|
|
4629
|
+
} else ret = `>=${M}.${m}.${p} <${+M + 1}.0.0-0`;
|
|
4615
4630
|
}
|
|
4616
4631
|
debug("caret return", ret);
|
|
4617
4632
|
return ret;
|
|
@@ -4632,9 +4647,10 @@ var require_range = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4632
4647
|
const anyX = xp;
|
|
4633
4648
|
if (gtlt === "=" && anyX) gtlt = "";
|
|
4634
4649
|
pr = options.includePrerelease ? "-0" : "";
|
|
4635
|
-
if (xM)
|
|
4636
|
-
|
|
4637
|
-
|
|
4650
|
+
if (xM) {
|
|
4651
|
+
if (gtlt === ">" || gtlt === "<") ret = "<0.0.0-0";
|
|
4652
|
+
else ret = "*";
|
|
4653
|
+
} else if (gtlt && anyX) {
|
|
4638
4654
|
if (xm) m = 0;
|
|
4639
4655
|
p = 0;
|
|
4640
4656
|
if (gtlt === ">") {
|
|
@@ -4708,8 +4724,10 @@ var require_comparator = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4708
4724
|
}
|
|
4709
4725
|
constructor(comp, options) {
|
|
4710
4726
|
options = parseOptions(options);
|
|
4711
|
-
if (comp instanceof Comparator)
|
|
4712
|
-
|
|
4727
|
+
if (comp instanceof Comparator) {
|
|
4728
|
+
if (comp.loose === !!options.loose) return comp;
|
|
4729
|
+
else comp = comp.value;
|
|
4730
|
+
}
|
|
4713
4731
|
comp = comp.trim().split(/\s+/).join(" ");
|
|
4714
4732
|
debug("comparator", comp, options);
|
|
4715
4733
|
this.options = options;
|
|
@@ -5029,11 +5047,15 @@ var require_subset = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
5029
5047
|
const minimumVersion = [new Comparator(">=0.0.0")];
|
|
5030
5048
|
const simpleSubset = (sub, dom, options) => {
|
|
5031
5049
|
if (sub === dom) return true;
|
|
5032
|
-
if (sub.length === 1 && sub[0].semver === ANY)
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5050
|
+
if (sub.length === 1 && sub[0].semver === ANY) {
|
|
5051
|
+
if (dom.length === 1 && dom[0].semver === ANY) return true;
|
|
5052
|
+
else if (options.includePrerelease) sub = minimumVersionWithPreRelease;
|
|
5053
|
+
else sub = minimumVersion;
|
|
5054
|
+
}
|
|
5055
|
+
if (dom.length === 1 && dom[0].semver === ANY) {
|
|
5056
|
+
if (options.includePrerelease) return true;
|
|
5057
|
+
else dom = minimumVersion;
|
|
5058
|
+
}
|
|
5037
5059
|
const eqSet = /* @__PURE__ */ new Set();
|
|
5038
5060
|
let gt, lt;
|
|
5039
5061
|
for (const c of sub) if (c.operator === ">" || c.operator === ">=") gt = higherGT(gt, c, options);
|
|
@@ -5167,7 +5189,7 @@ const VENDOR_READS_DEPENDENCIES = {
|
|
|
5167
5189
|
};
|
|
5168
5190
|
/** Vendors the canonical root manifest serves as-is: nothing is derived for them, so a declaration
|
|
5169
5191
|
* they cannot read is ignored at runtime rather than dropped from a file. */
|
|
5170
|
-
const CANONICAL_SERVED$1 = new Set(["copilot-cli"]);
|
|
5192
|
+
const CANONICAL_SERVED$1 = /* @__PURE__ */ new Set(["copilot-cli"]);
|
|
5171
5193
|
/** What Claude Code accepts as a dependency string: a plugin name, an optional `@marketplace`, and an
|
|
5172
5194
|
* optional `@^…` range tail that the runtime strips before resolving. */
|
|
5173
5195
|
const DEPENDENCY_STRING = /^[A-Za-z0-9][-A-Za-z0-9._]*(@[A-Za-z0-9][-A-Za-z0-9._]*)?(@\^[^@]*)?$/;
|
|
@@ -6230,12 +6252,12 @@ const STRING_FIELDS = [
|
|
|
6230
6252
|
"repository",
|
|
6231
6253
|
"license"
|
|
6232
6254
|
];
|
|
6233
|
-
const AUTHOR_FIELDS = new Set([
|
|
6255
|
+
const AUTHOR_FIELDS = /* @__PURE__ */ new Set([
|
|
6234
6256
|
"name",
|
|
6235
6257
|
"email",
|
|
6236
6258
|
"url"
|
|
6237
6259
|
]);
|
|
6238
|
-
const TOP_LEVEL_FIELDS = new Set([
|
|
6260
|
+
const TOP_LEVEL_FIELDS = /* @__PURE__ */ new Set([
|
|
6239
6261
|
"$schema",
|
|
6240
6262
|
"name",
|
|
6241
6263
|
...STRING_FIELDS,
|
|
@@ -6256,7 +6278,7 @@ function validateCanonicalManifest(manifest, opts) {
|
|
|
6256
6278
|
const uext = isObject(manifest.extensions) ? manifest.extensions[UP_NAMESPACE$2] : void 0;
|
|
6257
6279
|
const extension = isObject(uext) ? uext : {};
|
|
6258
6280
|
const harnesses = isObject(extension.harnesses) ? extension.harnesses : {};
|
|
6259
|
-
const declared = [
|
|
6281
|
+
const declared = [.../* @__PURE__ */ new Set([...Array.isArray(extension.vendors) ? extension.vendors.filter((v) => typeof v === "string") : [], ...Object.keys(harnesses)])];
|
|
6260
6282
|
for (const vendor of declared.filter((v) => !opts.knownVendors.has(v))) {
|
|
6261
6283
|
const message = `Unknown vendor "${vendor}" in harnesses`;
|
|
6262
6284
|
warnings.push(message);
|
|
@@ -6313,18 +6335,22 @@ function schemaRuleViolations(manifest, warnings) {
|
|
|
6313
6335
|
else for (const [key, value] of Object.entries(manifest.author)) if (!AUTHOR_FIELDS.has(key)) push(`author.${key}`, "additionalProperties", "unknown author field");
|
|
6314
6336
|
else if (typeof value !== "string") push(`author.${key}`, "type", "must be a string");
|
|
6315
6337
|
}
|
|
6316
|
-
if (manifest.keywords !== void 0)
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
const
|
|
6326
|
-
|
|
6327
|
-
|
|
6338
|
+
if (manifest.keywords !== void 0) {
|
|
6339
|
+
if (!Array.isArray(manifest.keywords)) push("keywords", "type", "must be an array of strings");
|
|
6340
|
+
else manifest.keywords.forEach((keyword, index) => {
|
|
6341
|
+
if (typeof keyword !== "string") push(`keywords[${index}]`, "type", "must be a string");
|
|
6342
|
+
});
|
|
6343
|
+
}
|
|
6344
|
+
if (manifest.extensions !== void 0) {
|
|
6345
|
+
if (!isObject(manifest.extensions)) push("extensions", "type", "must be an object keyed by namespace");
|
|
6346
|
+
else {
|
|
6347
|
+
for (const [namespace, value] of Object.entries(manifest.extensions)) if (!isObject(value)) push(`extensions.${namespace}`, "type", "must be an object");
|
|
6348
|
+
const uext = manifest.extensions[UP_NAMESPACE$2];
|
|
6349
|
+
if (isObject(uext)) {
|
|
6350
|
+
const dependencies = validateDependencies(uext.dependencies);
|
|
6351
|
+
for (const error of dependencies.errors) push(`extensions.${UP_NAMESPACE$2}.dependencies`, "dependencies", error);
|
|
6352
|
+
warnings.push(...dependencies.warnings);
|
|
6353
|
+
}
|
|
6328
6354
|
}
|
|
6329
6355
|
}
|
|
6330
6356
|
for (const key of Object.keys(manifest)) if (!TOP_LEVEL_FIELDS.has(key)) push(key, "additionalProperties", "not an Agent Plugins 1.0.0 field; move tool-specific data under extensions");
|
|
@@ -6344,7 +6370,7 @@ const KNOWN_VENDORS$1 = new Set(Object.keys(VENDOR_OUTPUT));
|
|
|
6344
6370
|
/** Vendors the canonical root manifest serves as-is. The build derives no *manifest* for these —
|
|
6345
6371
|
* writing one would either be shadowed by root (a lower-precedence path) or clobber root itself.
|
|
6346
6372
|
* Their components are a separate question: see COPILOT_NAMESPACE. */
|
|
6347
|
-
const CANONICAL_SERVED = new Set(["copilot-cli"]);
|
|
6373
|
+
const CANONICAL_SERVED = /* @__PURE__ */ new Set(["copilot-cli"]);
|
|
6348
6374
|
/** The reverse-domain directory Copilot CLI reads its native components from once a plugin declares
|
|
6349
6375
|
* the canonical `$schema` (ADR-0015). It REPLACES the plugin root for these kinds — a spec-mode
|
|
6350
6376
|
* runtime does not read `agents/` at all — so the build derives the tree rather than relying on the
|
|
@@ -6504,11 +6530,15 @@ function buildPlugin(root, opts = {}) {
|
|
|
6504
6530
|
}
|
|
6505
6531
|
for (const drop of dedupeDrops(hooks?.drops ?? [])) warnings.push(`${vendor} cannot run the "${drop.type}" hook handler on ${drop.event} — dropped from the derived hooks file`);
|
|
6506
6532
|
const derivedHooksPath = path.join(outputDir, "hooks.json");
|
|
6507
|
-
if (hooks?.changed)
|
|
6508
|
-
|
|
6533
|
+
if (hooks?.changed) {
|
|
6534
|
+
if (hooks.hooks) vendorManifest["hooks"] = `./${path.dirname(relPath).split(path.sep).join("/")}/hooks.json`;
|
|
6535
|
+
else delete vendorManifest["hooks"];
|
|
6536
|
+
}
|
|
6509
6537
|
const derivedMcpPath = path.join(outputDir, "mcp.json");
|
|
6510
|
-
if (mcp?.changed && declaredMcp)
|
|
6511
|
-
|
|
6538
|
+
if (mcp?.changed && declaredMcp) {
|
|
6539
|
+
if (declaredMcp.inline) vendorManifest["mcpServers"] = mcp.servers;
|
|
6540
|
+
else vendorManifest["mcpServers"] = `./${path.dirname(relPath).split(path.sep).join("/")}/mcp.json`;
|
|
6541
|
+
}
|
|
6512
6542
|
if (opts.verbose) {
|
|
6513
6543
|
console.log(`[${vendor}] → ${outputPath}`);
|
|
6514
6544
|
for (const key of Object.keys(vendorFields)) console.log(` + ${key} (from harnesses.${vendor})`);
|
|
@@ -6946,7 +6976,7 @@ function buildCommand() {
|
|
|
6946
6976
|
}
|
|
6947
6977
|
//#endregion
|
|
6948
6978
|
//#region src/pin/fs.ts
|
|
6949
|
-
const TEXT_EXTENSIONS = new Set([
|
|
6979
|
+
const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
6950
6980
|
".md",
|
|
6951
6981
|
".mdx",
|
|
6952
6982
|
".markdown",
|
|
@@ -7363,35 +7393,6 @@ function configCommand(fs = realConfigFs) {
|
|
|
7363
7393
|
return new Command("config").description("Read and write plugin-registered config in .agents/universal-plugin.json").addCommand(addCommand$1(fs)).addCommand(getCommand(fs));
|
|
7364
7394
|
}
|
|
7365
7395
|
//#endregion
|
|
7366
|
-
//#region src/governance/cli.ts
|
|
7367
|
-
const RETIRED = "universal-plugin governance is retired; buddy-agent-harness reference reads the same documents.\n";
|
|
7368
|
-
const IN_A_SKILL = "In a skill, load a document with the load-reference skill in the buddy-agent-harness plugin.\n";
|
|
7369
|
-
function retire(replacement) {
|
|
7370
|
-
process.stderr.write(`${RETIRED}${IN_A_SKILL}→ ${replacement}\n`);
|
|
7371
|
-
process.exit(1);
|
|
7372
|
-
}
|
|
7373
|
-
/** The flags `governance show` took that carried a value, so the value is not read as the name. */
|
|
7374
|
-
const VALUE_FLAGS = new Set(["--root", "--format"]);
|
|
7375
|
-
function documentName(args) {
|
|
7376
|
-
for (let i = 0; i < args.length; i++) {
|
|
7377
|
-
const arg = args[i];
|
|
7378
|
-
if (VALUE_FLAGS.has(arg)) i++;
|
|
7379
|
-
else if (!arg.startsWith("-")) return arg;
|
|
7380
|
-
}
|
|
7381
|
-
return "<name>";
|
|
7382
|
-
}
|
|
7383
|
-
/** Takes any argument or flag, so a caller still passing `--root` or `--format json` is told where
|
|
7384
|
-
* to go instead of what it typed wrong. */
|
|
7385
|
-
function retiredCommand(name) {
|
|
7386
|
-
return new Command(name).allowUnknownOption().allowExcessArguments().helpOption(false);
|
|
7387
|
-
}
|
|
7388
|
-
function governanceCommand() {
|
|
7389
|
-
const cmd = retiredCommand("governance").description("Retired: use buddy-agent-harness reference").helpOption("-h, --help", "Show help").helpCommand(false).addHelpText("after", "\nThis command is retired. buddy-agent-harness reference show|list|search reads the same documents.\nExample:\n $ buddy-agent-harness reference show plugin-design\n").action(() => retire("buddy-agent-harness reference list"));
|
|
7390
|
-
cmd.addCommand(retiredCommand("show").action((_opts, command) => retire(`buddy-agent-harness reference show ${documentName(command.args)}`)));
|
|
7391
|
-
cmd.addCommand(retiredCommand("list").action(() => retire("buddy-agent-harness reference list")));
|
|
7392
|
-
return cmd;
|
|
7393
|
-
}
|
|
7394
|
-
//#endregion
|
|
7395
7396
|
//#region src/init/fs.ts
|
|
7396
7397
|
function git(root, args) {
|
|
7397
7398
|
try {
|
|
@@ -7479,7 +7480,7 @@ const UP_NAMESPACE = "org.cyberuni.universal-plugin";
|
|
|
7479
7480
|
const SCAFFOLD_DIRS = [
|
|
7480
7481
|
"skills",
|
|
7481
7482
|
"agents",
|
|
7482
|
-
"
|
|
7483
|
+
"references",
|
|
7483
7484
|
"commands"
|
|
7484
7485
|
];
|
|
7485
7486
|
/** The closed canonical manifest init writes: `$schema` + `name`, plus the extensions namespace
|
|
@@ -7630,7 +7631,7 @@ function collect$1(value, previous) {
|
|
|
7630
7631
|
}
|
|
7631
7632
|
function initCommand$1(deps = { fs: realInitFs }) {
|
|
7632
7633
|
const cmd = new Command("init").description("Scaffold the canonical plugin.json (with --npm, wire an npm package to ship it)");
|
|
7633
|
-
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/
|
|
7634
|
+
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 'files' to ship the derived vendor manifests").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) => {
|
|
7634
7635
|
try {
|
|
7635
7636
|
const root = resolveRoot(opts.root);
|
|
7636
7637
|
const plan = planInit(deps.fs.gather(root), {
|
|
@@ -7663,6 +7664,241 @@ function initCommand$1(deps = { fs: realInitFs }) {
|
|
|
7663
7664
|
return cmd;
|
|
7664
7665
|
}
|
|
7665
7666
|
//#endregion
|
|
7667
|
+
//#region ../../node_modules/.pnpm/@cyberuni+agent-harness@0.1.0/node_modules/@cyberuni/agent-harness/dist/index.js
|
|
7668
|
+
const harnessIds = [
|
|
7669
|
+
"claude-code",
|
|
7670
|
+
"cursor",
|
|
7671
|
+
"codex",
|
|
7672
|
+
"copilot-cli",
|
|
7673
|
+
"opencode",
|
|
7674
|
+
"kilo",
|
|
7675
|
+
"gemini-cli",
|
|
7676
|
+
"qwen-code",
|
|
7677
|
+
"vscode-copilot",
|
|
7678
|
+
"cline"
|
|
7679
|
+
];
|
|
7680
|
+
function resolveHarnessEnvironment(environment = {}) {
|
|
7681
|
+
return {
|
|
7682
|
+
env: environment.env ?? process.env,
|
|
7683
|
+
homedir: environment.homedir ?? homedir(),
|
|
7684
|
+
cwd: environment.cwd ?? process.cwd(),
|
|
7685
|
+
platform: environment.platform ?? process.platform
|
|
7686
|
+
};
|
|
7687
|
+
}
|
|
7688
|
+
/**
|
|
7689
|
+
* Where a harness keeps plugins at the user level. Paths are computed, not checked: a location may
|
|
7690
|
+
* not exist until the first plugin is installed.
|
|
7691
|
+
*/
|
|
7692
|
+
function pluginStorage(harness, environment = {}) {
|
|
7693
|
+
return storages[harness](resolveHarnessEnvironment(environment));
|
|
7694
|
+
}
|
|
7695
|
+
/** `$XDG_CONFIG_HOME`, else `~/.config`, as the `xdg-basedir` package resolves it. */
|
|
7696
|
+
const xdgConfig = ({ env, homedir }) => env.XDG_CONFIG_HOME || join(homedir, ".config");
|
|
7697
|
+
/** The agent plugin folder of the stable VS Code build, per OS (E-VSC-P3). */
|
|
7698
|
+
function vscodeAgentPlugins({ env, homedir, platform }) {
|
|
7699
|
+
if (platform === "win32") {
|
|
7700
|
+
const configDir = win32.join(env.APPDATA || win32.join(homedir, "AppData", "Roaming"), "Code");
|
|
7701
|
+
return {
|
|
7702
|
+
configDir,
|
|
7703
|
+
path: win32.join(configDir, "agentPlugins")
|
|
7704
|
+
};
|
|
7705
|
+
}
|
|
7706
|
+
const configDir = platform === "darwin" ? join(homedir, "Library", "Application Support", "Code") : join(homedir, ".config", "Code");
|
|
7707
|
+
return {
|
|
7708
|
+
configDir,
|
|
7709
|
+
path: join(configDir, "agentPlugins")
|
|
7710
|
+
};
|
|
7711
|
+
}
|
|
7712
|
+
const storages = {
|
|
7713
|
+
"claude-code": ({ env, homedir }) => {
|
|
7714
|
+
const configDir = env.CLAUDE_CONFIG_DIR || join(homedir, ".claude");
|
|
7715
|
+
const plugins = join(configDir, "plugins");
|
|
7716
|
+
return {
|
|
7717
|
+
harness: "claude-code",
|
|
7718
|
+
configDir,
|
|
7719
|
+
locations: [
|
|
7720
|
+
{
|
|
7721
|
+
kind: "plugin-cache",
|
|
7722
|
+
path: join(plugins, "cache"),
|
|
7723
|
+
description: "Installed plugins as <marketplace>/<plugin>/<version>/",
|
|
7724
|
+
research: "E-CC-P1"
|
|
7725
|
+
},
|
|
7726
|
+
{
|
|
7727
|
+
kind: "marketplaces",
|
|
7728
|
+
path: join(plugins, "marketplaces"),
|
|
7729
|
+
description: "Marketplace checkouts",
|
|
7730
|
+
research: "E-CC-P1"
|
|
7731
|
+
},
|
|
7732
|
+
{
|
|
7733
|
+
kind: "install-record",
|
|
7734
|
+
path: join(plugins, "installed_plugins.json"),
|
|
7735
|
+
description: "Installs keyed by plugin@marketplace, each with scope, installPath, and version",
|
|
7736
|
+
research: "E-CC-P2"
|
|
7737
|
+
},
|
|
7738
|
+
{
|
|
7739
|
+
kind: "enabled-record",
|
|
7740
|
+
path: join(configDir, "settings.json"),
|
|
7741
|
+
description: "User settings; enabledPlugins maps plugin@marketplace to a boolean",
|
|
7742
|
+
research: "E-CC-P3"
|
|
7743
|
+
}
|
|
7744
|
+
]
|
|
7745
|
+
};
|
|
7746
|
+
},
|
|
7747
|
+
cursor: ({ homedir }) => {
|
|
7748
|
+
const configDir = join(homedir, ".cursor");
|
|
7749
|
+
return {
|
|
7750
|
+
harness: "cursor",
|
|
7751
|
+
configDir,
|
|
7752
|
+
locations: [{
|
|
7753
|
+
kind: "local-plugins",
|
|
7754
|
+
path: join(configDir, "plugins", "local"),
|
|
7755
|
+
description: "Plugins under development, one folder each",
|
|
7756
|
+
research: "E-CUR-P2"
|
|
7757
|
+
}, {
|
|
7758
|
+
kind: "marketplaces",
|
|
7759
|
+
path: join(configDir, "plugins", "marketplaces"),
|
|
7760
|
+
description: "Marketplace checkouts",
|
|
7761
|
+
research: "E-CUR-P3"
|
|
7762
|
+
}]
|
|
7763
|
+
};
|
|
7764
|
+
},
|
|
7765
|
+
codex: ({ env, homedir }) => {
|
|
7766
|
+
const configDir = env.CODEX_HOME || join(homedir, ".codex");
|
|
7767
|
+
return {
|
|
7768
|
+
harness: "codex",
|
|
7769
|
+
configDir,
|
|
7770
|
+
locations: [{
|
|
7771
|
+
kind: "plugin-cache",
|
|
7772
|
+
path: join(configDir, "plugins", "cache"),
|
|
7773
|
+
description: "Installed plugins as <marketplace>/<plugin>/<version>/",
|
|
7774
|
+
research: "E-CODEX-P3"
|
|
7775
|
+
}, {
|
|
7776
|
+
kind: "enabled-record",
|
|
7777
|
+
path: join(configDir, "config.toml"),
|
|
7778
|
+
description: "User config; [plugins.\"<plugin>@<marketplace>\"] tables carry enabled = <bool>",
|
|
7779
|
+
research: "E-CODEX-P2"
|
|
7780
|
+
}]
|
|
7781
|
+
};
|
|
7782
|
+
},
|
|
7783
|
+
"copilot-cli": ({ env, homedir }) => {
|
|
7784
|
+
const configDir = env.COPILOT_HOME || join(homedir, ".copilot");
|
|
7785
|
+
return {
|
|
7786
|
+
harness: "copilot-cli",
|
|
7787
|
+
configDir,
|
|
7788
|
+
locations: [
|
|
7789
|
+
{
|
|
7790
|
+
kind: "installed-plugins",
|
|
7791
|
+
path: join(configDir, "installed-plugins"),
|
|
7792
|
+
description: "Installed plugins as <marketplace>/<plugin>/, or _direct/<source-id>/ for direct installs",
|
|
7793
|
+
research: "E-COPILOT-P9"
|
|
7794
|
+
},
|
|
7795
|
+
{
|
|
7796
|
+
kind: "install-record",
|
|
7797
|
+
path: join(configDir, "config.json"),
|
|
7798
|
+
description: "Application state, including installedPlugins",
|
|
7799
|
+
research: "E-COPILOT-P9"
|
|
7800
|
+
},
|
|
7801
|
+
{
|
|
7802
|
+
kind: "enabled-record",
|
|
7803
|
+
path: join(configDir, "settings.json"),
|
|
7804
|
+
description: "User settings (JSON with comments); enabledPlugins maps plugin specs to a boolean",
|
|
7805
|
+
research: "E-COPILOT-P4"
|
|
7806
|
+
}
|
|
7807
|
+
]
|
|
7808
|
+
};
|
|
7809
|
+
},
|
|
7810
|
+
opencode: (environment) => {
|
|
7811
|
+
const configDir = join(xdgConfig(environment), "opencode");
|
|
7812
|
+
return {
|
|
7813
|
+
harness: "opencode",
|
|
7814
|
+
configDir,
|
|
7815
|
+
locations: [{
|
|
7816
|
+
kind: "local-plugins",
|
|
7817
|
+
path: join(configDir, "plugins"),
|
|
7818
|
+
description: "JS/TS plugin modules loaded at startup; npm plugins are named in the plugin config array",
|
|
7819
|
+
research: "E-OC-P2"
|
|
7820
|
+
}]
|
|
7821
|
+
};
|
|
7822
|
+
},
|
|
7823
|
+
kilo: (environment) => {
|
|
7824
|
+
const configDir = join(xdgConfig(environment), "kilo");
|
|
7825
|
+
return {
|
|
7826
|
+
harness: "kilo",
|
|
7827
|
+
configDir,
|
|
7828
|
+
locations: [{
|
|
7829
|
+
kind: "local-plugins",
|
|
7830
|
+
path: join(configDir, "plugin"),
|
|
7831
|
+
description: "JS/TS plugin modules loaded at startup; npm plugins are named in the plugin config array",
|
|
7832
|
+
research: "E-KILO-P2"
|
|
7833
|
+
}]
|
|
7834
|
+
};
|
|
7835
|
+
},
|
|
7836
|
+
"gemini-cli": ({ env, homedir }) => {
|
|
7837
|
+
const configDir = join(env.GEMINI_CLI_HOME || homedir, ".gemini");
|
|
7838
|
+
const extensions = join(configDir, "extensions");
|
|
7839
|
+
return {
|
|
7840
|
+
harness: "gemini-cli",
|
|
7841
|
+
configDir,
|
|
7842
|
+
locations: [{
|
|
7843
|
+
kind: "installed-plugins",
|
|
7844
|
+
path: extensions,
|
|
7845
|
+
description: "Installed extensions, one folder each with a gemini-extension.json manifest",
|
|
7846
|
+
research: "E-GEM-P1"
|
|
7847
|
+
}, {
|
|
7848
|
+
kind: "enabled-record",
|
|
7849
|
+
path: join(extensions, "extension-enablement.json"),
|
|
7850
|
+
description: "Per-extension path-glob overrides that enable or disable it by folder, not a boolean",
|
|
7851
|
+
research: "E-GEM-P3"
|
|
7852
|
+
}]
|
|
7853
|
+
};
|
|
7854
|
+
},
|
|
7855
|
+
"qwen-code": ({ env, homedir }) => {
|
|
7856
|
+
const configDir = env.QWEN_HOME || join(homedir, ".qwen");
|
|
7857
|
+
const extensions = join(configDir, "extensions");
|
|
7858
|
+
return {
|
|
7859
|
+
harness: "qwen-code",
|
|
7860
|
+
configDir,
|
|
7861
|
+
locations: [{
|
|
7862
|
+
kind: "installed-plugins",
|
|
7863
|
+
path: extensions,
|
|
7864
|
+
description: "Installed extensions, one folder each with a qwen-extension.json manifest",
|
|
7865
|
+
research: "E-QWEN-P1"
|
|
7866
|
+
}, {
|
|
7867
|
+
kind: "enabled-record",
|
|
7868
|
+
path: join(extensions, "extension-enablement.json"),
|
|
7869
|
+
description: "Extension enable/disable state, inherited from Gemini CLI's format",
|
|
7870
|
+
research: "E-QWEN-P2"
|
|
7871
|
+
}]
|
|
7872
|
+
};
|
|
7873
|
+
},
|
|
7874
|
+
"vscode-copilot": (environment) => {
|
|
7875
|
+
const { configDir, path } = vscodeAgentPlugins(environment);
|
|
7876
|
+
return {
|
|
7877
|
+
harness: "vscode-copilot",
|
|
7878
|
+
configDir,
|
|
7879
|
+
locations: [{
|
|
7880
|
+
kind: "installed-plugins",
|
|
7881
|
+
path,
|
|
7882
|
+
description: "Installed agent plugins as <host>/<org>/<repo>/",
|
|
7883
|
+
research: "E-VSC-P3"
|
|
7884
|
+
}]
|
|
7885
|
+
};
|
|
7886
|
+
},
|
|
7887
|
+
cline: ({ homedir }) => {
|
|
7888
|
+
const configDir = join(homedir, ".cline");
|
|
7889
|
+
return {
|
|
7890
|
+
harness: "cline",
|
|
7891
|
+
configDir,
|
|
7892
|
+
locations: [{
|
|
7893
|
+
kind: "installed-plugins",
|
|
7894
|
+
path: join(configDir, "plugins", "_installed"),
|
|
7895
|
+
description: "Installed plugins, grouped by source: npm, git, remote, or local",
|
|
7896
|
+
research: "E-CLINE-P3"
|
|
7897
|
+
}]
|
|
7898
|
+
};
|
|
7899
|
+
}
|
|
7900
|
+
};
|
|
7901
|
+
//#endregion
|
|
7666
7902
|
//#region src/vendor-registry/vendor-registry.ts
|
|
7667
7903
|
function lookupVendor(registry, vendorId) {
|
|
7668
7904
|
return registry[vendorId] ?? null;
|
|
@@ -7675,6 +7911,15 @@ function mergeRegistries(base, override) {
|
|
|
7675
7911
|
};
|
|
7676
7912
|
return result;
|
|
7677
7913
|
}
|
|
7914
|
+
/** Fills each vendor's `localPluginDir` from `locate`, which returns the harness's `local-plugins`
|
|
7915
|
+
* folder when `@cyberuni/agent-harness` knows one. A vendor it knows nothing about keeps its
|
|
7916
|
+
* registry value, or `null`. */
|
|
7917
|
+
function withLocalPluginDirs(registry, locate) {
|
|
7918
|
+
return Object.fromEntries(Object.entries(registry).map(([id, config]) => [id, {
|
|
7919
|
+
...config,
|
|
7920
|
+
localPluginDir: locate(id) ?? config.localPluginDir ?? null
|
|
7921
|
+
}]));
|
|
7922
|
+
}
|
|
7678
7923
|
//#endregion
|
|
7679
7924
|
//#region src/vendor-registry/fs.ts
|
|
7680
7925
|
function bundledRegistryPath() {
|
|
@@ -7684,8 +7929,15 @@ function bundledRegistryPath() {
|
|
|
7684
7929
|
function userOverridePath() {
|
|
7685
7930
|
return path.join(os.homedir(), ".agents", "universal-plugin-vendors.json");
|
|
7686
7931
|
}
|
|
7932
|
+
/** The harness's `local-plugins` folder, as `@cyberuni/agent-harness` locates it. */
|
|
7933
|
+
function localPluginsDir(vendorId) {
|
|
7934
|
+
if (!harnessIds.includes(vendorId)) return void 0;
|
|
7935
|
+
return pluginStorage(vendorId).locations.find((l) => l.kind === "local-plugins")?.path;
|
|
7936
|
+
}
|
|
7937
|
+
/** The bundled registry, with the per-harness facts `@cyberuni/agent-harness` owns filled in, then
|
|
7938
|
+
* the user's override on top. */
|
|
7687
7939
|
function loadRegistry() {
|
|
7688
|
-
const bundled = JSON.parse(fs.readFileSync(bundledRegistryPath(), "utf8"));
|
|
7940
|
+
const bundled = withLocalPluginDirs(JSON.parse(fs.readFileSync(bundledRegistryPath(), "utf8")), localPluginsDir);
|
|
7689
7941
|
try {
|
|
7690
7942
|
return mergeRegistries(bundled, JSON.parse(fs.readFileSync(userOverridePath(), "utf8")));
|
|
7691
7943
|
} catch (err) {
|
|
@@ -7740,7 +7992,7 @@ function readPluginName(manifestPath) {
|
|
|
7740
7992
|
}
|
|
7741
7993
|
/** Entries a copy never carries: the working copy's history and its installed dependencies. Neither
|
|
7742
7994
|
* is part of the plugin, and both dwarf it. */
|
|
7743
|
-
const COPY_EXCLUDED = new Set([".git", "node_modules"]);
|
|
7995
|
+
const COPY_EXCLUDED = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
7744
7996
|
/** Applies one planned write. A replace removes the destination first, so a copy never merges into
|
|
7745
7997
|
* what was there before. A copy dereferences symlinks — a skill linked into the tree has to travel
|
|
7746
7998
|
* as its content, since the destination is read on its own. */
|
|
@@ -8791,10 +9043,67 @@ function marketplaceCommand() {
|
|
|
8791
9043
|
return new Command("marketplace").description("Generate repository-local marketplace metadata").addCommand(addCommand()).addCommand(initCommand()).addCommand(validateCommand$1());
|
|
8792
9044
|
}
|
|
8793
9045
|
//#endregion
|
|
8794
|
-
//#region src/prepare/
|
|
8795
|
-
|
|
8796
|
-
|
|
9046
|
+
//#region src/prepare/installed-plugins.ts
|
|
9047
|
+
/**
|
|
9048
|
+
* Reads Claude Code's `installed_plugins.json` (version 2): `plugins` maps `<plugin>@<marketplace>`
|
|
9049
|
+
* to one entry per install, each with a `scope`, `installPath`, `version`, and, for project
|
|
9050
|
+
* installs, `projectPath`. Global scope takes the `user` installs; project scope takes the installs
|
|
9051
|
+
* whose `projectPath` is the project root.
|
|
9052
|
+
*/
|
|
9053
|
+
function parseClaudeInstallRecord(raw, { scope, projectRoot }) {
|
|
9054
|
+
const plugins = isRecord(raw) && isRecord(raw["plugins"]) ? raw["plugins"] : {};
|
|
9055
|
+
const result = [];
|
|
9056
|
+
for (const [key, entries] of Object.entries(plugins)) {
|
|
9057
|
+
if (!Array.isArray(entries)) continue;
|
|
9058
|
+
const entry = entries.filter(isRecord).find((e) => scope === "global" ? e["scope"] === "user" : e["projectPath"] === projectRoot);
|
|
9059
|
+
if (!entry || typeof entry["installPath"] !== "string") continue;
|
|
9060
|
+
result.push({
|
|
9061
|
+
name: pluginName(key),
|
|
9062
|
+
version: typeof entry["version"] === "string" ? entry["version"] : "unknown",
|
|
9063
|
+
root: entry["installPath"]
|
|
9064
|
+
});
|
|
9065
|
+
}
|
|
9066
|
+
return result;
|
|
8797
9067
|
}
|
|
9068
|
+
/** Strips the `@<marketplace>` suffix from a `<plugin>@<marketplace>` key. */
|
|
9069
|
+
function pluginName(key) {
|
|
9070
|
+
const at = key.lastIndexOf("@");
|
|
9071
|
+
return at > 0 ? key.slice(0, at) : key;
|
|
9072
|
+
}
|
|
9073
|
+
/**
|
|
9074
|
+
* Picks the newest of a plugin's cached version folders. Dotted numeric versions compare
|
|
9075
|
+
* numerically, part by part; anything else falls back to string order.
|
|
9076
|
+
*/
|
|
9077
|
+
function latestVersion(versions) {
|
|
9078
|
+
return [...versions].sort(compareVersions).at(-1);
|
|
9079
|
+
}
|
|
9080
|
+
function compareVersions(a, b) {
|
|
9081
|
+
const pa = a.split(/[.+-]/);
|
|
9082
|
+
const pb = b.split(/[.+-]/);
|
|
9083
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
9084
|
+
const x = pa[i] ?? "";
|
|
9085
|
+
const y = pb[i] ?? "";
|
|
9086
|
+
if (x === y) continue;
|
|
9087
|
+
const nx = Number(x);
|
|
9088
|
+
const ny = Number(y);
|
|
9089
|
+
if (x !== "" && y !== "" && Number.isInteger(nx) && Number.isInteger(ny)) return nx - ny;
|
|
9090
|
+
return x < y ? -1 : 1;
|
|
9091
|
+
}
|
|
9092
|
+
return 0;
|
|
9093
|
+
}
|
|
9094
|
+
/** Maps installed plugins to the name → version record `prepare` snapshots. */
|
|
9095
|
+
function toVersions(plugins) {
|
|
9096
|
+
return Object.fromEntries(plugins.map((p) => [p.name, p.version]));
|
|
9097
|
+
}
|
|
9098
|
+
/** Maps installed plugins to name → root, with the home directory written as `~`. */
|
|
9099
|
+
function toRoots(plugins, home) {
|
|
9100
|
+
return Object.fromEntries(plugins.map((p) => [p.name, p.root.startsWith(home) ? "~" + p.root.slice(home.length) : p.root]));
|
|
9101
|
+
}
|
|
9102
|
+
function isRecord(value) {
|
|
9103
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9104
|
+
}
|
|
9105
|
+
//#endregion
|
|
9106
|
+
//#region src/prepare/fs.ts
|
|
8798
9107
|
function globalStatePath$2() {
|
|
8799
9108
|
return path.join(os.homedir(), ".agents", "universal-plugin.json");
|
|
8800
9109
|
}
|
|
@@ -8813,29 +9122,14 @@ function writeStateFile(filePath, state) {
|
|
|
8813
9122
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
8814
9123
|
fs.writeFileSync(filePath, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
8815
9124
|
}
|
|
8816
|
-
|
|
9125
|
+
/** `environment` locates the harness's plugin folders; it defaults to the current process. */
|
|
9126
|
+
function realPrepareFs(harness, installScope, environment = {}) {
|
|
9127
|
+
const projectRoot = installScope.projectRoot;
|
|
9128
|
+
let installed;
|
|
9129
|
+
const readInstalled = () => installed ??= readInstalledPlugins(harness, installScope, environment);
|
|
8817
9130
|
return {
|
|
8818
|
-
readManifest()
|
|
8819
|
-
|
|
8820
|
-
const manifestPath = expandHome(vendor.globalManifest);
|
|
8821
|
-
try {
|
|
8822
|
-
const raw = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
8823
|
-
return Object.fromEntries(Object.entries(raw).map(([k, v]) => [k, typeof v === "object" && v !== null && "version" in v ? String(v.version) : String(v)]));
|
|
8824
|
-
} catch (err) {
|
|
8825
|
-
if (err.code === "ENOENT") return {};
|
|
8826
|
-
throw err;
|
|
8827
|
-
}
|
|
8828
|
-
},
|
|
8829
|
-
readPluginRoots() {
|
|
8830
|
-
if (!vendor.globalPluginDir) return {};
|
|
8831
|
-
const pluginDir = expandHome(vendor.globalPluginDir);
|
|
8832
|
-
const manifest = this.readManifest();
|
|
8833
|
-
const home = os.homedir();
|
|
8834
|
-
return Object.fromEntries(Object.keys(manifest).map((name) => {
|
|
8835
|
-
const absPath = path.join(pluginDir, name);
|
|
8836
|
-
return [name, absPath.startsWith(home) ? "~" + absPath.slice(home.length) : absPath];
|
|
8837
|
-
}));
|
|
8838
|
-
},
|
|
9131
|
+
readManifest: () => toVersions(readInstalled()),
|
|
9132
|
+
readPluginRoots: () => toRoots(readInstalled(), environment.homedir ?? os.homedir()),
|
|
8839
9133
|
readGlobalState: () => readStateFile(globalStatePath$2()) ?? emptyState(),
|
|
8840
9134
|
readProjectState: () => projectRoot ? readStateFile(projectStatePath(projectRoot)) : null,
|
|
8841
9135
|
writeGlobalState: (s) => writeStateFile(globalStatePath$2(), s),
|
|
@@ -8849,7 +9143,85 @@ function populateStoreFromVendorCache(pluginRoots, versions) {
|
|
|
8849
9143
|
for (const [pluginName, pluginRoot] of Object.entries(pluginRoots)) {
|
|
8850
9144
|
const entryPath = storeEntryPath(storePath, `npm/${pluginName}@${versions[pluginName] ?? "unknown"}`);
|
|
8851
9145
|
if (entryExists(entryPath)) continue;
|
|
8852
|
-
populateEntry(entryPath, pluginRoot);
|
|
9146
|
+
populateEntry(entryPath, expandHome(pluginRoot));
|
|
9147
|
+
}
|
|
9148
|
+
}
|
|
9149
|
+
function expandHome(p) {
|
|
9150
|
+
return p.startsWith("~") ? path.join(os.homedir(), p.slice(1)) : p;
|
|
9151
|
+
}
|
|
9152
|
+
/** Lists what a harness has installed, the way that harness records it: Claude Code in its install
|
|
9153
|
+
* record, Copilot CLI and Codex only in the layout of their plugin folders. Cursor keeps no record
|
|
9154
|
+
* of installed plugins. */
|
|
9155
|
+
function readInstalledPlugins(harness, installScope, environment) {
|
|
9156
|
+
const storage = pluginStorage(harness, environment);
|
|
9157
|
+
const location = (kind) => storage.locations.find((l) => l.kind === kind)?.path;
|
|
9158
|
+
switch (harness) {
|
|
9159
|
+
case "claude-code": {
|
|
9160
|
+
const record = location("install-record");
|
|
9161
|
+
const raw = record ? readJson(record) : null;
|
|
9162
|
+
return raw === null ? [] : parseClaudeInstallRecord(raw, installScope);
|
|
9163
|
+
}
|
|
9164
|
+
case "copilot-cli": {
|
|
9165
|
+
const dir = location("installed-plugins");
|
|
9166
|
+
return dir && installScope.scope === "global" ? readCopilotInstalls(dir) : [];
|
|
9167
|
+
}
|
|
9168
|
+
case "codex": {
|
|
9169
|
+
const dir = location("plugin-cache");
|
|
9170
|
+
return dir && installScope.scope === "global" ? readCodexCache(dir) : [];
|
|
9171
|
+
}
|
|
9172
|
+
default: return [];
|
|
9173
|
+
}
|
|
9174
|
+
}
|
|
9175
|
+
/** `<dir>/<marketplace>/<plugin>/` for marketplace installs, `<dir>/_direct/<source-id>/` for
|
|
9176
|
+
* direct ones; the plugin's own manifest carries its name and version. */
|
|
9177
|
+
function readCopilotInstalls(dir) {
|
|
9178
|
+
return subdirs(dir).flatMap((marketplace) => subdirs(path.join(dir, marketplace)).map((folder) => {
|
|
9179
|
+
const root = path.join(dir, marketplace, folder);
|
|
9180
|
+
const manifest = readPluginManifest(root);
|
|
9181
|
+
return {
|
|
9182
|
+
name: typeof manifest["name"] === "string" ? manifest["name"] : folder,
|
|
9183
|
+
version: typeof manifest["version"] === "string" ? manifest["version"] : "unknown",
|
|
9184
|
+
root
|
|
9185
|
+
};
|
|
9186
|
+
}));
|
|
9187
|
+
}
|
|
9188
|
+
/** `<dir>/<marketplace>/<plugin>/<version>/`; the newest cached version is the installed one. */
|
|
9189
|
+
function readCodexCache(dir) {
|
|
9190
|
+
return subdirs(dir).flatMap((marketplace) => subdirs(path.join(dir, marketplace)).flatMap((name) => {
|
|
9191
|
+
const version = latestVersion(subdirs(path.join(dir, marketplace, name)));
|
|
9192
|
+
return version ? [{
|
|
9193
|
+
name,
|
|
9194
|
+
version,
|
|
9195
|
+
root: path.join(dir, marketplace, name, version)
|
|
9196
|
+
}] : [];
|
|
9197
|
+
}));
|
|
9198
|
+
}
|
|
9199
|
+
const PLUGIN_MANIFESTS = [
|
|
9200
|
+
"plugin.json",
|
|
9201
|
+
".github/plugin/plugin.json",
|
|
9202
|
+
".claude-plugin/plugin.json"
|
|
9203
|
+
];
|
|
9204
|
+
function readPluginManifest(root) {
|
|
9205
|
+
for (const file of PLUGIN_MANIFESTS) {
|
|
9206
|
+
const raw = readJson(path.join(root, file));
|
|
9207
|
+
if (typeof raw === "object" && raw !== null) return raw;
|
|
9208
|
+
}
|
|
9209
|
+
return {};
|
|
9210
|
+
}
|
|
9211
|
+
function readJson(filePath) {
|
|
9212
|
+
try {
|
|
9213
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
9214
|
+
} catch (err) {
|
|
9215
|
+
if (err.code === "ENOENT") return null;
|
|
9216
|
+
throw err;
|
|
9217
|
+
}
|
|
9218
|
+
}
|
|
9219
|
+
function subdirs(dir) {
|
|
9220
|
+
try {
|
|
9221
|
+
return fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
9222
|
+
} catch (err) {
|
|
9223
|
+
if (err.code === "ENOENT") return [];
|
|
9224
|
+
throw err;
|
|
8853
9225
|
}
|
|
8854
9226
|
}
|
|
8855
9227
|
//#endregion
|
|
@@ -8958,16 +9330,17 @@ function runPrepare(opts) {
|
|
|
8958
9330
|
version
|
|
8959
9331
|
});
|
|
8960
9332
|
}
|
|
8961
|
-
if (!dryRun)
|
|
8962
|
-
|
|
9333
|
+
if (!dryRun) {
|
|
9334
|
+
if (scope === "global") prepareFs.writeGlobalState(updatedState);
|
|
9335
|
+
else prepareFs.writeProjectState(updatedState);
|
|
9336
|
+
}
|
|
8963
9337
|
return { newActionCount: actions.length };
|
|
8964
9338
|
}
|
|
8965
9339
|
//#endregion
|
|
8966
9340
|
//#region src/prepare/cli.ts
|
|
8967
9341
|
function prepareCommand() {
|
|
8968
9342
|
return new Command("prepare").description("Detect cross-vendor plugin sync actions").argument("<vendor-id>", "Vendor to read manifest from (e.g. claude-code)").addOption(new Option("--scope <scope>", "global or project").default("global").choices(["global", "project"])).option("--root <path>", "Project root for project-scope state file").option("--dry-run", "Print action count without writing state").action((vendorId, opts) => {
|
|
8969
|
-
|
|
8970
|
-
if (!vendor) {
|
|
9343
|
+
if (!lookupVendor(loadRegistry(), vendorId) || !harnessIds.includes(vendorId)) {
|
|
8971
9344
|
process.stderr.write(`Unknown vendor: ${vendorId}\n`);
|
|
8972
9345
|
process.exit(1);
|
|
8973
9346
|
}
|
|
@@ -8976,12 +9349,16 @@ function prepareCommand() {
|
|
|
8976
9349
|
process.exit(1);
|
|
8977
9350
|
}
|
|
8978
9351
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8979
|
-
const
|
|
9352
|
+
const scope = opts.scope;
|
|
9353
|
+
const prepareFs = realPrepareFs(vendorId, {
|
|
9354
|
+
scope,
|
|
9355
|
+
projectRoot: opts.root ? path.resolve(opts.root) : void 0
|
|
9356
|
+
});
|
|
8980
9357
|
const pluginRoots = prepareFs.readPluginRoots();
|
|
8981
9358
|
const manifest = prepareFs.readManifest();
|
|
8982
9359
|
const { newActionCount } = runPrepare({
|
|
8983
9360
|
vendorId,
|
|
8984
|
-
scope
|
|
9361
|
+
scope,
|
|
8985
9362
|
fs: prepareFs,
|
|
8986
9363
|
now,
|
|
8987
9364
|
dryRun: opts.dryRun
|
|
@@ -9497,7 +9874,6 @@ function pluginCommand() {
|
|
|
9497
9874
|
program.addCommand(pluginCommand());
|
|
9498
9875
|
program.addCommand(cleanCommand());
|
|
9499
9876
|
program.addCommand(configCommand());
|
|
9500
|
-
program.addCommand(governanceCommand());
|
|
9501
9877
|
program.addCommand(marketplaceCommand());
|
|
9502
9878
|
program.addCommand(prepareCommand());
|
|
9503
9879
|
program.addCommand(publishCommand());
|
package/dist/data/vendors.json
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"claude-code": {
|
|
3
3
|
"sessionStartEvent": "SessionStart",
|
|
4
|
-
"globalManifest": "~/.claude/plugins/installed_plugins.json",
|
|
5
4
|
"projectManifest": null,
|
|
6
5
|
"hookGlob": "~/.claude/plugins/universal-plugin/hooks/hooks.json",
|
|
7
|
-
"globalPluginDir": "~/.claude/plugins/",
|
|
8
6
|
"pluginRootSuffix": ".claude-plugin/plugin.json",
|
|
9
7
|
"localPluginDir": "~/.claude/skills/",
|
|
10
8
|
"localPluginLink": true,
|
|
@@ -15,12 +13,9 @@
|
|
|
15
13
|
},
|
|
16
14
|
"cursor": {
|
|
17
15
|
"sessionStartEvent": "sessionStart",
|
|
18
|
-
"globalManifest": null,
|
|
19
16
|
"projectManifest": null,
|
|
20
17
|
"hookGlob": null,
|
|
21
|
-
"globalPluginDir": null,
|
|
22
18
|
"pluginRootSuffix": ".cursor-plugin/plugin.json",
|
|
23
|
-
"localPluginDir": "~/.cursor/plugins/local/",
|
|
24
19
|
"localPluginLink": false,
|
|
25
20
|
"localReload": "run Developer: Reload Window in Cursor",
|
|
26
21
|
"installCommand": null,
|
|
@@ -29,10 +24,8 @@
|
|
|
29
24
|
},
|
|
30
25
|
"codex": {
|
|
31
26
|
"sessionStartEvent": "SessionStart",
|
|
32
|
-
"globalManifest": null,
|
|
33
27
|
"projectManifest": null,
|
|
34
28
|
"hookGlob": null,
|
|
35
|
-
"globalPluginDir": null,
|
|
36
29
|
"pluginRootSuffix": ".codex-plugin/plugin.json",
|
|
37
30
|
"localPluginDir": null,
|
|
38
31
|
"localPluginLink": false,
|
|
@@ -43,10 +36,8 @@
|
|
|
43
36
|
},
|
|
44
37
|
"copilot-cli": {
|
|
45
38
|
"sessionStartEvent": "sessionStart",
|
|
46
|
-
"globalManifest": null,
|
|
47
39
|
"projectManifest": null,
|
|
48
40
|
"hookGlob": null,
|
|
49
|
-
"globalPluginDir": null,
|
|
50
41
|
"pluginRootSuffix": "plugin.json",
|
|
51
42
|
"localPluginDir": null,
|
|
52
43
|
"localPluginLink": false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Universal AI agent plugin build tool",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-plugin",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"agents"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
+
"@cyberuni/agent-harness": "^0.1.0",
|
|
42
43
|
"@repobuddy/upx": "^0.1.0",
|
|
43
44
|
"@toon-format/toon": "^4.1.1",
|
|
44
45
|
"commander": "^14.0.3",
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
"@types/semver": "^7.7.1",
|
|
50
51
|
"ajv": "^8.20.0",
|
|
51
52
|
"knip": "^6.14.1",
|
|
52
|
-
"tsdown": "^0.
|
|
53
|
+
"tsdown": "^0.23.0",
|
|
53
54
|
"tsx": "^4.22.3",
|
|
54
55
|
"typescript": "^6.0.0",
|
|
55
56
|
"vitest": "^4.1.7"
|
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.11.1",
|
|
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
|
@@ -106,14 +106,12 @@ npx universal-plugin config add --key sdd-plugins --entry '{"name":"aces","handl
|
|
|
106
106
|
`add` appends the entry, or replaces the existing entry with the same `name`. Both commands print
|
|
107
107
|
TOON by default; pass `--format json` for JSON.
|
|
108
108
|
|
|
109
|
-
###
|
|
110
|
-
|
|
111
|
-
`governance list` and `governance show` are retired. They print the replacement and exit 1 for one
|
|
112
|
-
release, then go away. Read the documents with `buddy-agent-harness reference show <name>`, or, from a
|
|
113
|
-
skill, with the `load-reference` skill in the `buddy-agent-harness` plugin.
|
|
109
|
+
### Reference documents
|
|
114
110
|
|
|
115
111
|
This package ships its own documents (`plugin-design`, `slash-invocation`, `universal-plugin`) under
|
|
116
|
-
`references
|
|
112
|
+
`references/`. Read one with `buddy-agent-harness reference show <name>` in any project that depends
|
|
113
|
+
on `universal-plugin`, or, from a skill, with the `reference` skill in the `buddy-agent-harness`
|
|
114
|
+
plugin.
|
|
117
115
|
|
|
118
116
|
### Housekeeping
|
|
119
117
|
|
|
@@ -330,6 +330,6 @@ Portability rules for skill bodies: keep each `SKILL.md` body under 6,000 chars;
|
|
|
330
330
|
|
|
331
331
|
## References
|
|
332
332
|
|
|
333
|
-
Load `skill-design`, `skill-repo-structure`, and `agent-tool-output` with the `
|
|
333
|
+
Load `skill-design`, `skill-repo-structure`, and `agent-tool-output` with the `reference` skill in the `buddy-agent-harness` plugin.
|
|
334
334
|
|
|
335
335
|
Spec: https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/spec.md
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
# Universal Plugin Format
|
|
2
2
|
|
|
3
3
|
The rules for the universal plugin format live in `plugin-design`, which this package also ships.
|
|
4
|
-
Load `plugin-design` with the `
|
|
4
|
+
Load `plugin-design` with the `reference` skill in the `buddy-agent-harness` plugin.
|
|
@@ -117,7 +117,7 @@ Validation runs before the first write, so a failed build leaves the tree untouc
|
|
|
117
117
|
- **Expect a build to pin skill `npx` references.** That is the release-time `plugin bundle`, not
|
|
118
118
|
this step.
|
|
119
119
|
- **Expect a build to copy governances into skills.** A skill loads a reference through the
|
|
120
|
-
`
|
|
120
|
+
`reference` skill in the `buddy-agent-harness` plugin; the build no longer copies them, and
|
|
121
121
|
`--check` is gone.
|
|
122
122
|
|
|
123
123
|
## Related skills
|
|
@@ -38,7 +38,7 @@ This skill owns the **authoring** side: the plugin a project ships. Setting a re
|
|
|
38
38
|
|
|
39
39
|
## Prerequisites
|
|
40
40
|
|
|
41
|
-
Before any work that adds or removes components, load `plugin-design` with the `
|
|
41
|
+
Before any work that adds or removes components, load `plugin-design` with the `reference`
|
|
42
42
|
skill in the `buddy-agent-harness` plugin.
|
|
43
43
|
|
|
44
44
|
It is the authoritative source for which component to reach for and which anti-patterns to avoid.
|
|
@@ -233,7 +233,7 @@ This skill is not a formatter. If the project has one, run it over the written f
|
|
|
233
233
|
|
|
234
234
|
## References
|
|
235
235
|
|
|
236
|
-
- Reference: `plugin-design`, loaded with the `
|
|
236
|
+
- Reference: `plugin-design`, loaded with the `reference` skill in the `buddy-agent-harness` plugin
|
|
237
237
|
- Spec: https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/spec.md
|
|
238
238
|
- Manifest schema (Agent Plugins Specification v1.0.0): https://agent-plugins.org/schemas/1.0.0/plugin.schema.json
|
|
239
239
|
- Extension schema (the body of `extensions["org.cyberuni.universal-plugin"]`), shipped in the package at
|
|
@@ -62,7 +62,7 @@ what each build gets. A vendor listed in `vendors` with no `harnesses` entry sti
|
|
|
62
62
|
└── README.md
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
`plugin init --scaffold` creates the standard `skills/`, `agents/`, `
|
|
65
|
+
`plugin init --scaffold` creates the standard `skills/`, `agents/`, `references/`, and `commands/`
|
|
66
66
|
directories. Create the rest only when the plugin has content for them.
|
|
67
67
|
|
|
68
68
|
## Components
|