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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-plugin",
3
- "version": "0.11.0",
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.0",
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.0",
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 = /* @__PURE__ */ createRequire(import.meta.url);
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) if (option.negate) val = false;
1601
- else if (option.isBoolean() || option.optional) val = true;
1602
- else val = "";
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
- handleOptionValue(val, `error: option '${option.flags}' argument '${val}' is invalid.`, "cli");
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
- handleOptionValue(val, `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`, "env");
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") if (launchWithNode) {
2001
- args.unshift(executableFile);
2002
- args = incrementNodeInspectorPort(process$1.execArgv).concat(args);
2003
- proc = childProcess$1.spawn(process$1.argv[0], args, { stdio: "inherit" });
2004
- } else proc = childProcess$1.spawn(executableFile, args, { stdio: "inherit" });
2005
- else {
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))) if (option.required || option.optional) this.emit(`optionEnv:${option.name()}`, process$1.env[option.envVar]);
2450
- else this.emit(`optionEnv:${option.name()}`);
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: MAX_LENGTH - 6,
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) if (version.loose === !!options.loose && version.includePrerelease === !!options.includePrerelease) return version;
3937
- else version = version.version;
3938
- else if (typeof version !== "string") throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`);
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
- return parse(`${major}.${match[3] || "0"}.${match[4] || "0"}${options.includePrerelease && match[5] ? `-${match[5]}` : ""}${options.includePrerelease && match[6] ? `+${match[6]}` : ""}`, options);
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
- return parse(version instanceof SemVer ? version.version : version, options);
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) if (range.loose === !!options.loose && range.includePrerelease === !!options.includePrerelease) return range;
4437
- else return new Range(range.raw, options);
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)) if (M === "0") ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`;
4606
- else ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0`;
4607
- else if (pr) {
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") if (m === "0") ret = `>=${M}.${m}.${p}-${pr} <${M}.${m}.${+p + 1}-0`;
4610
- else ret = `>=${M}.${m}.${p}-${pr} <${M}.${+m + 1}.0-0`;
4611
- else ret = `>=${M}.${m}.${p}-${pr} <${+M + 1}.0.0-0`;
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") if (m === "0") ret = `>=${M}.${m}.${p}${z} <${M}.${m}.${+p + 1}-0`;
4615
- else ret = `>=${M}.${m}.${p}${z} <${M}.${+m + 1}.0-0`;
4616
- else ret = `>=${M}.${m}.${p} <${+M + 1}.0.0-0`;
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) if (gtlt === ">" || gtlt === "<") ret = "<0.0.0-0";
4638
- else ret = "*";
4639
- else if (gtlt && anyX) {
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) if (comp.loose === !!options.loose) return comp;
4714
- else comp = comp.value;
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) if (dom.length === 1 && dom[0].semver === ANY) return true;
5035
- else if (options.includePrerelease) sub = minimumVersionWithPreRelease;
5036
- else sub = minimumVersion;
5037
- if (dom.length === 1 && dom[0].semver === ANY) if (options.includePrerelease) return true;
5038
- else dom = minimumVersion;
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 = [...new Set([...Array.isArray(extension.vendors) ? extension.vendors.filter((v) => typeof v === "string") : [], ...Object.keys(harnesses)])];
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) if (!Array.isArray(manifest.keywords)) push("keywords", "type", "must be an array of strings");
6319
- else manifest.keywords.forEach((keyword, index) => {
6320
- if (typeof keyword !== "string") push(`keywords[${index}]`, "type", "must be a string");
6321
- });
6322
- if (manifest.extensions !== void 0) if (!isObject(manifest.extensions)) push("extensions", "type", "must be an object keyed by namespace");
6323
- else {
6324
- for (const [namespace, value] of Object.entries(manifest.extensions)) if (!isObject(value)) push(`extensions.${namespace}`, "type", "must be an object");
6325
- const uext = manifest.extensions[UP_NAMESPACE$2];
6326
- if (isObject(uext)) {
6327
- const dependencies = validateDependencies(uext.dependencies);
6328
- for (const error of dependencies.errors) push(`extensions.${UP_NAMESPACE$2}.dependencies`, "dependencies", error);
6329
- warnings.push(...dependencies.warnings);
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
- ...componentConfig,
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, warnings);
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) if (hooks.hooks) vendorManifest["hooks"] = `./${path.dirname(relPath).split(path.sep).join("/")}/hooks.json`;
6510
- else delete vendorManifest["hooks"];
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) if (declaredMcp.inline) vendorManifest["mcpServers"] = mcp.servers;
6513
- else vendorManifest["mcpServers"] = `./${path.dirname(relPath).split(path.sep).join("/")}/mcp.json`;
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, warnings);
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, warnings) {
6619
- for (const skill of skills) {
6620
- if (vendor === "claude-code") {
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.1.0/node_modules/@cyberuni/agent-harness/dist/index.js
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) if (scope === "global") prepareFs.writeGlobalState(updatedState);
9306
- else prepareFs.writeProjectState(updatedState);
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.0",
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.1.0",
43
- "@repobuddy/upx": "^0.1.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.22.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.0",
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 `load-reference` skill in the `buddy-agent-harness`
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 `load-reference` skill in the `buddy-agent-harness` plugin.
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 receives a best-effort, local-only `~/.codex/prompts/<skill>.md` for `user` and `both` skills. Codex has deprecated custom prompts, so the skill remains the primary integration.
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 `load-reference` skill in the `buddy-agent-harness` plugin.
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
- `load-reference` skill in the `buddy-agent-harness` plugin; the build no longer copies them, and
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 `load-reference`
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 `load-reference` skill in the `buddy-agent-harness` plugin
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, plus whatever
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
- ## Skills
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
- For every skill that is not `invocation-policy: model`, the build also writes
39
- `~/.codex/prompts/<name>.md` — the skill body, as a Codex prompt.
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
- Two things follow. It writes **outside the repository**, into the current machine's home directory,
42
- so it is not part of the plugin's tracked output and does not travel with a clone. And it is
43
- **best-effort**: a failure there becomes a build warning, not a failed build. Read the warnings.
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