universal-plugin 0.11.0 → 0.11.2
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 +225 -97
- package/package.json +4 -4
- package/plugin.json +1 -1
- package/readme.md +1 -1
- package/references/plugin-design.md +1 -1
- package/references/slash-invocation.md +1 -1
- package/references/universal-plugin.md +1 -1
- package/skills/build-plugin/SKILL.md +1 -1
- package/skills/doctor-universal-plugin/SKILL.md +1 -0
- package/skills/doctor-universal-plugin/scripts/doctor.mjs +7 -0
- package/skills/init-universal-plugin/SKILL.md +2 -2
- package/skills/init-universal-plugin/references/vendors/claude-code.md +2 -1
- package/skills/init-universal-plugin/references/vendors/codex.md +27 -6
- package/skills/init-universal-plugin/references/vendors/cursor.md +6 -0
- package/skills/init-universal-plugin/scripts/init.mjs +0 -0
- package/skills/marketplace/scripts/add.mjs +0 -0
- package/skills/marketplace/scripts/validate.mjs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-plugin",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
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.11.
|
|
3
|
+
"version": "0.11.2",
|
|
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.11.
|
|
3
|
+
"version": "0.11.2",
|
|
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
|
@@ -26,11 +26,11 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
26
26
|
}
|
|
27
27
|
return to;
|
|
28
28
|
};
|
|
29
|
-
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", {
|
|
30
30
|
value: mod,
|
|
31
31
|
enumerable: true
|
|
32
32
|
}) : target, mod));
|
|
33
|
-
var __require = /*
|
|
33
|
+
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
34
34
|
//#endregion
|
|
35
35
|
//#region ../../node_modules/.pnpm/commander@14.0.3/node_modules/commander/lib/error.js
|
|
36
36
|
var require_error = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
@@ -102,7 +102,6 @@ var require_argument = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
102
102
|
default:
|
|
103
103
|
this.required = true;
|
|
104
104
|
this._name = name;
|
|
105
|
-
break;
|
|
106
105
|
}
|
|
107
106
|
if (this._name.endsWith("...")) {
|
|
108
107
|
this.variadic = true;
|
|
@@ -1597,16 +1596,20 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1597
1596
|
const oldValue = this.getOptionValue(name);
|
|
1598
1597
|
if (val !== null && option.parseArg) val = this._callParseArg(option, val, oldValue, invalidValueMessage);
|
|
1599
1598
|
else if (val !== null && option.variadic) val = option._collectValue(val, oldValue);
|
|
1600
|
-
if (val == null)
|
|
1601
|
-
|
|
1602
|
-
|
|
1599
|
+
if (val == null) {
|
|
1600
|
+
if (option.negate) val = false;
|
|
1601
|
+
else if (option.isBoolean() || option.optional) val = true;
|
|
1602
|
+
else val = "";
|
|
1603
|
+
}
|
|
1603
1604
|
this.setOptionValueWithSource(name, val, valueSource);
|
|
1604
1605
|
};
|
|
1605
1606
|
this.on("option:" + oname, (val) => {
|
|
1606
|
-
|
|
1607
|
+
const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`;
|
|
1608
|
+
handleOptionValue(val, invalidValueMessage, "cli");
|
|
1607
1609
|
});
|
|
1608
1610
|
if (option.envVar) this.on("optionEnv:" + oname, (val) => {
|
|
1609
|
-
|
|
1611
|
+
const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`;
|
|
1612
|
+
handleOptionValue(val, invalidValueMessage, "env");
|
|
1610
1613
|
});
|
|
1611
1614
|
return this;
|
|
1612
1615
|
}
|
|
@@ -1997,12 +2000,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1997
2000
|
}
|
|
1998
2001
|
launchWithNode = sourceExt.includes(path$1.extname(executableFile));
|
|
1999
2002
|
let proc;
|
|
2000
|
-
if (process$1.platform !== "win32")
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
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 {
|
|
2006
2010
|
this._checkForMissingExecutable(executableFile, executableDir, subcommand._name);
|
|
2007
2011
|
args.unshift(executableFile);
|
|
2008
2012
|
args = incrementNodeInspectorPort(process$1.execArgv).concat(args);
|
|
@@ -2446,8 +2450,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2446
2450
|
"default",
|
|
2447
2451
|
"config",
|
|
2448
2452
|
"env"
|
|
2449
|
-
].includes(this.getOptionValueSource(optionKey)))
|
|
2450
|
-
|
|
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
|
+
}
|
|
2451
2457
|
}
|
|
2452
2458
|
});
|
|
2453
2459
|
}
|
|
@@ -3071,7 +3077,7 @@ function resolveOwnVersion(packageJsonPath, deps = {}) {
|
|
|
3071
3077
|
}
|
|
3072
3078
|
//#endregion
|
|
3073
3079
|
//#region src/state/state.ts
|
|
3074
|
-
const KNOWN_ACTION_TYPES = new Set([
|
|
3080
|
+
const KNOWN_ACTION_TYPES = /* @__PURE__ */ new Set([
|
|
3075
3081
|
"install",
|
|
3076
3082
|
"upgrade",
|
|
3077
3083
|
"remove"
|
|
@@ -3784,14 +3790,12 @@ function output(data, view) {
|
|
|
3784
3790
|
//#endregion
|
|
3785
3791
|
//#region ../../node_modules/.pnpm/semver@7.8.1/node_modules/semver/internal/constants.js
|
|
3786
3792
|
var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
3787
|
-
const SEMVER_SPEC_VERSION = "2.0.0";
|
|
3788
|
-
const MAX_LENGTH = 256;
|
|
3789
|
-
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
|
|
3790
3793
|
module.exports = {
|
|
3791
|
-
MAX_LENGTH,
|
|
3794
|
+
MAX_LENGTH: 256,
|
|
3792
3795
|
MAX_SAFE_COMPONENT_LENGTH: 16,
|
|
3793
|
-
MAX_SAFE_BUILD_LENGTH:
|
|
3794
|
-
MAX_SAFE_INTEGER
|
|
3796
|
+
MAX_SAFE_BUILD_LENGTH: 250,
|
|
3797
|
+
MAX_SAFE_INTEGER: Number.MAX_SAFE_INTEGER ||
|
|
3798
|
+
/* istanbul ignore next */ 9007199254740991,
|
|
3795
3799
|
RELEASE_TYPES: [
|
|
3796
3800
|
"major",
|
|
3797
3801
|
"premajor",
|
|
@@ -3801,7 +3805,7 @@ var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
3801
3805
|
"prepatch",
|
|
3802
3806
|
"prerelease"
|
|
3803
3807
|
],
|
|
3804
|
-
SEMVER_SPEC_VERSION,
|
|
3808
|
+
SEMVER_SPEC_VERSION: "2.0.0",
|
|
3805
3809
|
FLAG_INCLUDE_PRERELEASE: 1,
|
|
3806
3810
|
FLAG_LOOSE: 2
|
|
3807
3811
|
};
|
|
@@ -3933,9 +3937,10 @@ var require_semver$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
3933
3937
|
module.exports = class SemVer {
|
|
3934
3938
|
constructor(version, options) {
|
|
3935
3939
|
options = parseOptions(options);
|
|
3936
|
-
if (version instanceof SemVer)
|
|
3937
|
-
|
|
3938
|
-
|
|
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}".`);
|
|
3939
3944
|
if (version.length > MAX_LENGTH) throw new TypeError(`version is longer than ${MAX_LENGTH} characters`);
|
|
3940
3945
|
debug("SemVer", version, options);
|
|
3941
3946
|
this.options = options;
|
|
@@ -4356,7 +4361,11 @@ var require_coerce = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4356
4361
|
}
|
|
4357
4362
|
if (match === null) return null;
|
|
4358
4363
|
const major = match[2];
|
|
4359
|
-
|
|
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);
|
|
4360
4369
|
};
|
|
4361
4370
|
module.exports = coerce;
|
|
4362
4371
|
}));
|
|
@@ -4372,7 +4381,8 @@ var require_truncate = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4372
4381
|
return clonedVersion && doTruncation(clonedVersion, truncation);
|
|
4373
4382
|
};
|
|
4374
4383
|
const cloneInputVersion = (version, options) => {
|
|
4375
|
-
|
|
4384
|
+
const versionStringToParse = version instanceof SemVer ? version.version : version;
|
|
4385
|
+
return parse(versionStringToParse, options);
|
|
4376
4386
|
};
|
|
4377
4387
|
const doTruncation = (version, truncation) => {
|
|
4378
4388
|
if (isPrerelease(truncation)) return version.version;
|
|
@@ -4382,9 +4392,7 @@ var require_truncate = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4382
4392
|
version.minor = 0;
|
|
4383
4393
|
version.patch = 0;
|
|
4384
4394
|
break;
|
|
4385
|
-
case "minor":
|
|
4386
|
-
version.patch = 0;
|
|
4387
|
-
break;
|
|
4395
|
+
case "minor": version.patch = 0;
|
|
4388
4396
|
}
|
|
4389
4397
|
return version.format();
|
|
4390
4398
|
};
|
|
@@ -4433,8 +4441,10 @@ var require_range = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4433
4441
|
module.exports = class Range {
|
|
4434
4442
|
constructor(range, options) {
|
|
4435
4443
|
options = parseOptions(options);
|
|
4436
|
-
if (range instanceof Range)
|
|
4437
|
-
|
|
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
|
+
}
|
|
4438
4448
|
if (range instanceof Comparator) {
|
|
4439
4449
|
this.raw = range.value;
|
|
4440
4450
|
this.set = [[range]];
|
|
@@ -4602,18 +4612,21 @@ var require_range = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4602
4612
|
let ret;
|
|
4603
4613
|
if (isX(M)) ret = "";
|
|
4604
4614
|
else if (isX(m)) ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`;
|
|
4605
|
-
else if (isX(p))
|
|
4606
|
-
|
|
4607
|
-
|
|
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) {
|
|
4608
4619
|
debug("replaceCaret pr", pr);
|
|
4609
|
-
if (M === "0")
|
|
4610
|
-
|
|
4611
|
-
|
|
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`;
|
|
4612
4624
|
} else {
|
|
4613
4625
|
debug("no pr");
|
|
4614
|
-
if (M === "0")
|
|
4615
|
-
|
|
4616
|
-
|
|
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`;
|
|
4617
4630
|
}
|
|
4618
4631
|
debug("caret return", ret);
|
|
4619
4632
|
return ret;
|
|
@@ -4634,9 +4647,10 @@ var require_range = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4634
4647
|
const anyX = xp;
|
|
4635
4648
|
if (gtlt === "=" && anyX) gtlt = "";
|
|
4636
4649
|
pr = options.includePrerelease ? "-0" : "";
|
|
4637
|
-
if (xM)
|
|
4638
|
-
|
|
4639
|
-
|
|
4650
|
+
if (xM) {
|
|
4651
|
+
if (gtlt === ">" || gtlt === "<") ret = "<0.0.0-0";
|
|
4652
|
+
else ret = "*";
|
|
4653
|
+
} else if (gtlt && anyX) {
|
|
4640
4654
|
if (xm) m = 0;
|
|
4641
4655
|
p = 0;
|
|
4642
4656
|
if (gtlt === ">") {
|
|
@@ -4710,8 +4724,10 @@ var require_comparator = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
4710
4724
|
}
|
|
4711
4725
|
constructor(comp, options) {
|
|
4712
4726
|
options = parseOptions(options);
|
|
4713
|
-
if (comp instanceof Comparator)
|
|
4714
|
-
|
|
4727
|
+
if (comp instanceof Comparator) {
|
|
4728
|
+
if (comp.loose === !!options.loose) return comp;
|
|
4729
|
+
else comp = comp.value;
|
|
4730
|
+
}
|
|
4715
4731
|
comp = comp.trim().split(/\s+/).join(" ");
|
|
4716
4732
|
debug("comparator", comp, options);
|
|
4717
4733
|
this.options = options;
|
|
@@ -5031,11 +5047,15 @@ var require_subset = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
5031
5047
|
const minimumVersion = [new Comparator(">=0.0.0")];
|
|
5032
5048
|
const simpleSubset = (sub, dom, options) => {
|
|
5033
5049
|
if (sub === dom) return true;
|
|
5034
|
-
if (sub.length === 1 && sub[0].semver === ANY)
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
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
|
+
}
|
|
5039
5059
|
const eqSet = /* @__PURE__ */ new Set();
|
|
5040
5060
|
let gt, lt;
|
|
5041
5061
|
for (const c of sub) if (c.operator === ">" || c.operator === ">=") gt = higherGT(gt, c, options);
|
|
@@ -5169,7 +5189,7 @@ const VENDOR_READS_DEPENDENCIES = {
|
|
|
5169
5189
|
};
|
|
5170
5190
|
/** Vendors the canonical root manifest serves as-is: nothing is derived for them, so a declaration
|
|
5171
5191
|
* they cannot read is ignored at runtime rather than dropped from a file. */
|
|
5172
|
-
const CANONICAL_SERVED$1 = new Set(["copilot-cli"]);
|
|
5192
|
+
const CANONICAL_SERVED$1 = /* @__PURE__ */ new Set(["copilot-cli"]);
|
|
5173
5193
|
/** What Claude Code accepts as a dependency string: a plugin name, an optional `@marketplace`, and an
|
|
5174
5194
|
* optional `@^…` range tail that the runtime strips before resolving. */
|
|
5175
5195
|
const DEPENDENCY_STRING = /^[A-Za-z0-9][-A-Za-z0-9._]*(@[A-Za-z0-9][-A-Za-z0-9._]*)?(@\^[^@]*)?$/;
|
|
@@ -6232,12 +6252,12 @@ const STRING_FIELDS = [
|
|
|
6232
6252
|
"repository",
|
|
6233
6253
|
"license"
|
|
6234
6254
|
];
|
|
6235
|
-
const AUTHOR_FIELDS = new Set([
|
|
6255
|
+
const AUTHOR_FIELDS = /* @__PURE__ */ new Set([
|
|
6236
6256
|
"name",
|
|
6237
6257
|
"email",
|
|
6238
6258
|
"url"
|
|
6239
6259
|
]);
|
|
6240
|
-
const TOP_LEVEL_FIELDS = new Set([
|
|
6260
|
+
const TOP_LEVEL_FIELDS = /* @__PURE__ */ new Set([
|
|
6241
6261
|
"$schema",
|
|
6242
6262
|
"name",
|
|
6243
6263
|
...STRING_FIELDS,
|
|
@@ -6258,7 +6278,7 @@ function validateCanonicalManifest(manifest, opts) {
|
|
|
6258
6278
|
const uext = isObject(manifest.extensions) ? manifest.extensions[UP_NAMESPACE$2] : void 0;
|
|
6259
6279
|
const extension = isObject(uext) ? uext : {};
|
|
6260
6280
|
const harnesses = isObject(extension.harnesses) ? extension.harnesses : {};
|
|
6261
|
-
const declared = [
|
|
6281
|
+
const declared = [.../* @__PURE__ */ new Set([...Array.isArray(extension.vendors) ? extension.vendors.filter((v) => typeof v === "string") : [], ...Object.keys(harnesses)])];
|
|
6262
6282
|
for (const vendor of declared.filter((v) => !opts.knownVendors.has(v))) {
|
|
6263
6283
|
const message = `Unknown vendor "${vendor}" in harnesses`;
|
|
6264
6284
|
warnings.push(message);
|
|
@@ -6315,18 +6335,22 @@ function schemaRuleViolations(manifest, warnings) {
|
|
|
6315
6335
|
else for (const [key, value] of Object.entries(manifest.author)) if (!AUTHOR_FIELDS.has(key)) push(`author.${key}`, "additionalProperties", "unknown author field");
|
|
6316
6336
|
else if (typeof value !== "string") push(`author.${key}`, "type", "must be a string");
|
|
6317
6337
|
}
|
|
6318
|
-
if (manifest.keywords !== void 0)
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
const
|
|
6328
|
-
|
|
6329
|
-
|
|
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
|
+
}
|
|
6330
6354
|
}
|
|
6331
6355
|
}
|
|
6332
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");
|
|
@@ -6346,7 +6370,7 @@ const KNOWN_VENDORS$1 = new Set(Object.keys(VENDOR_OUTPUT));
|
|
|
6346
6370
|
/** Vendors the canonical root manifest serves as-is. The build derives no *manifest* for these —
|
|
6347
6371
|
* writing one would either be shadowed by root (a lower-precedence path) or clobber root itself.
|
|
6348
6372
|
* Their components are a separate question: see COPILOT_NAMESPACE. */
|
|
6349
|
-
const CANONICAL_SERVED = new Set(["copilot-cli"]);
|
|
6373
|
+
const CANONICAL_SERVED = /* @__PURE__ */ new Set(["copilot-cli"]);
|
|
6350
6374
|
/** The reverse-domain directory Copilot CLI reads its native components from once a plugin declares
|
|
6351
6375
|
* the canonical `$schema` (ADR-0015). It REPLACES the plugin root for these kinds — a spec-mode
|
|
6352
6376
|
* runtime does not read `agents/` at all — so the build derives the tree rather than relying on the
|
|
@@ -6368,6 +6392,66 @@ const COPILOT_LSP_PATH = "lsp.json";
|
|
|
6368
6392
|
/** Authored under the namespace, never derived: Copilot's canvas extensions have no canonical root
|
|
6369
6393
|
* location to derive from, so `--clean` must leave this subtree alone. */
|
|
6370
6394
|
const COPILOT_AUTHORED_DIR = "extensions";
|
|
6395
|
+
/** The component paths each derived manifest accepts. A path on the shared extension reaches every
|
|
6396
|
+
* derived manifest unless the vendor is listed here without it — then the build leaves it out and
|
|
6397
|
+
* warns, as it does for `dependencies` (issue #132). A harness override is applied after this
|
|
6398
|
+
* filter, so `harnesses.<vendor>` can still set any key on purpose.
|
|
6399
|
+
* Each row is what the runtime reads: Claude Code's SchemaStore manifest schema, Cursor's published
|
|
6400
|
+
* `plugin.schema.json`, and Codex's manifest loader, which reads `commands` too (migrated into
|
|
6401
|
+
* skills on install). Codex's plugin-creator validator is narrower — it rejects `hooks` and
|
|
6402
|
+
* `commands` — and is not what the row follows. With a root Agent Plugins `$schema`, Codex reads only
|
|
6403
|
+
* `apps`, `hooks` and `interface` from its manifest and ignores the rest, so the Codex row is the
|
|
6404
|
+
* superset its legacy (no-`$schema`) mode reads (`.research/plugin-schema/evidence.md` E28).
|
|
6405
|
+
* Copilot CLI is absent: it reads the canonical manifest and derives no manifest of its own. */
|
|
6406
|
+
const VENDOR_COMPONENTS = {
|
|
6407
|
+
"claude-code": /* @__PURE__ */ new Set([
|
|
6408
|
+
"skills",
|
|
6409
|
+
"commands",
|
|
6410
|
+
"agents",
|
|
6411
|
+
"hooks",
|
|
6412
|
+
"mcpServers",
|
|
6413
|
+
"lspServers",
|
|
6414
|
+
"outputStyles",
|
|
6415
|
+
"themes",
|
|
6416
|
+
"channels",
|
|
6417
|
+
"monitors"
|
|
6418
|
+
]),
|
|
6419
|
+
cursor: /* @__PURE__ */ new Set([
|
|
6420
|
+
"skills",
|
|
6421
|
+
"commands",
|
|
6422
|
+
"agents",
|
|
6423
|
+
"rules",
|
|
6424
|
+
"hooks",
|
|
6425
|
+
"mcpServers"
|
|
6426
|
+
]),
|
|
6427
|
+
codex: /* @__PURE__ */ new Set([
|
|
6428
|
+
"skills",
|
|
6429
|
+
"commands",
|
|
6430
|
+
"apps",
|
|
6431
|
+
"hooks",
|
|
6432
|
+
"mcpServers"
|
|
6433
|
+
])
|
|
6434
|
+
};
|
|
6435
|
+
/** Every component key some vendor reads. Only these are filtered; any other key on the extension is
|
|
6436
|
+
* not a component and passes through as before. */
|
|
6437
|
+
const COMPONENT_KEYS = new Set(Object.values(VENDOR_COMPONENTS).flatMap((set) => [...set ?? []]));
|
|
6438
|
+
/** Splits the shared component config into what `vendor` reads and the component keys it has none
|
|
6439
|
+
* of. A vendor without a table entry keeps everything. */
|
|
6440
|
+
function vendorComponentConfig(componentConfig, vendor) {
|
|
6441
|
+
const supported = VENDOR_COMPONENTS[vendor];
|
|
6442
|
+
if (!supported) return {
|
|
6443
|
+
config: componentConfig,
|
|
6444
|
+
dropped: []
|
|
6445
|
+
};
|
|
6446
|
+
const config = {};
|
|
6447
|
+
const dropped = [];
|
|
6448
|
+
for (const [key, value] of Object.entries(componentConfig)) if (COMPONENT_KEYS.has(key) && !supported.has(key)) dropped.push(key);
|
|
6449
|
+
else config[key] = value;
|
|
6450
|
+
return {
|
|
6451
|
+
config,
|
|
6452
|
+
dropped
|
|
6453
|
+
};
|
|
6454
|
+
}
|
|
6371
6455
|
/** What each vendor's build output occupies in the published package, for `plugin init --npm`'s
|
|
6372
6456
|
* `package.json` `files` wiring. */
|
|
6373
6457
|
const VENDOR_SHIPPED_PATHS = {
|
|
@@ -6477,9 +6561,10 @@ function buildPlugin(root, opts = {}) {
|
|
|
6477
6561
|
const outputPath = path.join(root, relPath);
|
|
6478
6562
|
const outputDir = path.dirname(outputPath);
|
|
6479
6563
|
const vendorFields = harnesses[vendor] ?? {};
|
|
6564
|
+
const components = vendorComponentConfig(componentConfig, vendor);
|
|
6480
6565
|
const vendorManifest = {
|
|
6481
6566
|
...metadata,
|
|
6482
|
-
...
|
|
6567
|
+
...components.config,
|
|
6483
6568
|
...vendorFields
|
|
6484
6569
|
};
|
|
6485
6570
|
const hooks = canonicalHooks ? translateHooks(canonicalHooks, vendor) : null;
|
|
@@ -6491,7 +6576,7 @@ function buildPlugin(root, opts = {}) {
|
|
|
6491
6576
|
if (mcp?.changed) warnings.push(`${vendor} reads the canonical plugin.json directly — the pinned mcpServers is not delivered to it`);
|
|
6492
6577
|
const overrides = Object.keys(vendorFields);
|
|
6493
6578
|
if (overrides.length > 0) warnings.push(`harnesses.${vendor} sets ${overrides.join(", ")}, but ${vendor} reads the canonical plugin.json directly — these fields are not delivered`);
|
|
6494
|
-
writeSkillArtifacts(vendor, skills, opts, written
|
|
6579
|
+
writeSkillArtifacts(vendor, skills, opts, written);
|
|
6495
6580
|
const derived = deriveCopilotNamespace(root, componentConfig, hooks, indent, opts, written, warnings);
|
|
6496
6581
|
rows.push(derived ? {
|
|
6497
6582
|
vendor,
|
|
@@ -6504,13 +6589,18 @@ function buildPlugin(root, opts = {}) {
|
|
|
6504
6589
|
});
|
|
6505
6590
|
continue;
|
|
6506
6591
|
}
|
|
6592
|
+
for (const key of components.dropped.filter((k) => !(k in vendorFields))) warnings.push(`${vendor} has no "${key}" component — the path is left out of ${relPath}`);
|
|
6507
6593
|
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`);
|
|
6508
6594
|
const derivedHooksPath = path.join(outputDir, "hooks.json");
|
|
6509
|
-
if (hooks?.changed)
|
|
6510
|
-
|
|
6595
|
+
if (hooks?.changed) {
|
|
6596
|
+
if (hooks.hooks) vendorManifest["hooks"] = `./${path.dirname(relPath).split(path.sep).join("/")}/hooks.json`;
|
|
6597
|
+
else delete vendorManifest["hooks"];
|
|
6598
|
+
}
|
|
6511
6599
|
const derivedMcpPath = path.join(outputDir, "mcp.json");
|
|
6512
|
-
if (mcp?.changed && declaredMcp)
|
|
6513
|
-
|
|
6600
|
+
if (mcp?.changed && declaredMcp) {
|
|
6601
|
+
if (declaredMcp.inline) vendorManifest["mcpServers"] = mcp.servers;
|
|
6602
|
+
else vendorManifest["mcpServers"] = `./${path.dirname(relPath).split(path.sep).join("/")}/mcp.json`;
|
|
6603
|
+
}
|
|
6514
6604
|
if (opts.verbose) {
|
|
6515
6605
|
console.log(`[${vendor}] → ${outputPath}`);
|
|
6516
6606
|
for (const key of Object.keys(vendorFields)) console.log(` + ${key} (from harnesses.${vendor})`);
|
|
@@ -6527,7 +6617,7 @@ function buildPlugin(root, opts = {}) {
|
|
|
6527
6617
|
else if (!opts.dryRun && fs.existsSync(derivedHooksPath)) fs.unlinkSync(derivedHooksPath);
|
|
6528
6618
|
}
|
|
6529
6619
|
if (mcp?.changed && declaredMcp && !declaredMcp.inline) writeArtifact(derivedMcpPath, `${JSON.stringify({ mcpServers: mcp.servers }, null, indent)}\n`, opts, written);
|
|
6530
|
-
writeSkillArtifacts(vendor, skills, opts, written
|
|
6620
|
+
writeSkillArtifacts(vendor, skills, opts, written);
|
|
6531
6621
|
rows.push({
|
|
6532
6622
|
vendor,
|
|
6533
6623
|
path: relPath,
|
|
@@ -6615,20 +6705,9 @@ function refreshCatalogs(root, manifest, vendors, opts, written, warnings) {
|
|
|
6615
6705
|
catalogRoot: repo.root
|
|
6616
6706
|
} : { catalogs: rows };
|
|
6617
6707
|
}
|
|
6618
|
-
function writeSkillArtifacts(vendor, skills, opts, written
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
writeClaudeSkill(skill, opts, written);
|
|
6622
|
-
continue;
|
|
6623
|
-
}
|
|
6624
|
-
if (vendor === "cursor") continue;
|
|
6625
|
-
if (skill.invocationPolicy === "model") continue;
|
|
6626
|
-
if (vendor === "codex") try {
|
|
6627
|
-
writeArtifact(path.join(os.homedir(), ".codex", "prompts", `${skill.name}.md`), skill.body, opts, written);
|
|
6628
|
-
} catch (err) {
|
|
6629
|
-
warnings.push(`Failed to write Codex prompt for skill "${skill.name}" (best-effort): ${err instanceof Error ? err.message : String(err)}`);
|
|
6630
|
-
}
|
|
6631
|
-
}
|
|
6708
|
+
function writeSkillArtifacts(vendor, skills, opts, written) {
|
|
6709
|
+
if (vendor !== "claude-code") return;
|
|
6710
|
+
for (const skill of skills) writeClaudeSkill(skill, opts, written);
|
|
6632
6711
|
}
|
|
6633
6712
|
/** Resolves a `pathValue` declaration — a single "./" path, an array of them, or a { paths: [...] }
|
|
6634
6713
|
* object — into the list of declared paths. Returns null when the field is absent or malformed, so
|
|
@@ -6948,7 +7027,7 @@ function buildCommand() {
|
|
|
6948
7027
|
}
|
|
6949
7028
|
//#endregion
|
|
6950
7029
|
//#region src/pin/fs.ts
|
|
6951
|
-
const TEXT_EXTENSIONS = new Set([
|
|
7030
|
+
const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
6952
7031
|
".md",
|
|
6953
7032
|
".mdx",
|
|
6954
7033
|
".markdown",
|
|
@@ -7636,7 +7715,7 @@ function initCommand$1(deps = { fs: realInitFs }) {
|
|
|
7636
7715
|
return cmd;
|
|
7637
7716
|
}
|
|
7638
7717
|
//#endregion
|
|
7639
|
-
//#region ../../node_modules/.pnpm/@cyberuni+agent-harness@0.
|
|
7718
|
+
//#region ../../node_modules/.pnpm/@cyberuni+agent-harness@0.3.0/node_modules/@cyberuni/agent-harness/dist/index.js
|
|
7640
7719
|
const harnessIds = [
|
|
7641
7720
|
"claude-code",
|
|
7642
7721
|
"cursor",
|
|
@@ -7647,7 +7726,10 @@ const harnessIds = [
|
|
|
7647
7726
|
"gemini-cli",
|
|
7648
7727
|
"qwen-code",
|
|
7649
7728
|
"vscode-copilot",
|
|
7650
|
-
"cline"
|
|
7729
|
+
"cline",
|
|
7730
|
+
"crush",
|
|
7731
|
+
"openhands",
|
|
7732
|
+
"augment"
|
|
7651
7733
|
];
|
|
7652
7734
|
function resolveHarnessEnvironment(environment = {}) {
|
|
7653
7735
|
return {
|
|
@@ -7868,6 +7950,50 @@ const storages = {
|
|
|
7868
7950
|
research: "E-CLINE-P3"
|
|
7869
7951
|
}]
|
|
7870
7952
|
};
|
|
7953
|
+
},
|
|
7954
|
+
crush: ({ env, homedir }) => {
|
|
7955
|
+
return {
|
|
7956
|
+
harness: "crush",
|
|
7957
|
+
configDir: env.CRUSH_GLOBAL_CONFIG || join(env.XDG_CONFIG_HOME || join(homedir, ".config"), "crush"),
|
|
7958
|
+
locations: []
|
|
7959
|
+
};
|
|
7960
|
+
},
|
|
7961
|
+
openhands: ({ env, homedir }) => {
|
|
7962
|
+
const configDir = env.OH_PERSISTENCE_DIR || env.OPENHANDS_PERSISTENCE_DIR || join(homedir, ".openhands");
|
|
7963
|
+
const installed = join(configDir, "plugins", "installed");
|
|
7964
|
+
return {
|
|
7965
|
+
harness: "openhands",
|
|
7966
|
+
configDir,
|
|
7967
|
+
locations: [{
|
|
7968
|
+
kind: "installed-plugins",
|
|
7969
|
+
path: installed,
|
|
7970
|
+
description: "Installed plugins, one folder each",
|
|
7971
|
+
research: "E-OH-P3"
|
|
7972
|
+
}, {
|
|
7973
|
+
kind: "enabled-record",
|
|
7974
|
+
path: join(installed, ".installed.json"),
|
|
7975
|
+
description: "Install metadata keyed by plugin name, each with enabled: <bool>",
|
|
7976
|
+
research: "E-OH-P4"
|
|
7977
|
+
}]
|
|
7978
|
+
};
|
|
7979
|
+
},
|
|
7980
|
+
augment: ({ homedir }) => {
|
|
7981
|
+
const configDir = join(homedir, ".augment");
|
|
7982
|
+
return {
|
|
7983
|
+
harness: "augment",
|
|
7984
|
+
configDir,
|
|
7985
|
+
locations: [{
|
|
7986
|
+
kind: "marketplaces",
|
|
7987
|
+
path: join(configDir, "plugins", "marketplaces"),
|
|
7988
|
+
description: "Marketplace checkouts",
|
|
7989
|
+
research: "E-AUG-P2"
|
|
7990
|
+
}, {
|
|
7991
|
+
kind: "enabled-record",
|
|
7992
|
+
path: join(configDir, "settings.json"),
|
|
7993
|
+
description: "User settings; enabledPlugins maps plugin@marketplace to a boolean",
|
|
7994
|
+
research: "E-AUG-P4"
|
|
7995
|
+
}]
|
|
7996
|
+
};
|
|
7871
7997
|
}
|
|
7872
7998
|
};
|
|
7873
7999
|
//#endregion
|
|
@@ -7964,7 +8090,7 @@ function readPluginName(manifestPath) {
|
|
|
7964
8090
|
}
|
|
7965
8091
|
/** Entries a copy never carries: the working copy's history and its installed dependencies. Neither
|
|
7966
8092
|
* is part of the plugin, and both dwarf it. */
|
|
7967
|
-
const COPY_EXCLUDED = new Set([".git", "node_modules"]);
|
|
8093
|
+
const COPY_EXCLUDED = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
7968
8094
|
/** Applies one planned write. A replace removes the destination first, so a copy never merges into
|
|
7969
8095
|
* what was there before. A copy dereferences symlinks — a skill linked into the tree has to travel
|
|
7970
8096
|
* as its content, since the destination is read on its own. */
|
|
@@ -9302,8 +9428,10 @@ function runPrepare(opts) {
|
|
|
9302
9428
|
version
|
|
9303
9429
|
});
|
|
9304
9430
|
}
|
|
9305
|
-
if (!dryRun)
|
|
9306
|
-
|
|
9431
|
+
if (!dryRun) {
|
|
9432
|
+
if (scope === "global") prepareFs.writeGlobalState(updatedState);
|
|
9433
|
+
else prepareFs.writeProjectState(updatedState);
|
|
9434
|
+
}
|
|
9307
9435
|
return { newActionCount: actions.length };
|
|
9308
9436
|
}
|
|
9309
9437
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-plugin",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Universal AI agent plugin build tool",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-plugin",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"agents"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@cyberuni/agent-harness": "^0.
|
|
43
|
-
"@repobuddy/upx": "^0.
|
|
42
|
+
"@cyberuni/agent-harness": "^0.3.0",
|
|
43
|
+
"@repobuddy/upx": "^0.2.0",
|
|
44
44
|
"@toon-format/toon": "^4.1.1",
|
|
45
45
|
"commander": "^14.0.3",
|
|
46
46
|
"semver": "^7.8.1"
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@types/semver": "^7.7.1",
|
|
51
51
|
"ajv": "^8.20.0",
|
|
52
52
|
"knip": "^6.14.1",
|
|
53
|
-
"tsdown": "^0.
|
|
53
|
+
"tsdown": "^0.23.0",
|
|
54
54
|
"tsx": "^4.22.3",
|
|
55
55
|
"typescript": "^6.0.0",
|
|
56
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.11.
|
|
4
|
+
"version": "0.11.2",
|
|
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
|
@@ -110,7 +110,7 @@ TOON by default; pass `--format json` for JSON.
|
|
|
110
110
|
|
|
111
111
|
This package ships its own documents (`plugin-design`, `slash-invocation`, `universal-plugin`) under
|
|
112
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 `
|
|
113
|
+
on `universal-plugin`, or, from a skill, with the `reference` skill in the `buddy-agent-harness`
|
|
114
114
|
plugin.
|
|
115
115
|
|
|
116
116
|
### Housekeeping
|
|
@@ -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
|
|
@@ -23,7 +23,7 @@ Deploy $ARGUMENTS.
|
|
|
23
23
|
|
|
24
24
|
- Claude Code uses the same `SKILL.md` and adds its native invocation flag.
|
|
25
25
|
- Cursor receives a thin `.cursor/commands/<skill>.md` prompt insert for `user` and `both` skills.
|
|
26
|
-
- Codex
|
|
26
|
+
- Codex uses the skill itself. A user invokes it with `$skill` or `/skills`, so no command is derived. Codex custom prompts are deprecated and home-only, and `plugin build` never writes outside the plugin tree.
|
|
27
27
|
- Copilot CLI receives no derived command. Its `/skill-name` form is only a prompt hint, not deterministic invocation.
|
|
28
28
|
|
|
29
29
|
If a workflow requires deterministic user-triggered invocation, document Copilot
|
|
@@ -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
|
|
@@ -77,6 +77,7 @@ Each `code` below is what the script emits.
|
|
|
77
77
|
| `stale` | a derived manifest older than `plugin.json` | `universal-plugin plugin build` |
|
|
78
78
|
| `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 |
|
|
79
79
|
| `unknown-vendor` | a `vendors` entry no build target matches; reported as `skipped` plus a warning | fix the id in `plugin.json` |
|
|
80
|
+
| `unsupported-component` | a component path on the shared extension names a component one targeted vendor has none of (`agents` for Codex, `rules` for Claude Code, …); the build leaves it out of that vendor's manifest | declare the path under `harnesses.<vendor>` for the vendors that read it, or leave it — nothing is lost |
|
|
80
81
|
| `undeliverable-override` | `harnesses["copilot-cli"]` sets fields that reach nothing | `/universal-plugin:init-universal-plugin`, update route — move them to a vendor that has a derived manifest, or drop them |
|
|
81
82
|
| `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 |
|
|
82
83
|
| `version-drift` | the `packagePath` `package.json` and the canonical manifest carry different versions | `/universal-plugin:version` |
|
|
@@ -232,6 +232,13 @@ if (build === null) {
|
|
|
232
232
|
'no vendor is declared — the build writes nothing, so no runtime reads this plugin',
|
|
233
233
|
'/universal-plugin:init-universal-plugin, update route',
|
|
234
234
|
)
|
|
235
|
+
} else if (/has no "[^"]+" component/.test(warning)) {
|
|
236
|
+
add(
|
|
237
|
+
'unsupported-component',
|
|
238
|
+
'low',
|
|
239
|
+
warning,
|
|
240
|
+
'declare the path under harnesses.<vendor> for the vendors that read it, or leave it — the build already drops it',
|
|
241
|
+
)
|
|
235
242
|
} else if (/^Unknown vendor/.test(warning)) {
|
|
236
243
|
add('unknown-vendor', 'medium', warning, 'fix the vendor id in plugin.json')
|
|
237
244
|
} else {
|
|
@@ -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
|
|
@@ -8,7 +8,8 @@ npx universal-plugin plugin build --vendor claude-code
|
|
|
8
8
|
|
|
9
9
|
## What lands in the derived manifest
|
|
10
10
|
|
|
11
|
-
The shared metadata from the canonical top level, plus the component paths
|
|
11
|
+
The shared metadata from the canonical top level, plus the component paths Claude Code reads (every
|
|
12
|
+
one except `rules` and Codex's `apps`; the build drops those with a warning), plus whatever
|
|
12
13
|
`extensions["org.cyberuni.universal-plugin"].harnesses["claude-code"]` sets. `$schema`, `extensions`,
|
|
13
14
|
`vendors`, and `harnesses` are universal-plugin's own orchestration — they never
|
|
14
15
|
appear in a vendor manifest.
|
|
@@ -33,14 +33,35 @@ Codex's presentation metadata goes under its `harnesses` entry:
|
|
|
33
33
|
}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
##
|
|
36
|
+
## Components
|
|
37
|
+
|
|
38
|
+
What Codex reads from `.codex-plugin/plugin.json` depends on the root `plugin.json`:
|
|
39
|
+
|
|
40
|
+
- **Root declares the canonical `$schema`** (`https://agent-plugins.org/schemas/1.0.0/plugin.schema.json`,
|
|
41
|
+
the normal universal-plugin layout). Codex reads root `plugin.json` as an Agent Plugins manifest,
|
|
42
|
+
takes skills from `./skills` and MCP servers from `./mcp.json`, and uses `.codex-plugin/plugin.json`
|
|
43
|
+
only as an overlay for `apps`, `hooks`, and `interface`. Any other key there is ignored, and
|
|
44
|
+
commands are not read at all.
|
|
45
|
+
- **Root has no `$schema`**. `.codex-plugin/plugin.json` is the manifest. Codex reads `skills`,
|
|
46
|
+
`commands`, `hooks`, `mcpServers`, and `apps` from it, and migrates each command into a skill on
|
|
47
|
+
install, falling back to `./commands/` when `commands` is absent.
|
|
37
48
|
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
Codex has no `agents`, `rules`, `lspServers`, `outputStyles`, `themes`, `channels`, or `monitors` in
|
|
50
|
+
either case. `plugin build` leaves any of those out of `.codex-plugin/plugin.json` and warns
|
|
51
|
+
(`codex has no "agents" component — the path is left out of .codex-plugin/plugin.json`); the other
|
|
52
|
+
vendors still get the path. Set the path under `harnesses.codex` only if you mean Codex to see it
|
|
53
|
+
anyway — a harness override is never filtered.
|
|
54
|
+
|
|
55
|
+
The Codex runtime ignores keys it does not read. The plugin-creator validator Codex ships
|
|
56
|
+
(`validate_plugin.py`) is stricter: it rejects any field outside its allowlist, including `commands`
|
|
57
|
+
and `hooks`. That matters only if you submit the plugin through OpenAI's ingestion flow.
|
|
58
|
+
|
|
59
|
+
## Skills
|
|
40
60
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
61
|
+
Codex reaches a plugin's skills natively. A user invokes one with `$name` or `/skills`, so the
|
|
62
|
+
build derives nothing per skill for Codex. Codex custom prompts (`~/.codex/prompts/`) are deprecated
|
|
63
|
+
and load only from the user's home directory, and the build never writes there. See
|
|
64
|
+
[`codex-skill-invocation`](https://github.com/cyberuni/universal-plugin/blob/main/.research/codex-skill-invocation/conclusion.md).
|
|
44
65
|
|
|
45
66
|
## Hooks
|
|
46
67
|
|
|
@@ -22,6 +22,12 @@ Cursor's catalog metadata goes under its `harnesses` entry, not at the canonical
|
|
|
22
22
|
}
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
## Components
|
|
26
|
+
|
|
27
|
+
Cursor reads `skills`, `commands`, `agents`, `rules`, `hooks`, and `mcpServers`. Its manifest schema
|
|
28
|
+
is closed, so the build leaves `lspServers`, `outputStyles`, `apps`, and Claude Code's other
|
|
29
|
+
components out of `.cursor-plugin/plugin.json` and warns.
|
|
30
|
+
|
|
25
31
|
## Skills
|
|
26
32
|
|
|
27
33
|
Cursor reads `SKILL.md` straight from the path the manifest's `skills` field names, and lets the user
|
|
File without changes
|
|
File without changes
|
|
File without changes
|