agents-inc 0.171.0 → 0.173.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 (93) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/{chunk-W4KAY26P.js → chunk-2RTBSLSR.js} +12 -3
  3. package/dist/{chunk-W4KAY26P.js.map → chunk-2RTBSLSR.js.map} +1 -1
  4. package/dist/{chunk-WM2N2D6Q.js → chunk-3N4FSTVX.js} +12 -6
  5. package/dist/{chunk-WM2N2D6Q.js.map → chunk-3N4FSTVX.js.map} +1 -1
  6. package/dist/{chunk-ZC6GC66J.js → chunk-3R3LHA4Q.js} +2 -2
  7. package/dist/{chunk-24OD7IP4.js → chunk-7OG635DK.js} +26 -4
  8. package/dist/chunk-7OG635DK.js.map +1 -0
  9. package/dist/{chunk-WDH6HHA3.js → chunk-A3ZAXL5T.js} +4 -5
  10. package/dist/{chunk-WDH6HHA3.js.map → chunk-A3ZAXL5T.js.map} +1 -1
  11. package/dist/{chunk-KMOCMVK5.js → chunk-AARQBW5L.js} +106 -60
  12. package/dist/chunk-AARQBW5L.js.map +1 -0
  13. package/dist/{chunk-WII6GOPM.js → chunk-BRZA7ZCX.js} +52 -31
  14. package/dist/chunk-BRZA7ZCX.js.map +1 -0
  15. package/dist/{chunk-AWWCRL5A.js → chunk-EHJIQDXR.js} +8 -6
  16. package/dist/{chunk-AWWCRL5A.js.map → chunk-EHJIQDXR.js.map} +1 -1
  17. package/dist/{chunk-RDXY4GRO.js → chunk-F437ICEG.js} +2 -2
  18. package/dist/chunk-G7NUK3BF.js +253 -0
  19. package/dist/chunk-G7NUK3BF.js.map +1 -0
  20. package/dist/{chunk-7IVLVEHX.js → chunk-ITQF35UQ.js} +3 -3
  21. package/dist/{chunk-QIR56IDM.js → chunk-LJQXQTFC.js} +29 -102
  22. package/dist/chunk-LJQXQTFC.js.map +1 -0
  23. package/dist/{chunk-A6OWEJGC.js → chunk-MHAQWMNJ.js} +35 -5
  24. package/dist/{chunk-A6OWEJGC.js.map → chunk-MHAQWMNJ.js.map} +1 -1
  25. package/dist/{chunk-YLUOGAW7.js → chunk-N33AKFLR.js} +2 -2
  26. package/dist/{chunk-TNFYBKVD.js → chunk-Q4UNHVPX.js} +25 -14
  27. package/dist/chunk-Q4UNHVPX.js.map +1 -0
  28. package/dist/chunk-Q6WRR4BN.js +195 -0
  29. package/dist/chunk-Q6WRR4BN.js.map +1 -0
  30. package/dist/{chunk-JGPE4RRE.js → chunk-SPQSNZMM.js} +2 -2
  31. package/dist/{chunk-EOKNEGRD.js → chunk-VR6QTK5Z.js} +8 -8
  32. package/dist/{chunk-S2SKVJT2.js → chunk-VSPC74SS.js} +48 -26
  33. package/dist/chunk-VSPC74SS.js.map +1 -0
  34. package/dist/{chunk-3YYHL76L.js → chunk-WVFBBZEP.js} +648 -394
  35. package/dist/chunk-WVFBBZEP.js.map +1 -0
  36. package/dist/commands/build/marketplace.js +16 -14
  37. package/dist/commands/build/marketplace.js.map +1 -1
  38. package/dist/commands/build/plugins.js +6 -6
  39. package/dist/commands/compile.js +12 -12
  40. package/dist/commands/doctor.js +87 -86
  41. package/dist/commands/doctor.js.map +1 -1
  42. package/dist/commands/edit.js +150 -152
  43. package/dist/commands/edit.js.map +1 -1
  44. package/dist/commands/eject.js +25 -30
  45. package/dist/commands/eject.js.map +1 -1
  46. package/dist/commands/init.js +19 -16
  47. package/dist/commands/list.js +14 -17
  48. package/dist/commands/list.js.map +1 -1
  49. package/dist/commands/new/marketplace.js +4 -4
  50. package/dist/commands/search.js +15 -13
  51. package/dist/commands/search.js.map +1 -1
  52. package/dist/commands/share.js +11 -11
  53. package/dist/commands/uninstall.js +145 -130
  54. package/dist/commands/uninstall.js.map +1 -1
  55. package/dist/commands/update.js +6 -6
  56. package/dist/components/common/scroll-affordance.js +2 -2
  57. package/dist/components/common/select-list.js +2 -2
  58. package/dist/components/common/skill-agent-summary.js +3 -4
  59. package/dist/components/common/summary-panel.js +11 -146
  60. package/dist/components/common/summary-panel.js.map +1 -1
  61. package/dist/hooks/init.js +39 -27
  62. package/dist/hooks/init.js.map +1 -1
  63. package/dist/load-agent-defs-E67DSYTN.js +14 -0
  64. package/dist/{recompile-project-agents-IQANWGOP.js → recompile-project-agents-6G6M2RS6.js} +8 -8
  65. package/dist/{source-loader-YGG7DY2W.js → source-loader-W7XCNKNA.js} +4 -4
  66. package/dist/src/agents/tester/web-tester/playbook.md +1 -1
  67. package/package.json +1 -1
  68. package/src/agents/tester/web-tester/playbook.md +1 -1
  69. package/dist/chunk-24OD7IP4.js.map +0 -1
  70. package/dist/chunk-3YYHL76L.js.map +0 -1
  71. package/dist/chunk-4RQKCV6I.js +0 -103
  72. package/dist/chunk-4RQKCV6I.js.map +0 -1
  73. package/dist/chunk-5FM77WKW.js +0 -69
  74. package/dist/chunk-5FM77WKW.js.map +0 -1
  75. package/dist/chunk-KMOCMVK5.js.map +0 -1
  76. package/dist/chunk-QIR56IDM.js.map +0 -1
  77. package/dist/chunk-R572RLTI.js +0 -108
  78. package/dist/chunk-R572RLTI.js.map +0 -1
  79. package/dist/chunk-S2SKVJT2.js.map +0 -1
  80. package/dist/chunk-TNFYBKVD.js.map +0 -1
  81. package/dist/chunk-WII6GOPM.js.map +0 -1
  82. package/dist/components/common/removal-plan-confirm.js +0 -10
  83. package/dist/load-agent-defs-UBWITIEI.js +0 -14
  84. package/dist/source-loader-YGG7DY2W.js.map +0 -1
  85. /package/dist/{chunk-ZC6GC66J.js.map → chunk-3R3LHA4Q.js.map} +0 -0
  86. /package/dist/{chunk-RDXY4GRO.js.map → chunk-F437ICEG.js.map} +0 -0
  87. /package/dist/{chunk-7IVLVEHX.js.map → chunk-ITQF35UQ.js.map} +0 -0
  88. /package/dist/{chunk-YLUOGAW7.js.map → chunk-N33AKFLR.js.map} +0 -0
  89. /package/dist/{chunk-JGPE4RRE.js.map → chunk-SPQSNZMM.js.map} +0 -0
  90. /package/dist/{chunk-EOKNEGRD.js.map → chunk-VR6QTK5Z.js.map} +0 -0
  91. /package/dist/{components/common/removal-plan-confirm.js.map → load-agent-defs-E67DSYTN.js.map} +0 -0
  92. /package/dist/{recompile-project-agents-IQANWGOP.js.map → recompile-project-agents-6G6M2RS6.js.map} +0 -0
  93. /package/dist/{load-agent-defs-UBWITIEI.js.map → source-loader-W7XCNKNA.js.map} +0 -0
@@ -2,12 +2,18 @@
2
2
  import {
3
3
  BUILT_IN_MATRIX,
4
4
  getSkillById,
5
+ getSkillDisplayName,
6
+ inertLine,
5
7
  initializeMatrix,
6
8
  matrix,
7
9
  plural,
8
10
  stripTerminalControls,
9
- toTitleCase
10
- } from "./chunk-24OD7IP4.js";
11
+ toTitleCase,
12
+ typedEntries,
13
+ typedFromEntries,
14
+ typedKeys,
15
+ typedValues
16
+ } from "./chunk-7OG635DK.js";
11
17
  import {
12
18
  AGENT_NAMES,
13
19
  AUTHOR_HANDLE_PATTERN,
@@ -76,7 +82,7 @@ import {
76
82
  seedAgentScope,
77
83
  sourceDirName,
78
84
  validateSelection
79
- } from "./chunk-A6OWEJGC.js";
85
+ } from "./chunk-MHAQWMNJ.js";
80
86
  import {
81
87
  defaultCategories,
82
88
  defaultRules,
@@ -86,7 +92,7 @@ import {
86
92
  // src/cli/lib/loading/source-loader.ts
87
93
  import os14 from "os";
88
94
  import { partition as partition4, unique as unique6 } from "remeda";
89
- import path28 from "path";
95
+ import path29 from "path";
90
96
 
91
97
  // src/cli/lib/installation/is-home-directory.ts
92
98
  import fs from "fs";
@@ -485,13 +491,15 @@ function sourceRootOf(root) {
485
491
  }
486
492
  var RETIRED_SOURCE_FOLDER = ".claude-src";
487
493
  function retiredSourceFolderIn(roots) {
488
- if (!roots.some(holdsTheRetiredSourceFolder)) return null;
489
- return sourceDirName("claude");
494
+ return [...new Set(roots)].filter(holdsTheRetiredSourceFolder);
490
495
  }
491
496
  function holdsTheRetiredSourceFolder(root) {
492
497
  return directoryExists(path.join(root, RETIRED_SOURCE_FOLDER));
493
498
  }
494
499
  var PROVIDER_OF_A_NEW_INSTALLATION = "claude";
500
+ function isTheProviderOfANewInstallation(provider) {
501
+ return provider === PROVIDER_OF_A_NEW_INSTALLATION;
502
+ }
495
503
  var providerChosenForThisRun;
496
504
  function chooseProviderForThisRun(provider) {
497
505
  providerChosenForThisRun = provider;
@@ -736,8 +744,8 @@ async function fileExists(filePath) {
736
744
  }
737
745
  async function directoryExists2(dirPath) {
738
746
  try {
739
- const stat = await fs3.stat(dirPath);
740
- return stat.isDirectory();
747
+ const stat2 = await fs3.stat(dirPath);
748
+ return stat2.isDirectory();
741
749
  } catch {
742
750
  return false;
743
751
  }
@@ -862,6 +870,9 @@ function warnOnce(msg) {
862
870
  sayingEachWarningOnce(() => warn(msg));
863
871
  }
864
872
 
873
+ // src/cli/utils/messages.ts
874
+ import path3 from "path";
875
+
865
876
  // src/cli/lib/validate-kebab-name.ts
866
877
  import { unique } from "remeda";
867
878
  var KEBAB_CASE_CHARACTER = /[a-z0-9-]/;
@@ -942,6 +953,8 @@ var INFO_MESSAGES = {
942
953
  CONFIG_TYPES_REFRESHED: `Refreshed ${STANDARD_FILES.CONFIG_TYPES_TS}`,
943
954
  EJECTED_SKILLS_USER_OWNED: `Ejected skills are yours to own \u2014 '${CLI_INVOKE_COMMAND} update' does not change them.`,
944
955
  NO_PLUGIN_MARKETPLACES: "No plugin marketplaces are configured \u2014 nothing to refresh.",
956
+ /** Under doctor's marketplace row, when its download replaced a saved copy whose files differed. */
957
+ SAVED_COPY_REPLACED: "The saved copy differed from the marketplace and has been replaced",
945
958
  /** Printed under the destination already named, so it names files only. */
946
959
  AGENT_PARTIALS_CUSTOMIZABLE: `Each sub-agent directory there holds ${AGENT_PARTIAL_FILES.join(", ")} \u2014 edit those to customize that sub-agent.`
947
960
  };
@@ -974,9 +987,9 @@ function completedWithFailures(failures) {
974
987
  function failureLines({ what, recovery }) {
975
988
  return [` ${what}`, ` ${recovery}`];
976
989
  }
977
- function propagatedRecompileSummary(rewritten, unchanged, failed) {
990
+ function propagatedRecompileSummary(recompiled, failed) {
978
991
  const failureSuffix = failed > 0 ? ` (${failed} failed)` : "";
979
- return `Registered projects: ${rewritten} rewritten, ${unchanged} unchanged${failureSuffix}`;
992
+ return `Registered projects: ${recompiled} recompiled${failureSuffix}`;
980
993
  }
981
994
  function skillAssignedToNoAgent(skillId) {
982
995
  return `Skill '${skillId}' is assigned to no sub-agent \u2014 nothing will load it.`;
@@ -1133,22 +1146,25 @@ function sharedSkillsLeftBehind(skillIds) {
1133
1146
  function sharedConfigExistingInstall(configPath, id, provider) {
1134
1147
  return `An installation already exists at ${configPath}. Run '${CLI_INVOKE_COMMAND} uninstall${providerArgument(provider)}' first \u2014 ${SHARED_CONFIG_GREENFIELD_HINT}. ${applySharedConfigHere(id, true, provider)}`;
1135
1148
  }
1136
- function sharedConfigProjectScopeAtHome(skillIds, agentNames) {
1149
+ function sharedConfigProjectScopeAtHome(skillIds, agentNames, installedHere, provider) {
1137
1150
  return [
1138
1151
  "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:",
1139
1152
  ...skillIds.map((id) => ` skill ${id} (scope: project)`),
1140
1153
  ...agentNames.map((name) => ` sub-agent ${name} (scope: project)`),
1141
- "Run this from inside a project directory, or re-share it with each entry above at global scope."
1154
+ installedHere ? changeTheGlobalInstallFromHome(providerArgument(provider)) : "Run this from inside a project directory, or re-share it with each entry above at global scope."
1142
1155
  ].join("\n");
1143
1156
  }
1157
+ function changeTheGlobalInstallFromHome(named) {
1158
+ return `Run this from inside a project directory, or change the global install from here: run '${CLI_INVOKE_COMMAND} edit${named}', make the change in the editor, then apply the id it gives you with '${CLI_INVOKE_COMMAND} edit --from <id>${named}'.`;
1159
+ }
1144
1160
  function unknownKeys(keys) {
1145
- return keys.map(stripTerminalControls).join(", ");
1161
+ return keys.map(inertLine).join(", ");
1146
1162
  }
1147
- function skippedUnknownSkills(skillIds, skippedWith) {
1163
+ function skippedUnknownSkills(skillIds, installedWithout) {
1148
1164
  return [
1149
1165
  `Skipped ${skillIds.length} skill(s) this catalog does not know: ${unknownKeys(skillIds)}`,
1150
- ...skippedWith.map(
1151
- ({ skillId, agentNames }) => ` sub-agent(s) skipped with ${unknownKeys([skillId])}, carrying nothing else this catalog knows: ${agentNames.join(", ")}`
1166
+ ...installedWithout.map(
1167
+ ({ skillId, agentNames }) => ` sub-agent(s) installed without ${unknownKeys([skillId])}, carrying nothing else this catalog knows: ${agentNames.join(", ")}`
1152
1168
  )
1153
1169
  ].join("\n");
1154
1170
  }
@@ -1159,30 +1175,18 @@ function carriedSkillsWritten(skillIds) {
1159
1175
  return `Wrote ${skillIds.length} skill(s) this configuration carries: ${skillIds.join(", ")}`;
1160
1176
  }
1161
1177
  var SHARED_CONFIG_APPLY = {
1162
- PREVIEW_HEADING: "Applying this configuration will remove:",
1163
- SKILLS_HEADING: "Skills:",
1164
- AGENTS_HEADING: "Sub-agents:",
1165
- NOTHING_REMOVED: "Nothing is removed \u2014 this configuration only adds and re-tunes.",
1166
- OVERWRITTEN_HEADING: "Will be overwritten:",
1167
1178
  CONFIRM: "Apply this configuration?"
1168
1179
  };
1169
- function projectCopyRemoved(subject) {
1170
- return `${subject}: this project's copy is removed; the global one takes over`;
1171
- }
1172
- function projectCopyMovedToGlobal(subject) {
1173
- return `${subject}: moves to the global install, so this project's copy is removed`;
1174
- }
1175
- function registeredProjectsRewritten(projectPaths) {
1180
+ function registeredProjectsRecompiled(projectPaths) {
1176
1181
  return [
1177
- "Each project registered with this global install is rewritten to match:",
1182
+ "Each project registered with this global install is recompiled to match:",
1178
1183
  ...projectPaths.map((projectPath) => ` ${projectPath}`)
1179
1184
  ].join("\n");
1180
1185
  }
1181
- function sharedConfigNeedsTerminal(id, provider) {
1182
- const named = providerArgument(provider);
1186
+ function sharedConfigNeedsTerminal(id, editProvider, initProvider) {
1183
1187
  return [
1184
1188
  `Applying configuration '${id}' removes whatever it leaves out, so it has to be confirmed \u2014 and there is no terminal here to confirm it at.`,
1185
- `Run '${CLI_INVOKE_COMMAND} edit --from ${id}${named}' from a terminal, or '${CLI_INVOKE_COMMAND} init --from ${id}${named}' in a clean directory, which installs without removing anything.`
1189
+ `Run '${CLI_INVOKE_COMMAND} edit --from ${id}${providerArgument(editProvider)}' from a terminal, or '${CLI_INVOKE_COMMAND} init --from ${id}${providerArgument(initProvider)}' in a clean directory, which installs without removing anything.`
1186
1190
  ].join("\n");
1187
1191
  }
1188
1192
  function nothingInstalledToApplyTo(id, provider) {
@@ -1199,21 +1203,38 @@ function addMarketplaceInTheEditor(ref, isFolder) {
1199
1203
  return isFolder ? `${ref} is a folder on this machine, which the editor cannot read \u2014 push it to GitHub, then add it from the editor's Marketplace button.` : `Add ${ref} from the editor's Marketplace button.`;
1200
1204
  }
1201
1205
  var SHARED_CONFIG_ARRIVALS = {
1202
- PROJECT_HEADING: "Into this project:",
1203
- GLOBAL_HEADING: "Into the global install:",
1204
- SKIPPED_HEADING: "Skipped, already in the global install:",
1205
1206
  INSTALL_CONFIRM: "Install this configuration?"
1206
1207
  };
1207
- function keptAsInstalledGlobally(skillIds, agentNames, idAppliedAtHome) {
1208
+ function keptAsInstalledGlobally(skillIds, agentNames, idAppliedAtHome, provider) {
1208
1209
  return [
1209
1210
  "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:",
1210
1211
  ...skillIds.map((id) => ` skill ${id}`),
1211
1212
  ...agentNames.map((name) => ` sub-agent ${name}`),
1212
1213
  ...idAppliedAtHome === void 0 ? [] : [
1213
- `Change them with '${CLI_INVOKE_COMMAND} edit --from ${idAppliedAtHome}' from your home directory.`
1214
+ `Change them with '${CLI_INVOKE_COMMAND} edit --from ${idAppliedAtHome}${providerArgument(provider)}' from your home directory.`
1214
1215
  ]
1215
1216
  ].join("\n");
1216
1217
  }
1218
+ function skillsMoved(verb, skillIds) {
1219
+ return skillIds.length === 0 ? [] : [`${verb} ${listed(skillIds.map(getSkillDisplayName))}`];
1220
+ }
1221
+ function settingMoved({ setting, from, to }) {
1222
+ return `${setting} ${from ?? "default"} \u2192 ${to ?? "default"}`;
1223
+ }
1224
+ function listed(names) {
1225
+ if (names.length <= 1) return names.join("");
1226
+ return `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
1227
+ }
1228
+ var SKILLS_REASSIGNED = "skills reassigned";
1229
+ function subAgentChanged({ name, scope, gains, loses, settings }) {
1230
+ const details = [
1231
+ ...skillsMoved("gains", gains),
1232
+ ...skillsMoved("loses", loses),
1233
+ ...settings.map(settingMoved)
1234
+ ];
1235
+ const where = scope === "global" ? " (global)" : "";
1236
+ return `~ ${name}${where}: ${details.length > 0 ? details.join(", ") : SKILLS_REASSIGNED}`;
1237
+ }
1217
1238
  function addedToGlobalSubAgents(joined) {
1218
1239
  return [
1219
1240
  "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:",
@@ -1260,17 +1281,18 @@ function uninstallSkillMetadataUnreadable(metadataPath) {
1260
1281
  }
1261
1282
  function recreateConfigFrom({
1262
1283
  scopeRoot,
1263
- providerToName
1284
+ providerToName,
1285
+ initProviderToName
1264
1286
  }) {
1265
- const named = providerArgument(providerToName);
1266
- return `'${CLI_INVOKE_COMMAND} uninstall${named}' from '${scopeRoot}', which still works on a config it cannot read, then '${CLI_INVOKE_COMMAND} init --from <id>${named}' with an id from ${EDITOR_URL}`;
1287
+ return `'${CLI_INVOKE_COMMAND} uninstall${providerArgument(providerToName)}' from '${scopeRoot}', which still works on a config it cannot read, then '${CLI_INVOKE_COMMAND} init --from <id>${providerArgument(initProviderToName)}' with an id from ${EDITOR_URL}`;
1267
1288
  }
1268
1289
  function providerArgument(provider) {
1269
- return provider === void 0 ? "" : ` --provider ${provider}`;
1290
+ if (provider === void 0) return "";
1291
+ return ` --provider ${typeof provider === "string" ? provider : `<${provider.join("|")}>`}`;
1270
1292
  }
1271
- function configUnreadableError(configLoadFailure2) {
1293
+ function configUnreadableError(configLoadFailure3) {
1272
1294
  return [
1273
- configLoadFailure2,
1295
+ configLoadFailure3,
1274
1296
  "This is the marketplace's own config rather than an installation's, so it is fixed in the marketplace repository it comes from."
1275
1297
  ].join("\n");
1276
1298
  }
@@ -1281,17 +1303,8 @@ function unmarkedAgentsKept(agentsDir2, count) {
1281
1303
  const object = count === 1 ? "it" : "them";
1282
1304
  return `Kept ${plural(count, "agent")} in ${agentsDir2}/ \u2014 no ${DEFAULT_PLUGIN_NAME} marker, so this CLI did not compile ${object}.`;
1283
1305
  }
1284
- var UNINSTALL_PLAN = {
1285
- PREVIEW_HEADING: "The following will be removed:",
1286
- PLUGINS_HEADING: "Plugins:",
1287
- CLI_MANAGED_FILES_HEADING: "CLI-managed files:",
1288
- CONFIG_HEADING: "Config:"
1289
- };
1290
- function localSkillsRemoval(skillsDir2) {
1291
- return `${skillsDir2}/ (matching the marketplace)`;
1292
- }
1293
- function compiledAgentsRemoval(agentsDir2) {
1294
- return `${agentsDir2}/ (CLI-compiled)`;
1306
+ function configFileRemoved(filePath) {
1307
+ return `- ${filePath}`;
1295
1308
  }
1296
1309
  function scopeNoun(kind) {
1297
1310
  return kind === "project" ? "this project" : "the global installation";
@@ -1305,8 +1318,8 @@ function sentenceCase(text) {
1305
1318
  function layoutIsCurrent(label, to) {
1306
1319
  return `${label} is on ${to}/`;
1307
1320
  }
1308
- function retiredSourceFolderUnsupported(replacement) {
1309
- return `${RETIRED_SOURCE_FOLDER}/ is no longer supported (agents-inc now serves Claude and Codex). Move it to ${replacement}/.`;
1321
+ function retiredSourceFolderUnsupported(root) {
1322
+ return `${path3.join(root, RETIRED_SOURCE_FOLDER)}/ is no longer supported (agents-inc now serves Claude and Codex). Move it to ${path3.join(root, sourceDirName("claude"))}/.`;
1310
1323
  }
1311
1324
 
1312
1325
  // src/cli/types/generated/source-types.ts
@@ -1930,24 +1943,10 @@ function isSkillAssignment(value) {
1930
1943
  return typeof value.id === "string";
1931
1944
  }
1932
1945
 
1933
- // src/cli/utils/typed-object.ts
1934
- function typedEntries(obj) {
1935
- return Object.entries(obj);
1936
- }
1937
- function typedKeys(obj) {
1938
- return Object.keys(obj);
1939
- }
1940
- function typedFromEntries(entries) {
1941
- return Object.fromEntries(entries);
1942
- }
1943
- function typedValues(obj) {
1944
- return Object.values(obj).filter((value) => value !== void 0);
1945
- }
1946
-
1947
1946
  // src/cli/lib/configuration/config.ts
1948
1947
  import { BlockList, isIPv4 } from "net";
1949
1948
  import os4 from "os";
1950
- import path5 from "path";
1949
+ import path6 from "path";
1951
1950
  import { z as z3 } from "zod";
1952
1951
 
1953
1952
  // src/cli/lib/schemas.ts
@@ -1957,11 +1956,11 @@ import { partition } from "remeda";
1957
1956
  // src/cli/lib/schema-validator.ts
1958
1957
  import "zod";
1959
1958
  function formatZodIssue(issue) {
1960
- const path29 = stripTerminalControls(issue.path.join("."));
1959
+ const path30 = stripTerminalControls(issue.path.join("."));
1961
1960
  if (issue.code === "unrecognized_keys") {
1962
1961
  return `Unrecognized key: "${stripTerminalControls(issue.keys.join('", "'))}"`;
1963
1962
  }
1964
- return path29 ? `${path29}: ${stripTerminalControls(issue.message)}` : stripTerminalControls(issue.message);
1963
+ return path30 ? `${path30}: ${stripTerminalControls(issue.message)}` : stripTerminalControls(issue.message);
1965
1964
  }
1966
1965
  function formatZodErrors(error) {
1967
1966
  return error.issues.map(formatZodIssue);
@@ -2103,12 +2102,12 @@ var RENAMED_SKILL_ENTRY_FIELDS = { source: "origin" };
2103
2102
  function renamedFieldMessage(oldKey, newKey) {
2104
2103
  return `"${oldKey}" was renamed to "${newKey}". Rename the key in this config \u2014 the CLI does not read "${oldKey}" any more and will not fall back to it.`;
2105
2104
  }
2106
- function reportRenamedKeys(record, renames, path29, ctx) {
2105
+ function reportRenamedKeys(record, renames, path30, ctx) {
2107
2106
  for (const [oldKey, newKey] of Object.entries(renames)) {
2108
2107
  if (!(oldKey in record)) continue;
2109
2108
  ctx.addIssue({
2110
2109
  code: "custom",
2111
- path: [...path29, oldKey],
2110
+ path: [...path30, oldKey],
2112
2111
  message: renamedFieldMessage(oldKey, newKey)
2113
2112
  });
2114
2113
  }
@@ -2426,9 +2425,16 @@ var stackConfigValidationSchema = z2.object({
2426
2425
  function formatZodIssues(issues) {
2427
2426
  return issues.map(formatZodIssue).join("; ");
2428
2427
  }
2428
+ var carriedMetadataValidationSchema = customMetadataValidationSchema.extend({
2429
+ displayName: z2.string().min(1),
2430
+ slug: z2.string().regex(KEBAB_CASE_PATTERN).min(1)
2431
+ });
2429
2432
  function validateSkillMetadata(rawMetadata) {
2430
- const schema = isCustomMetadata(rawMetadata) ? customMetadataValidationSchema : metadataValidationSchema;
2431
- return schema.safeParse(rawMetadata);
2433
+ return metadataSchemaFor(rawMetadata).safeParse(rawMetadata);
2434
+ }
2435
+ function metadataSchemaFor(raw) {
2436
+ if (!isCustomMetadata(raw)) return metadataValidationSchema;
2437
+ return isCarriedMetadata(raw) ? carriedMetadataValidationSchema : customMetadataValidationSchema;
2432
2438
  }
2433
2439
  function describeMetadataSchemaFailure(issues, rawMetadata) {
2434
2440
  const [absent, malformed] = partition(issues, (issue) => isAbsentField(issue, rawMetadata));
@@ -2479,6 +2485,9 @@ function validateNestingDepth(value, maxDepth) {
2479
2485
  function isCustomMetadata(raw) {
2480
2486
  return isRecord(raw) && raw.custom === true;
2481
2487
  }
2488
+ function isCarriedMetadata(raw) {
2489
+ return isRecord(raw) && isRecord(raw.forkedFrom) && typeof raw.forkedFrom.path === "string";
2490
+ }
2482
2491
  function warnUnknownFields(parsed, expectedKeys, context) {
2483
2492
  const expectedSet = new Set(expectedKeys);
2484
2493
  const unknownKeys2 = Object.keys(parsed).filter((k) => !expectedSet.has(k));
@@ -2488,11 +2497,11 @@ function warnUnknownFields(parsed, expectedKeys, context) {
2488
2497
  }
2489
2498
 
2490
2499
  // src/cli/lib/configuration/config-loader.ts
2491
- import path3 from "path";
2500
+ import path4 from "path";
2492
2501
  import { fileURLToPath } from "url";
2493
2502
  import { createJiti } from "jiti";
2494
- var CONFIG_EXPORTS_PATH = path3.resolve(
2495
- path3.dirname(fileURLToPath(import.meta.url)),
2503
+ var CONFIG_EXPORTS_PATH = path4.resolve(
2504
+ path4.dirname(fileURLToPath(import.meta.url)),
2496
2505
  "../../config-exports.ts"
2497
2506
  );
2498
2507
  var ConfigSchemaError = class extends Error {
@@ -2515,13 +2524,16 @@ var ConfigDefaultExportError = class extends Error {
2515
2524
  };
2516
2525
  var ES_MODULE_MARKER = "__esModule";
2517
2526
  function unwrapDefaultExport(namespace) {
2518
- if (isRecord(namespace) && namespace.default !== void 0) return namespace.default;
2527
+ if (isRecord(namespace) && "default" in namespace) return namespace.default;
2519
2528
  return namespace;
2520
2529
  }
2521
2530
  function declaresNoDefaultExport(namespace) {
2522
2531
  return isRecord(namespace) && ES_MODULE_MARKER in namespace && !("default" in namespace);
2523
2532
  }
2524
2533
  var NO_VALID_DEFAULT_EXPORT = "the file has no valid default export";
2534
+ function configLoadFailure(configPath, reason) {
2535
+ return `Failed to load config from '${configPath}': ${reason}`;
2536
+ }
2525
2537
  function exportsNothing(value) {
2526
2538
  return value == null || typeof value === "object" && Object.keys(value).length === 0;
2527
2539
  }
@@ -2539,14 +2551,12 @@ async function loadConfig(configPath, schema) {
2539
2551
  if (declaresNoDefaultExport(namespace)) {
2540
2552
  throw new ConfigDefaultExportError(configPath);
2541
2553
  }
2542
- if (schema) {
2543
- const result = schema.safeParse(raw);
2544
- if (!result.success) {
2545
- throw new ConfigSchemaError(configPath, formatZodIssues(result.error.issues));
2546
- }
2547
- return result.data;
2554
+ if (!schema) return raw;
2555
+ const result = schema.safeParse(raw);
2556
+ if (!result.success) {
2557
+ throw new ConfigSchemaError(configPath, formatZodIssues(result.error.issues));
2548
2558
  }
2549
- return raw;
2559
+ return result.data;
2550
2560
  }
2551
2561
  async function importConfigModule(configPath) {
2552
2562
  try {
@@ -2557,16 +2567,14 @@ async function importConfigModule(configPath) {
2557
2567
  });
2558
2568
  return await jiti.import(configPath);
2559
2569
  } catch (error) {
2560
- throw new Error(`Failed to load config from '${configPath}': ${getErrorMessage(error)}`, {
2561
- cause: error
2562
- });
2570
+ throw new Error(configLoadFailure(configPath, getErrorMessage(error)), { cause: error });
2563
2571
  }
2564
2572
  }
2565
2573
 
2566
2574
  // src/cli/lib/installation/install-base-dir.ts
2567
- import path4 from "path";
2575
+ import path5 from "path";
2568
2576
  function getProjectConfigPath(dir, provider) {
2569
- return path4.join(sourceFolderInUse(dir, provider).dir, STANDARD_FILES.CONFIG_TS);
2577
+ return path5.join(sourceFolderInUse(dir, provider).dir, STANDARD_FILES.CONFIG_TS);
2570
2578
  }
2571
2579
  function getInstalledConfigPath(dir) {
2572
2580
  return getProjectConfigPath(dir, providerInUse(dir));
@@ -2589,6 +2597,9 @@ function providerToNameAt(root, provider) {
2589
2597
  );
2590
2598
  return besideAnother ? provider : void 0;
2591
2599
  }
2600
+ function initProviderToNameAt(root, provider) {
2601
+ return providerToNameAt(root, provider) ?? (isTheProviderOfANewInstallation(provider) ? void 0 : provider);
2602
+ }
2592
2603
 
2593
2604
  // src/cli/lib/configuration/config.ts
2594
2605
  var SOURCE_ENV_VAR = "CC_MARKETPLACE";
@@ -2613,8 +2624,11 @@ async function loadSourceConfig(dir, scope, provider) {
2613
2624
  }
2614
2625
  function refuseInstallationConfig(configPath, scope, scopeRoot, provider) {
2615
2626
  const providerToName = providerToNameAt(scopeRoot, provider);
2627
+ const initProviderToName = initProviderToNameAt(scopeRoot, provider);
2616
2628
  return (reason, cause) => new Error(
2617
- installationConfigsUnreadable([{ configPath, reason, scope, scopeRoot, providerToName }]),
2629
+ installationConfigsUnreadable([
2630
+ { configPath, reason, scope, scopeRoot, providerToName, initProviderToName }
2631
+ ]),
2618
2632
  { cause }
2619
2633
  );
2620
2634
  }
@@ -2638,7 +2652,7 @@ function describesItsOwnFault(error) {
2638
2652
  }
2639
2653
  var unreadableSourceRepoConfig = (reason, cause) => new Error(configUnreadableError(reason), { cause });
2640
2654
  async function readSourceRepoConfig(basePath) {
2641
- const configPath = path5.join(basePath, SOURCE_ROOT_DIR, STANDARD_FILES.CONFIG_TS);
2655
+ const configPath = path6.join(basePath, SOURCE_ROOT_DIR, STANDARD_FILES.CONFIG_TS);
2642
2656
  if (!await fileExists(configPath)) {
2643
2657
  verbose(`Source config not found at ${configPath}`);
2644
2658
  return { kind: "absent" };
@@ -2902,6 +2916,7 @@ function validateSourceFormat(source, carrier) {
2902
2916
  function validateRemoteSource(source, protocol, carrier) {
2903
2917
  const isHttp = protocol === "https://" || protocol === "http://";
2904
2918
  if (isHttp) refuseCredentials(source, protocol, carrier);
2919
+ refuseLineBreaksAndControls(source, carrier, REMOTE_EXAMPLES);
2905
2920
  const pathAfterProtocol = source.slice(protocol.length).trim();
2906
2921
  if (pathAfterProtocol.length < MIN_REMOTE_PATH_LENGTH) {
2907
2922
  throw refusal(
@@ -3041,16 +3056,18 @@ function validateGitShorthand(source, repoPath, carrier) {
3041
3056
  );
3042
3057
  }
3043
3058
  }
3059
+ function refuseLineBreaksAndControls(source, carrier, examples) {
3060
+ const inert = inertLine(source);
3061
+ if (inert === source) return;
3062
+ throw refusal(
3063
+ carrier,
3064
+ `contains invalid characters: "${inert}"`,
3065
+ ["Marketplace values must not contain line breaks or control characters."],
3066
+ examples
3067
+ );
3068
+ }
3044
3069
  function validateLocalPath(source, carrier) {
3045
- const CONTROL_CHAR_PATTERN2 = /[\x00-\x08\x0E-\x1F\x7F]/u;
3046
- if (CONTROL_CHAR_PATTERN2.test(source)) {
3047
- throw refusal(
3048
- carrier,
3049
- `contains invalid characters: "${source}"`,
3050
- ["Marketplace paths must not contain control characters."],
3051
- LOCAL_EXAMPLES
3052
- );
3053
- }
3070
+ refuseLineBreaksAndControls(source, carrier, LOCAL_EXAMPLES);
3054
3071
  if (UNC_PATH_PATTERN.test(source)) {
3055
3072
  throw refusal(
3056
3073
  carrier,
@@ -3068,7 +3085,7 @@ function isDefaultSource(source) {
3068
3085
  }
3069
3086
  var packageIdentitySchema = z3.object({ name: z3.string() });
3070
3087
  async function isPublicCatalogueCheckout(basePath) {
3071
- const raw = await readFileOptional(path5.join(basePath, STANDARD_FILES.PACKAGE_JSON));
3088
+ const raw = await readFileOptional(path6.join(basePath, STANDARD_FILES.PACKAGE_JSON));
3072
3089
  return declaredPackageName(raw) === PUBLIC_CATALOGUE_PACKAGE;
3073
3090
  }
3074
3091
  function declaredPackageName(raw) {
@@ -3099,7 +3116,10 @@ function isLocalSource(source) {
3099
3116
  }
3100
3117
 
3101
3118
  // src/cli/lib/compile-seat.ts
3102
- seatDiagnostics({ warn, verbose });
3119
+ seatDiagnostics({
3120
+ warn: (message) => warn(inertLine(message)),
3121
+ verbose: (message) => verbose(inertLine(message))
3122
+ });
3103
3123
 
3104
3124
  // ../compile/src/seed-to-config.ts
3105
3125
  import { groupBy, indexBy, isDefined, mapValues, omit as omit2, partition as partition2 } from "remeda";
@@ -3157,7 +3177,16 @@ function brokenUnionLayout(members, bodyIndent) {
3157
3177
  }
3158
3178
  function renderUnionBody(members, layout, bodyIndent) {
3159
3179
  const padding = spaces(bodyIndent);
3160
- return layout === "indented" ? `${padding}${flatUnion(members)}` : members.map((member) => `${padding}| ${member}`).join("\n");
3180
+ switch (layout) {
3181
+ case "indented":
3182
+ return `${padding}${flatUnion(members)}`;
3183
+ case "stacked":
3184
+ return members.map((member) => `${padding}| ${member}`).join("\n");
3185
+ default: {
3186
+ const _exhaustive = layout;
3187
+ return _exhaustive;
3188
+ }
3189
+ }
3161
3190
  }
3162
3191
  function flatUnion(members) {
3163
3192
  return members.join(" | ");
@@ -3205,9 +3234,18 @@ function renderArray(entries, position) {
3205
3234
  return ["[", ...lines, `${spaces(position.indent)}]`].join("\n");
3206
3235
  }
3207
3236
  function renderEntryLine(entry, indent) {
3208
- if (entry.kind === "comment") return `${spaces(indent)}// ${entry.text}`;
3209
- if (entry.kind === "source") return `${spaces(indent)}${entry.source},`;
3210
- return renderValueLine(entry.value, { indent, prefix: "", suffix: "," });
3237
+ switch (entry.kind) {
3238
+ case "comment":
3239
+ return `${spaces(indent)}// ${entry.text}`;
3240
+ case "source":
3241
+ return `${spaces(indent)}${entry.source},`;
3242
+ case "value":
3243
+ return renderValueLine(entry.value, { indent, prefix: "", suffix: "," });
3244
+ default: {
3245
+ const _exhaustive = entry;
3246
+ return _exhaustive;
3247
+ }
3248
+ }
3211
3249
  }
3212
3250
  function renderRecord(record, position) {
3213
3251
  const properties = Object.entries(record);
@@ -3227,11 +3265,27 @@ function propertyPosition(key, indent) {
3227
3265
  function flatArray(entries) {
3228
3266
  const items = [];
3229
3267
  for (const entry of entries) {
3230
- if (entry.kind === "comment") return void 0;
3231
- items.push(entry.kind === "source" ? entry.source : flatValue(entry.value));
3268
+ const item = flatEntry(entry);
3269
+ if (item === void 0) return void 0;
3270
+ items.push(item);
3232
3271
  }
3233
3272
  return flatList(items);
3234
3273
  }
3274
+ function flatEntry(entry) {
3275
+ switch (entry.kind) {
3276
+ // A `//` line cannot fold onto one line.
3277
+ case "comment":
3278
+ return void 0;
3279
+ case "source":
3280
+ return entry.source;
3281
+ case "value":
3282
+ return flatValue(entry.value);
3283
+ default: {
3284
+ const _exhaustive = entry;
3285
+ return _exhaustive;
3286
+ }
3287
+ }
3288
+ }
3235
3289
  function flatValue(value) {
3236
3290
  if (Array.isArray(value)) return flatList(value.map(flatValue));
3237
3291
  if (isKeyedRecord(value)) return flatRecord(value);
@@ -3273,12 +3327,20 @@ var HAND_WRITTEN_FIELDS = [
3273
3327
  "categoriesFile",
3274
3328
  "rulesFile"
3275
3329
  ];
3276
- var EXTRACTED_FIELDS = /* @__PURE__ */ new Set([
3330
+ var GLOBAL_IDENTITY_FIELDS = [
3331
+ ...HAND_WRITTEN_FIELDS,
3332
+ "description"
3333
+ ];
3334
+ var EXTRACTED_FIELDS = [
3277
3335
  "skills",
3278
3336
  "agents",
3279
3337
  "stack",
3280
3338
  "selectedDomains"
3281
- ]);
3339
+ ];
3340
+ var extractedFields = new Set(EXTRACTED_FIELDS);
3341
+ function isExtractedField(key) {
3342
+ return extractedFields.has(key);
3343
+ }
3282
3344
  var CANONICAL_FIELD_ORDER = [
3283
3345
  "name",
3284
3346
  "description",
@@ -3479,11 +3541,21 @@ function generateStandaloneConfig(cleaned) {
3479
3541
  });
3480
3542
  }
3481
3543
  function standaloneExportField(key, value, emitted) {
3482
- if (!EXTRACTED_FIELDS.has(key)) return [renderScalarField(key, value)];
3483
- if (key === "skills") return [` skills${emitted.hasSkills ? "" : ": []"},`];
3484
- if (key === "agents") return [` agents${emitted.hasAgents ? "" : ": []"},`];
3485
- if (key === "stack") return emitted.hasStack ? [` stack,`] : [];
3486
- return [` selectedDomains${emitted.hasSelectedDomains ? "" : ": []"},`];
3544
+ if (!isExtractedField(key)) return [renderScalarField(key, value)];
3545
+ switch (key) {
3546
+ case "skills":
3547
+ return [` skills${emitted.hasSkills ? "" : ": []"},`];
3548
+ case "agents":
3549
+ return [` agents${emitted.hasAgents ? "" : ": []"},`];
3550
+ case "stack":
3551
+ return emitted.hasStack ? [` stack,`] : [];
3552
+ case "selectedDomains":
3553
+ return [` selectedDomains${emitted.hasSelectedDomains ? "" : ": []"},`];
3554
+ default: {
3555
+ const _exhaustive = key;
3556
+ return _exhaustive;
3557
+ }
3558
+ }
3487
3559
  }
3488
3560
  function assembleConfigSource({
3489
3561
  imports,
@@ -3514,7 +3586,7 @@ function buildTypeImports(flags) {
3514
3586
  ].filter((t) => Boolean(t));
3515
3587
  }
3516
3588
  function isCopiedScalarField(key) {
3517
- return !EXTRACTED_FIELDS.has(key) && key !== "name";
3589
+ return !isExtractedField(key) && key !== "name";
3518
3590
  }
3519
3591
  function resolveProjectName(cleaned) {
3520
3592
  return cleaned.name && cleaned.name !== GLOBAL_CONFIG_NAME ? cleaned.name : DEFAULT_PLUGIN_NAME;
@@ -3560,7 +3632,7 @@ function stackOfAgents(stack, agents) {
3560
3632
  function mergeInlinedScalarFields(cleaned, cleanedGlobal) {
3561
3633
  return Object.entries(
3562
3634
  canonicalizeFieldOrder({
3563
- ...omit(cleanedGlobal, HAND_WRITTEN_FIELDS),
3635
+ ...omit(cleanedGlobal, GLOBAL_IDENTITY_FIELDS),
3564
3636
  ...cleaned
3565
3637
  })
3566
3638
  ).filter(([key]) => isCopiedScalarField(key));
@@ -4008,9 +4080,9 @@ function seedToInstallSelection(payload, catalog) {
4008
4080
  const rows = installable.flatMap((skill) => assignmentRows(skill, agentMap));
4009
4081
  refuseUnwritableAssignments(rows);
4010
4082
  const placed = rows.filter((row) => row.kind === "placed");
4011
- const selectedAgents = selectedAgentsOf(placed, agentMap);
4083
+ const carried = new Set(agentsCarriedIn(placed, agentMap));
4084
+ const selectedAgents = withAgentsSkippedSkillsName(carried, skipped, agentMap);
4012
4085
  const skills = installable.map(toSeedSkillConfig);
4013
- const selected = new Set(selectedAgents);
4014
4086
  return {
4015
4087
  result: {
4016
4088
  skills,
@@ -4036,7 +4108,7 @@ function seedToInstallSelection(payload, catalog) {
4036
4108
  skippedSkillIds: skipped.map(({ skillId }) => skillId),
4037
4109
  skippedAgentNames: skippedAgentNamesOf(agentMap, rows),
4038
4110
  agentsOnlySkippedSkillsCarried: skipped.flatMap(
4039
- (skill) => agentsOnlyItCarried(skill, agentMap, selected)
4111
+ (skill) => agentsOnlyItCarried(skill, agentMap, carried)
4040
4112
  )
4041
4113
  };
4042
4114
  }
@@ -4046,11 +4118,14 @@ function refuseUnwritableAssignments(rows) {
4046
4118
  throw new Error(unwritableAssignmentsError(unwritable));
4047
4119
  }
4048
4120
  }
4049
- function selectedAgentsOf(placed, agentMap) {
4121
+ function agentsCarriedIn(placed, agentMap) {
4122
+ return [...placed.map((row) => row.agent), ...agentsSwitchedOn(agentMap)];
4123
+ }
4124
+ function withAgentsSkippedSkillsName(carried, skipped, agentMap) {
4050
4125
  return [
4051
4126
  .../* @__PURE__ */ new Set([
4052
- ...placed.map((row) => row.agent),
4053
- ...agentsSwitchedOn(agentMap)
4127
+ ...carried,
4128
+ ...skipped.flatMap((skill) => agentsNamedBy(skill, agentMap))
4054
4129
  ])
4055
4130
  ];
4056
4131
  }
@@ -4058,9 +4133,14 @@ function skippedAgentNamesOf(agentMap, rows) {
4058
4133
  const unknownAssignees = rows.filter((row) => row.kind === "unknownAgent").map((row) => row.agentName);
4059
4134
  return [.../* @__PURE__ */ new Set([...agentMap.unknown, ...unknownAssignees])];
4060
4135
  }
4061
- function agentsOnlyItCarried({ skillId, seedSkill }, agentMap, selected) {
4062
- const agentNames = Object.keys(seedSkill.assignments).filter(isAgentName2).filter((name) => !agentMap.switchedOff.has(name) && !selected.has(name));
4063
- return agentNames.length === 0 ? [] : [{ skillId, agentNames }];
4136
+ function agentsNamedBy({ seedSkill }, agentMap) {
4137
+ return Object.keys(seedSkill.assignments).filter(isAgentName2).filter((name) => !agentMap.switchedOff.has(name));
4138
+ }
4139
+ function agentsOnlyItCarried(skill, agentMap, carried) {
4140
+ const agentNames = agentsNamedBy(skill, agentMap).filter(
4141
+ (name) => !carried.has(name)
4142
+ );
4143
+ return agentNames.length === 0 ? [] : [{ skillId: skill.skillId, agentNames }];
4064
4144
  }
4065
4145
  function readAgentMap(agents) {
4066
4146
  const known = /* @__PURE__ */ new Map();
@@ -4178,10 +4258,10 @@ import { indexBy as indexBy2, omit as omit3, pick, uniqueBy as uniqueBy2 } from
4178
4258
 
4179
4259
  // src/cli/lib/configuration/project-config.ts
4180
4260
  import os5 from "os";
4181
- import path7 from "path";
4261
+ import path8 from "path";
4182
4262
 
4183
4263
  // src/cli/lib/stacks/stacks-loader.ts
4184
- import path6 from "path";
4264
+ import path7 from "path";
4185
4265
  import { flatMap, groupBy as groupBy2, mapValues as mapValues2, partition as partition3, pipe, unique as unique2, uniqueBy } from "remeda";
4186
4266
  var stacksCache = /* @__PURE__ */ new Map();
4187
4267
  function normalizeAgentConfig(agentConfig) {
@@ -4241,7 +4321,7 @@ async function loadStacks(configDir, stacksFile) {
4241
4321
  const cacheKey = `${configDir}:${resolvedStacksFile}`;
4242
4322
  const cached = stacksCache.get(cacheKey);
4243
4323
  if (cached) return cached;
4244
- const stacksPath = path6.join(configDir, resolvedStacksFile);
4324
+ const stacksPath = path7.join(configDir, resolvedStacksFile);
4245
4325
  try {
4246
4326
  const raw = await loadConfig(stacksPath, stacksConfigSchema);
4247
4327
  if (raw == null) {
@@ -4278,16 +4358,16 @@ async function loadStackById(stackId, configDir, source) {
4278
4358
  );
4279
4359
  return null;
4280
4360
  }
4281
- const defaultStack = defaultStacks.find((s) => s.id === stackId) ?? null;
4282
- if (defaultStack) {
4283
- verbose(`Found default stack: ${defaultStack.name} (${stackId})`);
4284
- } else {
4361
+ const defaultStack = defaultStacks.find((s) => s.id === stackId);
4362
+ if (!defaultStack) {
4285
4363
  verbose(`Stack '${stackId}' not found in the default marketplace or the built-ins`);
4364
+ return null;
4286
4365
  }
4366
+ verbose(`Found default stack: ${defaultStack.name} (${stackId})`);
4287
4367
  return defaultStack;
4288
4368
  }
4289
4369
  function stackNotOfferedMessage(stackId, source) {
4290
- return `Stack '${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.`;
4370
+ 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.`;
4291
4371
  }
4292
4372
  function defaultUsageGuidance(category) {
4293
4373
  return `Use when working with ${category}.`;
@@ -4327,6 +4407,7 @@ var ConfigLoadError = class extends Error {
4327
4407
  this.name = "ConfigLoadError";
4328
4408
  this.scope = isHomeDirectory(scopeRoot) ? "global" : "project";
4329
4409
  this.providerToName = providerToNameAt(scopeRoot, provider);
4410
+ this.initProviderToName = initProviderToNameAt(scopeRoot, provider);
4330
4411
  }
4331
4412
  configPath;
4332
4413
  reason;
@@ -4334,6 +4415,8 @@ var ConfigLoadError = class extends Error {
4334
4415
  scope;
4335
4416
  /** Named with `--provider` in a remedy handed out for this installation; beside another only. */
4336
4417
  providerToName;
4418
+ /** Named in the remedy's `init --from` half, which a bare run would answer with another one. */
4419
+ initProviderToName;
4337
4420
  };
4338
4421
  async function loadProjectConfigFromDir(projectDir, provider) {
4339
4422
  return loadProjectConfigAt(getProjectConfigPath(projectDir, provider), projectDir, provider);
@@ -4372,7 +4455,7 @@ function asProjectConfig(parsed, projectDir) {
4372
4455
  const normalizedStack = parsed.stack ? normalizeStackRecord(parsed.stack) : void 0;
4373
4456
  return {
4374
4457
  ...parsed,
4375
- name: parsed.name ?? path7.basename(projectDir),
4458
+ name: parsed.name ?? path8.basename(projectDir),
4376
4459
  skills: parsed.skills ?? [],
4377
4460
  agents: parsed.agents ?? [],
4378
4461
  ...normalizedStack && { stack: normalizedStack }
@@ -4395,10 +4478,10 @@ function configDirsInPlay(projectDir) {
4395
4478
  return isHomeDirectory(projectDir) ? [projectDir] : [projectDir, os5.homedir()];
4396
4479
  }
4397
4480
  async function findConfigLoadFailures(projectDir) {
4398
- const outcomes = await Promise.all(configDirsInPlay(projectDir).map(configLoadFailure));
4481
+ const outcomes = await Promise.all(configDirsInPlay(projectDir).map(configLoadFailure2));
4399
4482
  return outcomes.filter((failure) => failure !== void 0);
4400
4483
  }
4401
- async function configLoadFailure(dir) {
4484
+ async function configLoadFailure2(dir) {
4402
4485
  try {
4403
4486
  await loadInstalledConfig(dir);
4404
4487
  return void 0;
@@ -4411,10 +4494,8 @@ async function loadProjectConfig(projectDir) {
4411
4494
  const provider = providerInUse(projectDir);
4412
4495
  const projectResult = await loadProjectConfigFromDir(projectDir, provider);
4413
4496
  if (projectResult) return projectResult;
4414
- if (!isHomeDirectory(projectDir)) {
4415
- return loadProjectConfigFromDir(os5.homedir(), provider);
4416
- }
4417
- return null;
4497
+ if (isHomeDirectory(projectDir)) return null;
4498
+ return loadProjectConfigFromDir(os5.homedir(), provider);
4418
4499
  }
4419
4500
  function validateProjectConfig(config) {
4420
4501
  const result = projectConfigLoaderSchema.safeParse(config);
@@ -4499,7 +4580,6 @@ function mergeConfigs(newConfig, existingConfig, options) {
4499
4580
  }
4500
4581
  return merged;
4501
4582
  }
4502
- var GLOBAL_IDENTITY_FIELDS = [...HAND_WRITTEN_FIELDS, "description"];
4503
4583
  function existingConfigForMerge(loaded, projectDir, ownName) {
4504
4584
  const isProjectsOwnFile = loaded.configPath === getProjectConfigPath(projectDir, loaded.provider);
4505
4585
  if (isProjectsOwnFile) return loaded.config;
@@ -4521,13 +4601,13 @@ async function mergeWithExistingConfig(newConfig, context) {
4521
4601
  }
4522
4602
 
4523
4603
  // src/cli/lib/skills/skill-metadata.ts
4524
- import path9 from "path";
4604
+ import path10 from "path";
4525
4605
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
4526
4606
 
4527
4607
  // src/cli/lib/versioning.ts
4528
4608
  import { createHash } from "crypto";
4529
4609
  import { readFile as readFileBytes } from "fs/promises";
4530
- import path8 from "path";
4610
+ import path9 from "path";
4531
4611
  var ISO_DATE_LENGTH = 10;
4532
4612
  function getCurrentDate() {
4533
4613
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, ISO_DATE_LENGTH);
@@ -4544,18 +4624,18 @@ async function computeFileHash(filePath) {
4544
4624
  async function computeSkillFolderHash(skillPath) {
4545
4625
  const contents = [];
4546
4626
  for (const fileName of SKILL_CONTENT_FILES) {
4547
- const filePath = path8.join(skillPath, fileName);
4627
+ const filePath = path9.join(skillPath, fileName);
4548
4628
  if (await fileExists(filePath)) {
4549
4629
  const content = await readFile(filePath);
4550
4630
  contents.push(`${fileName}:${content}`);
4551
4631
  }
4552
4632
  }
4553
4633
  for (const dirName of SKILL_CONTENT_DIRS) {
4554
- const dirPath = path8.join(skillPath, dirName);
4634
+ const dirPath = path9.join(skillPath, dirName);
4555
4635
  if (await fileExists(dirPath)) {
4556
4636
  const files = await glob("**/*", dirPath);
4557
4637
  for (const file of files.sort()) {
4558
- const filePath = path8.join(dirPath, file);
4638
+ const filePath = path9.join(dirPath, file);
4559
4639
  const content = await readFile(filePath);
4560
4640
  contents.push(`${dirName}/${file}:${content}`);
4561
4641
  }
@@ -4576,21 +4656,6 @@ function bumpMajorVersion(version) {
4576
4656
  const major = parseMajorVersion(version);
4577
4657
  return `${major + 1}.0.0`;
4578
4658
  }
4579
- async function readExistingPluginManifest(pluginDir, getManifestPath) {
4580
- const manifestPath = getManifestPath(pluginDir);
4581
- if (!await fileExists(manifestPath)) {
4582
- return null;
4583
- }
4584
- try {
4585
- const version = await readManifestVersion(manifestPath);
4586
- const hashFilePath = contentHashFileBeside(manifestPath);
4587
- const contentHash = await fileExists(hashFilePath) ? (await readFile(hashFilePath)).trim() : void 0;
4588
- return { version, contentHash };
4589
- } catch (error) {
4590
- warn(`Failed to read plugin manifest at '${manifestPath}': ${getErrorMessage(error)}`);
4591
- return null;
4592
- }
4593
- }
4594
4659
  async function readManifestVersion(manifestPath) {
4595
4660
  const content = await readFileSafe(manifestPath, MAX_PLUGIN_FILE_SIZE);
4596
4661
  return pluginManifestSchema.parse(JSON.parse(content)).version ?? DEFAULT_VERSION;
@@ -4599,7 +4664,7 @@ async function computeBytesHash(filePath) {
4599
4664
  return createHash("sha256").update(await readFileBytes(filePath)).digest("hex");
4600
4665
  }
4601
4666
  async function describeShippedFile(pluginDir, file) {
4602
- return `${file}\0${await computeBytesHash(path8.join(pluginDir, file))}\0`;
4667
+ return `${file}\0${await computeBytesHash(path9.join(pluginDir, file))}\0`;
4603
4668
  }
4604
4669
  async function computeShippedHash(pluginDir) {
4605
4670
  const files = await glob("**/*", pluginDir, { dot: true });
@@ -4620,25 +4685,6 @@ function versionAfterRebuild(earlier, shippedHash) {
4620
4685
  if (!earlier) return DEFAULT_VERSION;
4621
4686
  return earlier.shippedHash === shippedHash ? earlier.version : bumpMajorVersion(earlier.version);
4622
4687
  }
4623
- async function determinePluginVersion(newHash, pluginDir, getManifestPath) {
4624
- const existing = await readExistingPluginManifest(pluginDir, getManifestPath);
4625
- if (!existing) {
4626
- return {
4627
- version: DEFAULT_VERSION,
4628
- contentHash: newHash
4629
- };
4630
- }
4631
- if (existing.contentHash !== newHash) {
4632
- return {
4633
- version: bumpMajorVersion(existing.version),
4634
- contentHash: newHash
4635
- };
4636
- }
4637
- return {
4638
- version: existing.version,
4639
- contentHash: newHash
4640
- };
4641
- }
4642
4688
  async function writeContentHash(pluginDir, contentHash, getManifestPath) {
4643
4689
  await writeFile(contentHashFileBeside(getManifestPath(pluginDir)), contentHash);
4644
4690
  }
@@ -4662,7 +4708,7 @@ async function readForkedFromMetadata(skillDir) {
4662
4708
  return metadata?.forkedFrom ?? null;
4663
4709
  }
4664
4710
  async function readLocalSkillMetadata(skillDir) {
4665
- const metadataPath = path9.join(skillDir, STANDARD_FILES.METADATA_YAML);
4711
+ const metadataPath = path10.join(skillDir, STANDARD_FILES.METADATA_YAML);
4666
4712
  if (!await fileExists(metadataPath)) {
4667
4713
  return null;
4668
4714
  }
@@ -4680,7 +4726,7 @@ async function readLocalSkillMetadata(skillDir) {
4680
4726
  return result.data;
4681
4727
  }
4682
4728
  async function injectForkedFromMetadata(destPath, skillId, contentHash, origin = {}) {
4683
- const metadataPath = path9.join(destPath, STANDARD_FILES.METADATA_YAML);
4729
+ const metadataPath = path10.join(destPath, STANDARD_FILES.METADATA_YAML);
4684
4730
  const rawContent = await readFile(metadataPath);
4685
4731
  const { yamlContent } = stripYamlSchemaComment(rawContent);
4686
4732
  const metadata = {
@@ -4718,7 +4764,7 @@ function existingMetadataFields(yamlContent, metadataPath) {
4718
4764
  }
4719
4765
 
4720
4766
  // src/cli/lib/skills/skill-copier.ts
4721
- import path10 from "path";
4767
+ import path11 from "path";
4722
4768
  var NULL_BYTE_PATTERN2 = /\0/;
4723
4769
  function validateSkillPath(resolvedPath, expectedParent, skillPath) {
4724
4770
  if (NULL_BYTE_PATTERN2.test(skillPath)) {
@@ -4726,26 +4772,26 @@ function validateSkillPath(resolvedPath, expectedParent, skillPath) {
4726
4772
  }
4727
4773
  if (!isPathWithin(resolvedPath, expectedParent)) {
4728
4774
  throw new Error(
4729
- `Invalid skill path: '${skillPath}' escapes expected directory '${path10.resolve(expectedParent)}'`
4775
+ `Invalid skill path: '${skillPath}' escapes expected directory '${path11.resolve(expectedParent)}'`
4730
4776
  );
4731
4777
  }
4732
4778
  }
4733
4779
  function resolveSkillPath(basePath, skillPath) {
4734
- const resolved = path10.join(basePath, skillPath);
4780
+ const resolved = path11.join(basePath, skillPath);
4735
4781
  validateSkillPath(resolved, basePath, skillPath);
4736
4782
  return resolved;
4737
4783
  }
4738
4784
  function getSkillSourcePath(skill, rootDir) {
4739
- const srcDir = path10.join(rootDir, SOURCE_SRC_DIR);
4785
+ const srcDir = path11.join(rootDir, SOURCE_SRC_DIR);
4740
4786
  return resolveSkillPath(srcDir, skill.path);
4741
4787
  }
4742
4788
  async function generateSkillHash(skillSourcePath) {
4743
- const skillMdPath = path10.join(skillSourcePath, STANDARD_FILES.SKILL_MD);
4789
+ const skillMdPath = path11.join(skillSourcePath, STANDARD_FILES.SKILL_MD);
4744
4790
  return computeFileHash(skillMdPath);
4745
4791
  }
4746
4792
  async function copySkillTo(skill, sourcePath, destPath, source) {
4747
4793
  const contentHash = await generateSkillHash(sourcePath);
4748
- await ensureDir(path10.dirname(destPath));
4794
+ await ensureDir(path11.dirname(destPath));
4749
4795
  await copy(sourcePath, destPath);
4750
4796
  await injectForkedFromMetadata(destPath, skill.id, contentHash, {
4751
4797
  ...source !== void 0 && { source }
@@ -4812,7 +4858,7 @@ async function copySelectedSkill(skillId, localSkillsDir, sourceResult, selected
4812
4858
  return copySkillToLocalFlattened(skill, localSkillsDir, sourceResult);
4813
4859
  }
4814
4860
  const destPath = getFlattenedSkillDestPath(skill, localSkillsDir);
4815
- if (path10.resolve(localPath) === path10.resolve(destPath)) {
4861
+ if (path11.resolve(localPath) === path11.resolve(destPath)) {
4816
4862
  return resolveLocalCopiedSkill(skill, localPath);
4817
4863
  }
4818
4864
  return copyLocalSkillTo(skill, localPath, destPath);
@@ -4825,33 +4871,33 @@ function localPathOf(skill) {
4825
4871
  }
4826
4872
  async function copyLocalSkillTo(skill, localPath, destPath) {
4827
4873
  const contentHash = await generateSkillHash(localPath);
4828
- await ensureDir(path10.dirname(destPath));
4874
+ await ensureDir(path11.dirname(destPath));
4829
4875
  await copy(localPath, destPath);
4830
4876
  return { skillId: skill.id, sourcePath: localPath, destPath, contentHash };
4831
4877
  }
4832
4878
 
4833
4879
  // src/cli/lib/skills/skill-plugin-compiler.ts
4834
- import path23 from "path";
4880
+ import path24 from "path";
4835
4881
  import { parse as parseYaml4 } from "yaml";
4836
4882
 
4837
4883
  // src/cli/lib/plugins/plugin-manifest.ts
4838
- import path12 from "path";
4884
+ import path13 from "path";
4839
4885
 
4840
4886
  // src/cli/lib/plugins/plugin-finder.ts
4841
4887
  import os6 from "os";
4842
- import path11 from "path";
4888
+ import path12 from "path";
4843
4889
  function getUserPluginsDir() {
4844
- return path11.join(os6.homedir(), CLAUDE_DIR, PLUGINS_SUBDIR);
4890
+ return path12.join(os6.homedir(), CLAUDE_DIR, PLUGINS_SUBDIR);
4845
4891
  }
4846
4892
  function getProjectPluginsDir(projectDir) {
4847
4893
  const dir = projectDir ?? process.cwd();
4848
- return path11.join(dir, CLAUDE_DIR, PLUGINS_SUBDIR);
4894
+ return path12.join(dir, CLAUDE_DIR, PLUGINS_SUBDIR);
4849
4895
  }
4850
4896
  function getPluginAgentsDir(pluginDir) {
4851
- return path11.join(pluginDir, STANDARD_DIRS.AGENTS);
4897
+ return path12.join(pluginDir, STANDARD_DIRS.AGENTS);
4852
4898
  }
4853
4899
  function getPluginManifestPath(pluginDir) {
4854
- return path11.join(pluginDir, PLUGIN_MANIFEST_DIR, PLUGIN_MANIFEST_FILE);
4900
+ return path12.join(pluginDir, PLUGIN_MANIFEST_DIR, PLUGIN_MANIFEST_FILE);
4855
4901
  }
4856
4902
  async function readPluginManifest(pluginDir) {
4857
4903
  const manifestPath = getPluginManifestPath(pluginDir);
@@ -4906,7 +4952,7 @@ function generateAgentPluginManifest(options) {
4906
4952
  }
4907
4953
  async function writePluginManifest(outputDir, manifest) {
4908
4954
  const manifestPath = getPluginManifestPath(outputDir);
4909
- await ensureDir(path12.dirname(manifestPath));
4955
+ await ensureDir(path13.dirname(manifestPath));
4910
4956
  const content = JSON.stringify(manifest, null, 2);
4911
4957
  await writeFile(manifestPath, content);
4912
4958
  return manifestPath;
@@ -4921,12 +4967,24 @@ function parseMarketplacePluginRef(ref) {
4921
4967
  return separator === -1 ? ref : ref.slice(0, separator);
4922
4968
  }
4923
4969
 
4970
+ // src/cli/lib/plugins/earlier-build.ts
4971
+ async function isEarlierBuild(pluginDir) {
4972
+ return fileExists(getPluginManifestPath(pluginDir));
4973
+ }
4974
+ async function holdsContentNoBuildMade(pluginDir) {
4975
+ if (await isEarlierBuild(pluginDir)) return false;
4976
+ return !await isDirectoryEmpty(pluginDir);
4977
+ }
4978
+ async function clearEarlierBuild(pluginDir) {
4979
+ if (await isEarlierBuild(pluginDir)) await remove(pluginDir);
4980
+ }
4981
+
4924
4982
  // src/cli/lib/plugins/plugin-info.ts
4925
4983
  import { readdir } from "fs/promises";
4926
4984
 
4927
4985
  // src/cli/lib/installation/installation.ts
4928
4986
  import os7 from "os";
4929
- import path13 from "path";
4987
+ import path14 from "path";
4930
4988
  var INSTALL_MODE_LABELS = {
4931
4989
  plugin: "Plugin",
4932
4990
  mixed: "Mixed",
@@ -4965,7 +5023,7 @@ async function detectInstallationInDir(dir, scope) {
4965
5023
  configPath,
4966
5024
  agentsDir: agentsDir(provider, scope, dir),
4967
5025
  // Mixed mode uses the local skills dir, as eject does.
4968
- skillsDir: path13.join(
5026
+ skillsDir: path14.join(
4969
5027
  dir,
4970
5028
  CLAUDE_DIR,
4971
5029
  mode === "plugin" ? PLUGINS_SUBDIR : STANDARD_DIRS.SKILLS
@@ -4995,12 +5053,12 @@ async function detectInstallation(projectDir = process.cwd()) {
4995
5053
 
4996
5054
  // src/cli/lib/installation/local-installer.ts
4997
5055
  import os10 from "os";
4998
- import path17 from "path";
5056
+ import path18 from "path";
4999
5057
  import { unique as unique4 } from "remeda";
5000
5058
 
5001
5059
  // src/cli/lib/loading/loader.ts
5002
5060
  import { parse as parseYaml3 } from "yaml";
5003
- import path14 from "path";
5061
+ import path15 from "path";
5004
5062
 
5005
5063
  // src/cli/utils/frontmatter.ts
5006
5064
  import { parse as parseYaml2 } from "yaml";
@@ -5073,7 +5131,7 @@ async function loadAgentsFromDir(agentsDir2, sourceRoot, options) {
5073
5131
  return agents;
5074
5132
  }
5075
5133
  async function readAgentDefinition(agentsDir2, file, sourceRoot, options) {
5076
- const fullPath = path14.join(agentsDir2, file);
5134
+ const fullPath = path15.join(agentsDir2, file);
5077
5135
  try {
5078
5136
  const content = await readFile(fullPath);
5079
5137
  const parsed = agentYamlConfigSchema.safeParse(parseYaml3(content));
@@ -5106,7 +5164,7 @@ function toAgentDefinition(config, file, sourceRoot, options) {
5106
5164
  ...config.hooks !== void 0 && { hooks: config.hooks },
5107
5165
  ...config.experimental !== void 0 && { experimental: config.experimental },
5108
5166
  ...config.outputFormat !== void 0 && { outputFormat: config.outputFormat },
5109
- path: path14.dirname(file),
5167
+ path: path15.dirname(file),
5110
5168
  sourceRoot,
5111
5169
  ...options.agentBaseDir ? { agentBaseDir: options.agentBaseDir } : {},
5112
5170
  ...config.domain ? { domain: config.domain } : {},
@@ -5114,7 +5172,7 @@ function toAgentDefinition(config, file, sourceRoot, options) {
5114
5172
  };
5115
5173
  }
5116
5174
  async function loadAllAgents(projectRoot) {
5117
- return loadAgentsFromDir(path14.join(projectRoot, DIRS.agents), projectRoot, {
5175
+ return loadAgentsFromDir(path15.join(projectRoot, DIRS.agents), projectRoot, {
5118
5176
  includeCustomFlag: true,
5119
5177
  verboseLabel: "agent"
5120
5178
  });
@@ -5128,7 +5186,7 @@ async function loadMergedAgents(sourcePath) {
5128
5186
  }
5129
5187
  async function loadProjectAgents(projectRoot) {
5130
5188
  const sourceFolder = sourceFolderInUse(projectRoot, providerInUse(projectRoot));
5131
- const projectAgentsDir = path14.join(sourceFolder.dir, STANDARD_DIRS.AGENTS);
5189
+ const projectAgentsDir = path15.join(sourceFolder.dir, STANDARD_DIRS.AGENTS);
5132
5190
  if (!await directoryExists2(projectAgentsDir)) {
5133
5191
  verbose(`No project agents directory at ${projectAgentsDir}`);
5134
5192
  return {};
@@ -5160,19 +5218,19 @@ async function loadSkillsFromDir(skillsDir2, options = {}) {
5160
5218
  return { skills, unusableMetadata };
5161
5219
  }
5162
5220
  function locateSkill(skillsDir2, skillFile, pathPrefix) {
5163
- const skillMdPath = path14.join(skillsDir2, skillFile);
5164
- const skillDir = path14.dirname(skillMdPath);
5165
- const relativePath = path14.relative(skillsDir2, skillDir);
5221
+ const skillMdPath = path15.join(skillsDir2, skillFile);
5222
+ const skillDir = path15.dirname(skillMdPath);
5223
+ const relativePath = path15.relative(skillsDir2, skillDir);
5166
5224
  return {
5167
5225
  skillFile,
5168
5226
  skillMdPath,
5169
5227
  skillDir,
5170
- skillDirName: path14.basename(skillDir),
5228
+ skillDirName: path15.basename(skillDir),
5171
5229
  displayPath: pathPrefix ? `${pathPrefix}/${relativePath}/` : `${relativePath}/`
5172
5230
  };
5173
5231
  }
5174
5232
  async function judgeSkillMetadata(skill) {
5175
- const metadataPath = path14.join(skill.skillDir, STANDARD_FILES.METADATA_YAML);
5233
+ const metadataPath = path15.join(skill.skillDir, STANDARD_FILES.METADATA_YAML);
5176
5234
  if (!await fileExists(metadataPath)) {
5177
5235
  warn(
5178
5236
  `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.`
@@ -5214,7 +5272,7 @@ async function readSkillDefinition(skill) {
5214
5272
  }
5215
5273
  }
5216
5274
  async function loadPluginSkills(pluginDir) {
5217
- const { skills } = await loadSkillsFromDir(path14.join(pluginDir, "skills"), {
5275
+ const { skills } = await loadSkillsFromDir(path15.join(pluginDir, "skills"), {
5218
5276
  pathPrefix: "skills",
5219
5277
  requireMetadata: false
5220
5278
  });
@@ -5222,13 +5280,18 @@ async function loadPluginSkills(pluginDir) {
5222
5280
  }
5223
5281
 
5224
5282
  // src/cli/lib/loading/source-fetcher.ts
5283
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
5225
5284
  import { createHash as createHash2 } from "crypto";
5285
+ import { readFileSync } from "fs";
5286
+ import { rename, stat } from "fs/promises";
5226
5287
  import { downloadTemplate } from "giget";
5227
5288
  import os8 from "os";
5228
- import path16 from "path";
5289
+ import path17 from "path";
5290
+ import { isDeepEqual } from "remeda";
5291
+ import { z as z4 } from "zod";
5229
5292
 
5230
5293
  // src/cli/lib/marketplace-generator.ts
5231
- import path15 from "path";
5294
+ import path16 from "path";
5232
5295
  import { countBy, sortBy } from "remeda";
5233
5296
  var PLUGIN_MANIFEST_PATH = `${PLUGIN_MANIFEST_DIR}/${PLUGIN_MANIFEST_FILE}`;
5234
5297
  var MARKETPLACE_SCHEMA_URL = "https://anthropic.com/claude-code/marketplace.schema.json";
@@ -5260,7 +5323,7 @@ async function generateMarketplace(pluginsDir2, options) {
5260
5323
  const rootWithoutDotSlash = options.pluginRoot.replace(/^\.\//, "");
5261
5324
  for (const manifestFile of manifestFiles) {
5262
5325
  const pluginDirName = pluginDirNameOf(manifestFile);
5263
- const pluginDir = path15.join(pluginsDir2, pluginDirName);
5326
+ const pluginDir = path16.join(pluginsDir2, pluginDirName);
5264
5327
  const manifest = await readPluginManifest(pluginDir);
5265
5328
  if (!manifest) {
5266
5329
  warn(`Could not read plugin manifest: '${manifestFile}'`);
@@ -5315,17 +5378,17 @@ function reservedNameError(marketplaceName, wayOut) {
5315
5378
  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}.`;
5316
5379
  }
5317
5380
  function namespaceViolationError(foreignIds, marketplaceName) {
5318
- const listed = foreignIds.slice(0, MAX_REPORTED_NAMESPACE_VIOLATIONS).map((id) => ` ${id} -> ${marketplaceName}-${id}`);
5319
- const unlisted = foreignIds.length - listed.length;
5381
+ const listed2 = foreignIds.slice(0, MAX_REPORTED_NAMESPACE_VIOLATIONS).map((id) => ` ${id} -> ${marketplaceName}-${id}`);
5382
+ const unlisted = foreignIds.length - listed2.length;
5320
5383
  return [
5321
5384
  `Marketplace '${marketplaceName}' ships ${foreignIds.length} skill id(s) outside its own namespace. Every skill id must begin with the marketplace's name:`,
5322
- ...listed,
5385
+ ...listed2,
5323
5386
  ...unlisted > 0 ? [` ... and ${unlisted} more`] : [],
5324
5387
  `Rename each skill directory and the 'name' in its SKILL.md, re-run 'build plugins', then build the marketplace again.`
5325
5388
  ].join("\n");
5326
5389
  }
5327
5390
  async function writeMarketplace(outputPath, marketplace) {
5328
- await ensureDir(path15.dirname(outputPath));
5391
+ await ensureDir(path16.dirname(outputPath));
5329
5392
  const content = `${JSON.stringify(marketplace, null, 2)}
5330
5393
  `;
5331
5394
  await writeFile(outputPath, content);
@@ -5343,6 +5406,30 @@ var MAX_NAME_LENGTH = 200;
5343
5406
  var QUOTED_NAME_LENGTH = 50;
5344
5407
  var SOURCE_PROTO_RE = /^([\w-.]+):/;
5345
5408
  var GIT_URI_RE = /^(?<repo>[\w.-]+\/[\w.-]+)(?<subdir>[^#]+)?(?<ref>#[\w./@-]+)?/;
5409
+ var MarketplaceUnreachableError = class extends Error {
5410
+ constructor(source, reason) {
5411
+ super(`Could not reach ${source}: ${reason}`);
5412
+ this.source = source;
5413
+ this.reason = reason;
5414
+ this.name = "MarketplaceUnreachableError";
5415
+ }
5416
+ source;
5417
+ reason;
5418
+ };
5419
+ var TarballWithheldError = class extends Error {
5420
+ constructor({ status, statusText }) {
5421
+ super(`the marketplace answered ${status} ${statusText}`.trimEnd());
5422
+ this.name = "TarballWithheldError";
5423
+ }
5424
+ };
5425
+ var marketplaceMustAnswer = new AsyncLocalStorage3();
5426
+ function askingEachMarketplaceItself(body) {
5427
+ const differingCopiesReplaced = /* @__PURE__ */ new Set();
5428
+ return marketplaceMustAnswer.run(differingCopiesReplaced, () => body(differingCopiesReplaced));
5429
+ }
5430
+ function mustAnswer() {
5431
+ return marketplaceMustAnswer.getStore() !== void 0;
5432
+ }
5346
5433
  function sanitizeSourceForCache(source) {
5347
5434
  const hash = createHash2("sha256").update(source).digest("hex").slice(0, CACHE_HASH_LENGTH);
5348
5435
  const readable = source.replace(/[^a-zA-Z0-9]/g, "-").replace(/--+/g, "-").replace(/^-|-$/g, "").slice(0, CACHE_READABLE_PREFIX_LENGTH);
@@ -5350,7 +5437,7 @@ function sanitizeSourceForCache(source) {
5350
5437
  }
5351
5438
  function getCacheDir(source) {
5352
5439
  const sanitized = sanitizeSourceForCache(source) || "unknown";
5353
- return path16.join(cacheRoot(), "sources", sanitized);
5440
+ return path17.join(cacheRoot(), "sources", sanitized);
5354
5441
  }
5355
5442
  async function fetchFromSource(source, options = {}) {
5356
5443
  const { subdir } = options;
@@ -5361,8 +5448,8 @@ async function fetchFromSource(source, options = {}) {
5361
5448
  return fetchFromRemoteSource(source, subdir);
5362
5449
  }
5363
5450
  async function fetchFromLocalSource(source, subdir) {
5364
- const fullPath = subdir ? path16.join(source, subdir) : source;
5365
- const absolutePath = path16.isAbsolute(fullPath) ? fullPath : path16.resolve(process.cwd(), fullPath);
5451
+ const fullPath = subdir ? path17.join(source, subdir) : source;
5452
+ const absolutePath = path17.isAbsolute(fullPath) ? fullPath : path17.resolve(process.cwd(), fullPath);
5366
5453
  if (!await directoryExists2(absolutePath)) {
5367
5454
  throw new Error(
5368
5455
  `Local marketplace not found: '${absolutePath}'
@@ -5393,8 +5480,10 @@ function getGigetCacheDir(source) {
5393
5480
  return void 0;
5394
5481
  }
5395
5482
  const templateName = uriMatch.groups.repo.replace("/", "-").replace(/[^\da-z-]/gi, "-");
5396
- const gigetCacheRoot = process.env.XDG_CACHE_HOME ? path16.resolve(process.env.XDG_CACHE_HOME, "giget") : path16.resolve(os8.homedir(), ".cache", "giget");
5397
- return path16.join(gigetCacheRoot, providerName, templateName);
5483
+ return path17.join(gigetCacheRoot(), providerName, templateName);
5484
+ }
5485
+ function gigetCacheRoot() {
5486
+ return process.env.XDG_CACHE_HOME ? path17.resolve(process.env.XDG_CACHE_HOME, "giget") : path17.resolve(os8.homedir(), ".cache", "giget");
5398
5487
  }
5399
5488
  async function clearGigetCache(source) {
5400
5489
  const gigetDir = getGigetCacheDir(source);
@@ -5421,25 +5510,26 @@ async function fetchFromRemoteSource(source, subdir) {
5421
5510
  };
5422
5511
  }
5423
5512
  announceRefetch(verdict);
5424
- await discardCachedCopy(cacheDir, source);
5513
+ if (!mustAnswer()) await discardCachedCopy(cacheDir, source);
5425
5514
  }
5426
- return downloadFreshCopy(cacheDir, source, fullSource);
5515
+ const differingCopiesReplaced = marketplaceMustAnswer.getStore();
5516
+ return differingCopiesReplaced ? downloadAnsweredFor(cacheDir, source, fullSource, differingCopiesReplaced) : downloadFreshCopy(cacheDir, source, fullSource);
5427
5517
  }
5428
5518
  async function discardCachedCopy(cacheDir, source) {
5429
5519
  await clearGigetCache(source);
5430
5520
  await remove(cacheDir);
5431
5521
  }
5432
5522
  async function downloadFreshCopy(cacheDir, source, fullSource) {
5433
- await ensureDir(path16.dirname(cacheDir));
5523
+ await ensureDir(path17.dirname(cacheDir));
5434
5524
  try {
5435
- const result = await downloadTemplate(fullSource, {
5525
+ const { result, savedTarball } = await gigetDownload(source, fullSource, {
5436
5526
  dir: cacheDir,
5437
5527
  force: true,
5438
5528
  // Always force when downloading to avoid "already exists" error
5439
5529
  offline: false
5440
5530
  });
5441
5531
  verbose(`Downloaded to: ${result.dir}`);
5442
- await recordFetchedCopy(cacheDir, result);
5532
+ await recordFetchedCopy(cacheDir, result, savedTarball);
5443
5533
  markCopyCurrentForThisRun(cacheDir);
5444
5534
  return {
5445
5535
  path: result.dir,
@@ -5451,51 +5541,184 @@ async function downloadFreshCopy(cacheDir, source, fullSource) {
5451
5541
  throw createDetailedFetchError(error, source);
5452
5542
  }
5453
5543
  }
5544
+ var STAGED_DOWNLOAD_SUFFIX = ".download";
5545
+ var ONLY_A_SAVED_COPY_CAME_BACK = "the download failed and only a saved copy came back";
5546
+ var SET_ASIDE_SUFFIX = ".set-aside";
5547
+ async function downloadAnsweredFor(cacheDir, source, fullSource, differingCopiesReplaced) {
5548
+ const staged = `${cacheDir}${STAGED_DOWNLOAD_SUFFIX}`;
5549
+ const downloadStaged = () => gigetDownload(source, fullSource, {
5550
+ dir: staged,
5551
+ force: true,
5552
+ forceClean: true,
5553
+ offline: false
5554
+ });
5555
+ await ensureDir(path17.dirname(cacheDir));
5556
+ try {
5557
+ const { result, savedTarball } = await downloadStaged();
5558
+ const record = await askAfterTheDownload(result, source);
5559
+ if (savedTarball) await downloadPast(savedTarball, record, downloadStaged, source);
5560
+ const differed = await savedCopyDiffers(cacheDir, staged);
5561
+ await replaceCopy(cacheDir, staged, record);
5562
+ if (differed) differingCopiesReplaced.add(source);
5563
+ markCopyCurrentForThisRun(cacheDir);
5564
+ return { path: cacheDir, fromCache: false, unreachable: false, source: fullSource };
5565
+ } catch (error) {
5566
+ if (error instanceof MarketplaceUnreachableError) throw error;
5567
+ throw createDetailedFetchError(error, source);
5568
+ } finally {
5569
+ await remove(staged);
5570
+ }
5571
+ }
5572
+ async function downloadPast(savedTarball, record, download, source) {
5573
+ if (!savedUnder(savedTarball, record)) {
5574
+ throw new MarketplaceUnreachableError(source, ONLY_A_SAVED_COPY_CAME_BACK);
5575
+ }
5576
+ await withTarballSetAside(savedTarball.path, download);
5577
+ }
5578
+ async function withTarballSetAside(tarball, download) {
5579
+ const record = gigetRecordOf(tarball);
5580
+ await rename(record, setAsideOf(record));
5581
+ await rename(tarball, setAsideOf(tarball));
5582
+ try {
5583
+ await download();
5584
+ } catch (error) {
5585
+ await rename(setAsideOf(tarball), tarball);
5586
+ await rename(setAsideOf(record), record);
5587
+ throw error;
5588
+ }
5589
+ await Promise.all([remove(setAsideOf(tarball)), remove(setAsideOf(record))]);
5590
+ }
5591
+ function setAsideOf(file) {
5592
+ return `${file}${SET_ASIDE_SUFFIX}`;
5593
+ }
5594
+ async function savedCopyDiffers(cacheDir, downloaded) {
5595
+ if (!await directoryExists2(cacheDir)) return false;
5596
+ try {
5597
+ return !await holdSameFiles(cacheDir, downloaded);
5598
+ } catch (error) {
5599
+ verbose(`Could not read the saved copy at ${cacheDir}: ${getErrorMessage(error)}`);
5600
+ return true;
5601
+ }
5602
+ }
5603
+ async function holdSameFiles(left, right) {
5604
+ const [leftFiles, rightFiles] = await Promise.all([everyFileIn(left), everyFileIn(right)]);
5605
+ return isDeepEqual(leftFiles, rightFiles) && leftFiles.every((file) => sameBytesAt(file, left, right));
5606
+ }
5607
+ var EVERY_FILE = "**/*";
5608
+ function everyFileIn(dir) {
5609
+ return glob(EVERY_FILE, dir, { dot: true });
5610
+ }
5611
+ function sameBytesAt(file, left, right) {
5612
+ return readFileSync(path17.join(left, file)).equals(readFileSync(path17.join(right, file)));
5613
+ }
5614
+ async function replaceCopy(cacheDir, staged, record) {
5615
+ await remove(cacheDir);
5616
+ await rename(staged, cacheDir);
5617
+ await writeFile(fetchRecordPath(cacheDir), JSON.stringify(record));
5618
+ }
5619
+ var GIGET_TARBALLS = "**/*.tar.gz";
5620
+ var gigetTarballRecordSchema = z4.object({ etag: z4.string().min(1).optional() });
5621
+ async function gigetDownload(source, fullSource, options) {
5622
+ const providerDir = gigetProviderDir(source);
5623
+ const timesBefore = await tarballTimes(providerDir);
5624
+ const result = await downloadTemplate(fullSource, options);
5625
+ const tarball = path17.join(providerDir, gigetTarballOf(result));
5626
+ const writtenByThisDownload = timesBefore.get(tarball) !== await modifiedAt(tarball);
5627
+ if (writtenByThisDownload) return { result };
5628
+ verbose(`giget extracted the tarball it saved earlier, not a download: ${tarball}`);
5629
+ return { result, savedTarball: { path: tarball, etag: await etagSavedWith(tarball) } };
5630
+ }
5631
+ function gigetProviderDir(source) {
5632
+ const provider = SOURCE_PROTO_RE.exec(source)?.[1];
5633
+ if (provider === void 0) throw new Error(`No provider named in '${source}'`);
5634
+ return path17.join(gigetCacheRoot(), provider);
5635
+ }
5636
+ async function tarballTimes(providerDir) {
5637
+ const tarballs = await glob(GIGET_TARBALLS, providerDir);
5638
+ const paths = tarballs.map((tarball) => path17.join(providerDir, tarball));
5639
+ return new Map(await Promise.all(paths.map(async (at) => [at, await modifiedAt(at)])));
5640
+ }
5641
+ async function modifiedAt(file) {
5642
+ return await fileExists(file) ? (await stat(file)).mtimeMs : void 0;
5643
+ }
5644
+ function gigetTarballOf(result) {
5645
+ const name = result.name;
5646
+ const version = result.version;
5647
+ if (typeof name !== "string") throw new Error("the download named no template");
5648
+ const stem = typeof version === "string" && version !== "" ? version : name;
5649
+ return path17.join(name, `${stem}.tar.gz`);
5650
+ }
5651
+ function gigetRecordOf(tarball) {
5652
+ return `${tarball}.json`;
5653
+ }
5654
+ async function etagSavedWith(tarballPath) {
5655
+ const raw = await readFileOptional(gigetRecordOf(tarballPath));
5656
+ const parsed = gigetTarballRecordSchema.safeParse(parseJsonOrUndefined(raw));
5657
+ return parsed.success ? parsed.data.etag : void 0;
5658
+ }
5659
+ function savedUnder(savedTarball, record) {
5660
+ return savedTarball.etag !== void 0 && savedTarball.etag === record.etag;
5661
+ }
5454
5662
  var REVALIDATION_TIMEOUT_MS = 5e3;
5455
5663
  var FETCH_RECORD_SUFFIX = ".etag.json";
5456
5664
  var askedThisRun = /* @__PURE__ */ new Map();
5665
+ var askedItselfThisRun = /* @__PURE__ */ new Map();
5666
+ function verdictsThisRun() {
5667
+ return mustAnswer() ? askedItselfThisRun : askedThisRun;
5668
+ }
5457
5669
  function announceRefetch(verdict) {
5458
5670
  if (verdict === "superseded") {
5459
5671
  log(STATUS_MESSAGES.MARKETPLACE_HAS_NEWER_CONTENT);
5460
5672
  }
5461
5673
  }
5462
5674
  function revalidateCachedCopy(cacheDir, source) {
5463
- const asked = askedThisRun.get(cacheDir);
5675
+ const verdicts = verdictsThisRun();
5676
+ const asked = verdicts.get(cacheDir);
5464
5677
  if (asked) return asked;
5465
5678
  const verdict = classifyCachedCopy(cacheDir, source);
5466
- askedThisRun.set(cacheDir, verdict);
5679
+ verdicts.set(cacheDir, verdict);
5467
5680
  return verdict;
5468
5681
  }
5469
5682
  function markCopyCurrentForThisRun(cacheDir) {
5470
- askedThisRun.set(cacheDir, Promise.resolve("current"));
5683
+ verdictsThisRun().set(cacheDir, Promise.resolve("current"));
5471
5684
  }
5472
5685
  async function classifyCachedCopy(cacheDir, source) {
5473
5686
  const record = await readFetchRecord(cacheDir);
5474
5687
  if (!record) return "unrecorded";
5475
- if (record.etag === void 0) {
5688
+ if (record.etag === void 0 && !mustAnswer()) {
5476
5689
  verbose(`No ETag was recorded for ${record.tar} \u2014 keeping the cached copy`);
5477
5690
  return "current";
5478
5691
  }
5479
5692
  try {
5480
- const live = await fetchEtag(record.tar);
5481
- if (live === void 0) {
5482
- verbose(`${record.tar} answered without an ETag \u2014 keeping the cached copy`);
5483
- return "current";
5484
- }
5485
- return live === record.etag ? "current" : "superseded";
5693
+ return judgeAnswer(record, await fetchEtag(record.tar));
5486
5694
  } catch (error) {
5695
+ if (error instanceof TarballWithheldError) throw createDetailedFetchError(error, source);
5487
5696
  verbose(`Could not revalidate ${record.tar}: ${getErrorMessage(error)}`);
5697
+ if (mustAnswer()) throw new MarketplaceUnreachableError(source, getErrorMessage(error));
5488
5698
  warn(sourceUnreachableUsingCache(source));
5489
5699
  return "unreachable";
5490
5700
  }
5491
5701
  }
5702
+ function judgeAnswer(record, live) {
5703
+ if (movedOn(record, live)) return "superseded";
5704
+ if (mustAnswer()) return "answered";
5705
+ if (live === void 0) {
5706
+ verbose(`${record.tar} answered without an ETag \u2014 keeping the cached copy`);
5707
+ }
5708
+ return "current";
5709
+ }
5710
+ function movedOn(record, live) {
5711
+ return live !== void 0 && record.etag !== void 0 && live !== record.etag;
5712
+ }
5492
5713
  async function fetchEtag(tar) {
5493
5714
  const auth = process.env.GIGET_AUTH;
5494
5715
  const response = await fetch(tar, {
5495
- method: "HEAD",
5716
+ method: mustAnswer() ? "GET" : "HEAD",
5496
5717
  ...auth && { headers: { Authorization: `Bearer ${auth}` } },
5497
5718
  signal: AbortSignal.timeout(REVALIDATION_TIMEOUT_MS)
5498
5719
  });
5720
+ await response.body?.cancel();
5721
+ if (mustAnswer() && !response.ok) throw new TarballWithheldError(response);
5499
5722
  return response.headers.get("etag") ?? void 0;
5500
5723
  }
5501
5724
  function fetchRecordPath(cacheDir) {
@@ -5511,15 +5734,41 @@ async function readFetchRecord(cacheDir) {
5511
5734
  }
5512
5735
  return parsed.data;
5513
5736
  }
5514
- async function recordFetchedCopy(cacheDir, result) {
5515
- const tar = result.tar;
5516
- if (typeof tar !== "string") {
5737
+ async function recordFetchedCopy(cacheDir, result, savedTarball) {
5738
+ const tar = tarballOf(result);
5739
+ if (tar === void 0) {
5517
5740
  verbose("No tarball URL came back from the download \u2014 the copy cannot be revalidated");
5518
5741
  return;
5519
5742
  }
5520
- const etag = await fetchEtag(tar).catch(() => void 0);
5521
- const record = { tar, ...etag !== void 0 && { etag } };
5522
- await writeFile(fetchRecordPath(cacheDir), JSON.stringify(record));
5743
+ const etag = savedTarball ? savedTarball.etag : await etagIfAnswered(tar);
5744
+ await writeFile(fetchRecordPath(cacheDir), JSON.stringify(fetchRecord(tar, etag)));
5745
+ }
5746
+ async function etagIfAnswered(tar) {
5747
+ try {
5748
+ return await fetchEtag(tar);
5749
+ } catch (error) {
5750
+ verbose(`Could not ask ${tar} after the download: ${getErrorMessage(error)}`);
5751
+ return void 0;
5752
+ }
5753
+ }
5754
+ async function askAfterTheDownload(result, source) {
5755
+ const tar = tarballOf(result);
5756
+ if (tar === void 0) {
5757
+ throw new Error("the download named no tarball to ask the marketplace about");
5758
+ }
5759
+ try {
5760
+ return fetchRecord(tar, await fetchEtag(tar));
5761
+ } catch (error) {
5762
+ if (error instanceof TarballWithheldError) throw error;
5763
+ throw new MarketplaceUnreachableError(source, getErrorMessage(error));
5764
+ }
5765
+ }
5766
+ function tarballOf(result) {
5767
+ const tar = result.tar;
5768
+ return typeof tar === "string" ? tar : void 0;
5769
+ }
5770
+ function fetchRecord(tar, etag) {
5771
+ return { tar, ...etag !== void 0 && { etag } };
5523
5772
  }
5524
5773
  function parseJsonOrUndefined(raw) {
5525
5774
  try {
@@ -5824,9 +6073,9 @@ function agentNotFoundError(agentName, agents) {
5824
6073
  }
5825
6074
  function describeAvailableAgents(availableAgents) {
5826
6075
  if (availableAgents.length === 0) return "No agents found in scanned directories";
5827
- const listed = availableAgents.slice(0, MAX_LISTED_AVAILABLE_AGENTS).join(", ");
6076
+ const listed2 = availableAgents.slice(0, MAX_LISTED_AVAILABLE_AGENTS).join(", ");
5828
6077
  const unlisted = availableAgents.length - MAX_LISTED_AVAILABLE_AGENTS;
5829
- return unlisted > 0 ? `Available agents: ${listed} (and ${unlisted} more)` : `Available agents: ${listed}`;
6078
+ return unlisted > 0 ? `Available agents: ${listed2} (and ${unlisted} more)` : `Available agents: ${listed2}`;
5830
6079
  }
5831
6080
  function assembleAgentConfig(agentName, definition, agentConfig, resolvedSkills) {
5832
6081
  const model = agentConfig.model ?? definition.model;
@@ -5863,10 +6112,10 @@ function resolveStackProperty(generated, assigned) {
5863
6112
  return generated === void 0 ? void 0 : assigned;
5864
6113
  }
5865
6114
  function resolveDescription(loadedStack, shared) {
5866
- return loadedStack === null ? shared : loadedStack.description;
6115
+ return shared ?? loadedStack?.description;
5867
6116
  }
5868
6117
  function configNameFor(projectDir) {
5869
- return isHomeDirectory(projectDir) ? DEFAULT_PLUGIN_NAME : path17.basename(projectDir);
6118
+ return isHomeDirectory(projectDir) ? DEFAULT_PLUGIN_NAME : path18.basename(projectDir);
5870
6119
  }
5871
6120
  async function loadCuratedStack(projectDir, projectStack) {
5872
6121
  const globalStack = isHomeDirectory(projectDir) ? void 0 : await loadGlobalStack();
@@ -6039,12 +6288,12 @@ async function installationIfPresent(candidate) {
6039
6288
 
6040
6289
  // src/cli/lib/hosts/claude-host.ts
6041
6290
  import os12 from "os";
6042
- import path20 from "path";
6043
- import { z as z5 } from "zod";
6291
+ import path21 from "path";
6292
+ import { z as z6 } from "zod";
6044
6293
 
6045
6294
  // src/cli/utils/exec.ts
6046
6295
  import { spawn } from "child_process";
6047
- import path18 from "path";
6296
+ import path19 from "path";
6048
6297
  var MAX_PLUGIN_PATH_LENGTH = 1024;
6049
6298
  var MAX_PLUGIN_NAME_LENGTH = 256;
6050
6299
  var MAX_MARKETPLACE_SOURCE_LENGTH = 1024;
@@ -6052,7 +6301,7 @@ var SAFE_NAME_PATTERN2 = /^[a-zA-Z0-9._@/-]+$/;
6052
6301
  var SAFE_PLUGIN_PATH_PATTERN = /^[a-zA-Z0-9._@/:~-]+$/;
6053
6302
  var OPENS_A_RELATIVE_PATH = /^\.\.?[/\\]/;
6054
6303
  function namesADirectoryOnThisMachine(source) {
6055
- return path18.isAbsolute(source) || OPENS_A_RELATIVE_PATH.test(source);
6304
+ return path19.isAbsolute(source) || OPENS_A_RELATIVE_PATH.test(source);
6056
6305
  }
6057
6306
  var CONTROL_CHAR_PATTERN = /[\x00-\x08\x0E-\x1F\x7F]/u;
6058
6307
  function validatePluginPath(pluginPath) {
@@ -6142,28 +6391,28 @@ async function execCommand(command, args, options) {
6142
6391
  }
6143
6392
 
6144
6393
  // src/cli/lib/plugins/plugin-settings.ts
6145
- import path19 from "path";
6146
- import { z as z4 } from "zod";
6147
- var pluginSettingsSchema = z4.object({
6394
+ import path20 from "path";
6395
+ import { z as z5 } from "zod";
6396
+ var pluginSettingsSchema = z5.object({
6148
6397
  // Absent when no plugin is enabled.
6149
- enabledPlugins: z4.record(z4.string(), z4.unknown()).exactOptional()
6398
+ enabledPlugins: z5.record(z5.string(), z5.unknown()).exactOptional()
6150
6399
  }).passthrough();
6151
6400
  var installationFields = {
6152
- installPath: z4.string(),
6153
- version: z4.string(),
6154
- installedAt: z4.string()
6401
+ installPath: z5.string(),
6402
+ version: z5.string(),
6403
+ installedAt: z5.string()
6155
6404
  };
6156
- var pluginInstallationSchema = z4.discriminatedUnion("scope", [
6157
- z4.object({ scope: z4.literal("project"), projectPath: z4.string(), ...installationFields }),
6158
- z4.object({ scope: z4.literal("user"), ...installationFields }),
6159
- z4.object({ scope: z4.literal("local"), ...installationFields })
6405
+ var pluginInstallationSchema = z5.discriminatedUnion("scope", [
6406
+ z5.object({ scope: z5.literal("project"), projectPath: z5.string(), ...installationFields }),
6407
+ z5.object({ scope: z5.literal("user"), ...installationFields }),
6408
+ z5.object({ scope: z5.literal("local"), ...installationFields })
6160
6409
  ]);
6161
- var installedPluginsSchema = z4.object({
6162
- version: z4.number(),
6163
- plugins: z4.record(z4.string(), z4.array(pluginInstallationSchema))
6410
+ var installedPluginsSchema = z5.object({
6411
+ version: z5.number(),
6412
+ plugins: z5.record(z5.string(), z5.array(pluginInstallationSchema))
6164
6413
  }).passthrough();
6165
6414
  function getInstalledPluginsRegistryPath(pluginsDir2) {
6166
- return path19.join(pluginsDir2, INSTALLED_PLUGINS_FILE);
6415
+ return path20.join(pluginsDir2, INSTALLED_PLUGINS_FILE);
6167
6416
  }
6168
6417
  async function readRegisteredInstallations(pluginsDir2) {
6169
6418
  const registryPath = getInstalledPluginsRegistryPath(pluginsDir2);
@@ -6196,7 +6445,7 @@ async function listPluginInstallsForProject(pluginsDir2, projectDir) {
6196
6445
  });
6197
6446
  }
6198
6447
  async function getEnabledPluginKeys(projectDir) {
6199
- const settingsPath = path19.join(projectDir, CLAUDE_DIR, STANDARD_FILES.SETTINGS_JSON);
6448
+ const settingsPath = path20.join(projectDir, CLAUDE_DIR, STANDARD_FILES.SETTINGS_JSON);
6200
6449
  if (!await fileExists(settingsPath)) {
6201
6450
  verbose(`No settings.json found at '${settingsPath}'`);
6202
6451
  return [];
@@ -6264,12 +6513,12 @@ async function isClaudeCLIAvailable(options) {
6264
6513
  return false;
6265
6514
  }
6266
6515
  }
6267
- var marketplaceInfoListSchema = z5.array(
6268
- z5.object({
6269
- name: z5.string(),
6270
- source: z5.string(),
6271
- repo: z5.string().exactOptional(),
6272
- path: z5.string().exactOptional()
6516
+ var marketplaceInfoListSchema = z6.array(
6517
+ z6.object({
6518
+ name: z6.string(),
6519
+ source: z6.string(),
6520
+ repo: z6.string().exactOptional(),
6521
+ path: z6.string().exactOptional()
6273
6522
  })
6274
6523
  );
6275
6524
  async function claudePluginMarketplaceList(options) {
@@ -6344,7 +6593,7 @@ function namesNothingThere(message) {
6344
6593
  return NOT_INSTALLED_MESSAGES.some((phrase) => message.includes(phrase));
6345
6594
  }
6346
6595
  function claudePluginsRoot(projectDir, options) {
6347
- if (options?.configDir !== void 0) return path20.join(options.configDir, PLUGINS_SUBDIR);
6596
+ if (options?.configDir !== void 0) return path21.join(options.configDir, PLUGINS_SUBDIR);
6348
6597
  return pluginsDir(CLAUDE, "global", projectDir);
6349
6598
  }
6350
6599
  async function claudeListPlugins(projectDir, options) {
@@ -6379,8 +6628,8 @@ function claudeHost() {
6379
6628
 
6380
6629
  // src/cli/lib/hosts/codex-host.ts
6381
6630
  import os13 from "os";
6382
- import path21 from "path";
6383
- import { z as z6 } from "zod";
6631
+ import path22 from "path";
6632
+ import { z as z7 } from "zod";
6384
6633
  var CODEX = "codex";
6385
6634
  var PLUGIN_CACHE_SUBDIR = "cache";
6386
6635
  var STANDING_WARNING = /could not create PATH aliases/;
@@ -6443,27 +6692,27 @@ async function readJsonFromCodex(schema, args, options, attempt, askedFrom) {
6443
6692
  }
6444
6693
  return validated.data;
6445
6694
  }
6446
- var marketplaceRegistrySchema = z6.object({
6447
- marketplaces: z6.array(
6448
- z6.object({
6449
- name: z6.string(),
6450
- marketplaceSource: z6.object({ sourceType: z6.string(), source: z6.string() })
6695
+ var marketplaceRegistrySchema = z7.object({
6696
+ marketplaces: z7.array(
6697
+ z7.object({
6698
+ name: z7.string(),
6699
+ marketplaceSource: z7.object({ sourceType: z7.string(), source: z7.string() })
6451
6700
  })
6452
6701
  )
6453
6702
  });
6454
- var pluginListingSchema = z6.object({
6455
- installed: z6.array(
6456
- z6.object({
6457
- pluginId: z6.string(),
6458
- name: z6.string(),
6459
- marketplaceName: z6.string(),
6460
- version: z6.string(),
6461
- enabled: z6.boolean()
6703
+ var pluginListingSchema = z7.object({
6704
+ installed: z7.array(
6705
+ z7.object({
6706
+ pluginId: z7.string(),
6707
+ name: z7.string(),
6708
+ marketplaceName: z7.string(),
6709
+ version: z7.string(),
6710
+ enabled: z7.boolean()
6462
6711
  })
6463
6712
  )
6464
6713
  });
6465
6714
  function codexPluginsRoot(projectDir, options) {
6466
- if (options?.configDir !== void 0) return path21.join(options.configDir, PLUGINS_SUBDIR);
6715
+ if (options?.configDir !== void 0) return path22.join(options.configDir, PLUGINS_SUBDIR);
6467
6716
  return pluginsDir(CODEX, "global", projectDir);
6468
6717
  }
6469
6718
  async function isCodexCLIAvailable(options) {
@@ -6593,7 +6842,7 @@ async function codexListPlugins(projectDir, options) {
6593
6842
  async function codexPluginsOrUnreadable(projectDir, options) {
6594
6843
  const pluginsRoot = codexPluginsRoot(projectDir, options);
6595
6844
  if (pluginsRoot === null) return { kind: "listed", plugins: [] };
6596
- const cacheRoot2 = path21.join(pluginsRoot, PLUGIN_CACHE_SUBDIR);
6845
+ const cacheRoot2 = path22.join(pluginsRoot, PLUGIN_CACHE_SUBDIR);
6597
6846
  let installed;
6598
6847
  try {
6599
6848
  installed = await codexPluginListing(options, projectDir);
@@ -6605,7 +6854,7 @@ async function codexPluginsOrUnreadable(projectDir, options) {
6605
6854
  kind: "listed",
6606
6855
  plugins: installed.map((plugin) => ({
6607
6856
  pluginKey: plugin.pluginId,
6608
- installPath: path21.join(cacheRoot2, plugin.marketplaceName, plugin.name, plugin.version),
6857
+ installPath: path22.join(cacheRoot2, plugin.marketplaceName, plugin.name, plugin.version),
6609
6858
  enabled: plugin.enabled
6610
6859
  }))
6611
6860
  };
@@ -6915,7 +7164,7 @@ function formatInstallationDisplay(info) {
6915
7164
  }
6916
7165
 
6917
7166
  // src/cli/lib/plugins/plugin-validator.ts
6918
- import path22 from "path";
7167
+ import path23 from "path";
6919
7168
 
6920
7169
  // src/cli/lib/validation-result.ts
6921
7170
  function validResult() {
@@ -6947,15 +7196,15 @@ async function validatePluginStructure(pluginPath) {
6947
7196
  if (!await directoryExists2(pluginPath)) {
6948
7197
  return invalidResult(`Plugin directory does not exist: ${pluginPath}`);
6949
7198
  }
6950
- const pluginDir = path22.join(pluginPath, PLUGIN_MANIFEST_DIR);
7199
+ const pluginDir = path23.join(pluginPath, PLUGIN_MANIFEST_DIR);
6951
7200
  if (!await directoryExists2(pluginDir)) {
6952
7201
  errors.push(`Missing ${PLUGIN_MANIFEST_DIR}/ directory`);
6953
7202
  }
6954
- const manifestPath = path22.join(pluginDir, STANDARD_FILES.PLUGIN_JSON);
7203
+ const manifestPath = path23.join(pluginDir, STANDARD_FILES.PLUGIN_JSON);
6955
7204
  if (!await fileExists(manifestPath)) {
6956
7205
  errors.push(`Missing ${PLUGIN_MANIFEST_DIR}/${STANDARD_FILES.PLUGIN_JSON}`);
6957
7206
  }
6958
- const readmePath = path22.join(pluginPath, STANDARD_FILES.README_MD);
7207
+ const readmePath = path23.join(pluginPath, STANDARD_FILES.README_MD);
6959
7208
  if (!await fileExists(readmePath)) {
6960
7209
  warnings.push("Missing README.md (recommended for documentation)");
6961
7210
  }
@@ -6998,15 +7247,15 @@ async function validatePluginManifest(manifestPath) {
6998
7247
  if (!manifest.description) {
6999
7248
  warnings.push("Missing description field (recommended for discoverability)");
7000
7249
  }
7001
- const pluginDir = path22.dirname(path22.dirname(manifestPath));
7250
+ const pluginDir = path23.dirname(path23.dirname(manifestPath));
7002
7251
  if (isNonEmptyString(manifest.skills)) {
7003
- const skillsPath = path22.join(pluginDir, manifest.skills);
7252
+ const skillsPath = path23.join(pluginDir, manifest.skills);
7004
7253
  if (!await directoryExists2(skillsPath)) {
7005
7254
  errors.push(`Skills path does not exist: ${manifest.skills}`);
7006
7255
  }
7007
7256
  }
7008
7257
  if (isNonEmptyString(manifest.agents)) {
7009
- const agentsPath = path22.join(pluginDir, manifest.agents);
7258
+ const agentsPath = path23.join(pluginDir, manifest.agents);
7010
7259
  if (!await directoryExists2(agentsPath)) {
7011
7260
  errors.push(`Agents path does not exist: ${manifest.agents}`);
7012
7261
  }
@@ -7075,14 +7324,14 @@ function prefixResult(result, prefix) {
7075
7324
  async function validateEachFile(pluginPath, scannedDir, files, validate) {
7076
7325
  const results = await Promise.all(
7077
7326
  files.map(async (file) => {
7078
- const filePath = path22.join(scannedDir, file);
7079
- return prefixResult(await validate(filePath), path22.relative(pluginPath, filePath));
7327
+ const filePath = path23.join(scannedDir, file);
7328
+ return prefixResult(await validate(filePath), path23.relative(pluginPath, filePath));
7080
7329
  })
7081
7330
  );
7082
7331
  return mergeValidationResults(results);
7083
7332
  }
7084
7333
  async function validatePluginSkillFiles(pluginPath, skillsRelPath) {
7085
- const skillsDir2 = path22.join(pluginPath, skillsRelPath);
7334
+ const skillsDir2 = path23.join(pluginPath, skillsRelPath);
7086
7335
  if (!await directoryExists2(skillsDir2)) return validResult();
7087
7336
  const files = await glob(`**/${STANDARD_FILES.SKILL_MD}`, skillsDir2);
7088
7337
  if (files.length === 0) {
@@ -7095,7 +7344,7 @@ async function validatePluginSkillFiles(pluginPath, skillsRelPath) {
7095
7344
  return validateEachFile(pluginPath, skillsDir2, files, validateSkillFrontmatter);
7096
7345
  }
7097
7346
  async function validatePluginAgentFiles(pluginPath, agentsRelPath) {
7098
- const agentsDir2 = path22.join(pluginPath, agentsRelPath);
7347
+ const agentsDir2 = path23.join(pluginPath, agentsRelPath);
7099
7348
  if (!await directoryExists2(agentsDir2)) return validResult();
7100
7349
  const files = await glob(agentCodec("claude").listGlob, agentsDir2);
7101
7350
  if (files.length === 0) {
@@ -7166,7 +7415,7 @@ async function listPluginDirNames(pluginsDir2) {
7166
7415
  const dirChecks = await Promise.all(
7167
7416
  allDirs.map(async (dirName) => ({
7168
7417
  dirName,
7169
- isPlugin: await directoryExists2(path22.join(pluginsDir2, dirName, PLUGIN_MANIFEST_DIR))
7418
+ isPlugin: await directoryExists2(path23.join(pluginsDir2, dirName, PLUGIN_MANIFEST_DIR))
7170
7419
  }))
7171
7420
  );
7172
7421
  return dirChecks.filter((c) => c.isPlugin).map((c) => c.dirName);
@@ -7174,7 +7423,7 @@ async function listPluginDirNames(pluginsDir2) {
7174
7423
  async function validateEachPlugin(pluginsDir2, pluginDirs) {
7175
7424
  const results = [];
7176
7425
  for (const pluginName of pluginDirs) {
7177
- const result = await validatePlugin(path22.join(pluginsDir2, pluginName));
7426
+ const result = await validatePlugin(path23.join(pluginsDir2, pluginName));
7178
7427
  results.push({ name: pluginName, result });
7179
7428
  }
7180
7429
  return results;
@@ -7257,11 +7506,11 @@ function sanitizeSkillName(name) {
7257
7506
  return name.replace(/\+/g, "-");
7258
7507
  }
7259
7508
  function isSingleFolderName(name) {
7260
- return name !== "" && name !== "." && name !== ".." && path23.basename(name) === name;
7509
+ return name !== "" && name !== "." && name !== ".." && path24.basename(name) === name;
7261
7510
  }
7262
7511
  async function assertClearable(pluginDir, skillPath, skillName) {
7263
- const skill = `Skill '${path23.basename(skillPath)}'`;
7264
- const file = `File: ${path23.join(skillPath, STANDARD_FILES.SKILL_MD)}`;
7512
+ const skill = `Skill '${path24.basename(skillPath)}'`;
7513
+ const file = `File: ${path24.join(skillPath, STANDARD_FILES.SKILL_MD)}`;
7265
7514
  if (!isSingleFolderName(skillName)) {
7266
7515
  throw new Error(
7267
7516
  `${skill} is named '${skillName}', which is not a single folder name, so its plugin cannot be built inside the output directory. ${file}`
@@ -7274,22 +7523,12 @@ async function assertClearable(pluginDir, skillPath, skillName) {
7274
7523
  }
7275
7524
  if (await holdsContentNoBuildMade(pluginDir)) {
7276
7525
  throw new Error(
7277
- `${skill} would be built into ${pluginDir}, which holds no ${path23.join(PLUGIN_MANIFEST_DIR, PLUGIN_MANIFEST_FILE)} and is not empty, so no earlier build made it and a rebuild would delete what it holds. Move its contents out, or build into another output directory. ${file}`
7526
+ `${skill} would be built into ${pluginDir}, which holds no ${path24.join(PLUGIN_MANIFEST_DIR, PLUGIN_MANIFEST_FILE)} and is not empty, so no earlier build made it and a rebuild would delete what it holds. Move its contents out, or build into another output directory. ${file}`
7278
7527
  );
7279
7528
  }
7280
7529
  }
7281
- async function isEarlierBuild(pluginDir) {
7282
- return fileExists(getPluginManifestPath(pluginDir));
7283
- }
7284
- async function holdsContentNoBuildMade(pluginDir) {
7285
- if (await isEarlierBuild(pluginDir)) return false;
7286
- return !await isDirectoryEmpty(pluginDir);
7287
- }
7288
- async function clearEarlierBuild(pluginDir) {
7289
- if (await isEarlierBuild(pluginDir)) await remove(pluginDir);
7290
- }
7291
7530
  async function readPluginSkillMetadata(skillPath) {
7292
- const metadataPath = path23.join(skillPath, STANDARD_FILES.METADATA_YAML);
7531
+ const metadataPath = path24.join(skillPath, STANDARD_FILES.METADATA_YAML);
7293
7532
  if (!await fileExists(metadataPath)) {
7294
7533
  return null;
7295
7534
  }
@@ -7333,28 +7572,28 @@ This skill is automatically available when installed.
7333
7572
  }
7334
7573
  async function writeSkillFiles(skillPath, pluginSkillDir, skillMdContent) {
7335
7574
  await ensureDir(pluginSkillDir);
7336
- await writeFile(path23.join(pluginSkillDir, STANDARD_FILES.SKILL_MD), skillMdContent);
7575
+ await writeFile(path24.join(pluginSkillDir, STANDARD_FILES.SKILL_MD), skillMdContent);
7337
7576
  verbose(` Copied ${STANDARD_FILES.SKILL_MD}`);
7338
7577
  for (const fileName of SKILL_CONTENT_FILES) {
7339
7578
  if (fileName === STANDARD_FILES.SKILL_MD) continue;
7340
- const sourcePath = path23.join(skillPath, fileName);
7579
+ const sourcePath = path24.join(skillPath, fileName);
7341
7580
  if (await fileExists(sourcePath)) {
7342
7581
  const content = await readFile(sourcePath);
7343
- await writeFile(path23.join(pluginSkillDir, fileName), content);
7582
+ await writeFile(path24.join(pluginSkillDir, fileName), content);
7344
7583
  verbose(` Copied ${fileName}`);
7345
7584
  }
7346
7585
  }
7347
7586
  for (const dirName of SKILL_CONTENT_DIRS) {
7348
- const sourceDir2 = path23.join(skillPath, dirName);
7587
+ const sourceDir2 = path24.join(skillPath, dirName);
7349
7588
  if (await fileExists(sourceDir2)) {
7350
- await copy(sourceDir2, path23.join(pluginSkillDir, dirName));
7589
+ await copy(sourceDir2, path24.join(pluginSkillDir, dirName));
7351
7590
  verbose(` Copied ${dirName}/`);
7352
7591
  }
7353
7592
  }
7354
7593
  }
7355
7594
  async function readSkillMdToBuild(skillPath) {
7356
- const dirBasename = path23.basename(skillPath);
7357
- const skillMdPath = path23.join(skillPath, STANDARD_FILES.SKILL_MD);
7595
+ const dirBasename = path24.basename(skillPath);
7596
+ const skillMdPath = path24.join(skillPath, STANDARD_FILES.SKILL_MD);
7358
7597
  if (!await fileExists(skillMdPath)) {
7359
7598
  throw new Error(
7360
7599
  `Skill '${dirBasename}' is missing required ${STANDARD_FILES.SKILL_MD} file. Expected at: ${skillMdPath}`
@@ -7373,7 +7612,7 @@ async function compileSkillPlugin(options) {
7373
7612
  const { skillPath, outputDir, skillName: overrideName } = options;
7374
7613
  const { skillMdContent, frontmatter } = await readSkillMdToBuild(skillPath);
7375
7614
  const skillName = overrideName ?? sanitizeSkillName(frontmatter.name);
7376
- const pluginDir = path23.join(outputDir, skillName);
7615
+ const pluginDir = path24.join(outputDir, skillName);
7377
7616
  await assertClearable(pluginDir, skillPath, skillName);
7378
7617
  verbose(`Compiling skill plugin: ${skillName} from ${skillPath}`);
7379
7618
  const metadata = await readPluginSkillMetadata(skillPath);
@@ -7390,11 +7629,11 @@ async function compileSkillPlugin(options) {
7390
7629
  await writeContentHash(pluginDir, await computeSkillFolderHash(skillPath), getPluginManifestPath);
7391
7630
  await writeSkillFiles(
7392
7631
  skillPath,
7393
- path23.join(pluginDir, STANDARD_DIRS.SKILLS, skillName),
7632
+ path24.join(pluginDir, STANDARD_DIRS.SKILLS, skillName),
7394
7633
  skillMdContent
7395
7634
  );
7396
7635
  const readme = generateReadme(skillName, frontmatter);
7397
- await writeFile(path23.join(pluginDir, STANDARD_FILES.README_MD), readme);
7636
+ await writeFile(path24.join(pluginDir, STANDARD_FILES.README_MD), readme);
7398
7637
  verbose(" Generated README.md");
7399
7638
  const manifest = manifestAt(
7400
7639
  versionAfterRebuild(earlierBuild, await computeShippedHash(pluginDir))
@@ -7412,7 +7651,7 @@ async function compileAllSkillPlugins(skillsDir2, outputDir) {
7412
7651
  const failed = [];
7413
7652
  const skillMdFiles = await glob(`**/${STANDARD_FILES.SKILL_MD}`, skillsDir2);
7414
7653
  for (const skillMdFile of skillMdFiles) {
7415
- const skillPath = path23.join(skillsDir2, path23.dirname(skillMdFile));
7654
+ const skillPath = path24.join(skillsDir2, path24.dirname(skillMdFile));
7416
7655
  try {
7417
7656
  const result = await compileSkillPlugin({
7418
7657
  skillPath,
@@ -7422,7 +7661,7 @@ async function compileAllSkillPlugins(skillsDir2, outputDir) {
7422
7661
  log(` [OK] ${result.skillName}`);
7423
7662
  } catch (error) {
7424
7663
  const errorMessage = getErrorMessage(error);
7425
- const dirBasename = path23.basename(skillPath);
7664
+ const dirBasename = path24.basename(skillPath);
7426
7665
  failed.push(dirBasename);
7427
7666
  warn(`Failed to compile skill from '${dirBasename}': ${errorMessage}`);
7428
7667
  }
@@ -7438,16 +7677,23 @@ Compiled ${plural(results.length, "skill plugin")}:`);
7438
7677
  }
7439
7678
 
7440
7679
  // src/cli/lib/skills/local-skill-loader.ts
7441
- import path24 from "path";
7442
- async function discoverLocalSkills(projectDir) {
7443
- const localSkillsPath = path24.join(projectDir, LOCAL_SKILLS_PATH);
7680
+ import path25 from "path";
7681
+ function copiedInSkillsAt(root, provider) {
7682
+ const scope = isHomeDirectory(root) ? "global" : "project";
7683
+ return {
7684
+ dir: skillsDir(provider, scope, root),
7685
+ pathPrefix: skillsPathPrefix(provider, scope, root)
7686
+ };
7687
+ }
7688
+ async function discoverLocalSkills(root, provider = providerInUse(root)) {
7689
+ const { dir: localSkillsPath, pathPrefix } = copiedInSkillsAt(root, provider);
7444
7690
  if (!await directoryExists2(localSkillsPath)) {
7445
7691
  verbose(`Local skills directory not found: ${localSkillsPath}`);
7446
7692
  return null;
7447
7693
  }
7448
7694
  const skillDirs = await listDirectories(localSkillsPath);
7449
7695
  const extracted = await Promise.all(
7450
- skillDirs.map((skillDirName) => extractLocalSkill(localSkillsPath, skillDirName))
7696
+ skillDirs.map((skillDirName) => extractLocalSkill(localSkillsPath, pathPrefix, skillDirName))
7451
7697
  );
7452
7698
  const skills = extracted.filter((skill) => skill !== null);
7453
7699
  verbose(`Discovered ${plural(skills.length, "local skill")} from ${localSkillsPath}`);
@@ -7456,10 +7702,10 @@ async function discoverLocalSkills(projectDir) {
7456
7702
  localSkillsPath
7457
7703
  };
7458
7704
  }
7459
- async function extractLocalSkill(localSkillsPath, skillDirName) {
7460
- const skillDir = path24.join(localSkillsPath, skillDirName);
7461
- const metadataPath = path24.join(skillDir, STANDARD_FILES.METADATA_YAML);
7462
- const skillMdPath = path24.join(skillDir, STANDARD_FILES.SKILL_MD);
7705
+ async function extractLocalSkill(localSkillsPath, pathPrefix, skillDirName) {
7706
+ const skillDir = path25.join(localSkillsPath, skillDirName);
7707
+ const metadataPath = path25.join(skillDir, STANDARD_FILES.METADATA_YAML);
7708
+ const skillMdPath = path25.join(skillDir, STANDARD_FILES.SKILL_MD);
7463
7709
  if (!await fileExists(metadataPath)) {
7464
7710
  verbose(`Skipping local skill '${skillDirName}': No metadata.yaml found`);
7465
7711
  return null;
@@ -7488,8 +7734,8 @@ async function extractLocalSkill(localSkillsPath, skillDirName) {
7488
7734
  verbose(`Skipping local skill '${skillDirName}': invalid SKILL.md frontmatter`);
7489
7735
  return null;
7490
7736
  }
7491
- const relativePath = `${LOCAL_SKILLS_PATH}/${skillDirName}/`;
7492
- const absolutePath = path24.join(localSkillsPath, skillDirName) + path24.sep;
7737
+ const relativePath = `${pathPrefix}/${skillDirName}/`;
7738
+ const absolutePath = path25.join(localSkillsPath, skillDirName) + path25.sep;
7493
7739
  const skillId = frontmatter.name;
7494
7740
  const extracted = {
7495
7741
  id: skillId,
@@ -7512,7 +7758,7 @@ async function extractLocalSkill(localSkillsPath, skillDirName) {
7512
7758
  }
7513
7759
 
7514
7760
  // src/cli/lib/skills/unresolved-skill-entries.ts
7515
- import path25 from "path";
7761
+ import path26 from "path";
7516
7762
  async function findUnusableSavedSkillMetadata(savedSkills, projectDir) {
7517
7763
  const verdicts = await Promise.all(
7518
7764
  savedSkills.map((saved) => unusableMetadataOf(saved, projectDir))
@@ -7531,11 +7777,11 @@ function claimsLocalCopy(saved) {
7531
7777
  }
7532
7778
  function installedMetadataPath(saved, projectDir) {
7533
7779
  const { skillsDir: skillsDir2 } = resolveInstallPaths(projectDir, saved.scope);
7534
- return path25.join(skillsDir2, saved.id, STANDARD_FILES.METADATA_YAML);
7780
+ return path26.join(skillsDir2, saved.id, STANDARD_FILES.METADATA_YAML);
7535
7781
  }
7536
7782
 
7537
7783
  // src/cli/lib/skills/local-skill-mover.ts
7538
- import path26 from "path";
7784
+ import path27 from "path";
7539
7785
  var THE_DIRECTORY_ITSELF = ".";
7540
7786
  function validateSkillId(skillId) {
7541
7787
  return !(skillId.length === 0 || skillId === THE_DIRECTORY_ITSELF || skillId.includes("\0") || skillId.includes("..") || skillId.includes("/") || skillId.includes("\\"));
@@ -7545,8 +7791,8 @@ async function deleteLocalSkill(projectDir, skillId, scope = "project") {
7545
7791
  warn(`Invalid skill ID for deletion: '${skillId}'`);
7546
7792
  return;
7547
7793
  }
7548
- const skillsDir2 = path26.resolve(resolveInstallPaths(projectDir, scope).skillsDir);
7549
- const skillPath = path26.resolve(skillsDir2, skillId);
7794
+ const skillsDir2 = path27.resolve(resolveInstallPaths(projectDir, scope).skillsDir);
7795
+ const skillPath = path27.resolve(skillsDir2, skillId);
7550
7796
  if (!isPathWithin(skillPath, skillsDir2)) {
7551
7797
  warn(`Skill ID '${skillId}' resolves outside the skills directory.`);
7552
7798
  return;
@@ -7564,10 +7810,10 @@ async function migrateLocalSkillScope(skillId, fromScope, projectDir) {
7564
7810
  return;
7565
7811
  }
7566
7812
  const toScope = fromScope === "global" ? "project" : "global";
7567
- const fromSkillsDir = path26.resolve(resolveInstallPaths(projectDir, fromScope).skillsDir);
7568
- const toSkillsDir = path26.resolve(resolveInstallPaths(projectDir, toScope).skillsDir);
7569
- const fromPath = path26.resolve(fromSkillsDir, skillId);
7570
- const toPath = path26.resolve(toSkillsDir, skillId);
7813
+ const fromSkillsDir = path27.resolve(resolveInstallPaths(projectDir, fromScope).skillsDir);
7814
+ const toSkillsDir = path27.resolve(resolveInstallPaths(projectDir, toScope).skillsDir);
7815
+ const fromPath = path27.resolve(fromSkillsDir, skillId);
7816
+ const toPath = path27.resolve(toSkillsDir, skillId);
7571
7817
  if (!isPathWithin(fromPath, fromSkillsDir)) {
7572
7818
  warn(`Skill ID '${skillId}' resolves outside the source skills directory.`);
7573
7819
  return;
@@ -7594,7 +7840,7 @@ async function migrateLocalSkillScope(skillId, fromScope, projectDir) {
7594
7840
 
7595
7841
  // src/cli/lib/matrix/matrix-loader.ts
7596
7842
  import { parse as parseYaml5 } from "yaml";
7597
- import path27 from "path";
7843
+ import path28 from "path";
7598
7844
  async function loadSkillCategories(configPath) {
7599
7845
  const data = await loadConfig(configPath, skillCategoriesFileSchema);
7600
7846
  if (!data) {
@@ -7630,20 +7876,21 @@ async function extractAllSkills(skillsDir2) {
7630
7876
  return skills;
7631
7877
  }
7632
7878
  async function extractSkill(skillsDir2, metadataFile) {
7633
- const skillDir = path27.dirname(metadataFile);
7634
- const skillMdPath = path27.join(skillsDir2, skillDir, STANDARD_FILES.SKILL_MD);
7879
+ const skillDir = path28.dirname(metadataFile);
7880
+ const skillMdPath = path28.join(skillsDir2, skillDir, STANDARD_FILES.SKILL_MD);
7635
7881
  if (!await fileExists(skillMdPath)) {
7636
7882
  verbose(`Skipping ${metadataFile}: No ${STANDARD_FILES.SKILL_MD} found`);
7637
7883
  return null;
7638
7884
  }
7639
- const metadata = await readMatrixMetadataOrWarn(metadataFile, path27.join(skillsDir2, metadataFile));
7885
+ const metadata = await readMatrixMetadataOrWarn(metadataFile, path28.join(skillsDir2, metadataFile));
7640
7886
  if (!metadata) return null;
7641
7887
  const frontmatter = parseFrontmatterOrWarn(skillDir, skillMdPath, await readFile(skillMdPath));
7642
7888
  if (!frontmatter) return null;
7643
7889
  if (!metadata.displayName) {
7644
- throw new Error(
7645
- `Skill at ${metadataFile} is missing required '${METADATA_KEYS.DISPLAY_NAME}' field in metadata.yaml`
7890
+ warn(
7891
+ `Skipping '${metadataFile}': invalid ${STANDARD_FILES.METADATA_YAML} \u2014 missing required field: ${METADATA_KEYS.DISPLAY_NAME}`
7646
7892
  );
7893
+ return null;
7647
7894
  }
7648
7895
  const skillId = frontmatter.name;
7649
7896
  const extracted = {
@@ -7689,7 +7936,7 @@ function parseFrontmatterOrWarn(skillDir, skillMdPath, skillMdContent) {
7689
7936
  const unreadable = unreadableFrontmatter(skillMdContent);
7690
7937
  if (unreadable !== void 0) {
7691
7938
  warn(
7692
- `Skipping '${path27.join(skillDir, STANDARD_FILES.SKILL_MD)}': unparseable ${STANDARD_FILES.SKILL_MD} frontmatter at ${skillMdPath} \u2014 ${unreadable}`
7939
+ `Skipping '${path28.join(skillDir, STANDARD_FILES.SKILL_MD)}': unparseable ${STANDARD_FILES.SKILL_MD} frontmatter at ${skillMdPath} \u2014 ${unreadable}`
7693
7940
  );
7694
7941
  }
7695
7942
  return null;
@@ -9580,7 +9827,7 @@ async function loadCatalogue(options) {
9580
9827
  ...catalogue,
9581
9828
  marketplaceSkillIds: new Set(typedKeys(catalogue.matrix.skills))
9582
9829
  };
9583
- result.matrix = await mergeLocalSkills(result.matrix, resolvedProjectDir);
9830
+ result.matrix = await mergeLocalSkills(result.matrix, resolvedProjectDir, provider);
9584
9831
  await seatGlobalInstallationSkills(result.matrix, source, purpose);
9585
9832
  if (!options.skipExtraSources) {
9586
9833
  await loadSkillsFromAllSources(
@@ -9742,12 +9989,12 @@ function goesOnWithoutManifest(source, purpose) {
9742
9989
  function configuredLabel(sourceConfig) {
9743
9990
  return sourceConfig.marketplace === void 0 ? {} : { marketplace: sourceConfig.marketplace };
9744
9991
  }
9745
- async function mergeLocalSkills(matrix2, projectDir) {
9746
- const withGlobal = isHomeDirectory(projectDir) ? matrix2 : await mergeDiscoveredLocalSkills(matrix2, os14.homedir(), "global");
9747
- return mergeDiscoveredLocalSkills(withGlobal, projectDir, "project");
9992
+ async function mergeLocalSkills(matrix2, projectDir, provider) {
9993
+ const withGlobal = isHomeDirectory(projectDir) ? matrix2 : await mergeDiscoveredLocalSkills(matrix2, os14.homedir(), "global", provider);
9994
+ return mergeDiscoveredLocalSkills(withGlobal, projectDir, "project", provider);
9748
9995
  }
9749
- async function mergeDiscoveredLocalSkills(matrix2, dir, label) {
9750
- const discovered = await discoverLocalSkills(dir);
9996
+ async function mergeDiscoveredLocalSkills(matrix2, dir, label, provider) {
9997
+ const discovered = await discoverLocalSkills(dir, provider);
9751
9998
  if (!discovered || discovered.skills.length === 0) return matrix2;
9752
9999
  verbose(
9753
10000
  `Found ${discovered.skills.length} ${label} local skill(s) in ${discovered.localSkillsPath}`
@@ -9800,7 +10047,7 @@ async function loadAndMergeFromBasePath(basePath, source) {
9800
10047
  const skillsDirRelPath = sourceProjectConfig?.skillsDir ?? SKILLS_DIR_PATH;
9801
10048
  const stacksRelFile = sourceProjectConfig?.stacksFile;
9802
10049
  const { categories, sourceRules } = await loadSourceTaxonomy(basePath);
9803
- const skillsDir2 = path28.join(basePath, skillsDirRelPath);
10050
+ const skillsDir2 = path29.join(basePath, skillsDirRelPath);
9804
10051
  verbose(`Skills from source: ${skillsDir2}`);
9805
10052
  const skills = await extractAllSkills(skillsDir2);
9806
10053
  await refuseCatalogueCollisions(basePath, source, skills);
@@ -9823,7 +10070,7 @@ async function loadSourceTaxonomy(basePath) {
9823
10070
  return { categories, sourceRules };
9824
10071
  }
9825
10072
  async function loadSourceCategories(basePath) {
9826
- const categoriesPath = path28.join(basePath, SKILL_CATEGORIES_PATH);
10073
+ const categoriesPath = path29.join(basePath, SKILL_CATEGORIES_PATH);
9827
10074
  if (!await fileExists(categoriesPath)) return void 0;
9828
10075
  const categories = await loadSkillCategories(categoriesPath);
9829
10076
  verbose(
@@ -9832,7 +10079,7 @@ async function loadSourceCategories(basePath) {
9832
10079
  return categories;
9833
10080
  }
9834
10081
  async function loadSourceRules(basePath) {
9835
- const rulesPath = path28.join(basePath, SKILL_RULES_PATH);
10082
+ const rulesPath = path29.join(basePath, SKILL_RULES_PATH);
9836
10083
  if (!await fileExists(rulesPath)) return void 0;
9837
10084
  const rules = await loadSkillRules(rulesPath);
9838
10085
  verbose(`Loaded source rules: ${rulesPath}`);
@@ -9847,11 +10094,11 @@ async function refuseCatalogueCollisions(basePath, source, skills) {
9847
10094
  throw new Error(catalogueCollisionError(collidingIds, source));
9848
10095
  }
9849
10096
  function catalogueCollisionError(collidingIds, source) {
9850
- const listed = collidingIds.slice(0, MAX_REPORTED_COLLISIONS).map((id) => ` ${id}`);
9851
- const unlisted = collidingIds.length - listed.length;
10097
+ const listed2 = collidingIds.slice(0, MAX_REPORTED_COLLISIONS).map((id) => ` ${id}`);
10098
+ const unlisted = collidingIds.length - listed2.length;
9852
10099
  return [
9853
10100
  `Marketplace '${source}' ships ${collidingIds.length} skill id(s) the public catalogue already owns:`,
9854
- ...listed,
10101
+ ...listed2,
9855
10102
  ...unlisted > 0 ? [` ... and ${unlisted} more`] : [],
9856
10103
  `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.`
9857
10104
  ].join("\n");
@@ -9940,7 +10187,7 @@ function declaresCategory(matrix2, category) {
9940
10187
  return category !== LOCAL_PSEUDO_CATEGORY && matrix2.categories[category] !== void 0;
9941
10188
  }
9942
10189
  function undeclaredCategoryRefusal(metadata, category) {
9943
- const metadataPath = path28.join(metadata.path, STANDARD_FILES.METADATA_YAML);
10190
+ const metadataPath = path29.join(metadata.path, STANDARD_FILES.METADATA_YAML);
9944
10191
  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.`;
9945
10192
  }
9946
10193
  function mergeLocalSkillsIntoMatrix(matrix2, localResult) {
@@ -10076,15 +10323,14 @@ export {
10076
10323
  skippedUnknownAgents,
10077
10324
  carriedSkillsWritten,
10078
10325
  SHARED_CONFIG_APPLY,
10079
- projectCopyRemoved,
10080
- projectCopyMovedToGlobal,
10081
- registeredProjectsRewritten,
10326
+ registeredProjectsRecompiled,
10082
10327
  sharedConfigNeedsTerminal,
10083
10328
  nothingInstalledToApplyTo,
10084
10329
  uninstallNeedsTerminal,
10085
10330
  addMarketplaceInTheEditor,
10086
10331
  SHARED_CONFIG_ARRIVALS,
10087
10332
  keptAsInstalledGlobally,
10333
+ subAgentChanged,
10088
10334
  addedToGlobalSubAgents,
10089
10335
  authoredHereKept,
10090
10336
  unplaceableKept,
@@ -10094,11 +10340,10 @@ export {
10094
10340
  uninstallSkillMetadataUnreadable,
10095
10341
  recreateConfigFrom,
10096
10342
  providerArgument,
10343
+ configUnreadableError,
10097
10344
  registeredProjectsUpdateFailed,
10098
10345
  unmarkedAgentsKept,
10099
- UNINSTALL_PLAN,
10100
- localSkillsRemoval,
10101
- compiledAgentsRemoval,
10346
+ configFileRemoved,
10102
10347
  scopeNoun,
10103
10348
  scopeLabel,
10104
10349
  layoutIsCurrent,
@@ -10106,6 +10351,7 @@ export {
10106
10351
  GlobalPairWriteViolation,
10107
10352
  withGateToken,
10108
10353
  assertGateToken,
10354
+ isPathWithin,
10109
10355
  readFile,
10110
10356
  readFileSafe,
10111
10357
  readFileOptional,
@@ -10135,11 +10381,14 @@ export {
10135
10381
  validateSkillMetadata,
10136
10382
  splitMetadataValidationIssues,
10137
10383
  ConfigDefaultExportError,
10384
+ NO_VALID_DEFAULT_EXPORT,
10385
+ configLoadFailure,
10138
10386
  loadConfig,
10139
10387
  getProjectConfigPath,
10140
10388
  getInstalledConfigPath,
10141
10389
  resolveInstallPaths,
10142
10390
  providerToNameAt,
10391
+ initProviderToNameAt,
10143
10392
  isHomeDirectory,
10144
10393
  SOURCE_ENV_VAR,
10145
10394
  readSourceRepoConfig,
@@ -10150,11 +10399,8 @@ export {
10150
10399
  resolvePrimarySourceEntry,
10151
10400
  MarketplaceRefusedError,
10152
10401
  isLocalSource,
10153
- typedEntries,
10154
- typedKeys,
10155
- typedFromEntries,
10156
- typedValues,
10157
10402
  normalizeStackRecord,
10403
+ loadStackById,
10158
10404
  defaultUsageGuidance,
10159
10405
  resolveAgentConfigToSkills,
10160
10406
  getStackSkillIds,
@@ -10183,11 +10429,12 @@ export {
10183
10429
  flatUnion,
10184
10430
  renderTypeImportLine,
10185
10431
  spaces,
10432
+ EXTRACTED_FIELDS,
10186
10433
  generateConfigSource,
10187
10434
  refuseUnwritableStack,
10188
10435
  generateBlankGlobalConfigSource,
10189
- typedEntries2,
10190
- typedKeys2,
10436
+ typedEntries2 as typedEntries,
10437
+ typedKeys2 as typedKeys,
10191
10438
  isScopePairCompatible,
10192
10439
  splitConfigByScope,
10193
10440
  seedToInstallSelection,
@@ -10201,9 +10448,12 @@ export {
10201
10448
  loadSkillsFromDir,
10202
10449
  getCurrentDate,
10203
10450
  computeStringHash,
10204
- determinePluginVersion,
10451
+ computeShippedHash,
10452
+ readEarlierBuild,
10453
+ versionAfterRebuild,
10205
10454
  writeContentHash,
10206
10455
  readForkedFromMetadata,
10456
+ readLocalSkillMetadata,
10207
10457
  validateSkillPath,
10208
10458
  copySkillsToLocalFlattened,
10209
10459
  getUserPluginsDir,
@@ -10216,6 +10466,8 @@ export {
10216
10466
  writePluginManifest,
10217
10467
  buildMarketplacePluginRef,
10218
10468
  parseMarketplacePluginRef,
10469
+ holdsContentNoBuildMade,
10470
+ clearEarlierBuild,
10219
10471
  getInstallationInfo,
10220
10472
  getInstallationsByProvider,
10221
10473
  formatInstallationDisplay,
@@ -10248,6 +10500,8 @@ export {
10248
10500
  validateSkillIdNamespace,
10249
10501
  writeMarketplace,
10250
10502
  getMarketplaceStats,
10503
+ MarketplaceUnreachableError,
10504
+ askingEachMarketplaceItself,
10251
10505
  MarketplaceNameRefusedError,
10252
10506
  fetchMarketplace,
10253
10507
  isLocalOnlySkill,
@@ -10271,4 +10525,4 @@ export {
10271
10525
  executeMigration,
10272
10526
  keepsGlobalInstall
10273
10527
  };
10274
- //# sourceMappingURL=chunk-3YYHL76L.js.map
10528
+ //# sourceMappingURL=chunk-WVFBBZEP.js.map