agents-inc 0.175.0 → 0.176.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/{chunk-EGC7PNDI.js → chunk-3F5LT433.js} +138 -41
  3. package/dist/chunk-3F5LT433.js.map +1 -0
  4. package/dist/chunk-5CFHP6Z5.js +109 -0
  5. package/dist/chunk-5CFHP6Z5.js.map +1 -0
  6. package/dist/{chunk-MFCZARV2.js → chunk-B7AVWSK2.js} +18 -8
  7. package/dist/chunk-B7AVWSK2.js.map +1 -0
  8. package/dist/{chunk-6HJVH27C.js → chunk-C2BSCEDN.js} +2 -2
  9. package/dist/{chunk-SNXEYAFP.js → chunk-CKBWEWHZ.js} +60 -30
  10. package/dist/chunk-CKBWEWHZ.js.map +1 -0
  11. package/dist/{chunk-SI2LLEIR.js → chunk-EC3JXT23.js} +4 -4
  12. package/dist/{chunk-YM3VEDAP.js → chunk-ENET4IF6.js} +440 -192
  13. package/dist/chunk-ENET4IF6.js.map +1 -0
  14. package/dist/{chunk-ICHWAFAC.js → chunk-FACDJH6O.js} +11 -104
  15. package/dist/chunk-FACDJH6O.js.map +1 -0
  16. package/dist/{chunk-P5XEK7HC.js → chunk-H6WUOW5F.js} +2 -2
  17. package/dist/{chunk-DQB3E2SX.js → chunk-JNRACLNS.js} +3 -3
  18. package/dist/{chunk-37QFHJUW.js → chunk-JTMSFBCA.js} +106 -28
  19. package/dist/chunk-JTMSFBCA.js.map +1 -0
  20. package/dist/{chunk-GNMRXXRY.js → chunk-MQZNDK5F.js} +3 -3
  21. package/dist/chunk-MQZNDK5F.js.map +1 -0
  22. package/dist/{chunk-SRII2GMF.js → chunk-SN64WNWE.js} +2 -2
  23. package/dist/{chunk-EHEBN4NB.js → chunk-U672FZVC.js} +2 -2
  24. package/dist/{chunk-TG3SPPZY.js → chunk-UE4Z3YMB.js} +1 -1
  25. package/dist/chunk-UE4Z3YMB.js.map +1 -0
  26. package/dist/{chunk-Q4NWATGH.js → chunk-XMQIWPIR.js} +58 -10
  27. package/dist/chunk-XMQIWPIR.js.map +1 -0
  28. package/dist/{chunk-YLFKFVQP.js → chunk-ZBI6GFJD.js} +2 -2
  29. package/dist/commands/build/marketplace.js +4 -3
  30. package/dist/commands/build/marketplace.js.map +1 -1
  31. package/dist/commands/build/plugins.js +6 -5
  32. package/dist/commands/build/plugins.js.map +1 -1
  33. package/dist/commands/compile.js +15 -11
  34. package/dist/commands/compile.js.map +1 -1
  35. package/dist/commands/doctor.js +38 -14
  36. package/dist/commands/doctor.js.map +1 -1
  37. package/dist/commands/edit.js +46 -25
  38. package/dist/commands/edit.js.map +1 -1
  39. package/dist/commands/eject.js +12 -8
  40. package/dist/commands/eject.js.map +1 -1
  41. package/dist/commands/init.js +15 -14
  42. package/dist/commands/list.js +8 -6
  43. package/dist/commands/list.js.map +1 -1
  44. package/dist/commands/new/marketplace.js +4 -3
  45. package/dist/commands/new/marketplace.js.map +1 -1
  46. package/dist/commands/search.js +13 -8
  47. package/dist/commands/search.js.map +1 -1
  48. package/dist/commands/share.js +14 -16
  49. package/dist/commands/share.js.map +1 -1
  50. package/dist/commands/uninstall.js +18 -14
  51. package/dist/commands/uninstall.js.map +1 -1
  52. package/dist/commands/update.js +97 -17
  53. package/dist/commands/update.js.map +1 -1
  54. package/dist/components/common/skill-agent-summary.js +3 -2
  55. package/dist/components/common/summary-panel.js +4 -3
  56. package/dist/hooks/init.js +15 -14
  57. package/dist/hooks/init.js.map +1 -1
  58. package/dist/load-agent-defs-5BSECHE6.js +14 -0
  59. package/dist/{recompile-project-agents-2QTFMYDC.js → recompile-project-agents-U3UGHPWG.js} +7 -7
  60. package/dist/{source-loader-I6SWWDUF.js → source-loader-E2Y5MMN5.js} +3 -3
  61. package/package.json +1 -1
  62. package/dist/chunk-37QFHJUW.js.map +0 -1
  63. package/dist/chunk-EGC7PNDI.js.map +0 -1
  64. package/dist/chunk-GNMRXXRY.js.map +0 -1
  65. package/dist/chunk-ICHWAFAC.js.map +0 -1
  66. package/dist/chunk-MFCZARV2.js.map +0 -1
  67. package/dist/chunk-Q4NWATGH.js.map +0 -1
  68. package/dist/chunk-SNXEYAFP.js.map +0 -1
  69. package/dist/chunk-TG3SPPZY.js.map +0 -1
  70. package/dist/chunk-YM3VEDAP.js.map +0 -1
  71. package/dist/load-agent-defs-BJ4KBKBA.js +0 -14
  72. /package/dist/{chunk-6HJVH27C.js.map → chunk-C2BSCEDN.js.map} +0 -0
  73. /package/dist/{chunk-SI2LLEIR.js.map → chunk-EC3JXT23.js.map} +0 -0
  74. /package/dist/{chunk-P5XEK7HC.js.map → chunk-H6WUOW5F.js.map} +0 -0
  75. /package/dist/{chunk-DQB3E2SX.js.map → chunk-JNRACLNS.js.map} +0 -0
  76. /package/dist/{chunk-SRII2GMF.js.map → chunk-SN64WNWE.js.map} +0 -0
  77. /package/dist/{chunk-EHEBN4NB.js.map → chunk-U672FZVC.js.map} +0 -0
  78. /package/dist/{chunk-YLFKFVQP.js.map → chunk-ZBI6GFJD.js.map} +0 -0
  79. /package/dist/{load-agent-defs-BJ4KBKBA.js.map → load-agent-defs-5BSECHE6.js.map} +0 -0
  80. /package/dist/{recompile-project-agents-2QTFMYDC.js.map → recompile-project-agents-U3UGHPWG.js.map} +0 -0
  81. /package/dist/{source-loader-I6SWWDUF.js.map → source-loader-E2Y5MMN5.js.map} +0 -0
@@ -13,7 +13,7 @@ import {
13
13
  typedFromEntries,
14
14
  typedKeys,
15
15
  typedValues
16
- } from "./chunk-TG3SPPZY.js";
16
+ } from "./chunk-UE4Z3YMB.js";
17
17
  import {
18
18
  AGENT_NAMES,
19
19
  AUTHOR_HANDLE_PATTERN,
@@ -91,7 +91,7 @@ import {
91
91
 
92
92
  // src/cli/lib/loading/source-loader.ts
93
93
  import os14 from "os";
94
- import { partition as partition5, unique as unique6 } from "remeda";
94
+ import { partition as partition6, unique as unique6 } from "remeda";
95
95
  import path28 from "path";
96
96
 
97
97
  // src/cli/lib/installation/is-home-directory.ts
@@ -481,6 +481,9 @@ function chooseProviderForThisRun(provider) {
481
481
  function forgetTheProviderChosenForThisRun() {
482
482
  providerChosenForThisRun = void 0;
483
483
  }
484
+ function declaresNoContent(config) {
485
+ return config.skills.length === 0 && config.agents.length === 0;
486
+ }
484
487
  var declaresNothingAsRead = /* @__PURE__ */ new Map();
485
488
  function recordWhetherItDeclaresNothing(configPath, declaresNothing) {
486
489
  const stamp = stampOf(configPath);
@@ -505,9 +508,7 @@ function providerInUse(root) {
505
508
  return providerOnDisk(root);
506
509
  }
507
510
  function inheritedGlobalProvider(projectDir, own = providerInUse(projectDir)) {
508
- const home = os2.homedir();
509
- const inheritsItsOwn = holdsAnInstallation(projectDir, own) && holdsAnInstallation(home, own);
510
- return inheritsItsOwn ? own : providerInUse(home);
511
+ return holdsAnInstallation(projectDir, own) ? own : providerInUse(os2.homedir());
511
512
  }
512
513
  function providerAtScope(projectDir, scope) {
513
514
  return scope === "global" ? inheritedGlobalProvider(projectDir) : providerInUse(projectDir);
@@ -984,8 +985,16 @@ var INFO_MESSAGES = {
984
985
  CONFIG_TYPES_REFRESHED: `Refreshed ${STANDARD_FILES.CONFIG_TYPES_TS}`,
985
986
  EJECTED_SKILLS_USER_OWNED: `Ejected skills are yours to own \u2014 '${CLI_INVOKE_COMMAND} update' does not change them.`,
986
987
  NO_PLUGIN_MARKETPLACES: "No plugin marketplaces are configured \u2014 nothing to refresh.",
987
- /** The owner's words (2026-10-24), on stderr before the command's own output. */
988
+ /**
989
+ * The owner's words (2026-10-24), on stderr before the command's own output. The lines after it
990
+ * are each true of a run this one is not (owner, 2026-10-26: "A line worded for each").
991
+ */
988
992
  GLOBAL_INSTALLATION_STANDS_IN: "This project's config declares nothing, so this works on your global installation.",
993
+ NO_GLOBAL_INSTALLATION_STANDS_IN: "This project's config declares nothing, and there is no global installation for this to work on.",
994
+ DECLARES_NOTHING_TO_UNINSTALL: "This project's config declares nothing, so there is nothing to uninstall in this project.",
995
+ DECLARES_NOTHING_TO_EJECT_FROM: "This project's config declares nothing; eject works on this project, not on a global installation.",
996
+ DECLARES_NOTHING_TO_INIT_OVER: "This project's config declares nothing, so init treats this project as holding no installation.",
997
+ DECLARES_NOTHING_TO_SEARCH_FROM: "This project's config declares nothing; search reads the marketplace it names, else the global installation's, else the default one.",
989
998
  /** Under doctor's marketplace row, when its download replaced a saved copy whose files differed. */
990
999
  SAVED_COPY_REPLACED: "The saved copy differed from the marketplace and has been replaced",
991
1000
  /** Printed under the destination already named, so it names files only. */
@@ -1024,21 +1033,18 @@ function propagatedRecompileSummary(recompiled, failed) {
1024
1033
  const failureSuffix = failed > 0 ? ` (${failed} failed)` : "";
1025
1034
  return `Registered projects: ${recompiled} recompiled${failureSuffix}`;
1026
1035
  }
1027
- function skillAssignedToNoAgent(skillId) {
1028
- return `Skill '${skillId}' is assigned to no sub-agent \u2014 nothing will load it.`;
1036
+ function skillAssignedToNoAgent(skillId, namedBy) {
1037
+ return `Skill '${skillName(skillId, namedBy)}' is assigned to no sub-agent \u2014 nothing will load it.`;
1029
1038
  }
1030
- function scopeBlockedStackAssignment(agentNames, skillId) {
1039
+ function scopeBlockedStackAssignment(agentNames, skillId, namedBy) {
1031
1040
  const subject = agentNames.length === 1 ? "Sub-agent" : "Sub-agents";
1032
1041
  const named = agentNames.map((name) => `'${name}'`).join(", ");
1033
- return `${subject} ${named} cannot carry project-scoped skill '${skillId}' \u2014 global-scoped sub-agents only carry global-scoped skills.`;
1042
+ return `${subject} ${named} cannot carry project-scoped skill '${skillName(skillId, namedBy)}' \u2014 global-scoped sub-agents only carry global-scoped skills.`;
1034
1043
  }
1035
1044
  function globalScopedAgentsHint(count) {
1036
1045
  const [subject, object] = count === 1 ? ["agent is", "it"] : ["agents are", "them"];
1037
1046
  return `${count} ${subject} global-scoped \u2014 run '${CLI_INVOKE_COMMAND} compile' from your home directory to recompile ${object}.`;
1038
1047
  }
1039
- function marketplacesRefreshed(count) {
1040
- return `Refreshed ${plural(count, "marketplace listing")}. Installed plugins were not changed.`;
1041
- }
1042
1048
  function marketplaceRefreshFailed(marketplace, reason) {
1043
1049
  return `Could not update marketplace ${marketplace}: ${reason}`;
1044
1050
  }
@@ -1051,6 +1057,13 @@ function sourceRefusedUsingCache(source, status) {
1051
1057
  function marketplacesRefreshFailed(marketplaces) {
1052
1058
  return `${plural(marketplaces.length, "marketplace")} could not be updated: ${marketplaces.join(", ")}`;
1053
1059
  }
1060
+ function codexPluginLeftSwitchedOn(pluginKey, configFile, reason) {
1061
+ return `Codex switched plugin ${pluginKey} back on when it was updated, and it could not be switched off again in ${configFile}: ${reason}. To switch it off, set enabled = false in that file, in the table it already has for ${pluginKey}.`;
1062
+ }
1063
+ function pluginsLeftSwitchedOn(pluginKeys) {
1064
+ const are = pluginKeys.length === 1 ? "is" : "are";
1065
+ return `${plural(pluginKeys.length, "plugin")} you had switched off ${are} switched on again: ${pluginKeys.join(", ")}`;
1066
+ }
1054
1067
  function marketplaceOwnerHasNoName(packageJsonPath) {
1055
1068
  return [
1056
1069
  `A marketplace's owner must have a name, and no name could be read from 'author' in ${packageJsonPath}.`,
@@ -1118,6 +1131,10 @@ function registeredProjectOnAnotherProvider(projectPath) {
1118
1131
  function registeredProjectUpdateFailed(projectPath) {
1119
1132
  return `Skipped ${projectPath}: updating it failed.`;
1120
1133
  }
1134
+ function unregisteredSkillsSkipped(count, folder) {
1135
+ const are = count === 1 ? "isn't" : "aren't";
1136
+ return `Skipped ${plural(count, "skill")} in '${folder}' that ${are} registered with the CLI (no ${STANDARD_FILES.METADATA_YAML}).`;
1137
+ }
1121
1138
  function skillMetadataUnusableDetail({
1122
1139
  skillDirName,
1123
1140
  metadataPath,
@@ -1166,26 +1183,26 @@ function applySharedConfigHere(id, installedHere, provider) {
1166
1183
  const named = providerArgument(provider);
1167
1184
  return id === void 0 ? `Then ${verb} what it gives you with '${command} <id>${named}'.` : `To ${verb} it here instead, run '${command} ${id}${named}'.`;
1168
1185
  }
1169
- function shareRefusesEjectedSkills(skillIds) {
1186
+ function shareRefusesEjectedSkills(skillIds, namedBy) {
1170
1187
  return [
1171
1188
  "This installation holds ejected skills, and ejected skills cannot be shared:",
1172
- ...skillIds.map((id) => ` ${id}`),
1189
+ ...skillIds.map((id) => ` ${skillName(id, namedBy)}`),
1173
1190
  "Only plugins can be shared."
1174
1191
  ].join("\n");
1175
1192
  }
1176
1193
  function sharedIdNamesAFolder(folder) {
1177
1194
  return `This id names the marketplace folder ${folder}, so it installs only where that folder exists.`;
1178
1195
  }
1179
- function sharedSkillsLeftBehind(skillIds) {
1180
- return `A shared id names one marketplace, and these skill(s) come from another, so they will not install elsewhere: ${skillIds.join(", ")}`;
1196
+ function sharedSkillsLeftBehind(skillIds, namedBy) {
1197
+ return `A shared id names one marketplace, and these skill(s) come from another, so they will not install elsewhere: ${skillNames(skillIds, namedBy)}`;
1181
1198
  }
1182
1199
  function sharedConfigExistingInstall(configPath, id, provider) {
1183
1200
  return `An installation already exists at ${configPath}. Run '${CLI_INVOKE_COMMAND} uninstall${providerArgument(provider)}' first \u2014 ${SHARED_CONFIG_GREENFIELD_HINT}. ${applySharedConfigHere(id, true, provider)}`;
1184
1201
  }
1185
- function sharedConfigProjectScopeAtHome(skillIds, agentNames, installedHere, provider) {
1202
+ function sharedConfigProjectScopeAtHome(skillIds, agentNames, installedHere, provider, namedBy) {
1186
1203
  return [
1187
1204
  "This configuration cannot be installed here: the home directory is the global scope, and a global installation holds only global-scoped content \u2014 so these project-scoped entries have nowhere to be written:",
1188
- ...skillIds.map((id) => ` skill ${id} (scope: project)`),
1205
+ ...skillIds.map((id) => ` skill ${skillName(id, namedBy)} (scope: project)`),
1189
1206
  ...agentNames.map((name) => ` sub-agent ${name} (scope: project)`),
1190
1207
  installedHere ? changeTheGlobalInstallFromHome(providerArgument(provider)) : "Run this from inside a project directory, or re-share it with each entry above at global scope."
1191
1208
  ].join("\n");
@@ -1220,12 +1237,21 @@ function subAgentsInstalledWithout({ skillId, agentNames }) {
1220
1237
  function skippedUnknownAgents(agentNames) {
1221
1238
  return `Skipped ${agentNames.length} unknown sub-agent(s): ${unknownKeys(agentNames)}`;
1222
1239
  }
1223
- function carriedSkillsWritten(skillIds) {
1224
- return `Wrote ${skillIds.length} skill(s) this configuration carries: ${skillIds.join(", ")}`;
1240
+ function carriedSkillsWritten(skillIds, namedBy) {
1241
+ return `Wrote ${skillIds.length} skill(s) this configuration carries: ${skillNames(skillIds, namedBy)}`;
1242
+ }
1243
+ function copiedSkillsRemovedAfterRefusedSave(count) {
1244
+ return `Removed the ${plural(count, "skill")} it copied, since the configuration could not be saved`;
1245
+ }
1246
+ function carriedSkillsRemovedAfterRefusedSave(count) {
1247
+ return `Removed the ${plural(count, "skill")} this configuration carried, since the configuration could not be saved`;
1225
1248
  }
1226
1249
  var SHARED_CONFIG_APPLY = {
1227
1250
  CONFIRM: "Apply this configuration?"
1228
1251
  };
1252
+ function idNamesAnotherMarketplace(named, used) {
1253
+ return `This id names the marketplace ${inertLine(named)}; its skills come from this installation's marketplace, ${used}.`;
1254
+ }
1229
1255
  function registeredProjectsRecompiled(projectPaths) {
1230
1256
  return [
1231
1257
  "Each project registered with this global install is recompiled to match:",
@@ -1257,6 +1283,9 @@ var SHARED_CONFIG_ARRIVALS = {
1257
1283
  function skillName(id, namedBy) {
1258
1284
  return getSkillEntryDisplayName(namedBy[id] ?? { id });
1259
1285
  }
1286
+ function skillNames(skillIds, namedBy) {
1287
+ return skillIds.map((id) => skillName(id, namedBy)).join(", ");
1288
+ }
1260
1289
  function keptAsInstalledGlobally(skillIds, agentNames, idAppliedAtHome, provider, namedBy) {
1261
1290
  return [
1262
1291
  "Kept as installed in the global install \u2014 this configuration states them differently or leaves them out, and a project run only adds to the global install:",
@@ -1269,15 +1298,11 @@ function keptAsInstalledGlobally(skillIds, agentNames, idAppliedAtHome, provider
1269
1298
  }
1270
1299
  function skillsMoved(verb, skillIds, namedBy) {
1271
1300
  const names = skillIds.map((id) => skillName(id, namedBy));
1272
- return names.length === 0 ? [] : [`${verb} ${listed(names)}`];
1301
+ return names.length === 0 ? [] : [`${verb} ${inAnEnglishList(names)}`];
1273
1302
  }
1274
1303
  function settingMoved({ setting, from, to }) {
1275
1304
  return `${setting} ${from ?? "default"} \u2192 ${to ?? "default"}`;
1276
1305
  }
1277
- function listed(names) {
1278
- if (names.length <= 1) return names.join("");
1279
- return `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
1280
- }
1281
1306
  var SKILLS_REASSIGNED = "skills reassigned";
1282
1307
  function subAgentChanged({
1283
1308
  name,
@@ -1296,10 +1321,11 @@ function subAgentChanged({
1296
1321
  return `~ ${name}${where}: ${details.length > 0 ? details.join(", ") : SKILLS_REASSIGNED}`;
1297
1322
  }
1298
1323
  function addedToGlobalSubAgents(joined, namedBy) {
1299
- const gains = (skillIds) => skillIds.map((id) => skillName(id, namedBy)).join(", ");
1300
1324
  return [
1301
1325
  "Added to sub-agents in the global install \u2014 each keeps everything it already has, since a project run only adds to the global install:",
1302
- ...joined.map(({ agentName, skillIds }) => ` sub-agent ${agentName}: + ${gains(skillIds)}`)
1326
+ ...joined.map(
1327
+ ({ agentName, skillIds }) => ` sub-agent ${agentName}: + ${skillNames(skillIds, namedBy)}`
1328
+ )
1303
1329
  ].join("\n");
1304
1330
  }
1305
1331
  function authoredHereKept(skillIds, namedBy) {
@@ -2583,6 +2609,14 @@ var ConfigDefaultExportError = class extends Error {
2583
2609
  }
2584
2610
  configPath;
2585
2611
  };
2612
+ var ConfigEvaluationError = class extends Error {
2613
+ constructor(configPath, reason, options) {
2614
+ super(configLoadFailure(configPath, reason), options);
2615
+ this.configPath = configPath;
2616
+ this.name = "ConfigEvaluationError";
2617
+ }
2618
+ configPath;
2619
+ };
2586
2620
  var ES_MODULE_MARKER = "__esModule";
2587
2621
  function unwrapDefaultExport(namespace) {
2588
2622
  if (isRecord(namespace) && "default" in namespace) return namespace.default;
@@ -2628,7 +2662,7 @@ async function importConfigModule(configPath) {
2628
2662
  });
2629
2663
  return await jiti.import(configPath);
2630
2664
  } catch (error) {
2631
- throw new Error(configLoadFailure(configPath, getErrorMessage(error)), { cause: error });
2665
+ throw new ConfigEvaluationError(configPath, getErrorMessage(error), { cause: error });
2632
2666
  }
2633
2667
  }
2634
2668
 
@@ -2664,7 +2698,7 @@ function initProviderToNameAt(root, provider) {
2664
2698
 
2665
2699
  // src/cli/lib/configuration/config.ts
2666
2700
  var SOURCE_ENV_VAR = "CC_MARKETPLACE";
2667
- async function loadSourceConfig(dir, scope, provider) {
2701
+ async function readStoredConfig(dir, scope, provider) {
2668
2702
  const scopeLabel2 = scope === "project" ? "Project" : "Global";
2669
2703
  const configPath = storedConfigPath(dir, provider);
2670
2704
  if (!await fileExists(configPath)) {
@@ -2681,7 +2715,17 @@ async function loadSourceConfig(dir, scope, provider) {
2681
2715
  if (installation === null) throw unreadable(NO_VALID_DEFAULT_EXPORT);
2682
2716
  const settings = readSettings(configPath, installation);
2683
2717
  verbose(`Loaded ${scope} config from ${dir}`);
2684
- return settings;
2718
+ return {
2719
+ settings,
2720
+ declaresNothing: declaresNoContent({
2721
+ skills: installation.skills ?? [],
2722
+ agents: installation.agents ?? []
2723
+ })
2724
+ };
2725
+ }
2726
+ async function loadSourceConfig(dir, scope, provider) {
2727
+ const stored = await readStoredConfig(dir, scope, provider);
2728
+ return stored === null ? null : stored.settings;
2685
2729
  }
2686
2730
  function refuseInstallationConfig(configPath, scope, scopeRoot, provider) {
2687
2731
  const providerToName = providerToNameAt(scopeRoot, provider);
@@ -2738,14 +2782,23 @@ async function loadGlobalSourceConfig(provider) {
2738
2782
  async function loadEffectiveSourceConfig(projectDir, provider) {
2739
2783
  const ownDir = ownProjectDir(projectDir);
2740
2784
  const own = ownDir === null ? null : await readStoredRung(ownDir, "project", provider);
2741
- return own ?? await readStoredRung(os4.homedir(), "global", provider ?? inheritedBy(projectDir));
2785
+ if (own !== null && !readsAsNoConfig(own)) return own;
2786
+ return readStoredRung(os4.homedir(), "global", provider ?? inheritedBy(projectDir));
2787
+ }
2788
+ function readsAsNoConfig(own) {
2789
+ return own.declaresNothing && own.config.marketplace === void 0;
2742
2790
  }
2743
2791
  function inheritedBy(projectDir) {
2744
2792
  return inheritedGlobalProvider(projectDir ?? os4.homedir());
2745
2793
  }
2746
2794
  async function readStoredRung(dir, origin, provider) {
2747
- const config = await loadSourceConfig(dir, origin, provider);
2748
- return config && { config, origin, configPath: storedConfigPath(dir, provider) };
2795
+ const stored = await readStoredConfig(dir, origin, provider);
2796
+ return stored && {
2797
+ config: stored.settings,
2798
+ declaresNothing: stored.declaresNothing,
2799
+ origin,
2800
+ configPath: storedConfigPath(dir, provider)
2801
+ };
2749
2802
  }
2750
2803
  function storedConfigPath(dir, provider) {
2751
2804
  return provider === void 0 ? getInstalledConfigPath(dir) : getProjectConfigPath(dir, provider);
@@ -2779,7 +2832,7 @@ async function resolveSource(request) {
2779
2832
  if (envSource !== void 0) {
2780
2833
  return { source: envSource, sourceOrigin: "env", ...labelFor(envSource) };
2781
2834
  }
2782
- if (effective?.config.marketplace) {
2835
+ if (caller === "stored" && effective?.config.marketplace) {
2783
2836
  assertStoredSourceUsable(effective.config.marketplace, effective.configPath);
2784
2837
  verbose(`Marketplace from ${effective.origin} config: ${effective.config.marketplace}`);
2785
2838
  return {
@@ -3218,9 +3271,6 @@ function valueEntries(values) {
3218
3271
  function commentEntry(text) {
3219
3272
  return { kind: "comment", text };
3220
3273
  }
3221
- function sourceEntry(source) {
3222
- return { kind: "source", source };
3223
- }
3224
3274
  function renderValueLine(value, position) {
3225
3275
  return line(
3226
3276
  renderValue(value, position),
@@ -3301,8 +3351,6 @@ function renderEntryLine(entry, indent) {
3301
3351
  switch (entry.kind) {
3302
3352
  case "comment":
3303
3353
  return `${spaces(indent)}// ${entry.text}`;
3304
- case "source":
3305
- return `${spaces(indent)}${entry.source},`;
3306
3354
  case "value":
3307
3355
  return renderValueLine(entry.value, { indent, prefix: "", suffix: "," });
3308
3356
  default: {
@@ -3340,8 +3388,6 @@ function flatEntry(entry) {
3340
3388
  // A `//` line cannot fold onto one line.
3341
3389
  case "comment":
3342
3390
  return void 0;
3343
- case "source":
3344
- return entry.source;
3345
3391
  case "value":
3346
3392
  return flatValue(entry.value);
3347
3393
  default: {
@@ -3526,62 +3572,16 @@ function generateConfigSource(config, catalog, options) {
3526
3572
  }
3527
3573
  function generateProjectConfig(config, catalog, options) {
3528
3574
  const cleaned = cleanForEmission(config, catalog, { dropProjects: true });
3529
- if (options.globalConfig) {
3530
- return generateProjectConfigWithInlinedGlobal(
3531
- cleaned,
3532
- cleanForEmission(options.globalConfig, catalog, { dropProjects: true })
3533
- );
3534
- }
3535
- if (options.globalImportPath !== void 0) {
3536
- return generateProjectConfigWithGlobalImport(
3537
- cleaned,
3538
- options.globalImportPath
3575
+ if (!options.globalConfig) {
3576
+ throw new Error(
3577
+ "A project config needs the global config it extends: pass `globalConfig` to inline its entries."
3539
3578
  );
3540
3579
  }
3541
- throw new Error(
3542
- "A project config needs the global config it extends: pass `globalConfig` to inline its entries, or `globalImportPath` to import and spread them. Neither was given."
3580
+ return generateProjectConfigWithInlinedGlobal(
3581
+ cleaned,
3582
+ cleanForEmission(options.globalConfig, catalog, { dropProjects: true })
3543
3583
  );
3544
3584
  }
3545
- function generateProjectConfigWithGlobalImport(cleaned, globalImportPath) {
3546
- const { skills, agents, stack, selectedDomains } = extractConfigArrays(cleaned);
3547
- const emitted = {
3548
- hasSkills: true,
3549
- hasAgents: true,
3550
- hasStack: hasStackEntries(stack),
3551
- hasSelectedDomains: selectedDomains.length > 0
3552
- };
3553
- const scalarFields = Object.entries(cleaned).filter(([key]) => isCopiedScalarField(key)).map(([key, value]) => renderScalarField(key, value));
3554
- return assembleConfigSource({
3555
- imports: [
3556
- `import globalConfig from ${quoteText(`${globalImportPath}/config`)}`,
3557
- renderTypeImportLine(buildTypeImports(emitted), CONFIG_TYPES_SPECIFIER)
3558
- ],
3559
- declarations: [
3560
- ...entryDeclaration("skills", "SkillConfig[]", [
3561
- sourceEntry("...globalConfig.skills"),
3562
- ...valueEntries(skills)
3563
- ]),
3564
- ...entryDeclaration("agents", "AgentScopeConfig[]", [
3565
- sourceEntry("...globalConfig.agents"),
3566
- ...valueEntries(agents)
3567
- ]),
3568
- ...emitted.hasStack ? declaration("stack", STACK_TYPE, stack) : [],
3569
- ...emitted.hasSelectedDomains ? entryDeclaration("selectedDomains", "Domain[]", [
3570
- sourceEntry("...(globalConfig.selectedDomains ?? [])"),
3571
- ...valueEntries(selectedDomains)
3572
- ]) : []
3573
- ],
3574
- exportFields: [
3575
- ` ...globalConfig,`,
3576
- renderScalarField("name", resolveProjectName(cleaned)),
3577
- ` skills,`,
3578
- ` agents,`,
3579
- ...emitted.hasStack ? [` stack,`] : [],
3580
- ...emitted.hasSelectedDomains ? [` selectedDomains,`] : [],
3581
- ...scalarFields
3582
- ]
3583
- });
3584
- }
3585
3585
  function generateStandaloneConfig(cleaned) {
3586
3586
  const { skills, agents, stack, selectedDomains } = extractConfigArrays(cleaned);
3587
3587
  const emitted = {
@@ -3697,18 +3697,25 @@ function stackOfAgents(stack, agents) {
3697
3697
  function mergeInlinedScalarFields(cleaned, cleanedGlobal) {
3698
3698
  return Object.entries(
3699
3699
  canonicalizeFieldOrder({
3700
- ...omit(cleanedGlobal, GLOBAL_IDENTITY_FIELDS),
3700
+ ...globalScalarsLentTo(cleaned, cleanedGlobal),
3701
3701
  ...cleaned
3702
3702
  })
3703
3703
  ).filter(([key]) => isCopiedScalarField(key));
3704
3704
  }
3705
+ function globalScalarsLentTo(cleaned, cleanedGlobal) {
3706
+ const lent = omit(cleanedGlobal, GLOBAL_IDENTITY_FIELDS);
3707
+ return recordsAnotherMarketplace(cleaned, cleanedGlobal) ? omit(lent, ["marketplaceName"]) : lent;
3708
+ }
3709
+ function recordsAnotherMarketplace(cleaned, cleanedGlobal) {
3710
+ return cleaned.marketplace !== void 0 && cleaned.marketplace !== cleanedGlobal.marketplace;
3711
+ }
3705
3712
  function generateProjectConfigWithInlinedGlobal(cleaned, cleanedGlobal) {
3706
- const partition6 = partitionInlinedConfigEntries(cleaned, cleanedGlobal);
3713
+ const partition7 = partitionInlinedConfigEntries(cleaned, cleanedGlobal);
3707
3714
  const emitted = {
3708
- hasSkills: partition6.globalSkills.length > 0 || partition6.projectSkills.length > 0,
3709
- hasAgents: partition6.globalAgents.length > 0 || partition6.projectAgents.length > 0,
3710
- hasStack: hasStackEntries(partition6.filteredStack),
3711
- hasSelectedDomains: partition6.selectedDomains.length > 0
3715
+ hasSkills: partition7.globalSkills.length > 0 || partition7.projectSkills.length > 0,
3716
+ hasAgents: partition7.globalAgents.length > 0 || partition7.projectAgents.length > 0,
3717
+ hasStack: hasStackEntries(partition7.filteredStack),
3718
+ hasSelectedDomains: partition7.selectedDomains.length > 0
3712
3719
  };
3713
3720
  const scalarFields = mergeInlinedScalarFields(cleaned, cleanedGlobal);
3714
3721
  return assembleConfigSource({
@@ -3719,15 +3726,15 @@ function generateProjectConfigWithInlinedGlobal(cleaned, cleanedGlobal) {
3719
3726
  ...emitted.hasSkills ? entryDeclaration(
3720
3727
  "skills",
3721
3728
  "SkillConfig[]",
3722
- scopeSectionEntries(partition6.globalSkills, partition6.projectSkills)
3729
+ scopeSectionEntries(partition7.globalSkills, partition7.projectSkills)
3723
3730
  ) : [],
3724
3731
  ...emitted.hasAgents ? entryDeclaration(
3725
3732
  "agents",
3726
3733
  "AgentScopeConfig[]",
3727
- scopeSectionEntries(partition6.globalAgents, partition6.projectAgents)
3734
+ scopeSectionEntries(partition7.globalAgents, partition7.projectAgents)
3728
3735
  ) : [],
3729
- ...emitted.hasStack ? declaration("stack", STACK_TYPE, partition6.filteredStack) : [],
3730
- ...emitted.hasSelectedDomains ? declaration("selectedDomains", "Domain[]", partition6.selectedDomains) : []
3736
+ ...emitted.hasStack ? declaration("stack", STACK_TYPE, partition7.filteredStack) : [],
3737
+ ...emitted.hasSelectedDomains ? declaration("selectedDomains", "Domain[]", partition7.selectedDomains) : []
3731
3738
  ],
3732
3739
  exportFields: [
3733
3740
  renderScalarField("name", resolveProjectName(cleaned)),
@@ -4220,14 +4227,14 @@ function agentsOnlyItCarried(skill, agentMap, carried) {
4220
4227
  }
4221
4228
  function readAgentMap(agents) {
4222
4229
  const known = /* @__PURE__ */ new Map();
4223
- const switchedOff = /* @__PURE__ */ new Set();
4230
+ const switchedOff2 = /* @__PURE__ */ new Set();
4224
4231
  const unknown = [];
4225
4232
  for (const [name, entry] of Object.entries(agents)) {
4226
4233
  if (!isAgentName2(name)) unknown.push(name);
4227
- else if (entry.on === false) switchedOff.add(name);
4234
+ else if (entry.on === false) switchedOff2.add(name);
4228
4235
  else known.set(name, entry);
4229
4236
  }
4230
- return { known, switchedOff, unknown };
4237
+ return { known, switchedOff: switchedOff2, unknown };
4231
4238
  }
4232
4239
  function agentsSwitchedOn(agentMap) {
4233
4240
  return [...agentMap.known].filter(([, entry]) => entry.on === true).map(([name]) => name);
@@ -4389,6 +4396,12 @@ function normalizeStackRecord(rawStack) {
4389
4396
  (agentConfig) => rekeyToLiveCategories(normalizeAgentConfig(agentConfig))
4390
4397
  );
4391
4398
  }
4399
+ var StacksLoadError = class extends Error {
4400
+ constructor(stacksPath, reason, options) {
4401
+ super(`Failed to load stacks from '${stacksPath}': ${reason}`, options);
4402
+ this.name = "StacksLoadError";
4403
+ }
4404
+ };
4392
4405
  async function loadStacks(configDir, stacksFile) {
4393
4406
  const resolvedStacksFile = stacksFile ?? STACKS_FILE_PATH;
4394
4407
  const cacheKey = `${configDir}:${resolvedStacksFile}`;
@@ -4412,14 +4425,12 @@ async function loadStacks(configDir, stacksFile) {
4412
4425
  verbose(`Loaded ${stacks.length} stacks from ${stacksPath}`);
4413
4426
  return stacks;
4414
4427
  } catch (error) {
4415
- const errorMessage = getErrorMessage(error);
4416
- throw new Error(`Failed to load stacks from '${stacksPath}': ${errorMessage}`, {
4417
- cause: error
4418
- });
4428
+ throw new StacksLoadError(stacksPath, getErrorMessage(error), { cause: error });
4419
4429
  }
4420
4430
  }
4421
4431
  async function loadStackById(stackId, configDir, source) {
4422
- const stacks = await loadStacks(configDir);
4432
+ const declared = await loadSourceRepoConfig(configDir);
4433
+ const stacks = await loadStacks(configDir, declared?.stacksFile);
4423
4434
  const stack = stacks.find((s) => s.id === stackId);
4424
4435
  if (stack) {
4425
4436
  verbose(`Found stack: ${stack.name} (${stackId})`);
@@ -4439,8 +4450,15 @@ async function loadStackById(stackId, configDir, source) {
4439
4450
  verbose(`Found default stack: ${defaultStack.name} (${stackId})`);
4440
4451
  return defaultStack;
4441
4452
  }
4442
- function stackNotOfferedMessage(stackId, source) {
4443
- return `Stack '${inertLine(stackId)}' is not a stack the source '${source}' offers. A marketplace ships its own stacks; the CLI's built-in ones belong to the default public marketplace alone.`;
4453
+ function stackNotOfferedMessage(stackId, source, shippedStackIds) {
4454
+ return `Stack '${inertLine(stackId)}' is not a stack the source '${source}' offers. A marketplace ships its own stacks; the CLI's built-in ones belong to the default public marketplace alone. ${whatItShips(shippedStackIds)}`;
4455
+ }
4456
+ function whatItShips(stackIds) {
4457
+ if (stackIds.length === 0) return "It ships no stacks.";
4458
+ return withClosingFullStop(`It ships: ${inAnEnglishList(stackIds.map(inertLine))}`);
4459
+ }
4460
+ function withClosingFullStop(sentence) {
4461
+ return sentence.endsWith(".") ? sentence : `${sentence}.`;
4444
4462
  }
4445
4463
  function defaultUsageGuidance(category) {
4446
4464
  return `Use when working with ${category}.`;
@@ -4491,9 +4509,6 @@ var ConfigLoadError = class extends Error {
4491
4509
  /** Named in the remedy's `init --from` half, which a bare run would answer with another one. */
4492
4510
  initProviderToName;
4493
4511
  };
4494
- function declaresNoContent(config) {
4495
- return config.skills.length === 0 && config.agents.length === 0;
4496
- }
4497
4512
  async function configDeclaresNothing(projectDir, provider) {
4498
4513
  const configPath = getProjectConfigPath(projectDir, provider);
4499
4514
  const parsed = await parseConfigFile(configPath, refusalAt(configPath, projectDir, provider));
@@ -4635,6 +4650,16 @@ function editDrops(incoming, existing, identity, authoritativeScope) {
4635
4650
  incomingIdentities.has(identity.identityOf(entry)) || dualScopeIdentities.has(identity.identityOf(entry)) || authoritativeScope !== void 0 && isWithinSessionAuthority(entry, authoritativeScope)
4636
4651
  );
4637
4652
  }
4653
+ function marketplaceNameBeside(merged, existing) {
4654
+ if (merged.marketplace === existing.marketplace) {
4655
+ return existing.marketplaceName ?? merged.marketplaceName;
4656
+ }
4657
+ if (wouldBorrowAnotherMarketplaceName(merged, existing)) return DEFAULT_PUBLIC_SOURCE_NAME;
4658
+ return merged.marketplaceName;
4659
+ }
4660
+ function wouldBorrowAnotherMarketplaceName(merged, existing) {
4661
+ return merged.marketplace === DEFAULT_SOURCE && merged.marketplaceName === void 0 && existing.marketplaceName !== void 0;
4662
+ }
4638
4663
  function mergeConfigs(newConfig, existingConfig, options) {
4639
4664
  const merged = { ...newConfig };
4640
4665
  if (existingConfig.name) {
@@ -4664,8 +4689,9 @@ function mergeConfigs(newConfig, existingConfig, options) {
4664
4689
  if (existingConfig.author) {
4665
4690
  merged.author = existingConfig.author;
4666
4691
  }
4667
- if (existingConfig.marketplaceName && merged.marketplace === existingConfig.marketplace) {
4668
- merged.marketplaceName = existingConfig.marketplaceName;
4692
+ const marketplaceName = marketplaceNameBeside(merged, existingConfig);
4693
+ if (marketplaceName !== void 0) {
4694
+ merged.marketplaceName = marketplaceName;
4669
4695
  }
4670
4696
  Object.assign(merged, pick(existingConfig, HAND_WRITTEN_FIELDS));
4671
4697
  if (existingConfig.projects && !newConfig.projects) {
@@ -5294,9 +5320,10 @@ async function loadProjectAgents(projectRoot, provider = providerInUse(projectRo
5294
5320
  });
5295
5321
  }
5296
5322
  async function loadSkillsFromDir(skillsDir2, options = {}) {
5297
- const { pathPrefix = "", requireMetadata = false } = options;
5323
+ const { pathPrefix = "", requireMetadata = false, folderLabel = `${pathPrefix}/` } = options;
5298
5324
  const skills = {};
5299
5325
  const unusableMetadata = [];
5326
+ let unregisteredCount = 0;
5300
5327
  if (!await directoryExists2(skillsDir2)) {
5301
5328
  return { skills, unusableMetadata };
5302
5329
  }
@@ -5306,11 +5333,13 @@ async function loadSkillsFromDir(skillsDir2, options = {}) {
5306
5333
  if (requireMetadata) {
5307
5334
  const verdict = await judgeSkillMetadata(skill);
5308
5335
  if (verdict.kind === "unusable") unusableMetadata.push(verdict.refusal);
5336
+ if (verdict.kind === "unregistered") unregisteredCount++;
5309
5337
  if (verdict.kind !== "admitted") continue;
5310
5338
  }
5311
5339
  const definition = await readSkillDefinition(skill);
5312
5340
  if (definition) skills[definition.id] = definition;
5313
5341
  }
5342
+ if (unregisteredCount > 0) warn(unregisteredSkillsSkipped(unregisteredCount, folderLabel));
5314
5343
  return { skills, unusableMetadata };
5315
5344
  }
5316
5345
  function locateSkill(skillsDir2, skillFile, pathPrefix) {
@@ -5328,10 +5357,8 @@ function locateSkill(skillsDir2, skillFile, pathPrefix) {
5328
5357
  async function judgeSkillMetadata(skill) {
5329
5358
  const metadataPath = path14.join(skill.skillDir, STANDARD_FILES.METADATA_YAML);
5330
5359
  if (!await fileExists(metadataPath)) {
5331
- warn(
5332
- `Skill '${skill.skillDirName}' in '${skill.displayPath}' is missing ${STANDARD_FILES.METADATA_YAML} \u2014 skipped. Add ${STANDARD_FILES.METADATA_YAML} to register it with the CLI.`
5333
- );
5334
- return { kind: "skipped" };
5360
+ verbose(` No ${STANDARD_FILES.METADATA_YAML} in '${skill.skillDirName}'`);
5361
+ return { kind: "unregistered" };
5335
5362
  }
5336
5363
  const read = await readSkillMetadata(metadataPath);
5337
5364
  if (!read.usable) {
@@ -5475,11 +5502,11 @@ function reservedNameError(marketplaceName, wayOut) {
5475
5502
  return `Marketplace name '${marketplaceName}' is reserved. '${DEFAULT_PUBLIC_SOURCE_NAME}' is the public catalogue's own namespace, '${EXTERNAL_SKILL_NAMESPACE}' and '${LOCAL_SKILL_NAMESPACE}' hold the skills that belong to no marketplace, and '${EJECT_SOURCE}' is what an installation records for a skill copied into it rather than installed as a plugin \u2014 a marketplace under any of them is read back as something it is not. Choose a name of your own: ${wayOut}.`;
5476
5503
  }
5477
5504
  function namespaceViolationError(foreignIds, marketplaceName) {
5478
- const listed2 = foreignIds.slice(0, MAX_REPORTED_NAMESPACE_VIOLATIONS).map((id) => ` ${id} -> ${marketplaceName}-${id}`);
5479
- const unlisted = foreignIds.length - listed2.length;
5505
+ const listed = foreignIds.slice(0, MAX_REPORTED_NAMESPACE_VIOLATIONS).map((id) => ` ${id} -> ${marketplaceName}-${id}`);
5506
+ const unlisted = foreignIds.length - listed.length;
5480
5507
  return [
5481
5508
  `Marketplace '${marketplaceName}' ships ${foreignIds.length} skill id(s) outside its own namespace. Every skill id must begin with the marketplace's name:`,
5482
- ...listed2,
5509
+ ...listed,
5483
5510
  ...unlisted > 0 ? [` ... and ${unlisted} more`] : [],
5484
5511
  `Rename each skill directory and the 'name' in its SKILL.md, re-run 'build plugins', then build the marketplace again.`
5485
5512
  ].join("\n");
@@ -5521,6 +5548,9 @@ var TarballWithheldError = class extends Error {
5521
5548
  this.status = status;
5522
5549
  }
5523
5550
  };
5551
+ function isUnanswered(answer) {
5552
+ return answer === "unreachable" || answer instanceof TarballWithheldError;
5553
+ }
5524
5554
  var marketplaceMustAnswer = new AsyncLocalStorage3();
5525
5555
  function askingEachMarketplaceItself(body) {
5526
5556
  const differingCopiesReplaced = /* @__PURE__ */ new Set();
@@ -5620,38 +5650,95 @@ async function fetchFromRemoteSource(source, subdir) {
5620
5650
  verbose(`Cache directory: ${cacheDir}`);
5621
5651
  if (await directoryExists2(cacheDir)) {
5622
5652
  const verdict = await revalidateCachedCopy(cacheDir, source);
5623
- if (keepsTheCopy(verdict)) {
5624
- verbose(`Using cached marketplace: ${cacheDir}`);
5625
- return {
5626
- path: cacheDir,
5627
- fromCache: true,
5628
- unreachable: verdict === "unreachable",
5629
- source: fullSource
5630
- };
5631
- }
5653
+ if (keepsTheCopy(verdict)) return readSavedCopy(cacheDir, fullSource, verdict);
5632
5654
  announceRefetch(verdict);
5633
- if (!mustAnswer()) await discardCachedCopy(cacheDir, source);
5655
+ if (!mustAnswer()) return refreshSavedCopy(cacheDir, source, fullSource, verdict);
5634
5656
  }
5635
5657
  const differingCopiesReplaced = marketplaceMustAnswer.getStore();
5636
5658
  return differingCopiesReplaced ? downloadAnsweredFor(cacheDir, source, fullSource, differingCopiesReplaced) : downloadFreshCopy(cacheDir, source, fullSource);
5637
5659
  }
5638
- async function discardCachedCopy(cacheDir, source) {
5639
- await clearGigetCache(source);
5640
- await remove(cacheDir);
5660
+ function readSavedCopy(cacheDir, fullSource, verdict) {
5661
+ verbose(`Using cached marketplace: ${cacheDir}`);
5662
+ return {
5663
+ path: cacheDir,
5664
+ fromCache: true,
5665
+ unreachable: verdict === "unreachable",
5666
+ source: fullSource
5667
+ };
5668
+ }
5669
+ async function refreshSavedCopy(cacheDir, source, fullSource, verdict) {
5670
+ if (verdict === "superseded") await clearGigetCache(source);
5671
+ const staged = `${cacheDir}${STAGED_DOWNLOAD_SUFFIX}`;
5672
+ try {
5673
+ const download = await downloadUnlessItFails(source, fullSource, staged);
5674
+ if (isUnanswered(download)) {
5675
+ const failed = verdictOnFailedDownload(verdict, download, source);
5676
+ return keepSavedCopyUnlessStopped(cacheDir, fullSource, failed);
5677
+ }
5678
+ const downloaded = await verdictOnDownload(download, source);
5679
+ if (downloaded !== "current") {
5680
+ return keepSavedCopyUnlessStopped(cacheDir, fullSource, downloaded);
5681
+ }
5682
+ await moveIntoPlace(cacheDir, staged);
5683
+ await recordFetchedCopy(cacheDir, download.result, download.savedTarball);
5684
+ settleForThisRun(cacheDir, "current");
5685
+ return { path: cacheDir, fromCache: false, unreachable: false, source: fullSource };
5686
+ } catch (error) {
5687
+ throw createDetailedFetchError(error, source);
5688
+ } finally {
5689
+ await remove(staged);
5690
+ }
5691
+ }
5692
+ function keepSavedCopyUnlessStopped(cacheDir, fullSource, verdict) {
5693
+ if (verdict instanceof TarballWithheldError) throw verdict;
5694
+ settleForThisRun(cacheDir, verdict);
5695
+ return readSavedCopy(cacheDir, fullSource, verdict);
5696
+ }
5697
+ async function downloadUnlessItFails(source, fullSource, staged) {
5698
+ try {
5699
+ return await downloadStaged(source, fullSource, staged);
5700
+ } catch (error) {
5701
+ verbose(`Could not download ${fullSource}: ${getErrorMessage(error)}`);
5702
+ return refusalIn(error) ?? "unreachable";
5703
+ }
5704
+ }
5705
+ var GIGET_DOWNLOAD_REFUSED = /^Failed to download \S+: (?<status>\d{3}) (?<statusText>.*)$/;
5706
+ function refusalIn(error) {
5707
+ const answered = GIGET_DOWNLOAD_REFUSED.exec(getErrorMessage(error))?.groups;
5708
+ if (answered?.status === void 0 || answered.statusText === void 0) return void 0;
5709
+ const status = Number(answered.status);
5710
+ if (postureOn(status) === void 0) return void 0;
5711
+ return new TarballWithheldError({ status, statusText: answered.statusText });
5712
+ }
5713
+ function verdictOnFailedDownload(verdict, failure, source) {
5714
+ if (verdict === "unrecorded") return verdictOnUnanswered(failure, source);
5715
+ warn(updateNotFetchedUsingCache(source));
5716
+ return "outdated";
5717
+ }
5718
+ function updateNotFetchedUsingCache(source) {
5719
+ return `Could not fetch the update from ${source} \u2014 using the cached copy, which is out of date.`;
5720
+ }
5721
+ async function verdictOnDownload({ result, savedTarball }, source) {
5722
+ return savedTarball ? verdictOnSavedTarball(result, savedTarball, source) : "current";
5641
5723
  }
5642
5724
  async function downloadFreshCopy(cacheDir, source, fullSource) {
5643
5725
  await ensureDir(path16.dirname(cacheDir));
5644
5726
  try {
5645
- const { result, savedTarball } = await gigetDownload(source, fullSource, {
5727
+ const download = await gigetDownload(source, fullSource, {
5646
5728
  dir: cacheDir,
5647
5729
  force: true,
5648
5730
  // Always force when downloading to avoid "already exists" error
5649
5731
  offline: false
5650
5732
  });
5733
+ const { result, savedTarball } = download;
5651
5734
  verbose(`Downloaded to: ${result.dir}`);
5652
- if (savedTarball) await askBeforeKeepingTheSavedTarball(cacheDir, result, source);
5735
+ const downloaded = await verdictOnDownload(download, source);
5736
+ if (downloaded instanceof TarballWithheldError) {
5737
+ await remove(cacheDir);
5738
+ throw downloaded;
5739
+ }
5653
5740
  await recordFetchedCopy(cacheDir, result, savedTarball);
5654
- markCopyCurrentForThisRun(cacheDir);
5741
+ settleForThisRun(cacheDir, "current");
5655
5742
  return {
5656
5743
  path: result.dir,
5657
5744
  fromCache: false,
@@ -5662,25 +5749,27 @@ async function downloadFreshCopy(cacheDir, source, fullSource) {
5662
5749
  throw createDetailedFetchError(error, source);
5663
5750
  }
5664
5751
  }
5665
- async function askBeforeKeepingTheSavedTarball(cacheDir, result, source) {
5752
+ async function verdictOnSavedTarball(result, savedTarball, source) {
5666
5753
  const tar = tarballOf(result);
5667
- if (tar === void 0) return;
5754
+ if (tar === void 0) return "current";
5668
5755
  const answer = await answerAbout(tar);
5669
- if (answer === "answered") return;
5670
- if (answer === "unreachable") {
5756
+ if (isUnanswered(answer)) return verdictOnUnanswered(answer, source);
5757
+ if (answer.etag === savedTarball.etag) return "current";
5758
+ warn(updateNotFetchedUsingCache(source));
5759
+ return "outdated";
5760
+ }
5761
+ function verdictOnUnanswered(unanswered, source) {
5762
+ if (unanswered === "unreachable") {
5671
5763
  warn(sourceUnreachableUsingCache(source));
5672
- return;
5673
- }
5674
- if (stopsTheLoad(answer)) {
5675
- await remove(cacheDir);
5676
- throw answer;
5764
+ return "unreachable";
5677
5765
  }
5678
- announceTheCopyKept(answer, source);
5766
+ if (stopsTheLoad(unanswered)) return unanswered;
5767
+ announceTheCopyKept(unanswered, source);
5768
+ return "refused";
5679
5769
  }
5680
5770
  async function answerAbout(tar) {
5681
5771
  try {
5682
- await fetchEtag(tar);
5683
- return "answered";
5772
+ return { etag: await fetchEtag(tar) };
5684
5773
  } catch (error) {
5685
5774
  if (error instanceof TarballWithheldError) return error;
5686
5775
  verbose(`Could not ask ${tar} about the tarball giget saved: ${getErrorMessage(error)}`);
@@ -5692,21 +5781,16 @@ var ONLY_A_SAVED_COPY_CAME_BACK = "the download failed and only a saved copy cam
5692
5781
  var SET_ASIDE_SUFFIX = ".set-aside";
5693
5782
  async function downloadAnsweredFor(cacheDir, source, fullSource, differingCopiesReplaced) {
5694
5783
  const staged = `${cacheDir}${STAGED_DOWNLOAD_SUFFIX}`;
5695
- const downloadStaged = () => gigetDownload(source, fullSource, {
5696
- dir: staged,
5697
- force: true,
5698
- forceClean: true,
5699
- offline: false
5700
- });
5784
+ const download = () => downloadStaged(source, fullSource, staged);
5701
5785
  await ensureDir(path16.dirname(cacheDir));
5702
5786
  try {
5703
- const { result, savedTarball } = await downloadStaged();
5787
+ const { result, savedTarball } = await download();
5704
5788
  const record = await askAfterTheDownload(result, source);
5705
- if (savedTarball) await downloadPast(savedTarball, record, downloadStaged, source);
5789
+ if (savedTarball) await downloadPast(savedTarball, record, download, source);
5706
5790
  const differed = await savedCopyDiffers(cacheDir, staged);
5707
5791
  await replaceCopy(cacheDir, staged, record);
5708
5792
  if (differed) differingCopiesReplaced.add(source);
5709
- markCopyCurrentForThisRun(cacheDir);
5793
+ settleForThisRun(cacheDir, "current");
5710
5794
  return { path: cacheDir, fromCache: false, unreachable: false, source: fullSource };
5711
5795
  } catch (error) {
5712
5796
  if (error instanceof MarketplaceUnreachableError) throw error;
@@ -5715,6 +5799,14 @@ async function downloadAnsweredFor(cacheDir, source, fullSource, differingCopies
5715
5799
  await remove(staged);
5716
5800
  }
5717
5801
  }
5802
+ function downloadStaged(source, fullSource, staged) {
5803
+ return gigetDownload(source, fullSource, {
5804
+ dir: staged,
5805
+ force: true,
5806
+ forceClean: true,
5807
+ offline: false
5808
+ });
5809
+ }
5718
5810
  async function downloadPast(savedTarball, record, download, source) {
5719
5811
  if (!savedUnder(savedTarball, record)) {
5720
5812
  throw new MarketplaceUnreachableError(source, ONLY_A_SAVED_COPY_CAME_BACK);
@@ -5761,9 +5853,12 @@ function sameBytesAt(file, left, right) {
5761
5853
  return readFileSync(path16.join(left, file)).equals(readFileSync(path16.join(right, file)));
5762
5854
  }
5763
5855
  async function replaceCopy(cacheDir, staged, record) {
5856
+ await moveIntoPlace(cacheDir, staged);
5857
+ await writeFile(fetchRecordPath(cacheDir), JSON.stringify(record));
5858
+ }
5859
+ async function moveIntoPlace(cacheDir, staged) {
5764
5860
  await remove(cacheDir);
5765
5861
  await rename(staged, cacheDir);
5766
- await writeFile(fetchRecordPath(cacheDir), JSON.stringify(record));
5767
5862
  }
5768
5863
  var GIGET_TARBALLS = "**/*.tar.gz";
5769
5864
  var gigetTarballRecordSchema = z4.object({ etag: z4.string().min(1).optional() });
@@ -5932,7 +6027,7 @@ function savedUnder(savedTarball, record) {
5932
6027
  return savedTarball.etag !== void 0 && savedTarball.etag === record.etag;
5933
6028
  }
5934
6029
  function keepsTheCopy(verdict) {
5935
- return verdict === "current" || verdict === "unreachable" || verdict === "refused";
6030
+ return verdict === "current" || verdict === "unreachable" || verdict === "refused" || verdict === "outdated";
5936
6031
  }
5937
6032
  var REVALIDATION_TIMEOUT_MS = 5e3;
5938
6033
  var FETCH_RECORD_SUFFIX = ".etag.json";
@@ -5954,16 +6049,12 @@ function revalidateCachedCopy(cacheDir, source) {
5954
6049
  verdicts.set(cacheDir, verdict);
5955
6050
  return verdict;
5956
6051
  }
5957
- function markCopyCurrentForThisRun(cacheDir) {
5958
- verdictsThisRun().set(cacheDir, Promise.resolve("current"));
6052
+ function settleForThisRun(cacheDir, verdict) {
6053
+ verdictsThisRun().set(cacheDir, Promise.resolve(verdict));
5959
6054
  }
5960
6055
  async function classifyCachedCopy(cacheDir, source) {
5961
6056
  const record = await readFetchRecord(cacheDir);
5962
6057
  if (!record) return "unrecorded";
5963
- if (record.etag === void 0 && !mustAnswer()) {
5964
- verbose(`No ETag was recorded for ${record.tar} \u2014 keeping the cached copy`);
5965
- return "current";
5966
- }
5967
6058
  try {
5968
6059
  return judgeAnswer(record, await fetchEtag(record.tar));
5969
6060
  } catch (error) {
@@ -5982,6 +6073,7 @@ function stopOrKeepTheCopy(refusal2, source) {
5982
6073
  function judgeAnswer(record, live) {
5983
6074
  if (movedOn(record, live)) return "superseded";
5984
6075
  if (mustAnswer()) return "answered";
6076
+ if (record.etag === void 0 && live !== void 0) return "unstamped";
5985
6077
  if (live === void 0) {
5986
6078
  verbose(`${record.tar} answered without an ETag \u2014 keeping the cached copy`);
5987
6079
  }
@@ -6366,9 +6458,9 @@ function agentNotFoundError(agentName, agents) {
6366
6458
  }
6367
6459
  function describeAvailableAgents(availableAgents) {
6368
6460
  if (availableAgents.length === 0) return "No agents found in scanned directories";
6369
- const listed2 = availableAgents.slice(0, MAX_LISTED_AVAILABLE_AGENTS).join(", ");
6461
+ const listed = availableAgents.slice(0, MAX_LISTED_AVAILABLE_AGENTS).join(", ");
6370
6462
  const unlisted = availableAgents.length - MAX_LISTED_AVAILABLE_AGENTS;
6371
- return unlisted > 0 ? `Available agents: ${listed2} (and ${unlisted} more)` : `Available agents: ${listed2}`;
6463
+ return unlisted > 0 ? `Available agents: ${listed} (and ${unlisted} more)` : `Available agents: ${listed}`;
6372
6464
  }
6373
6465
  function assembleAgentConfig(agentName, definition, agentConfig, resolvedSkills) {
6374
6466
  const model = agentConfig.model ?? definition.model;
@@ -6458,7 +6550,10 @@ async function loadSelectedStack(stackId, sourceResult) {
6458
6550
  verbose(
6459
6551
  `buildInstallConfig: loadedStack=${loadedStack ? `found (id='${loadedStack.id}')` : "NOT FOUND"}`
6460
6552
  );
6461
- if (!loadedStack) throw new Error(stackNotOfferedMessage(stackId, source));
6553
+ if (!loadedStack) {
6554
+ const shippedStackIds = sourceResult.matrix.suggestedStacks.map((stack) => stack.id);
6555
+ throw new Error(stackNotOfferedMessage(stackId, source, shippedStackIds));
6556
+ }
6462
6557
  return loadedStack;
6463
6558
  }
6464
6559
  function stackToPreserve(loadedStack, curatedStack) {
@@ -6832,6 +6927,13 @@ async function claudePluginInstall(pluginPath, scope, projectDir, options) {
6832
6927
  throw new Error(`Plugin installation failed: ${whyClaudeFailed(result)}`);
6833
6928
  }
6834
6929
  }
6930
+ async function claudePluginUpdate(pluginRef, scope, projectDir, options) {
6931
+ validatePluginName(pluginRef);
6932
+ const cwd = resolvePluginCwd(scope, projectDir);
6933
+ const args = ["plugin", "update", pluginRef, "--scope", scope];
6934
+ const result = await execCommand("claude", args, { cwd, ...configDirEnv(options) });
6935
+ if (result.exitCode !== 0) throw new Error(whyClaudeFailed(result));
6936
+ }
6835
6937
  async function isClaudeCLIAvailable(options) {
6836
6938
  try {
6837
6939
  const result = await execCommand("claude", ["--version"], configDirEnv(options));
@@ -6948,6 +7050,7 @@ function claudeHost() {
6948
7050
  addMarketplace: claudePluginMarketplaceAdd,
6949
7051
  refreshMarketplace: claudePluginMarketplaceUpdate,
6950
7052
  installPlugin: (pluginRef, scope, projectDir, options) => claudePluginInstall(pluginRef, toClaudePluginScope(scope), projectDir, options),
7053
+ updatePlugin: (pluginRef, scope, projectDir, options) => claudePluginUpdate(pluginRef, toClaudePluginScope(scope), projectDir, options),
6951
7054
  uninstallPlugin: (pluginRef, scope, projectDir, options) => claudePluginUninstall(pluginRef, toClaudePluginScope(scope), projectDir, options),
6952
7055
  listPlugins: claudeListPlugins
6953
7056
  };
@@ -6957,6 +7060,87 @@ function claudeHost() {
6957
7060
  import os13 from "os";
6958
7061
  import path21 from "path";
6959
7062
  import { z as z7 } from "zod";
7063
+
7064
+ // src/cli/lib/hosts/codex-plugin-switch.ts
7065
+ import { partition as partition5 } from "remeda";
7066
+ function sayWhichAreLeftOn(leftOn) {
7067
+ return leftOn.map(({ pluginKey, reason }) => `Codex left ${pluginKey} switched on: ${reason}`).join("\n");
7068
+ }
7069
+ var PluginsLeftSwitchedOnError = class extends Error {
7070
+ constructor(leftOn) {
7071
+ super(sayWhichAreLeftOn(leftOn));
7072
+ this.leftOn = leftOn;
7073
+ }
7074
+ leftOn;
7075
+ };
7076
+ var FailedLeavingPluginsSwitchedOnError = class extends Error {
7077
+ constructor(failure, leftOn) {
7078
+ super(`${getErrorMessage(failure)}
7079
+ ${sayWhichAreLeftOn(leftOn)}`, { cause: failure });
7080
+ this.failure = failure;
7081
+ this.leftOn = leftOn;
7082
+ }
7083
+ failure;
7084
+ leftOn;
7085
+ };
7086
+ var SWITCH = /^(\s*enabled\s*=\s*)(true|false)(\s*)$/;
7087
+ var SWITCHED_OFF = "false";
7088
+ var NOT_FOUND = -1;
7089
+ async function switchCodexPluginsOff(configFile, pluginKeys) {
7090
+ if (pluginKeys.length === 0) return;
7091
+ const leftOn = await pluginsStillSwitchedOn(configFile, pluginKeys);
7092
+ if (leftOn.length > 0) throw new PluginsLeftSwitchedOnError(leftOn);
7093
+ }
7094
+ async function pluginsStillSwitchedOn(configFile, pluginKeys) {
7095
+ const leftOn = (pluginKey, reason) => ({ pluginKey, configFile, reason });
7096
+ try {
7097
+ const config = await readFile(configFile);
7098
+ const switched = withSwitchesOff(config, pluginKeys);
7099
+ if (switched.config !== config) await writeFile(configFile, switched.config);
7100
+ return switched.unfound.map(
7101
+ (pluginKey) => leftOn(pluginKey, notSpelledAsCodexWritesIt(pluginKey))
7102
+ );
7103
+ } catch (error) {
7104
+ return pluginKeys.map((pluginKey) => leftOn(pluginKey, getErrorMessage(error)));
7105
+ }
7106
+ }
7107
+ function withSwitchesOff(config, pluginKeys) {
7108
+ const lines = config.split("\n");
7109
+ const located = pluginKeys.map((pluginKey) => ({
7110
+ pluginKey,
7111
+ at: switchLineOf(lines, pluginKey)
7112
+ }));
7113
+ const [found, unfound] = partition5(located, ({ at }) => at !== NOT_FOUND);
7114
+ const toSwitch = new Set(found.map(({ at }) => at));
7115
+ return {
7116
+ config: lines.map((line2, at) => toSwitch.has(at) ? switchedOff(line2) : line2).join("\n"),
7117
+ unfound: unfound.map(({ pluginKey }) => pluginKey)
7118
+ };
7119
+ }
7120
+ function switchLineOf(lines, pluginKey) {
7121
+ const header = lines.findIndex((line2) => line2.trim() === pluginTableHeader(pluginKey));
7122
+ if (header === NOT_FOUND) return NOT_FOUND;
7123
+ const end = endOfTable(lines, header);
7124
+ return lines.findIndex((line2, at) => at > header && at < end && SWITCH.test(line2));
7125
+ }
7126
+ function endOfTable(lines, header) {
7127
+ const nextTable = lines.findIndex((line2, at) => at > header && opensATable(line2));
7128
+ return nextTable === NOT_FOUND ? lines.length : nextTable;
7129
+ }
7130
+ function switchedOff(line2) {
7131
+ return line2.replace(SWITCH, `$1${SWITCHED_OFF}$3`);
7132
+ }
7133
+ function opensATable(line2) {
7134
+ return line2.trim().startsWith("[");
7135
+ }
7136
+ function pluginTableHeader(pluginKey) {
7137
+ return `[plugins."${pluginKey}"]`;
7138
+ }
7139
+ function notSpelledAsCodexWritesIt(pluginKey) {
7140
+ return `it has no ${pluginTableHeader(pluginKey)} table with an 'enabled' line`;
7141
+ }
7142
+
7143
+ // src/cli/lib/hosts/codex-host.ts
6960
7144
  var CODEX = "codex";
6961
7145
  var PLUGIN_CACHE_SUBDIR = "cache";
6962
7146
  var STANDING_WARNING = /could not create PATH aliases/;
@@ -7113,23 +7297,56 @@ async function upgradeGitMarketplace(name, options) {
7113
7297
  );
7114
7298
  }
7115
7299
  async function reinstallEachPluginOf(name, options) {
7116
- const installed = await codexPluginListing(options, codexPluginCwd());
7117
- for (const plugin of installed.filter((entry) => entry.marketplaceName === name)) {
7118
- await installCodexPlugin(plugin.pluginId, options);
7300
+ const ofTheMarketplace = (await codexPluginListing(options, codexPluginCwd())).filter(
7301
+ (plugin) => plugin.marketplaceName === name
7302
+ );
7303
+ for (const [at, plugin] of ofTheMarketplace.entries()) {
7304
+ try {
7305
+ await installCodexPlugin(plugin.pluginId, options);
7306
+ } catch (refusal2) {
7307
+ await switchBackOffThenRethrow(refusal2, ofTheMarketplace.slice(0, at), options);
7308
+ }
7309
+ }
7310
+ await switchBackOff(ofTheMarketplace, options);
7311
+ }
7312
+ async function switchBackOffThenRethrow(refusal2, added, options) {
7313
+ try {
7314
+ await switchBackOff(added, options);
7315
+ } catch (error) {
7316
+ if (!(error instanceof PluginsLeftSwitchedOnError)) throw error;
7317
+ throw new FailedLeavingPluginsSwitchedOnError(refusal2, error.leftOn);
7119
7318
  }
7319
+ throw refusal2;
7320
+ }
7321
+ async function switchBackOff(heldBefore, options) {
7322
+ await switchCodexPluginsOff(
7323
+ codexConfigFile(options),
7324
+ heldBefore.filter((plugin) => !plugin.enabled).map((plugin) => plugin.pluginId)
7325
+ );
7326
+ }
7327
+ function codexConfigFile(options) {
7328
+ return path21.join(codexStateRoot(options, codexPluginCwd()), CODEX_CONFIG_FILE);
7120
7329
  }
7121
7330
  async function codexInstallPlugin(pluginRef, _scope, _projectDir, options) {
7122
7331
  await installCodexPlugin(pluginRef, options);
7123
7332
  }
7124
- async function installCodexPlugin(pluginRef, options) {
7333
+ async function installCodexPlugin(pluginRef, options, attempt = "install") {
7125
7334
  validatePluginPath(pluginRef);
7126
7335
  await runCodexOrThrow(
7127
7336
  ["plugin", "add", pluginRef, "--json"],
7128
7337
  options,
7129
- `install ${pluginRef}`,
7338
+ `${attempt} ${pluginRef}`,
7130
7339
  codexPluginCwd()
7131
7340
  );
7132
7341
  }
7342
+ async function codexUpdatePlugin(pluginRef, _scope, _projectDir, options) {
7343
+ const held = await heldPlugin(pluginRef, options);
7344
+ if (held === void 0) {
7345
+ throw new Error(`Failed to update ${pluginRef}: codex has not installed it`);
7346
+ }
7347
+ await installCodexPlugin(pluginRef, options, "update");
7348
+ await switchBackOff([held], options);
7349
+ }
7133
7350
  async function codexUninstallPlugin(pluginRef, _scope, _projectDir, options) {
7134
7351
  validatePluginName(pluginRef);
7135
7352
  const wasInstalled = await isListed(pluginRef, options);
@@ -7147,8 +7364,11 @@ async function codexUninstallPlugin(pluginRef, _scope, _projectDir, options) {
7147
7364
  return wasInstalled ? "removed" : "absent";
7148
7365
  }
7149
7366
  async function isListed(pluginRef, options) {
7367
+ return await heldPlugin(pluginRef, options) !== void 0;
7368
+ }
7369
+ async function heldPlugin(pluginRef, options) {
7150
7370
  const installed = await codexPluginListing(options, codexPluginCwd());
7151
- return installed.some((plugin) => plugin.pluginId === pluginRef);
7371
+ return installed.find((plugin) => plugin.pluginId === pluginRef);
7152
7372
  }
7153
7373
  async function codexPluginListing(options, askedFrom) {
7154
7374
  const listing = await readJsonFromCodex(
@@ -7196,6 +7416,7 @@ function codexHost() {
7196
7416
  addMarketplace: codexMarketplaceAdd,
7197
7417
  refreshMarketplace: refreshCodexMarketplace,
7198
7418
  installPlugin: codexInstallPlugin,
7419
+ updatePlugin: codexUpdatePlugin,
7199
7420
  uninstallPlugin: codexUninstallPlugin,
7200
7421
  listPlugins: codexListPlugins
7201
7422
  };
@@ -8179,10 +8400,18 @@ async function loadSkillCategories(configPath) {
8179
8400
  verbose(`Loaded skill categories: ${configPath}`);
8180
8401
  return data.categories;
8181
8402
  }
8403
+ var NoSkillRulesError = class extends Error {
8404
+ constructor(configPath) {
8405
+ super(`Invalid skill rules at '${configPath}': failed to load or validate`);
8406
+ this.configPath = configPath;
8407
+ this.name = "NoSkillRulesError";
8408
+ }
8409
+ configPath;
8410
+ };
8182
8411
  async function loadSkillRules(configPath) {
8183
8412
  const data = await loadConfig(configPath, skillRulesFileSchema);
8184
8413
  if (!data) {
8185
- throw new Error(`Invalid skill rules at '${configPath}': failed to load or validate`);
8414
+ throw new NoSkillRulesError(configPath);
8186
8415
  }
8187
8416
  const config = {
8188
8417
  version: data.version,
@@ -10161,11 +10390,16 @@ async function loadCatalogue(options) {
10161
10390
  devMode = false,
10162
10391
  matrixOnly = false
10163
10392
  } = options;
10164
- const sourceConfig = await resolveSource({ caller, flag: sourceFlag, projectDir, provider });
10393
+ const resolvedProjectDir = projectDir || process.cwd();
10394
+ const sourceConfig = await resolveSource({
10395
+ caller,
10396
+ flag: sourceFlag,
10397
+ projectDir: resolvedProjectDir,
10398
+ provider
10399
+ });
10165
10400
  const { source } = sourceConfig;
10166
10401
  verbose(`Loading skills from source: ${source}`);
10167
10402
  const catalogue = await resolveBaseResult(sourceConfig, { purpose, devMode, matrixOnly });
10168
- const resolvedProjectDir = projectDir || process.cwd();
10169
10403
  const result = {
10170
10404
  ...catalogue,
10171
10405
  marketplaceSkillIds: new Set(typedKeys(catalogue.matrix.skills)),
@@ -10457,11 +10691,11 @@ async function refuseCatalogueCollisions(basePath, source, skills) {
10457
10691
  throw new Error(catalogueCollisionError(collidingIds, source));
10458
10692
  }
10459
10693
  function catalogueCollisionError(collidingIds, source) {
10460
- const listed2 = collidingIds.slice(0, MAX_REPORTED_COLLISIONS).map((id) => ` ${id}`);
10461
- const unlisted = collidingIds.length - listed2.length;
10694
+ const listed = collidingIds.slice(0, MAX_REPORTED_COLLISIONS).map((id) => ` ${id}`);
10695
+ const unlisted = collidingIds.length - listed.length;
10462
10696
  return [
10463
10697
  `Marketplace '${source}' ships ${collidingIds.length} skill id(s) the public catalogue already owns:`,
10464
- ...listed2,
10698
+ ...listed,
10465
10699
  ...unlisted > 0 ? [` ... and ${unlisted} more`] : [],
10466
10700
  `A skill id is the directory the skill installs into, so these would shadow the public catalogue's own skills. Every id must carry its marketplace's name as a namespace \u2014 '<marketplace>-<id>'. Rename each skill and the 'name' in its SKILL.md, then re-run 'build marketplace', which refuses the same ids before they are published.`
10467
10701
  ].join("\n");
@@ -10501,7 +10735,7 @@ function convertStackToResolvedStack(stack, catalogue) {
10501
10735
  if (undeclared.length > 0) warn(undeclaredAgentsWarning(stack, undeclared));
10502
10736
  const isCarried = carriedBy(catalogue);
10503
10737
  const agentConfigs = agentConfigsFor(stack, declared);
10504
- const [allSkillIds, withdrawn] = partition5(namedSkillIds(agentConfigs), isCarried);
10738
+ const [allSkillIds, withdrawn] = partition6(namedSkillIds(agentConfigs), isCarried);
10505
10739
  if (withdrawn.length > 0) warn(withdrawnSkillsWarning(stack, withdrawn));
10506
10740
  verbose(
10507
10741
  `Stack '${stack.id}' has ${allSkillIds.length} skills from ${agentConfigs.length} agents`
@@ -10551,7 +10785,10 @@ function declaresCategory(matrix2, category) {
10551
10785
  return category !== LOCAL_PSEUDO_CATEGORY && matrix2.categories[category] !== void 0;
10552
10786
  }
10553
10787
  function undeclaredCategoryRefusal(metadata, category) {
10554
- const metadataPath = path28.join(metadata.path, STANDARD_FILES.METADATA_YAML);
10788
+ if (metadata.localPath === void 0) {
10789
+ throw new Error(`Local skill '${metadata.id}' was discovered without its directory`);
10790
+ }
10791
+ const metadataPath = path28.join(metadata.localPath, STANDARD_FILES.METADATA_YAML);
10555
10792
  return `Skipping local skill '${metadata.id}': ${METADATA_KEYS.CATEGORY} '${category}' is not one this installation declares, so the skill belongs in no grid tab and can be given to no sub-agent. Set ${METADATA_KEYS.CATEGORY} in ${metadataPath} to a category that already exists \u2014 a skill is placed in the taxonomy, it does not extend it.`;
10556
10793
  }
10557
10794
  function mergeLocalSkillsIntoMatrix(matrix2, localResult) {
@@ -10628,6 +10865,7 @@ export {
10628
10865
  rootsHoldingTheRetiredSourceFolder,
10629
10866
  chooseProviderForThisRun,
10630
10867
  forgetTheProviderChosenForThisRun,
10868
+ declaresNoContent,
10631
10869
  providerInUse,
10632
10870
  inheritedGlobalProvider,
10633
10871
  providerAtScope,
@@ -10663,9 +10901,10 @@ export {
10663
10901
  skillAssignedToNoAgent,
10664
10902
  scopeBlockedStackAssignment,
10665
10903
  globalScopedAgentsHint,
10666
- marketplacesRefreshed,
10667
10904
  marketplaceRefreshFailed,
10668
10905
  marketplacesRefreshFailed,
10906
+ codexPluginLeftSwitchedOn,
10907
+ pluginsLeftSwitchedOn,
10669
10908
  marketplaceOwnerHasNoName,
10670
10909
  marketplaceHasNoVersion,
10671
10910
  marketplaceNameNotPublishable,
@@ -10690,7 +10929,10 @@ export {
10690
10929
  skippedSkills,
10691
10930
  skippedUnknownAgents,
10692
10931
  carriedSkillsWritten,
10932
+ copiedSkillsRemovedAfterRefusedSave,
10933
+ carriedSkillsRemovedAfterRefusedSave,
10693
10934
  SHARED_CONFIG_APPLY,
10935
+ idNamesAnotherMarketplace,
10694
10936
  registeredProjectsRecompiled,
10695
10937
  sharedConfigNeedsTerminal,
10696
10938
  nothingInstalledToApplyTo,
@@ -10748,7 +10990,9 @@ export {
10748
10990
  formatZodIssues,
10749
10991
  validateSkillMetadata,
10750
10992
  splitMetadataValidationIssues,
10993
+ ConfigSchemaError,
10751
10994
  ConfigDefaultExportError,
10995
+ ConfigEvaluationError,
10752
10996
  NO_VALID_DEFAULT_EXPORT,
10753
10997
  configLoadFailure,
10754
10998
  loadConfig,
@@ -10767,12 +11011,13 @@ export {
10767
11011
  MarketplaceRefusedError,
10768
11012
  isLocalSource,
10769
11013
  normalizeStackRecord,
11014
+ StacksLoadError,
10770
11015
  loadStackById,
10771
11016
  defaultUsageGuidance,
10772
11017
  resolveAgentConfigToSkills,
10773
11018
  getStackSkillIds,
10774
11019
  ConfigLoadError,
10775
- declaresNoContent,
11020
+ configDeclaresNothing,
10776
11021
  loadProjectConfigFromDir,
10777
11022
  loadProjectConfigAt,
10778
11023
  loadInstalledConfig,
@@ -10851,6 +11096,8 @@ export {
10851
11096
  execCommand,
10852
11097
  getInstalledPluginsRegistryPath,
10853
11098
  listRegisteredPluginInstalls,
11099
+ PluginsLeftSwitchedOnError,
11100
+ FailedLeavingPluginsSwitchedOnError,
10854
11101
  refuseUnofferedPlacements,
10855
11102
  hostFor,
10856
11103
  hostAt,
@@ -10863,6 +11110,7 @@ export {
10863
11110
  findUnusableSavedSkillMetadata,
10864
11111
  deleteLocalSkill,
10865
11112
  migrateLocalSkillScope,
11113
+ NoSkillRulesError,
10866
11114
  claimSlug,
10867
11115
  matrixHealthIssues,
10868
11116
  generateMarketplace,
@@ -10898,4 +11146,4 @@ export {
10898
11146
  executeMigration,
10899
11147
  keepsGlobalInstall
10900
11148
  };
10901
- //# sourceMappingURL=chunk-YM3VEDAP.js.map
11149
+ //# sourceMappingURL=chunk-ENET4IF6.js.map