agents-inc 0.174.0 → 0.176.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/dist/{chunk-APWMMZOK.js → chunk-3F5LT433.js} +157 -53
  3. package/dist/chunk-3F5LT433.js.map +1 -0
  4. package/dist/chunk-5CFHP6Z5.js +109 -0
  5. package/dist/chunk-5CFHP6Z5.js.map +1 -0
  6. package/dist/{chunk-3PJZ4QNJ.js → chunk-B7AVWSK2.js} +21 -11
  7. package/dist/chunk-B7AVWSK2.js.map +1 -0
  8. package/dist/{chunk-H5E6IO2L.js → chunk-C2BSCEDN.js} +9 -11
  9. package/dist/chunk-C2BSCEDN.js.map +1 -0
  10. package/dist/{chunk-R7GKQBZJ.js → chunk-CKBWEWHZ.js} +96 -52
  11. package/dist/chunk-CKBWEWHZ.js.map +1 -0
  12. package/dist/{chunk-IXVGBUWI.js → chunk-EC3JXT23.js} +5 -5
  13. package/dist/{chunk-E45IT3HC.js → chunk-ENET4IF6.js} +998 -472
  14. package/dist/chunk-ENET4IF6.js.map +1 -0
  15. package/dist/{chunk-FQF6VHO3.js → chunk-FACDJH6O.js} +12 -105
  16. package/dist/chunk-FACDJH6O.js.map +1 -0
  17. package/dist/{chunk-TVEDNGVP.js → chunk-H6WUOW5F.js} +2 -2
  18. package/dist/{chunk-7BHDKEGK.js → chunk-JNRACLNS.js} +3 -3
  19. package/dist/{chunk-IAQFZFWD.js → chunk-JTMSFBCA.js} +231 -24
  20. package/dist/chunk-JTMSFBCA.js.map +1 -0
  21. package/dist/{chunk-EFXVYQGT.js → chunk-MQZNDK5F.js} +39 -40
  22. package/dist/chunk-MQZNDK5F.js.map +1 -0
  23. package/dist/{chunk-QCX66KBM.js → chunk-SN64WNWE.js} +3 -3
  24. package/dist/{chunk-WUSEZFER.js → chunk-U46LYYRA.js} +3 -2
  25. package/dist/{chunk-WUSEZFER.js.map → chunk-U46LYYRA.js.map} +1 -1
  26. package/dist/{chunk-6PGXTLA4.js → chunk-U672FZVC.js} +2 -2
  27. package/dist/{chunk-AIBVAYHS.js → chunk-UE4Z3YMB.js} +2 -2
  28. package/dist/chunk-UE4Z3YMB.js.map +1 -0
  29. package/dist/{chunk-REHVGFJE.js → chunk-XMQIWPIR.js} +84 -32
  30. package/dist/chunk-XMQIWPIR.js.map +1 -0
  31. package/dist/{chunk-I3WF6L43.js → chunk-XYBHH3M5.js} +2 -2
  32. package/dist/{chunk-ECPFYHDX.js → chunk-ZBI6GFJD.js} +11 -11
  33. package/dist/chunk-ZBI6GFJD.js.map +1 -0
  34. package/dist/commands/build/marketplace.js +5 -4
  35. package/dist/commands/build/marketplace.js.map +1 -1
  36. package/dist/commands/build/plugins.js +7 -6
  37. package/dist/commands/build/plugins.js.map +1 -1
  38. package/dist/commands/compile.js +20 -16
  39. package/dist/commands/compile.js.map +1 -1
  40. package/dist/commands/doctor.js +106 -87
  41. package/dist/commands/doctor.js.map +1 -1
  42. package/dist/commands/edit.js +76 -48
  43. package/dist/commands/edit.js.map +1 -1
  44. package/dist/commands/eject.js +31 -18
  45. package/dist/commands/eject.js.map +1 -1
  46. package/dist/commands/init.js +17 -17
  47. package/dist/commands/list.js +10 -9
  48. package/dist/commands/list.js.map +1 -1
  49. package/dist/commands/new/marketplace.js +5 -4
  50. package/dist/commands/new/marketplace.js.map +1 -1
  51. package/dist/commands/search.js +14 -9
  52. package/dist/commands/search.js.map +1 -1
  53. package/dist/commands/share.js +20 -22
  54. package/dist/commands/share.js.map +1 -1
  55. package/dist/commands/uninstall.js +24 -19
  56. package/dist/commands/uninstall.js.map +1 -1
  57. package/dist/commands/update.js +110 -31
  58. package/dist/commands/update.js.map +1 -1
  59. package/dist/components/common/select-list.js +2 -2
  60. package/dist/components/common/skill-agent-summary.js +4 -3
  61. package/dist/components/common/summary-panel.js +5 -4
  62. package/dist/hooks/init.js +24 -25
  63. package/dist/hooks/init.js.map +1 -1
  64. package/dist/load-agent-defs-5BSECHE6.js +14 -0
  65. package/dist/{recompile-project-agents-KYK44C24.js → recompile-project-agents-U3UGHPWG.js} +8 -8
  66. package/dist/{source-loader-G5I4RNUP.js → source-loader-E2Y5MMN5.js} +4 -4
  67. package/package.json +1 -1
  68. package/dist/chunk-3PJZ4QNJ.js.map +0 -1
  69. package/dist/chunk-AIBVAYHS.js.map +0 -1
  70. package/dist/chunk-APWMMZOK.js.map +0 -1
  71. package/dist/chunk-E45IT3HC.js.map +0 -1
  72. package/dist/chunk-ECPFYHDX.js.map +0 -1
  73. package/dist/chunk-EFXVYQGT.js.map +0 -1
  74. package/dist/chunk-FQF6VHO3.js.map +0 -1
  75. package/dist/chunk-H5E6IO2L.js.map +0 -1
  76. package/dist/chunk-IAQFZFWD.js.map +0 -1
  77. package/dist/chunk-ORO2T4K2.js +0 -96
  78. package/dist/chunk-ORO2T4K2.js.map +0 -1
  79. package/dist/chunk-R7GKQBZJ.js.map +0 -1
  80. package/dist/chunk-REHVGFJE.js.map +0 -1
  81. package/dist/load-agent-defs-5U5KAURA.js +0 -14
  82. /package/dist/{chunk-IXVGBUWI.js.map → chunk-EC3JXT23.js.map} +0 -0
  83. /package/dist/{chunk-TVEDNGVP.js.map → chunk-H6WUOW5F.js.map} +0 -0
  84. /package/dist/{chunk-7BHDKEGK.js.map → chunk-JNRACLNS.js.map} +0 -0
  85. /package/dist/{chunk-QCX66KBM.js.map → chunk-SN64WNWE.js.map} +0 -0
  86. /package/dist/{chunk-6PGXTLA4.js.map → chunk-U672FZVC.js.map} +0 -0
  87. /package/dist/{chunk-I3WF6L43.js.map → chunk-XYBHH3M5.js.map} +0 -0
  88. /package/dist/{load-agent-defs-5U5KAURA.js.map → load-agent-defs-5BSECHE6.js.map} +0 -0
  89. /package/dist/{recompile-project-agents-KYK44C24.js.map → recompile-project-agents-U3UGHPWG.js.map} +0 -0
  90. /package/dist/{source-loader-G5I4RNUP.js.map → source-loader-E2Y5MMN5.js.map} +0 -0
@@ -13,7 +13,7 @@ import {
13
13
  typedFromEntries,
14
14
  typedKeys,
15
15
  typedValues
16
- } from "./chunk-AIBVAYHS.js";
16
+ } from "./chunk-UE4Z3YMB.js";
17
17
  import {
18
18
  AGENT_NAMES,
19
19
  AUTHOR_HANDLE_PATTERN,
@@ -82,7 +82,7 @@ import {
82
82
  seedAgentScope,
83
83
  sourceDirName,
84
84
  validateSelection
85
- } from "./chunk-WUSEZFER.js";
85
+ } from "./chunk-U46LYYRA.js";
86
86
  import {
87
87
  defaultCategories,
88
88
  defaultRules,
@@ -91,8 +91,8 @@ import {
91
91
 
92
92
  // src/cli/lib/loading/source-loader.ts
93
93
  import os14 from "os";
94
- import { partition as partition4, unique as unique6 } from "remeda";
95
- import path29 from "path";
94
+ import { partition as partition6, unique as unique6 } from "remeda";
95
+ import path28 from "path";
96
96
 
97
97
  // src/cli/lib/installation/is-home-directory.ts
98
98
  import fs from "fs";
@@ -105,33 +105,6 @@ function isHomeDirectory(dir) {
105
105
  }
106
106
  }
107
107
 
108
- // src/cli/lib/metadata-keys.ts
109
- var METADATA_KEYS = {
110
- DISPLAY_NAME: "displayName",
111
- CLI_DESCRIPTION: "cliDescription",
112
- CATEGORY: "category",
113
- FORKED_FROM: "forkedFrom",
114
- CONTENT_HASH: "contentHash",
115
- USAGE_GUIDANCE: "usageGuidance",
116
- CUSTOM: "custom"
117
- };
118
- var LOCAL_DEFAULTS = {
119
- AUTHOR: "@dummy-author"
120
- };
121
- var SKILL_CONTENT_FILES = [STANDARD_FILES.SKILL_MD, STANDARD_FILES.REFERENCE_MD];
122
- var SKILL_CONTENT_DIRS = [STANDARD_DIRS.EXAMPLES, STANDARD_DIRS.SCRIPTS];
123
-
124
- // src/cli/utils/errors.ts
125
- function getErrorMessage(error) {
126
- return stripTerminalControls(error instanceof Error ? error.message : String(error));
127
- }
128
-
129
- // src/cli/utils/fs.ts
130
- import fs3 from "fs-extra";
131
- import fg from "fast-glob";
132
- import os3 from "os";
133
- import path2 from "path";
134
-
135
108
  // src/cli/lib/installation/install-layout.ts
136
109
  import fs2 from "fs";
137
110
  import os2 from "os";
@@ -508,11 +481,38 @@ function chooseProviderForThisRun(provider) {
508
481
  function forgetTheProviderChosenForThisRun() {
509
482
  providerChosenForThisRun = void 0;
510
483
  }
484
+ function declaresNoContent(config) {
485
+ return config.skills.length === 0 && config.agents.length === 0;
486
+ }
487
+ var declaresNothingAsRead = /* @__PURE__ */ new Map();
488
+ function recordWhetherItDeclaresNothing(configPath, declaresNothing) {
489
+ const stamp = stampOf(configPath);
490
+ if (stamp !== null) declaresNothingAsRead.set(configPath, { stamp, declaresNothing });
491
+ }
492
+ function declaresNothingAsLastRead(configPath) {
493
+ const read = declaresNothingAsRead.get(configPath);
494
+ if (read === void 0 || read.stamp !== stampOf(configPath)) return void 0;
495
+ return read.declaresNothing;
496
+ }
497
+ function stampOf(file) {
498
+ try {
499
+ const stats = fs2.statSync(file, { throwIfNoEntry: false });
500
+ return stats === void 0 ? null : `${stats.mtimeMs}:${stats.size}`;
501
+ } catch {
502
+ return null;
503
+ }
504
+ }
511
505
  function providerInUse(root) {
512
506
  if (providerChosenForThisRun !== void 0) return providerChosenForThisRun;
513
507
  if (root === void 0) return PROVIDER_OF_A_NEW_INSTALLATION;
514
508
  return providerOnDisk(root);
515
509
  }
510
+ function inheritedGlobalProvider(projectDir, own = providerInUse(projectDir)) {
511
+ return holdsAnInstallation(projectDir, own) ? own : providerInUse(os2.homedir());
512
+ }
513
+ function providerAtScope(projectDir, scope) {
514
+ return scope === "global" ? inheritedGlobalProvider(projectDir) : providerInUse(projectDir);
515
+ }
516
516
  function providerOnDisk(root) {
517
517
  const live = PROVIDERS.find((provider) => holdsAnInstallation(root, provider));
518
518
  if (live !== void 0) return live;
@@ -520,7 +520,8 @@ function providerOnDisk(root) {
520
520
  return started ?? PROVIDER_OF_A_NEW_INSTALLATION;
521
521
  }
522
522
  function holdsAnInstallation(root, provider) {
523
- return holdsAConfig(sourceDir(root, provider));
523
+ const dir = sourceDir(root, provider);
524
+ return holdsAConfig(dir) && declaresNothingAsLastRead(configIn(dir)) !== true;
524
525
  }
525
526
  function holdsASourceFolder(root, provider) {
526
527
  return holdsAnything(sourceDir(root, provider));
@@ -676,7 +677,10 @@ function advisesOnPermissions(provider) {
676
677
  }
677
678
  }
678
679
  function holdsAConfig(dir) {
679
- return fs2.existsSync(path.join(dir, STANDARD_FILES.CONFIG_TS));
680
+ return fs2.existsSync(configIn(dir));
681
+ }
682
+ function configIn(dir) {
683
+ return path.join(dir, STANDARD_FILES.CONFIG_TS);
680
684
  }
681
685
  function holdsAnything(dir) {
682
686
  return directoryExists(dir) && isNotEmpty(dir);
@@ -696,6 +700,29 @@ function directoryExists(dir) {
696
700
  }
697
701
  }
698
702
 
703
+ // src/cli/lib/metadata-keys.ts
704
+ var METADATA_KEYS = {
705
+ DISPLAY_NAME: "displayName",
706
+ CATEGORY: "category",
707
+ CUSTOM: "custom"
708
+ };
709
+ var LOCAL_DEFAULTS = {
710
+ AUTHOR: "@dummy-author"
711
+ };
712
+ var SKILL_CONTENT_FILES = [STANDARD_FILES.SKILL_MD, STANDARD_FILES.REFERENCE_MD];
713
+ var SKILL_CONTENT_DIRS = [STANDARD_DIRS.EXAMPLES, STANDARD_DIRS.SCRIPTS];
714
+
715
+ // src/cli/utils/errors.ts
716
+ function getErrorMessage(error) {
717
+ return stripTerminalControls(error instanceof Error ? error.message : String(error));
718
+ }
719
+
720
+ // src/cli/utils/fs.ts
721
+ import fs3 from "fs-extra";
722
+ import fg from "fast-glob";
723
+ import os3 from "os";
724
+ import path2 from "path";
725
+
699
726
  // src/cli/lib/config-gate/gate-token.ts
700
727
  import { AsyncLocalStorage } from "async_hooks";
701
728
  var gateToken = new AsyncLocalStorage();
@@ -876,6 +903,7 @@ function warnOnce(msg) {
876
903
 
877
904
  // src/cli/utils/messages.ts
878
905
  import path3 from "path";
906
+ import { partition } from "remeda";
879
907
 
880
908
  // src/cli/lib/validate-kebab-name.ts
881
909
  import { unique } from "remeda";
@@ -957,6 +985,16 @@ var INFO_MESSAGES = {
957
985
  CONFIG_TYPES_REFRESHED: `Refreshed ${STANDARD_FILES.CONFIG_TYPES_TS}`,
958
986
  EJECTED_SKILLS_USER_OWNED: `Ejected skills are yours to own \u2014 '${CLI_INVOKE_COMMAND} update' does not change them.`,
959
987
  NO_PLUGIN_MARKETPLACES: "No plugin marketplaces are configured \u2014 nothing to refresh.",
988
+ /**
989
+ * The owner's words (2026-10-24), on stderr before the command's own output. The lines after it
990
+ * are each true of a run this one is not (owner, 2026-10-26: "A line worded for each").
991
+ */
992
+ GLOBAL_INSTALLATION_STANDS_IN: "This project's config declares nothing, so this works on your global installation.",
993
+ NO_GLOBAL_INSTALLATION_STANDS_IN: "This project's config declares nothing, and there is no global installation for this to work on.",
994
+ DECLARES_NOTHING_TO_UNINSTALL: "This project's config declares nothing, so there is nothing to uninstall in this project.",
995
+ DECLARES_NOTHING_TO_EJECT_FROM: "This project's config declares nothing; eject works on this project, not on a global installation.",
996
+ DECLARES_NOTHING_TO_INIT_OVER: "This project's config declares nothing, so init treats this project as holding no installation.",
997
+ DECLARES_NOTHING_TO_SEARCH_FROM: "This project's config declares nothing; search reads the marketplace it names, else the global installation's, else the default one.",
960
998
  /** Under doctor's marketplace row, when its download replaced a saved copy whose files differed. */
961
999
  SAVED_COPY_REPLACED: "The saved copy differed from the marketplace and has been replaced",
962
1000
  /** Printed under the destination already named, so it names files only. */
@@ -971,8 +1009,8 @@ function pluginsInstalled(count) {
971
1009
  function localSkillsCopied(count) {
972
1010
  return `Copied ${count} local skill(s)`;
973
1011
  }
974
- function recompileSummary(rewritten, unchanged, subject) {
975
- return `${plural(rewritten, subject)} rewritten, ${unchanged} unchanged`;
1012
+ function recompileSummary(rewritten2, unchanged, subject) {
1013
+ return `${plural(rewritten2, subject)} rewritten, ${unchanged} unchanged`;
976
1014
  }
977
1015
  function agentsNotCompiled(agentNames) {
978
1016
  return `${agentNames.length} sub-agent(s) did not compile: ${agentNames.join(", ")}`;
@@ -995,21 +1033,18 @@ function propagatedRecompileSummary(recompiled, failed) {
995
1033
  const failureSuffix = failed > 0 ? ` (${failed} failed)` : "";
996
1034
  return `Registered projects: ${recompiled} recompiled${failureSuffix}`;
997
1035
  }
998
- function skillAssignedToNoAgent(skillId) {
999
- return `Skill '${skillId}' is assigned to no sub-agent \u2014 nothing will load it.`;
1036
+ function skillAssignedToNoAgent(skillId, namedBy) {
1037
+ return `Skill '${skillName(skillId, namedBy)}' is assigned to no sub-agent \u2014 nothing will load it.`;
1000
1038
  }
1001
- function scopeBlockedStackAssignment(agentNames, skillId) {
1039
+ function scopeBlockedStackAssignment(agentNames, skillId, namedBy) {
1002
1040
  const subject = agentNames.length === 1 ? "Sub-agent" : "Sub-agents";
1003
1041
  const named = agentNames.map((name) => `'${name}'`).join(", ");
1004
- return `${subject} ${named} cannot carry project-scoped skill '${skillId}' \u2014 global-scoped sub-agents only carry global-scoped skills.`;
1042
+ return `${subject} ${named} cannot carry project-scoped skill '${skillName(skillId, namedBy)}' \u2014 global-scoped sub-agents only carry global-scoped skills.`;
1005
1043
  }
1006
1044
  function globalScopedAgentsHint(count) {
1007
1045
  const [subject, object] = count === 1 ? ["agent is", "it"] : ["agents are", "them"];
1008
1046
  return `${count} ${subject} global-scoped \u2014 run '${CLI_INVOKE_COMMAND} compile' from your home directory to recompile ${object}.`;
1009
1047
  }
1010
- function marketplacesRefreshed(count) {
1011
- return `Refreshed ${plural(count, "marketplace listing")}. Installed plugins were not changed.`;
1012
- }
1013
1048
  function marketplaceRefreshFailed(marketplace, reason) {
1014
1049
  return `Could not update marketplace ${marketplace}: ${reason}`;
1015
1050
  }
@@ -1022,6 +1057,13 @@ function sourceRefusedUsingCache(source, status) {
1022
1057
  function marketplacesRefreshFailed(marketplaces) {
1023
1058
  return `${plural(marketplaces.length, "marketplace")} could not be updated: ${marketplaces.join(", ")}`;
1024
1059
  }
1060
+ function codexPluginLeftSwitchedOn(pluginKey, configFile, reason) {
1061
+ return `Codex switched plugin ${pluginKey} back on when it was updated, and it could not be switched off again in ${configFile}: ${reason}. To switch it off, set enabled = false in that file, in the table it already has for ${pluginKey}.`;
1062
+ }
1063
+ function pluginsLeftSwitchedOn(pluginKeys) {
1064
+ const are = pluginKeys.length === 1 ? "is" : "are";
1065
+ return `${plural(pluginKeys.length, "plugin")} you had switched off ${are} switched on again: ${pluginKeys.join(", ")}`;
1066
+ }
1025
1067
  function marketplaceOwnerHasNoName(packageJsonPath) {
1026
1068
  return [
1027
1069
  `A marketplace's owner must have a name, and no name could be read from 'author' in ${packageJsonPath}.`,
@@ -1089,6 +1131,10 @@ function registeredProjectOnAnotherProvider(projectPath) {
1089
1131
  function registeredProjectUpdateFailed(projectPath) {
1090
1132
  return `Skipped ${projectPath}: updating it failed.`;
1091
1133
  }
1134
+ function unregisteredSkillsSkipped(count, folder) {
1135
+ const are = count === 1 ? "isn't" : "aren't";
1136
+ return `Skipped ${plural(count, "skill")} in '${folder}' that ${are} registered with the CLI (no ${STANDARD_FILES.METADATA_YAML}).`;
1137
+ }
1092
1138
  function skillMetadataUnusableDetail({
1093
1139
  skillDirName,
1094
1140
  metadataPath,
@@ -1137,26 +1183,26 @@ function applySharedConfigHere(id, installedHere, provider) {
1137
1183
  const named = providerArgument(provider);
1138
1184
  return id === void 0 ? `Then ${verb} what it gives you with '${command} <id>${named}'.` : `To ${verb} it here instead, run '${command} ${id}${named}'.`;
1139
1185
  }
1140
- function shareRefusesEjectedSkills(skillIds) {
1186
+ function shareRefusesEjectedSkills(skillIds, namedBy) {
1141
1187
  return [
1142
1188
  "This installation holds ejected skills, and ejected skills cannot be shared:",
1143
- ...skillIds.map((id) => ` ${id}`),
1189
+ ...skillIds.map((id) => ` ${skillName(id, namedBy)}`),
1144
1190
  "Only plugins can be shared."
1145
1191
  ].join("\n");
1146
1192
  }
1147
1193
  function sharedIdNamesAFolder(folder) {
1148
1194
  return `This id names the marketplace folder ${folder}, so it installs only where that folder exists.`;
1149
1195
  }
1150
- function sharedSkillsLeftBehind(skillIds) {
1151
- return `A shared id names one marketplace, and these skill(s) come from another, so they will not install elsewhere: ${skillIds.join(", ")}`;
1196
+ function sharedSkillsLeftBehind(skillIds, namedBy) {
1197
+ return `A shared id names one marketplace, and these skill(s) come from another, so they will not install elsewhere: ${skillNames(skillIds, namedBy)}`;
1152
1198
  }
1153
1199
  function sharedConfigExistingInstall(configPath, id, provider) {
1154
1200
  return `An installation already exists at ${configPath}. Run '${CLI_INVOKE_COMMAND} uninstall${providerArgument(provider)}' first \u2014 ${SHARED_CONFIG_GREENFIELD_HINT}. ${applySharedConfigHere(id, true, provider)}`;
1155
1201
  }
1156
- function sharedConfigProjectScopeAtHome(skillIds, agentNames, installedHere, provider) {
1202
+ function sharedConfigProjectScopeAtHome(skillIds, agentNames, installedHere, provider, namedBy) {
1157
1203
  return [
1158
1204
  "This configuration cannot be installed here: the home directory is the global scope, and a global installation holds only global-scoped content \u2014 so these project-scoped entries have nowhere to be written:",
1159
- ...skillIds.map((id) => ` skill ${id} (scope: project)`),
1205
+ ...skillIds.map((id) => ` skill ${skillName(id, namedBy)} (scope: project)`),
1160
1206
  ...agentNames.map((name) => ` sub-agent ${name} (scope: project)`),
1161
1207
  installedHere ? changeTheGlobalInstallFromHome(providerArgument(provider)) : "Run this from inside a project directory, or re-share it with each entry above at global scope."
1162
1208
  ].join("\n");
@@ -1167,23 +1213,45 @@ function changeTheGlobalInstallFromHome(named) {
1167
1213
  function unknownKeys(keys) {
1168
1214
  return keys.map(inertLine).join(", ");
1169
1215
  }
1170
- function skippedUnknownSkills(skillIds, installedWithout) {
1216
+ var SKIP_CAUSE = {
1217
+ UNLOADED: "could not load",
1218
+ UNKNOWN: "does not know"
1219
+ };
1220
+ function skippedSkills(skillIds, installedWithout, unloadedSkillIds) {
1221
+ const [unloaded, unknown] = partition(skillIds, (id) => unloadedSkillIds.has(id));
1171
1222
  return [
1172
- `Skipped ${skillIds.length} skill(s) this catalog does not know: ${unknownKeys(skillIds)}`,
1173
- ...installedWithout.map(
1174
- ({ skillId, agentNames }) => ` sub-agent(s) installed without ${unknownKeys([skillId])}, carrying nothing else this catalog knows: ${agentNames.join(", ")}`
1175
- )
1223
+ ...skippedFor(SKIP_CAUSE.UNLOADED, unloaded, installedWithout),
1224
+ ...skippedFor(SKIP_CAUSE.UNKNOWN, unknown, installedWithout)
1176
1225
  ].join("\n");
1177
1226
  }
1227
+ function skippedFor(cause, skillIds, installedWithout) {
1228
+ if (skillIds.length === 0) return [];
1229
+ return [
1230
+ `Skipped ${skillIds.length} skill(s) this catalog ${cause}: ${unknownKeys(skillIds)}`,
1231
+ ...installedWithout.filter(({ skillId }) => skillIds.includes(skillId)).map(subAgentsInstalledWithout)
1232
+ ];
1233
+ }
1234
+ function subAgentsInstalledWithout({ skillId, agentNames }) {
1235
+ return ` sub-agent(s) installed without ${unknownKeys([skillId])}, carrying nothing else this catalog knows: ${agentNames.join(", ")}`;
1236
+ }
1178
1237
  function skippedUnknownAgents(agentNames) {
1179
1238
  return `Skipped ${agentNames.length} unknown sub-agent(s): ${unknownKeys(agentNames)}`;
1180
1239
  }
1181
- function carriedSkillsWritten(skillIds) {
1182
- return `Wrote ${skillIds.length} skill(s) this configuration carries: ${skillIds.join(", ")}`;
1240
+ function carriedSkillsWritten(skillIds, namedBy) {
1241
+ return `Wrote ${skillIds.length} skill(s) this configuration carries: ${skillNames(skillIds, namedBy)}`;
1242
+ }
1243
+ function copiedSkillsRemovedAfterRefusedSave(count) {
1244
+ return `Removed the ${plural(count, "skill")} it copied, since the configuration could not be saved`;
1245
+ }
1246
+ function carriedSkillsRemovedAfterRefusedSave(count) {
1247
+ return `Removed the ${plural(count, "skill")} this configuration carried, since the configuration could not be saved`;
1183
1248
  }
1184
1249
  var SHARED_CONFIG_APPLY = {
1185
1250
  CONFIRM: "Apply this configuration?"
1186
1251
  };
1252
+ function idNamesAnotherMarketplace(named, used) {
1253
+ return `This id names the marketplace ${inertLine(named)}; its skills come from this installation's marketplace, ${used}.`;
1254
+ }
1187
1255
  function registeredProjectsRecompiled(projectPaths) {
1188
1256
  return [
1189
1257
  "Each project registered with this global install is recompiled to match:",
@@ -1212,10 +1280,16 @@ function addMarketplaceInTheEditor(ref, isFolder) {
1212
1280
  var SHARED_CONFIG_ARRIVALS = {
1213
1281
  INSTALL_CONFIRM: "Install this configuration?"
1214
1282
  };
1215
- function keptAsInstalledGlobally(skillIds, agentNames, idAppliedAtHome, provider) {
1283
+ function skillName(id, namedBy) {
1284
+ return getSkillEntryDisplayName(namedBy[id] ?? { id });
1285
+ }
1286
+ function skillNames(skillIds, namedBy) {
1287
+ return skillIds.map((id) => skillName(id, namedBy)).join(", ");
1288
+ }
1289
+ function keptAsInstalledGlobally(skillIds, agentNames, idAppliedAtHome, provider, namedBy) {
1216
1290
  return [
1217
1291
  "Kept as installed in the global install \u2014 this configuration states them differently or leaves them out, and a project run only adds to the global install:",
1218
- ...skillIds.map((id) => ` skill ${id}`),
1292
+ ...skillIds.map((id) => ` skill ${skillName(id, namedBy)}`),
1219
1293
  ...agentNames.map((name) => ` sub-agent ${name}`),
1220
1294
  ...idAppliedAtHome === void 0 ? [] : [
1221
1295
  `Change them with '${CLI_INVOKE_COMMAND} edit --from ${idAppliedAtHome}${providerArgument(provider)}' from your home directory.`
@@ -1223,16 +1297,12 @@ function keptAsInstalledGlobally(skillIds, agentNames, idAppliedAtHome, provider
1223
1297
  ].join("\n");
1224
1298
  }
1225
1299
  function skillsMoved(verb, skillIds, namedBy) {
1226
- const names = skillIds.map((id) => getSkillEntryDisplayName(namedBy[id] ?? { id }));
1227
- return names.length === 0 ? [] : [`${verb} ${listed(names)}`];
1300
+ const names = skillIds.map((id) => skillName(id, namedBy));
1301
+ return names.length === 0 ? [] : [`${verb} ${inAnEnglishList(names)}`];
1228
1302
  }
1229
1303
  function settingMoved({ setting, from, to }) {
1230
1304
  return `${setting} ${from ?? "default"} \u2192 ${to ?? "default"}`;
1231
1305
  }
1232
- function listed(names) {
1233
- if (names.length <= 1) return names.join("");
1234
- return `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
1235
- }
1236
1306
  var SKILLS_REASSIGNED = "skills reassigned";
1237
1307
  function subAgentChanged({
1238
1308
  name,
@@ -1250,29 +1320,29 @@ function subAgentChanged({
1250
1320
  const where = scope === "global" ? " (global)" : "";
1251
1321
  return `~ ${name}${where}: ${details.length > 0 ? details.join(", ") : SKILLS_REASSIGNED}`;
1252
1322
  }
1253
- function addedToGlobalSubAgents(joined) {
1323
+ function addedToGlobalSubAgents(joined, namedBy) {
1254
1324
  return [
1255
1325
  "Added to sub-agents in the global install \u2014 each keeps everything it already has, since a project run only adds to the global install:",
1256
1326
  ...joined.map(
1257
- ({ agentName, skillIds }) => ` sub-agent ${agentName}: + ${skillIds.join(", ")}`
1327
+ ({ agentName, skillIds }) => ` sub-agent ${agentName}: + ${skillNames(skillIds, namedBy)}`
1258
1328
  )
1259
1329
  ].join("\n");
1260
1330
  }
1261
- function authoredHereKept(skillIds) {
1331
+ function authoredHereKept(skillIds, namedBy) {
1262
1332
  return [
1263
1333
  "Kept \u2014 written here rather than installed, so a shared configuration never carried them:",
1264
- ...skillIds.map((id) => ` skill ${id}`)
1334
+ ...skillIds.map((id) => ` skill ${skillName(id, namedBy)}`)
1265
1335
  ].join("\n");
1266
1336
  }
1267
- function unplaceableKept(skillIds) {
1337
+ function unplaceableKept(skillIds, namedBy) {
1268
1338
  return [
1269
1339
  "Kept \u2014 named by this configuration, and this catalogue cannot place them:",
1270
- ...skillIds.map((id) => ` skill ${id}`),
1340
+ ...skillIds.map((id) => ` skill ${skillName(id, namedBy)}`),
1271
1341
  `Run '${CLI_INVOKE_COMMAND} update' to refresh this installation's marketplace, then apply the configuration again.`
1272
1342
  ].join("\n");
1273
1343
  }
1274
- function keptUnassigned(skillId, agentName, category) {
1275
- return `${skillId}: kept, no longer assigned to ${agentName}'s ${category}`;
1344
+ function keptUnassigned({ skillId, agentName, category }, namedBy) {
1345
+ return `${skillName(skillId, namedBy)}: kept, no longer assigned to ${agentName}'s ${category}`;
1276
1346
  }
1277
1347
  function doctorReportsTheSame(count) {
1278
1348
  return `'${CLI_INVOKE_COMMAND} doctor' reports the same ${count === 1 ? "file" : "files"}, alongside whatever else is wrong here.`;
@@ -1967,16 +2037,16 @@ import { z as z3 } from "zod";
1967
2037
 
1968
2038
  // src/cli/lib/schemas.ts
1969
2039
  import { z as z2 } from "zod";
1970
- import { partition } from "remeda";
2040
+ import { partition as partition2 } from "remeda";
1971
2041
 
1972
2042
  // src/cli/lib/schema-validator.ts
1973
2043
  import "zod";
1974
2044
  function formatZodIssue(issue) {
1975
- const path30 = stripTerminalControls(issue.path.join("."));
1976
2045
  if (issue.code === "unrecognized_keys") {
1977
- return `Unrecognized key: "${stripTerminalControls(issue.keys.join('", "'))}"`;
2046
+ return `Unrecognized key: "${inertLine(issue.keys.join('", "'))}"`;
1978
2047
  }
1979
- return path30 ? `${path30}: ${stripTerminalControls(issue.message)}` : stripTerminalControls(issue.message);
2048
+ const path29 = inertLine(issue.path.join("."));
2049
+ return path29 ? `${path29}: ${inertLine(issue.message)}` : inertLine(issue.message);
1980
2050
  }
1981
2051
  function formatZodErrors(error) {
1982
2052
  return error.issues.map(formatZodIssue);
@@ -2118,12 +2188,12 @@ var RENAMED_SKILL_ENTRY_FIELDS = { source: "origin" };
2118
2188
  function renamedFieldMessage(oldKey, newKey) {
2119
2189
  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.`;
2120
2190
  }
2121
- function reportRenamedKeys(record, renames, path30, ctx) {
2191
+ function reportRenamedKeys(record, renames, path29, ctx) {
2122
2192
  for (const [oldKey, newKey] of Object.entries(renames)) {
2123
2193
  if (!(oldKey in record)) continue;
2124
2194
  ctx.addIssue({
2125
2195
  code: "custom",
2126
- path: [...path30, oldKey],
2196
+ path: [...path29, oldKey],
2127
2197
  message: renamedFieldMessage(oldKey, newKey)
2128
2198
  });
2129
2199
  }
@@ -2454,7 +2524,7 @@ function metadataSchemaFor(raw) {
2454
2524
  return isCarriedMetadata(raw) ? carriedMetadataValidationSchema : customMetadataValidationSchema;
2455
2525
  }
2456
2526
  function describeMetadataSchemaFailure(issues, rawMetadata) {
2457
- const [absent, malformed] = partition(issues, (issue) => isAbsentField(issue, rawMetadata));
2527
+ const [absent, malformed] = partition2(issues, (issue) => isAbsentField(issue, rawMetadata));
2458
2528
  return [
2459
2529
  ...absent.length > 0 ? [nameMissingFields(absent)] : [],
2460
2530
  ...malformed.map(formatZodIssue)
@@ -2481,7 +2551,7 @@ function formatOverLengthWarning(issue, cliDescription) {
2481
2551
  return `cliDescription is ${cliDescription.length} characters \u2014 exceeds the recommended maximum of ${CLI_DESCRIPTION_MAX_LENGTH}`;
2482
2552
  }
2483
2553
  function splitMetadataValidationIssues(error, rawMetadata) {
2484
- const [overLength, hardFailures] = partition(error.issues, isOverLengthCliDescription);
2554
+ const [overLength, hardFailures] = partition2(error.issues, isOverLengthCliDescription);
2485
2555
  const cliDescription = readCliDescription(rawMetadata);
2486
2556
  const warnings = overLength.map((issue) => formatOverLengthWarning(issue, cliDescription));
2487
2557
  return { errors: hardFailures.map(formatZodIssue), warnings };
@@ -2539,6 +2609,14 @@ var ConfigDefaultExportError = class extends Error {
2539
2609
  }
2540
2610
  configPath;
2541
2611
  };
2612
+ var ConfigEvaluationError = class extends Error {
2613
+ constructor(configPath, reason, options) {
2614
+ super(configLoadFailure(configPath, reason), options);
2615
+ this.configPath = configPath;
2616
+ this.name = "ConfigEvaluationError";
2617
+ }
2618
+ configPath;
2619
+ };
2542
2620
  var ES_MODULE_MARKER = "__esModule";
2543
2621
  function unwrapDefaultExport(namespace) {
2544
2622
  if (isRecord(namespace) && "default" in namespace) return namespace.default;
@@ -2584,7 +2662,7 @@ async function importConfigModule(configPath) {
2584
2662
  });
2585
2663
  return await jiti.import(configPath);
2586
2664
  } catch (error) {
2587
- throw new Error(configLoadFailure(configPath, getErrorMessage(error)), { cause: error });
2665
+ throw new ConfigEvaluationError(configPath, getErrorMessage(error), { cause: error });
2588
2666
  }
2589
2667
  }
2590
2668
 
@@ -2598,7 +2676,7 @@ function getInstalledConfigPath(dir) {
2598
2676
  }
2599
2677
  function resolveInstallPaths(projectDir, scope = "project") {
2600
2678
  const baseDir = installBaseDir(projectDir, scope);
2601
- const provider = providerInUse(baseDir);
2679
+ const provider = providerAtScope(projectDir, scope);
2602
2680
  return {
2603
2681
  skillsDir: skillsDir(provider, scope, projectDir),
2604
2682
  agentsDir: agentsDir(provider, scope, projectDir),
@@ -2620,7 +2698,7 @@ function initProviderToNameAt(root, provider) {
2620
2698
 
2621
2699
  // src/cli/lib/configuration/config.ts
2622
2700
  var SOURCE_ENV_VAR = "CC_MARKETPLACE";
2623
- async function loadSourceConfig(dir, scope, provider) {
2701
+ async function readStoredConfig(dir, scope, provider) {
2624
2702
  const scopeLabel2 = scope === "project" ? "Project" : "Global";
2625
2703
  const configPath = storedConfigPath(dir, provider);
2626
2704
  if (!await fileExists(configPath)) {
@@ -2637,7 +2715,17 @@ async function loadSourceConfig(dir, scope, provider) {
2637
2715
  if (installation === null) throw unreadable(NO_VALID_DEFAULT_EXPORT);
2638
2716
  const settings = readSettings(configPath, installation);
2639
2717
  verbose(`Loaded ${scope} config from ${dir}`);
2640
- return settings;
2718
+ return {
2719
+ settings,
2720
+ declaresNothing: declaresNoContent({
2721
+ skills: installation.skills ?? [],
2722
+ agents: installation.agents ?? []
2723
+ })
2724
+ };
2725
+ }
2726
+ async function loadSourceConfig(dir, scope, provider) {
2727
+ const stored = await readStoredConfig(dir, scope, provider);
2728
+ return stored === null ? null : stored.settings;
2641
2729
  }
2642
2730
  function refuseInstallationConfig(configPath, scope, scopeRoot, provider) {
2643
2731
  const providerToName = providerToNameAt(scopeRoot, provider);
@@ -2688,17 +2776,29 @@ async function loadSourceRepoConfig(basePath) {
2688
2776
  async function loadProjectSourceConfig(projectDir) {
2689
2777
  return loadSourceConfig(projectDir, isHomeDirectory(projectDir) ? "global" : "project");
2690
2778
  }
2691
- async function loadGlobalSourceConfig() {
2692
- return loadSourceConfig(os4.homedir(), "global");
2779
+ async function loadGlobalSourceConfig(provider) {
2780
+ return loadSourceConfig(os4.homedir(), "global", provider);
2693
2781
  }
2694
2782
  async function loadEffectiveSourceConfig(projectDir, provider) {
2695
2783
  const ownDir = ownProjectDir(projectDir);
2696
2784
  const own = ownDir === null ? null : await readStoredRung(ownDir, "project", provider);
2697
- return own ?? await readStoredRung(os4.homedir(), "global", provider);
2785
+ if (own !== null && !readsAsNoConfig(own)) return own;
2786
+ return readStoredRung(os4.homedir(), "global", provider ?? inheritedBy(projectDir));
2787
+ }
2788
+ function readsAsNoConfig(own) {
2789
+ return own.declaresNothing && own.config.marketplace === void 0;
2790
+ }
2791
+ function inheritedBy(projectDir) {
2792
+ return inheritedGlobalProvider(projectDir ?? os4.homedir());
2698
2793
  }
2699
2794
  async function readStoredRung(dir, origin, provider) {
2700
- const config = await loadSourceConfig(dir, origin, provider);
2701
- return config && { config, origin, configPath: storedConfigPath(dir, provider) };
2795
+ const stored = await readStoredConfig(dir, origin, provider);
2796
+ return stored && {
2797
+ config: stored.settings,
2798
+ declaresNothing: stored.declaresNothing,
2799
+ origin,
2800
+ configPath: storedConfigPath(dir, provider)
2801
+ };
2702
2802
  }
2703
2803
  function storedConfigPath(dir, provider) {
2704
2804
  return provider === void 0 ? getInstalledConfigPath(dir) : getProjectConfigPath(dir, provider);
@@ -2732,7 +2832,7 @@ async function resolveSource(request) {
2732
2832
  if (envSource !== void 0) {
2733
2833
  return { source: envSource, sourceOrigin: "env", ...labelFor(envSource) };
2734
2834
  }
2735
- if (effective?.config.marketplace) {
2835
+ if (caller === "stored" && effective?.config.marketplace) {
2736
2836
  assertStoredSourceUsable(effective.config.marketplace, effective.configPath);
2737
2837
  verbose(`Marketplace from ${effective.origin} config: ${effective.config.marketplace}`);
2738
2838
  return {
@@ -2793,7 +2893,7 @@ ${getErrorMessage(error)}`
2793
2893
  async function resolveBranding(projectDir) {
2794
2894
  const [own, global] = await Promise.all([
2795
2895
  loadOwnProjectSourceConfig(projectDir),
2796
- loadGlobalSourceConfig()
2896
+ loadGlobalSourceConfig(inheritedBy(projectDir))
2797
2897
  ]);
2798
2898
  return {
2799
2899
  name: own?.branding?.name ?? global?.branding?.name ?? DEFAULT_BRANDING.NAME
@@ -3139,7 +3239,7 @@ seatDiagnostics({
3139
3239
  });
3140
3240
 
3141
3241
  // ../compile/src/seed-to-config.ts
3142
- import { groupBy, indexBy, isDefined, mapValues, omit as omit2, partition as partition2 } from "remeda";
3242
+ import { groupBy, indexBy, isDefined, mapValues, omit as omit2, partition as partition3 } from "remeda";
3143
3243
 
3144
3244
  // ../compile/src/config-source.ts
3145
3245
  import { omit } from "remeda";
@@ -3171,9 +3271,6 @@ function valueEntries(values) {
3171
3271
  function commentEntry(text) {
3172
3272
  return { kind: "comment", text };
3173
3273
  }
3174
- function sourceEntry(source) {
3175
- return { kind: "source", source };
3176
- }
3177
3274
  function renderValueLine(value, position) {
3178
3275
  return line(
3179
3276
  renderValue(value, position),
@@ -3254,8 +3351,6 @@ function renderEntryLine(entry, indent) {
3254
3351
  switch (entry.kind) {
3255
3352
  case "comment":
3256
3353
  return `${spaces(indent)}// ${entry.text}`;
3257
- case "source":
3258
- return `${spaces(indent)}${entry.source},`;
3259
3354
  case "value":
3260
3355
  return renderValueLine(entry.value, { indent, prefix: "", suffix: "," });
3261
3356
  default: {
@@ -3293,8 +3388,6 @@ function flatEntry(entry) {
3293
3388
  // A `//` line cannot fold onto one line.
3294
3389
  case "comment":
3295
3390
  return void 0;
3296
- case "source":
3297
- return entry.source;
3298
3391
  case "value":
3299
3392
  return flatValue(entry.value);
3300
3393
  default: {
@@ -3479,62 +3572,16 @@ function generateConfigSource(config, catalog, options) {
3479
3572
  }
3480
3573
  function generateProjectConfig(config, catalog, options) {
3481
3574
  const cleaned = cleanForEmission(config, catalog, { dropProjects: true });
3482
- if (options.globalConfig) {
3483
- return generateProjectConfigWithInlinedGlobal(
3484
- cleaned,
3485
- cleanForEmission(options.globalConfig, catalog, { dropProjects: true })
3486
- );
3487
- }
3488
- if (options.globalImportPath !== void 0) {
3489
- return generateProjectConfigWithGlobalImport(
3490
- cleaned,
3491
- options.globalImportPath
3575
+ if (!options.globalConfig) {
3576
+ throw new Error(
3577
+ "A project config needs the global config it extends: pass `globalConfig` to inline its entries."
3492
3578
  );
3493
3579
  }
3494
- throw new Error(
3495
- "A project config needs the global config it extends: pass `globalConfig` to inline its entries, or `globalImportPath` to import and spread them. Neither was given."
3580
+ return generateProjectConfigWithInlinedGlobal(
3581
+ cleaned,
3582
+ cleanForEmission(options.globalConfig, catalog, { dropProjects: true })
3496
3583
  );
3497
3584
  }
3498
- function generateProjectConfigWithGlobalImport(cleaned, globalImportPath) {
3499
- const { skills, agents, stack, selectedDomains } = extractConfigArrays(cleaned);
3500
- const emitted = {
3501
- hasSkills: true,
3502
- hasAgents: true,
3503
- hasStack: hasStackEntries(stack),
3504
- hasSelectedDomains: selectedDomains.length > 0
3505
- };
3506
- const scalarFields = Object.entries(cleaned).filter(([key]) => isCopiedScalarField(key)).map(([key, value]) => renderScalarField(key, value));
3507
- return assembleConfigSource({
3508
- imports: [
3509
- `import globalConfig from ${quoteText(`${globalImportPath}/config`)}`,
3510
- renderTypeImportLine(buildTypeImports(emitted), CONFIG_TYPES_SPECIFIER)
3511
- ],
3512
- declarations: [
3513
- ...entryDeclaration("skills", "SkillConfig[]", [
3514
- sourceEntry("...globalConfig.skills"),
3515
- ...valueEntries(skills)
3516
- ]),
3517
- ...entryDeclaration("agents", "AgentScopeConfig[]", [
3518
- sourceEntry("...globalConfig.agents"),
3519
- ...valueEntries(agents)
3520
- ]),
3521
- ...emitted.hasStack ? declaration("stack", STACK_TYPE, stack) : [],
3522
- ...emitted.hasSelectedDomains ? entryDeclaration("selectedDomains", "Domain[]", [
3523
- sourceEntry("...(globalConfig.selectedDomains ?? [])"),
3524
- ...valueEntries(selectedDomains)
3525
- ]) : []
3526
- ],
3527
- exportFields: [
3528
- ` ...globalConfig,`,
3529
- renderScalarField("name", resolveProjectName(cleaned)),
3530
- ` skills,`,
3531
- ` agents,`,
3532
- ...emitted.hasStack ? [` stack,`] : [],
3533
- ...emitted.hasSelectedDomains ? [` selectedDomains,`] : [],
3534
- ...scalarFields
3535
- ]
3536
- });
3537
- }
3538
3585
  function generateStandaloneConfig(cleaned) {
3539
3586
  const { skills, agents, stack, selectedDomains } = extractConfigArrays(cleaned);
3540
3587
  const emitted = {
@@ -3650,18 +3697,25 @@ function stackOfAgents(stack, agents) {
3650
3697
  function mergeInlinedScalarFields(cleaned, cleanedGlobal) {
3651
3698
  return Object.entries(
3652
3699
  canonicalizeFieldOrder({
3653
- ...omit(cleanedGlobal, GLOBAL_IDENTITY_FIELDS),
3700
+ ...globalScalarsLentTo(cleaned, cleanedGlobal),
3654
3701
  ...cleaned
3655
3702
  })
3656
3703
  ).filter(([key]) => isCopiedScalarField(key));
3657
3704
  }
3705
+ function globalScalarsLentTo(cleaned, cleanedGlobal) {
3706
+ const lent = omit(cleanedGlobal, GLOBAL_IDENTITY_FIELDS);
3707
+ return recordsAnotherMarketplace(cleaned, cleanedGlobal) ? omit(lent, ["marketplaceName"]) : lent;
3708
+ }
3709
+ function recordsAnotherMarketplace(cleaned, cleanedGlobal) {
3710
+ return cleaned.marketplace !== void 0 && cleaned.marketplace !== cleanedGlobal.marketplace;
3711
+ }
3658
3712
  function generateProjectConfigWithInlinedGlobal(cleaned, cleanedGlobal) {
3659
- const partition5 = partitionInlinedConfigEntries(cleaned, cleanedGlobal);
3713
+ const partition7 = partitionInlinedConfigEntries(cleaned, cleanedGlobal);
3660
3714
  const emitted = {
3661
- hasSkills: partition5.globalSkills.length > 0 || partition5.projectSkills.length > 0,
3662
- hasAgents: partition5.globalAgents.length > 0 || partition5.projectAgents.length > 0,
3663
- hasStack: hasStackEntries(partition5.filteredStack),
3664
- hasSelectedDomains: partition5.selectedDomains.length > 0
3715
+ hasSkills: partition7.globalSkills.length > 0 || partition7.projectSkills.length > 0,
3716
+ hasAgents: partition7.globalAgents.length > 0 || partition7.projectAgents.length > 0,
3717
+ hasStack: hasStackEntries(partition7.filteredStack),
3718
+ hasSelectedDomains: partition7.selectedDomains.length > 0
3665
3719
  };
3666
3720
  const scalarFields = mergeInlinedScalarFields(cleaned, cleanedGlobal);
3667
3721
  return assembleConfigSource({
@@ -3672,15 +3726,15 @@ function generateProjectConfigWithInlinedGlobal(cleaned, cleanedGlobal) {
3672
3726
  ...emitted.hasSkills ? entryDeclaration(
3673
3727
  "skills",
3674
3728
  "SkillConfig[]",
3675
- scopeSectionEntries(partition5.globalSkills, partition5.projectSkills)
3729
+ scopeSectionEntries(partition7.globalSkills, partition7.projectSkills)
3676
3730
  ) : [],
3677
3731
  ...emitted.hasAgents ? entryDeclaration(
3678
3732
  "agents",
3679
3733
  "AgentScopeConfig[]",
3680
- scopeSectionEntries(partition5.globalAgents, partition5.projectAgents)
3734
+ scopeSectionEntries(partition7.globalAgents, partition7.projectAgents)
3681
3735
  ) : [],
3682
- ...emitted.hasStack ? declaration("stack", STACK_TYPE, partition5.filteredStack) : [],
3683
- ...emitted.hasSelectedDomains ? declaration("selectedDomains", "Domain[]", partition5.selectedDomains) : []
3736
+ ...emitted.hasStack ? declaration("stack", STACK_TYPE, partition7.filteredStack) : [],
3737
+ ...emitted.hasSelectedDomains ? declaration("selectedDomains", "Domain[]", partition7.selectedDomains) : []
3684
3738
  ],
3685
3739
  exportFields: [
3686
3740
  renderScalarField("name", resolveProjectName(cleaned)),
@@ -3835,7 +3889,7 @@ function assertScopeConfigsGiven(skillConfigs, agentConfigs) {
3835
3889
  }
3836
3890
  }
3837
3891
  function resolveValidSkills(selectedSkillIds, skillConfigs, catalog) {
3838
- const [found, absent] = partition2(
3892
+ const [found, absent] = partition3(
3839
3893
  selectedSkillIds.map((skillId) => ({
3840
3894
  skillId,
3841
3895
  skill: catalog.skills[skillId]
@@ -3885,7 +3939,7 @@ function skillIdsByCategory(skills) {
3885
3939
  );
3886
3940
  }
3887
3941
  function buildSkillScopeMap(skillConfigs) {
3888
- const [excludedConfigs, activeConfigs] = partition2(
3942
+ const [excludedConfigs, activeConfigs] = partition3(
3889
3943
  skillConfigs,
3890
3944
  (s) => Boolean(s.excluded)
3891
3945
  );
@@ -4030,11 +4084,11 @@ function mapCategoriesDroppingEmpty(agentStack, transform) {
4030
4084
  );
4031
4085
  }
4032
4086
  function splitConfigByScope(config) {
4033
- const [globalSkills, projectSkills] = partition2(
4087
+ const [globalSkills, projectSkills] = partition3(
4034
4088
  config.skills,
4035
4089
  (s) => isActiveAt(s, "global")
4036
4090
  );
4037
- const [globalAgents, projectAgents] = partition2(
4091
+ const [globalAgents, projectAgents] = partition3(
4038
4092
  config.agents,
4039
4093
  (a) => isActiveAt(a, "global")
4040
4094
  );
@@ -4094,9 +4148,9 @@ function splitAgentStack(agentStack, globalSkillIds) {
4094
4148
  }
4095
4149
  function seedToInstallSelection(payload, catalog) {
4096
4150
  const agentMap = readAgentMap(payload.agents);
4151
+ refuseUnwritableAssignments(payload.skills, agentMap);
4097
4152
  const { installable, skipped } = resolveSeedSkills(payload.skills, catalog);
4098
4153
  const rows = installable.flatMap((skill) => assignmentRows(skill, agentMap));
4099
- refuseUnwritableAssignments(rows);
4100
4154
  const placed = rows.filter((row) => row.kind === "placed");
4101
4155
  const carried = new Set(agentsCarriedIn(placed, agentMap));
4102
4156
  const selectedAgents = withAgentsSkippedSkillsName(carried, skipped, agentMap);
@@ -4130,12 +4184,23 @@ function seedToInstallSelection(payload, catalog) {
4130
4184
  )
4131
4185
  };
4132
4186
  }
4133
- function refuseUnwritableAssignments(rows) {
4134
- const unwritable = rows.filter((row) => row.kind === "unwritable");
4187
+ function refuseUnwritableAssignments(seedSkills, agentMap) {
4188
+ const unwritable = unwritableAssignmentsOf(seedSkills, agentMap);
4135
4189
  if (unwritable.length > 0) {
4136
4190
  throw new Error(unwritableAssignmentsError(unwritable));
4137
4191
  }
4138
4192
  }
4193
+ function unwritableAssignmentsOf(seedSkills, agentMap) {
4194
+ return Object.entries(seedSkills).flatMap(
4195
+ ([skillId, seedSkill]) => agentsNamedBy(seedSkill, agentMap).filter((agent) => !isWritablePair(seedSkill, agent, agentMap)).map((agent) => ({ skillId, agent }))
4196
+ );
4197
+ }
4198
+ function isWritablePair(seedSkill, agent, agentMap) {
4199
+ return isScopePairCompatible(
4200
+ seedSkill.scope,
4201
+ seedAgentScope(agentMap.known.get(agent))
4202
+ );
4203
+ }
4139
4204
  function agentsCarriedIn(placed, agentMap) {
4140
4205
  return [...placed.map((row) => row.agent), ...agentsSwitchedOn(agentMap)];
4141
4206
  }
@@ -4143,7 +4208,7 @@ function withAgentsSkippedSkillsName(carried, skipped, agentMap) {
4143
4208
  return [
4144
4209
  .../* @__PURE__ */ new Set([
4145
4210
  ...carried,
4146
- ...skipped.flatMap((skill) => agentsNamedBy(skill, agentMap))
4211
+ ...skipped.flatMap((skill) => agentsNamedBy(skill.seedSkill, agentMap))
4147
4212
  ])
4148
4213
  ];
4149
4214
  }
@@ -4151,25 +4216,25 @@ function skippedAgentNamesOf(agentMap, rows) {
4151
4216
  const unknownAssignees = rows.filter((row) => row.kind === "unknownAgent").map((row) => row.agentName);
4152
4217
  return [.../* @__PURE__ */ new Set([...agentMap.unknown, ...unknownAssignees])];
4153
4218
  }
4154
- function agentsNamedBy({ seedSkill }, agentMap) {
4219
+ function agentsNamedBy(seedSkill, agentMap) {
4155
4220
  return Object.keys(seedSkill.assignments).filter(isAgentName2).filter((name) => !agentMap.switchedOff.has(name));
4156
4221
  }
4157
4222
  function agentsOnlyItCarried(skill, agentMap, carried) {
4158
- const agentNames = agentsNamedBy(skill, agentMap).filter(
4223
+ const agentNames = agentsNamedBy(skill.seedSkill, agentMap).filter(
4159
4224
  (name) => !carried.has(name)
4160
4225
  );
4161
4226
  return agentNames.length === 0 ? [] : [{ skillId: skill.skillId, agentNames }];
4162
4227
  }
4163
4228
  function readAgentMap(agents) {
4164
4229
  const known = /* @__PURE__ */ new Map();
4165
- const switchedOff = /* @__PURE__ */ new Set();
4230
+ const switchedOff2 = /* @__PURE__ */ new Set();
4166
4231
  const unknown = [];
4167
4232
  for (const [name, entry] of Object.entries(agents)) {
4168
4233
  if (!isAgentName2(name)) unknown.push(name);
4169
- else if (entry.on === false) switchedOff.add(name);
4234
+ else if (entry.on === false) switchedOff2.add(name);
4170
4235
  else known.set(name, entry);
4171
4236
  }
4172
- return { known, switchedOff, unknown };
4237
+ return { known, switchedOff: switchedOff2, unknown };
4173
4238
  }
4174
4239
  function agentsSwitchedOn(agentMap) {
4175
4240
  return [...agentMap.known].filter(([, entry]) => entry.on === true).map(([name]) => name);
@@ -4219,10 +4284,6 @@ function assignmentRows(skill, agentMap) {
4219
4284
  function assignmentRow(skill, agentName, load, agentMap) {
4220
4285
  if (agentMap.switchedOff.has(agentName)) return void 0;
4221
4286
  if (!isAgentName2(agentName)) return { kind: "unknownAgent", agentName };
4222
- const agentScope = seedAgentScope(agentMap.known.get(agentName));
4223
- if (!isScopePairCompatible(skill.seedSkill.scope, agentScope)) {
4224
- return { kind: "unwritable", skillId: skill.id, agent: agentName };
4225
- }
4226
4287
  return {
4227
4288
  kind: "placed",
4228
4289
  agent: agentName,
@@ -4281,7 +4342,7 @@ import path8 from "path";
4281
4342
 
4282
4343
  // src/cli/lib/stacks/stacks-loader.ts
4283
4344
  import path7 from "path";
4284
- import { flatMap, groupBy as groupBy2, mapValues as mapValues2, partition as partition3, pipe, unique as unique2, uniqueBy } from "remeda";
4345
+ import { flatMap, groupBy as groupBy2, mapValues as mapValues2, partition as partition4, pipe, unique as unique2, uniqueBy } from "remeda";
4285
4346
  var stacksCache = /* @__PURE__ */ new Map();
4286
4347
  function normalizeAgentConfig(agentConfig) {
4287
4348
  return mapValues2(agentConfig, (value) => {
@@ -4312,7 +4373,7 @@ function placeAssignments(agentConfig) {
4312
4373
  );
4313
4374
  }
4314
4375
  function rekeyToLiveCategories(agentConfig) {
4315
- const [settled, moved] = partition3(
4376
+ const [settled, moved] = partition4(
4316
4377
  placeAssignments(agentConfig),
4317
4378
  (placed) => placed.liveCategory === placed.storedCategory
4318
4379
  );
@@ -4335,6 +4396,12 @@ function normalizeStackRecord(rawStack) {
4335
4396
  (agentConfig) => rekeyToLiveCategories(normalizeAgentConfig(agentConfig))
4336
4397
  );
4337
4398
  }
4399
+ var StacksLoadError = class extends Error {
4400
+ constructor(stacksPath, reason, options) {
4401
+ super(`Failed to load stacks from '${stacksPath}': ${reason}`, options);
4402
+ this.name = "StacksLoadError";
4403
+ }
4404
+ };
4338
4405
  async function loadStacks(configDir, stacksFile) {
4339
4406
  const resolvedStacksFile = stacksFile ?? STACKS_FILE_PATH;
4340
4407
  const cacheKey = `${configDir}:${resolvedStacksFile}`;
@@ -4358,14 +4425,12 @@ async function loadStacks(configDir, stacksFile) {
4358
4425
  verbose(`Loaded ${stacks.length} stacks from ${stacksPath}`);
4359
4426
  return stacks;
4360
4427
  } catch (error) {
4361
- const errorMessage = getErrorMessage(error);
4362
- throw new Error(`Failed to load stacks from '${stacksPath}': ${errorMessage}`, {
4363
- cause: error
4364
- });
4428
+ throw new StacksLoadError(stacksPath, getErrorMessage(error), { cause: error });
4365
4429
  }
4366
4430
  }
4367
4431
  async function loadStackById(stackId, configDir, source) {
4368
- const stacks = await loadStacks(configDir);
4432
+ const declared = await loadSourceRepoConfig(configDir);
4433
+ const stacks = await loadStacks(configDir, declared?.stacksFile);
4369
4434
  const stack = stacks.find((s) => s.id === stackId);
4370
4435
  if (stack) {
4371
4436
  verbose(`Found stack: ${stack.name} (${stackId})`);
@@ -4385,8 +4450,15 @@ async function loadStackById(stackId, configDir, source) {
4385
4450
  verbose(`Found default stack: ${defaultStack.name} (${stackId})`);
4386
4451
  return defaultStack;
4387
4452
  }
4388
- function stackNotOfferedMessage(stackId, source) {
4389
- return `Stack '${inertLine(stackId)}' is not a stack the source '${source}' offers. A marketplace ships its own stacks; the CLI's built-in ones belong to the default public marketplace alone.`;
4453
+ function stackNotOfferedMessage(stackId, source, shippedStackIds) {
4454
+ return `Stack '${inertLine(stackId)}' is not a stack the source '${source}' offers. A marketplace ships its own stacks; the CLI's built-in ones belong to the default public marketplace alone. ${whatItShips(shippedStackIds)}`;
4455
+ }
4456
+ function whatItShips(stackIds) {
4457
+ if (stackIds.length === 0) return "It ships no stacks.";
4458
+ return withClosingFullStop(`It ships: ${inAnEnglishList(stackIds.map(inertLine))}`);
4459
+ }
4460
+ function withClosingFullStop(sentence) {
4461
+ return sentence.endsWith(".") ? sentence : `${sentence}.`;
4390
4462
  }
4391
4463
  function defaultUsageGuidance(category) {
4392
4464
  return `Use when working with ${category}.`;
@@ -4437,6 +4509,11 @@ var ConfigLoadError = class extends Error {
4437
4509
  /** Named in the remedy's `init --from` half, which a bare run would answer with another one. */
4438
4510
  initProviderToName;
4439
4511
  };
4512
+ async function configDeclaresNothing(projectDir, provider) {
4513
+ const configPath = getProjectConfigPath(projectDir, provider);
4514
+ const parsed = await parseConfigFile(configPath, refusalAt(configPath, projectDir, provider));
4515
+ return declaresNoContent({ skills: parsed.skills ?? [], agents: parsed.agents ?? [] });
4516
+ }
4440
4517
  async function loadProjectConfigFromDir(projectDir, provider) {
4441
4518
  return loadProjectConfigAt(getProjectConfigPath(projectDir, provider), projectDir, provider);
4442
4519
  }
@@ -4447,8 +4524,7 @@ async function loadProjectConfigAt(configPath, projectDir, provider) {
4447
4524
  verbose(`${scopeLabel2} config not found at ${configPath}`);
4448
4525
  return null;
4449
4526
  }
4450
- const refuse = (reason) => new ConfigLoadError(configPath, reason, projectDir, provider);
4451
- const parsed = await parseConfigFile(configPath, refuse);
4527
+ const parsed = await parseConfigFile(configPath, refusalAt(configPath, projectDir, provider));
4452
4528
  const config = asProjectConfig(parsed, projectDir);
4453
4529
  warnOfDefaultedFields(parsed, configPath);
4454
4530
  verbose(`Loaded ${scope} config from ${configPath}`);
@@ -4458,6 +4534,9 @@ async function loadProjectConfigAt(configPath, projectDir, provider) {
4458
4534
  provider
4459
4535
  };
4460
4536
  }
4537
+ function refusalAt(configPath, projectDir, provider) {
4538
+ return (reason) => new ConfigLoadError(configPath, reason, projectDir, provider);
4539
+ }
4461
4540
  async function parseConfigFile(configPath, refuse) {
4462
4541
  let raw;
4463
4542
  try {
@@ -4493,29 +4572,39 @@ function warnOfDefaultedFields(parsed, configPath) {
4493
4572
  async function loadInstalledConfig(dir) {
4494
4573
  return loadProjectConfigFromDir(dir, providerInUse(dir));
4495
4574
  }
4496
- function configDirsInPlay(projectDir) {
4497
- return isHomeDirectory(projectDir) ? [projectDir] : [projectDir, os5.homedir()];
4575
+ function configsInPlay(projectDir) {
4576
+ const own = { dir: projectDir, provider: providerInUse(projectDir) };
4577
+ if (isHomeDirectory(projectDir)) return [own];
4578
+ return [own, { dir: os5.homedir(), provider: inheritedGlobalProvider(projectDir) }];
4498
4579
  }
4499
4580
  async function findConfigLoadFailures(projectDir) {
4500
- const outcomes = await Promise.all(configDirsInPlay(projectDir).map(configLoadFailure2));
4581
+ const outcomes = await Promise.all(configsInPlay(projectDir).map(configLoadFailure2));
4501
4582
  return outcomes.filter((failure) => failure !== void 0);
4502
4583
  }
4503
- async function configLoadFailure2(dir) {
4584
+ async function configLoadFailure2({
4585
+ dir,
4586
+ provider
4587
+ }) {
4504
4588
  try {
4505
- await loadInstalledConfig(dir);
4589
+ await loadProjectConfigFromDir(dir, provider);
4506
4590
  return void 0;
4507
4591
  } catch (error) {
4508
4592
  if (error instanceof ConfigLoadError) return error;
4509
4593
  throw error;
4510
4594
  }
4511
4595
  }
4512
- async function loadProjectConfig(projectDir) {
4513
- const provider = providerInUse(projectDir);
4596
+ async function loadProjectConfig(projectDir, provider = providerInUse(projectDir)) {
4514
4597
  const projectResult = await loadProjectConfigFromDir(projectDir, provider);
4515
4598
  if (projectResult) return projectResult;
4516
4599
  if (isHomeDirectory(projectDir)) return null;
4517
4600
  return loadProjectConfigFromDir(os5.homedir(), provider);
4518
4601
  }
4602
+ async function loadConfigInPlay(projectDir) {
4603
+ const own = await loadInstalledConfig(projectDir);
4604
+ if (isHomeDirectory(projectDir)) return own;
4605
+ if (own !== null && !declaresNoContent(own.config)) return own;
4606
+ return loadInstalledConfig(os5.homedir());
4607
+ }
4519
4608
  function validateProjectConfig(config) {
4520
4609
  const result = projectConfigLoaderSchema.safeParse(config);
4521
4610
  if (!result.success) {
@@ -4561,6 +4650,16 @@ function editDrops(incoming, existing, identity, authoritativeScope) {
4561
4650
  incomingIdentities.has(identity.identityOf(entry)) || dualScopeIdentities.has(identity.identityOf(entry)) || authoritativeScope !== void 0 && isWithinSessionAuthority(entry, authoritativeScope)
4562
4651
  );
4563
4652
  }
4653
+ function marketplaceNameBeside(merged, existing) {
4654
+ if (merged.marketplace === existing.marketplace) {
4655
+ return existing.marketplaceName ?? merged.marketplaceName;
4656
+ }
4657
+ if (wouldBorrowAnotherMarketplaceName(merged, existing)) return DEFAULT_PUBLIC_SOURCE_NAME;
4658
+ return merged.marketplaceName;
4659
+ }
4660
+ function wouldBorrowAnotherMarketplaceName(merged, existing) {
4661
+ return merged.marketplace === DEFAULT_SOURCE && merged.marketplaceName === void 0 && existing.marketplaceName !== void 0;
4662
+ }
4564
4663
  function mergeConfigs(newConfig, existingConfig, options) {
4565
4664
  const merged = { ...newConfig };
4566
4665
  if (existingConfig.name) {
@@ -4590,8 +4689,9 @@ function mergeConfigs(newConfig, existingConfig, options) {
4590
4689
  if (existingConfig.author) {
4591
4690
  merged.author = existingConfig.author;
4592
4691
  }
4593
- if (existingConfig.marketplaceName && merged.marketplace === existingConfig.marketplace) {
4594
- merged.marketplaceName = existingConfig.marketplaceName;
4692
+ const marketplaceName = marketplaceNameBeside(merged, existingConfig);
4693
+ if (marketplaceName !== void 0) {
4694
+ merged.marketplaceName = marketplaceName;
4595
4695
  }
4596
4696
  Object.assign(merged, pick(existingConfig, HAND_WRITTEN_FIELDS));
4597
4697
  if (existingConfig.projects && !newConfig.projects) {
@@ -4870,6 +4970,21 @@ async function copySkillsToLocalFlattened(selectedSkillIds, localSkillsDir, sour
4870
4970
  (skillId) => copySelectedSkill(skillId, localSkillsDir, sourceResult, sourceSelections?.[skillId])
4871
4971
  );
4872
4972
  }
4973
+ async function copyMarketplaceSkillsToLocalFlattened(marketplaceSkillIds, localSkillsDir, sourceResult) {
4974
+ return copyEachSkill(
4975
+ marketplaceSkillIds,
4976
+ (skillId) => copySkillToLocalFlattened(
4977
+ marketplaceSkillOf(skillId, sourceResult),
4978
+ localSkillsDir,
4979
+ sourceResult
4980
+ )
4981
+ );
4982
+ }
4983
+ function marketplaceSkillOf(skillId, sourceResult) {
4984
+ const skill = sourceResult.marketplaceSkills[skillId];
4985
+ if (!skill) throw new Error(`Not one of the marketplace's skills: ${skillId}`);
4986
+ return skill;
4987
+ }
4873
4988
  async function copySelectedSkill(skillId, localSkillsDir, sourceResult, selectedSource) {
4874
4989
  const skill = getSkillById(skillId);
4875
4990
  const localPath = picksRemoteSource(selectedSource) ? void 0 : localPathOf(skill);
@@ -4896,7 +5011,7 @@ async function copyLocalSkillTo(skill, localPath, destPath) {
4896
5011
  }
4897
5012
 
4898
5013
  // src/cli/lib/skills/skill-plugin-compiler.ts
4899
- import path24 from "path";
5014
+ import path23 from "path";
4900
5015
  import { parse as parseYaml4 } from "yaml";
4901
5016
 
4902
5017
  // src/cli/lib/plugins/plugin-manifest.ts
@@ -5003,7 +5118,6 @@ import { readdir } from "fs/promises";
5003
5118
 
5004
5119
  // src/cli/lib/installation/installation.ts
5005
5120
  import os7 from "os";
5006
- import path14 from "path";
5007
5121
  var INSTALL_MODE_LABELS = {
5008
5122
  plugin: "Plugin",
5009
5123
  mixed: "Mixed",
@@ -5013,9 +5127,6 @@ var INSTALL_MODE_DESCRIPTIONS = {
5013
5127
  plugin: `${INSTALL_MODE_LABELS.plugin} (native install)`,
5014
5128
  eject: `${INSTALL_MODE_LABELS.eject} (local copy)`
5015
5129
  };
5016
- function declaresNoContent(config) {
5017
- return config.skills.length === 0 && config.agents.length === 0;
5018
- }
5019
5130
  function deriveInstallMode(skills) {
5020
5131
  if (skills.length === 0) return "eject";
5021
5132
  const hasEject = skills.some((s) => s.origin === EJECT_SOURCE);
@@ -5023,8 +5134,7 @@ function deriveInstallMode(skills) {
5023
5134
  if (hasEject && hasPlugin) return "mixed";
5024
5135
  return hasEject ? "eject" : "plugin";
5025
5136
  }
5026
- async function detectInstallationInDir(dir, scope) {
5027
- const provider = providerInUse(dir);
5137
+ async function detectInstallationInDir(dir, scope, provider = providerInUse(dir)) {
5028
5138
  const configPath = getProjectConfigPath(dir, provider);
5029
5139
  if (!await fileExists(configPath)) {
5030
5140
  return null;
@@ -5036,17 +5146,10 @@ async function detectInstallationInDir(dir, scope) {
5036
5146
  if (declaresNoContent(loaded.config)) {
5037
5147
  return null;
5038
5148
  }
5039
- const mode = deriveInstallMode(loaded.config.skills);
5040
5149
  return {
5041
- mode,
5150
+ mode: deriveInstallMode(loaded.config.skills),
5042
5151
  configPath,
5043
5152
  agentsDir: agentsDir(provider, scope, dir),
5044
- // Mixed mode uses the local skills dir, as eject does.
5045
- skillsDir: path14.join(
5046
- dir,
5047
- CLAUDE_DIR,
5048
- mode === "plugin" ? PLUGINS_SUBDIR : STANDARD_DIRS.SKILLS
5049
- ),
5050
5153
  projectDir: dir
5051
5154
  };
5052
5155
  }
@@ -5061,23 +5164,23 @@ async function holdsItsOwnInstallation(dir) {
5061
5164
  throw error;
5062
5165
  }
5063
5166
  }
5064
- async function detectGlobalInstallation() {
5065
- return detectInstallationInDir(os7.homedir(), "global");
5167
+ async function detectGlobalInstallation(projectDir) {
5168
+ return detectInstallationInDir(os7.homedir(), "global", inheritedGlobalProvider(projectDir));
5066
5169
  }
5067
5170
  async function detectInstallation(projectDir = process.cwd()) {
5068
5171
  const projectInstallation = await detectProjectInstallation(projectDir);
5069
5172
  if (projectInstallation) return projectInstallation;
5070
- return detectGlobalInstallation();
5173
+ return detectGlobalInstallation(projectDir);
5071
5174
  }
5072
5175
 
5073
5176
  // src/cli/lib/installation/local-installer.ts
5074
5177
  import os10 from "os";
5075
- import path18 from "path";
5178
+ import path17 from "path";
5076
5179
  import { unique as unique4 } from "remeda";
5077
5180
 
5078
5181
  // src/cli/lib/loading/loader.ts
5079
5182
  import { parse as parseYaml3 } from "yaml";
5080
- import path15 from "path";
5183
+ import path14 from "path";
5081
5184
 
5082
5185
  // src/cli/utils/frontmatter.ts
5083
5186
  import { parse as parseYaml2 } from "yaml";
@@ -5150,7 +5253,7 @@ async function loadAgentsFromDir(agentsDir2, sourceRoot, options) {
5150
5253
  return agents;
5151
5254
  }
5152
5255
  async function readAgentDefinition(agentsDir2, file, sourceRoot, options) {
5153
- const fullPath = path15.join(agentsDir2, file);
5256
+ const fullPath = path14.join(agentsDir2, file);
5154
5257
  try {
5155
5258
  const content = await readFile(fullPath);
5156
5259
  const parsed = agentYamlConfigSchema.safeParse(parseYaml3(content));
@@ -5183,7 +5286,7 @@ function toAgentDefinition(config, file, sourceRoot, options) {
5183
5286
  ...config.hooks !== void 0 && { hooks: config.hooks },
5184
5287
  ...config.experimental !== void 0 && { experimental: config.experimental },
5185
5288
  ...config.outputFormat !== void 0 && { outputFormat: config.outputFormat },
5186
- path: path15.dirname(file),
5289
+ path: path14.dirname(file),
5187
5290
  sourceRoot,
5188
5291
  ...options.agentBaseDir ? { agentBaseDir: options.agentBaseDir } : {},
5189
5292
  ...config.domain ? { domain: config.domain } : {},
@@ -5191,7 +5294,7 @@ function toAgentDefinition(config, file, sourceRoot, options) {
5191
5294
  };
5192
5295
  }
5193
5296
  async function loadAllAgents(projectRoot) {
5194
- return loadAgentsFromDir(path15.join(projectRoot, DIRS.agents), projectRoot, {
5297
+ return loadAgentsFromDir(path14.join(projectRoot, DIRS.agents), projectRoot, {
5195
5298
  includeCustomFlag: true,
5196
5299
  verboseLabel: "agent"
5197
5300
  });
@@ -5203,9 +5306,9 @@ async function loadMergedAgents(sourcePath) {
5203
5306
  ]);
5204
5307
  return { ...cliAgents, ...sourceAgents };
5205
5308
  }
5206
- async function loadProjectAgents(projectRoot) {
5207
- const sourceFolder = sourceFolderInUse(projectRoot, providerInUse(projectRoot));
5208
- const projectAgentsDir = path15.join(sourceFolder.dir, STANDARD_DIRS.AGENTS);
5309
+ async function loadProjectAgents(projectRoot, provider = providerInUse(projectRoot)) {
5310
+ const sourceFolder = sourceFolderInUse(projectRoot, provider);
5311
+ const projectAgentsDir = path14.join(sourceFolder.dir, STANDARD_DIRS.AGENTS);
5209
5312
  if (!await directoryExists2(projectAgentsDir)) {
5210
5313
  verbose(`No project agents directory at ${projectAgentsDir}`);
5211
5314
  return {};
@@ -5217,9 +5320,10 @@ async function loadProjectAgents(projectRoot) {
5217
5320
  });
5218
5321
  }
5219
5322
  async function loadSkillsFromDir(skillsDir2, options = {}) {
5220
- const { pathPrefix = "", requireMetadata = false } = options;
5323
+ const { pathPrefix = "", requireMetadata = false, folderLabel = `${pathPrefix}/` } = options;
5221
5324
  const skills = {};
5222
5325
  const unusableMetadata = [];
5326
+ let unregisteredCount = 0;
5223
5327
  if (!await directoryExists2(skillsDir2)) {
5224
5328
  return { skills, unusableMetadata };
5225
5329
  }
@@ -5229,32 +5333,32 @@ async function loadSkillsFromDir(skillsDir2, options = {}) {
5229
5333
  if (requireMetadata) {
5230
5334
  const verdict = await judgeSkillMetadata(skill);
5231
5335
  if (verdict.kind === "unusable") unusableMetadata.push(verdict.refusal);
5336
+ if (verdict.kind === "unregistered") unregisteredCount++;
5232
5337
  if (verdict.kind !== "admitted") continue;
5233
5338
  }
5234
5339
  const definition = await readSkillDefinition(skill);
5235
5340
  if (definition) skills[definition.id] = definition;
5236
5341
  }
5342
+ if (unregisteredCount > 0) warn(unregisteredSkillsSkipped(unregisteredCount, folderLabel));
5237
5343
  return { skills, unusableMetadata };
5238
5344
  }
5239
5345
  function locateSkill(skillsDir2, skillFile, pathPrefix) {
5240
- const skillMdPath = path15.join(skillsDir2, skillFile);
5241
- const skillDir = path15.dirname(skillMdPath);
5242
- const relativePath = path15.relative(skillsDir2, skillDir);
5346
+ const skillMdPath = path14.join(skillsDir2, skillFile);
5347
+ const skillDir = path14.dirname(skillMdPath);
5348
+ const relativePath = path14.relative(skillsDir2, skillDir);
5243
5349
  return {
5244
5350
  skillFile,
5245
5351
  skillMdPath,
5246
5352
  skillDir,
5247
- skillDirName: path15.basename(skillDir),
5353
+ skillDirName: path14.basename(skillDir),
5248
5354
  displayPath: pathPrefix ? `${pathPrefix}/${relativePath}/` : `${relativePath}/`
5249
5355
  };
5250
5356
  }
5251
5357
  async function judgeSkillMetadata(skill) {
5252
- const metadataPath = path15.join(skill.skillDir, STANDARD_FILES.METADATA_YAML);
5358
+ const metadataPath = path14.join(skill.skillDir, STANDARD_FILES.METADATA_YAML);
5253
5359
  if (!await fileExists(metadataPath)) {
5254
- warn(
5255
- `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.`
5256
- );
5257
- return { kind: "skipped" };
5360
+ verbose(` No ${STANDARD_FILES.METADATA_YAML} in '${skill.skillDirName}'`);
5361
+ return { kind: "unregistered" };
5258
5362
  }
5259
5363
  const read = await readSkillMetadata(metadataPath);
5260
5364
  if (!read.usable) {
@@ -5291,7 +5395,7 @@ async function readSkillDefinition(skill) {
5291
5395
  }
5292
5396
  }
5293
5397
  async function loadPluginSkills(pluginDir) {
5294
- const { skills } = await loadSkillsFromDir(path15.join(pluginDir, "skills"), {
5398
+ const { skills } = await loadSkillsFromDir(path14.join(pluginDir, "skills"), {
5295
5399
  pathPrefix: "skills",
5296
5400
  requireMetadata: false
5297
5401
  });
@@ -5300,17 +5404,18 @@ async function loadPluginSkills(pluginDir) {
5300
5404
 
5301
5405
  // src/cli/lib/loading/source-fetcher.ts
5302
5406
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
5303
- import { createHash as createHash2 } from "crypto";
5407
+ import { createHash as createHash2, randomUUID } from "crypto";
5304
5408
  import { readFileSync } from "fs";
5305
- import { rename, stat } from "fs/promises";
5409
+ import { open, rename, stat } from "fs/promises";
5410
+ import { setTimeout as sleep } from "timers/promises";
5306
5411
  import { downloadTemplate } from "giget";
5307
5412
  import os8 from "os";
5308
- import path17 from "path";
5413
+ import path16 from "path";
5309
5414
  import { isDeepEqual } from "remeda";
5310
5415
  import { z as z4 } from "zod";
5311
5416
 
5312
5417
  // src/cli/lib/marketplace-generator.ts
5313
- import path16 from "path";
5418
+ import path15 from "path";
5314
5419
  import { countBy, sortBy } from "remeda";
5315
5420
  var PLUGIN_MANIFEST_PATH = `${PLUGIN_MANIFEST_DIR}/${PLUGIN_MANIFEST_FILE}`;
5316
5421
  var MARKETPLACE_SCHEMA_URL = "https://anthropic.com/claude-code/marketplace.schema.json";
@@ -5342,7 +5447,7 @@ async function generateMarketplace(pluginsDir2, options) {
5342
5447
  const rootWithoutDotSlash = options.pluginRoot.replace(/^\.\//, "");
5343
5448
  for (const manifestFile of manifestFiles) {
5344
5449
  const pluginDirName = pluginDirNameOf(manifestFile);
5345
- const pluginDir = path16.join(pluginsDir2, pluginDirName);
5450
+ const pluginDir = path15.join(pluginsDir2, pluginDirName);
5346
5451
  const manifest = await readPluginManifest(pluginDir);
5347
5452
  if (!manifest) {
5348
5453
  warn(`Could not read plugin manifest: '${manifestFile}'`);
@@ -5397,17 +5502,17 @@ function reservedNameError(marketplaceName, wayOut) {
5397
5502
  return `Marketplace name '${marketplaceName}' is reserved. '${DEFAULT_PUBLIC_SOURCE_NAME}' is the public catalogue's own namespace, '${EXTERNAL_SKILL_NAMESPACE}' and '${LOCAL_SKILL_NAMESPACE}' hold the skills that belong to no marketplace, and '${EJECT_SOURCE}' is what an installation records for a skill copied into it rather than installed as a plugin \u2014 a marketplace under any of them is read back as something it is not. Choose a name of your own: ${wayOut}.`;
5398
5503
  }
5399
5504
  function namespaceViolationError(foreignIds, marketplaceName) {
5400
- const listed2 = foreignIds.slice(0, MAX_REPORTED_NAMESPACE_VIOLATIONS).map((id) => ` ${id} -> ${marketplaceName}-${id}`);
5401
- const unlisted = foreignIds.length - listed2.length;
5505
+ const listed = foreignIds.slice(0, MAX_REPORTED_NAMESPACE_VIOLATIONS).map((id) => ` ${id} -> ${marketplaceName}-${id}`);
5506
+ const unlisted = foreignIds.length - listed.length;
5402
5507
  return [
5403
5508
  `Marketplace '${marketplaceName}' ships ${foreignIds.length} skill id(s) outside its own namespace. Every skill id must begin with the marketplace's name:`,
5404
- ...listed2,
5509
+ ...listed,
5405
5510
  ...unlisted > 0 ? [` ... and ${unlisted} more`] : [],
5406
5511
  `Rename each skill directory and the 'name' in its SKILL.md, re-run 'build plugins', then build the marketplace again.`
5407
5512
  ].join("\n");
5408
5513
  }
5409
5514
  async function writeMarketplace(outputPath, marketplace) {
5410
- await ensureDir(path16.dirname(outputPath));
5515
+ await ensureDir(path15.dirname(outputPath));
5411
5516
  const content = `${JSON.stringify(marketplace, null, 2)}
5412
5517
  `;
5413
5518
  await writeFile(outputPath, content);
@@ -5443,6 +5548,9 @@ var TarballWithheldError = class extends Error {
5443
5548
  this.status = status;
5444
5549
  }
5445
5550
  };
5551
+ function isUnanswered(answer) {
5552
+ return answer === "unreachable" || answer instanceof TarballWithheldError;
5553
+ }
5446
5554
  var marketplaceMustAnswer = new AsyncLocalStorage3();
5447
5555
  function askingEachMarketplaceItself(body) {
5448
5556
  const differingCopiesReplaced = /* @__PURE__ */ new Set();
@@ -5481,7 +5589,7 @@ function sanitizeSourceForCache(source) {
5481
5589
  }
5482
5590
  function getCacheDir(source) {
5483
5591
  const sanitized = sanitizeSourceForCache(source) || "unknown";
5484
- return path17.join(cacheRoot(), "sources", sanitized);
5592
+ return path16.join(cacheRoot(), "sources", sanitized);
5485
5593
  }
5486
5594
  async function fetchFromSource(source, options = {}) {
5487
5595
  const { subdir } = options;
@@ -5492,8 +5600,8 @@ async function fetchFromSource(source, options = {}) {
5492
5600
  return fetchFromRemoteSource(source, subdir);
5493
5601
  }
5494
5602
  async function fetchFromLocalSource(source, subdir) {
5495
- const fullPath = subdir ? path17.join(source, subdir) : source;
5496
- const absolutePath = path17.isAbsolute(fullPath) ? fullPath : path17.resolve(process.cwd(), fullPath);
5603
+ const fullPath = subdir ? path16.join(source, subdir) : source;
5604
+ const absolutePath = path16.isAbsolute(fullPath) ? fullPath : path16.resolve(process.cwd(), fullPath);
5497
5605
  if (!await directoryExists2(absolutePath)) {
5498
5606
  throw new Error(
5499
5607
  `Local marketplace not found: '${absolutePath}'
@@ -5524,18 +5632,16 @@ function getGigetCacheDir(source) {
5524
5632
  return void 0;
5525
5633
  }
5526
5634
  const templateName = uriMatch.groups.repo.replace("/", "-").replace(/[^\da-z-]/gi, "-");
5527
- return path17.join(gigetCacheRoot(), providerName, templateName);
5635
+ return path16.join(gigetCacheRoot(), providerName, templateName);
5528
5636
  }
5529
5637
  function gigetCacheRoot() {
5530
- return process.env.XDG_CACHE_HOME ? path17.resolve(process.env.XDG_CACHE_HOME, "giget") : path17.resolve(os8.homedir(), ".cache", "giget");
5638
+ return process.env.XDG_CACHE_HOME ? path16.resolve(process.env.XDG_CACHE_HOME, "giget") : path16.resolve(os8.homedir(), ".cache", "giget");
5531
5639
  }
5532
5640
  async function clearGigetCache(source) {
5533
5641
  const gigetDir = getGigetCacheDir(source);
5534
- if (!gigetDir) return;
5535
- if (await directoryExists2(gigetDir)) {
5536
- verbose(`Clearing giget cache: ${gigetDir}`);
5537
- await remove(gigetDir);
5538
- }
5642
+ if (!gigetDir || !await directoryExists2(gigetDir)) return;
5643
+ verbose(`Clearing giget cache: ${gigetDir}`);
5644
+ await holdingGigetFolder(path16.dirname(gigetDir), () => remove(gigetDir));
5539
5645
  }
5540
5646
  async function fetchFromRemoteSource(source, subdir) {
5541
5647
  const cacheDir = getCacheDir(source);
@@ -5544,38 +5650,95 @@ async function fetchFromRemoteSource(source, subdir) {
5544
5650
  verbose(`Cache directory: ${cacheDir}`);
5545
5651
  if (await directoryExists2(cacheDir)) {
5546
5652
  const verdict = await revalidateCachedCopy(cacheDir, source);
5547
- if (keepsTheCopy(verdict)) {
5548
- verbose(`Using cached marketplace: ${cacheDir}`);
5549
- return {
5550
- path: cacheDir,
5551
- fromCache: true,
5552
- unreachable: verdict === "unreachable",
5553
- source: fullSource
5554
- };
5555
- }
5653
+ if (keepsTheCopy(verdict)) return readSavedCopy(cacheDir, fullSource, verdict);
5556
5654
  announceRefetch(verdict);
5557
- if (!mustAnswer()) await discardCachedCopy(cacheDir, source);
5655
+ if (!mustAnswer()) return refreshSavedCopy(cacheDir, source, fullSource, verdict);
5558
5656
  }
5559
5657
  const differingCopiesReplaced = marketplaceMustAnswer.getStore();
5560
5658
  return differingCopiesReplaced ? downloadAnsweredFor(cacheDir, source, fullSource, differingCopiesReplaced) : downloadFreshCopy(cacheDir, source, fullSource);
5561
5659
  }
5562
- async function discardCachedCopy(cacheDir, source) {
5563
- await clearGigetCache(source);
5564
- await remove(cacheDir);
5660
+ function readSavedCopy(cacheDir, fullSource, verdict) {
5661
+ verbose(`Using cached marketplace: ${cacheDir}`);
5662
+ return {
5663
+ path: cacheDir,
5664
+ fromCache: true,
5665
+ unreachable: verdict === "unreachable",
5666
+ source: fullSource
5667
+ };
5668
+ }
5669
+ async function refreshSavedCopy(cacheDir, source, fullSource, verdict) {
5670
+ if (verdict === "superseded") await clearGigetCache(source);
5671
+ const staged = `${cacheDir}${STAGED_DOWNLOAD_SUFFIX}`;
5672
+ try {
5673
+ const download = await downloadUnlessItFails(source, fullSource, staged);
5674
+ if (isUnanswered(download)) {
5675
+ const failed = verdictOnFailedDownload(verdict, download, source);
5676
+ return keepSavedCopyUnlessStopped(cacheDir, fullSource, failed);
5677
+ }
5678
+ const downloaded = await verdictOnDownload(download, source);
5679
+ if (downloaded !== "current") {
5680
+ return keepSavedCopyUnlessStopped(cacheDir, fullSource, downloaded);
5681
+ }
5682
+ await moveIntoPlace(cacheDir, staged);
5683
+ await recordFetchedCopy(cacheDir, download.result, download.savedTarball);
5684
+ settleForThisRun(cacheDir, "current");
5685
+ return { path: cacheDir, fromCache: false, unreachable: false, source: fullSource };
5686
+ } catch (error) {
5687
+ throw createDetailedFetchError(error, source);
5688
+ } finally {
5689
+ await remove(staged);
5690
+ }
5691
+ }
5692
+ function keepSavedCopyUnlessStopped(cacheDir, fullSource, verdict) {
5693
+ if (verdict instanceof TarballWithheldError) throw verdict;
5694
+ settleForThisRun(cacheDir, verdict);
5695
+ return readSavedCopy(cacheDir, fullSource, verdict);
5696
+ }
5697
+ async function downloadUnlessItFails(source, fullSource, staged) {
5698
+ try {
5699
+ return await downloadStaged(source, fullSource, staged);
5700
+ } catch (error) {
5701
+ verbose(`Could not download ${fullSource}: ${getErrorMessage(error)}`);
5702
+ return refusalIn(error) ?? "unreachable";
5703
+ }
5704
+ }
5705
+ var GIGET_DOWNLOAD_REFUSED = /^Failed to download \S+: (?<status>\d{3}) (?<statusText>.*)$/;
5706
+ function refusalIn(error) {
5707
+ const answered = GIGET_DOWNLOAD_REFUSED.exec(getErrorMessage(error))?.groups;
5708
+ if (answered?.status === void 0 || answered.statusText === void 0) return void 0;
5709
+ const status = Number(answered.status);
5710
+ if (postureOn(status) === void 0) return void 0;
5711
+ return new TarballWithheldError({ status, statusText: answered.statusText });
5712
+ }
5713
+ function verdictOnFailedDownload(verdict, failure, source) {
5714
+ if (verdict === "unrecorded") return verdictOnUnanswered(failure, source);
5715
+ warn(updateNotFetchedUsingCache(source));
5716
+ return "outdated";
5717
+ }
5718
+ function updateNotFetchedUsingCache(source) {
5719
+ return `Could not fetch the update from ${source} \u2014 using the cached copy, which is out of date.`;
5720
+ }
5721
+ async function verdictOnDownload({ result, savedTarball }, source) {
5722
+ return savedTarball ? verdictOnSavedTarball(result, savedTarball, source) : "current";
5565
5723
  }
5566
5724
  async function downloadFreshCopy(cacheDir, source, fullSource) {
5567
- await ensureDir(path17.dirname(cacheDir));
5725
+ await ensureDir(path16.dirname(cacheDir));
5568
5726
  try {
5569
- const { result, savedTarball } = await gigetDownload(source, fullSource, {
5727
+ const download = await gigetDownload(source, fullSource, {
5570
5728
  dir: cacheDir,
5571
5729
  force: true,
5572
5730
  // Always force when downloading to avoid "already exists" error
5573
5731
  offline: false
5574
5732
  });
5733
+ const { result, savedTarball } = download;
5575
5734
  verbose(`Downloaded to: ${result.dir}`);
5576
- if (savedTarball) await askBeforeKeepingTheSavedTarball(cacheDir, result, source);
5735
+ const downloaded = await verdictOnDownload(download, source);
5736
+ if (downloaded instanceof TarballWithheldError) {
5737
+ await remove(cacheDir);
5738
+ throw downloaded;
5739
+ }
5577
5740
  await recordFetchedCopy(cacheDir, result, savedTarball);
5578
- markCopyCurrentForThisRun(cacheDir);
5741
+ settleForThisRun(cacheDir, "current");
5579
5742
  return {
5580
5743
  path: result.dir,
5581
5744
  fromCache: false,
@@ -5586,25 +5749,27 @@ async function downloadFreshCopy(cacheDir, source, fullSource) {
5586
5749
  throw createDetailedFetchError(error, source);
5587
5750
  }
5588
5751
  }
5589
- async function askBeforeKeepingTheSavedTarball(cacheDir, result, source) {
5752
+ async function verdictOnSavedTarball(result, savedTarball, source) {
5590
5753
  const tar = tarballOf(result);
5591
- if (tar === void 0) return;
5754
+ if (tar === void 0) return "current";
5592
5755
  const answer = await answerAbout(tar);
5593
- if (answer === "answered") return;
5594
- if (answer === "unreachable") {
5756
+ if (isUnanswered(answer)) return verdictOnUnanswered(answer, source);
5757
+ if (answer.etag === savedTarball.etag) return "current";
5758
+ warn(updateNotFetchedUsingCache(source));
5759
+ return "outdated";
5760
+ }
5761
+ function verdictOnUnanswered(unanswered, source) {
5762
+ if (unanswered === "unreachable") {
5595
5763
  warn(sourceUnreachableUsingCache(source));
5596
- return;
5597
- }
5598
- if (stopsTheLoad(answer)) {
5599
- await remove(cacheDir);
5600
- throw answer;
5764
+ return "unreachable";
5601
5765
  }
5602
- announceTheCopyKept(answer, source);
5766
+ if (stopsTheLoad(unanswered)) return unanswered;
5767
+ announceTheCopyKept(unanswered, source);
5768
+ return "refused";
5603
5769
  }
5604
5770
  async function answerAbout(tar) {
5605
5771
  try {
5606
- await fetchEtag(tar);
5607
- return "answered";
5772
+ return { etag: await fetchEtag(tar) };
5608
5773
  } catch (error) {
5609
5774
  if (error instanceof TarballWithheldError) return error;
5610
5775
  verbose(`Could not ask ${tar} about the tarball giget saved: ${getErrorMessage(error)}`);
@@ -5616,21 +5781,16 @@ var ONLY_A_SAVED_COPY_CAME_BACK = "the download failed and only a saved copy cam
5616
5781
  var SET_ASIDE_SUFFIX = ".set-aside";
5617
5782
  async function downloadAnsweredFor(cacheDir, source, fullSource, differingCopiesReplaced) {
5618
5783
  const staged = `${cacheDir}${STAGED_DOWNLOAD_SUFFIX}`;
5619
- const downloadStaged = () => gigetDownload(source, fullSource, {
5620
- dir: staged,
5621
- force: true,
5622
- forceClean: true,
5623
- offline: false
5624
- });
5625
- await ensureDir(path17.dirname(cacheDir));
5784
+ const download = () => downloadStaged(source, fullSource, staged);
5785
+ await ensureDir(path16.dirname(cacheDir));
5626
5786
  try {
5627
- const { result, savedTarball } = await downloadStaged();
5787
+ const { result, savedTarball } = await download();
5628
5788
  const record = await askAfterTheDownload(result, source);
5629
- if (savedTarball) await downloadPast(savedTarball, record, downloadStaged, source);
5789
+ if (savedTarball) await downloadPast(savedTarball, record, download, source);
5630
5790
  const differed = await savedCopyDiffers(cacheDir, staged);
5631
5791
  await replaceCopy(cacheDir, staged, record);
5632
5792
  if (differed) differingCopiesReplaced.add(source);
5633
- markCopyCurrentForThisRun(cacheDir);
5793
+ settleForThisRun(cacheDir, "current");
5634
5794
  return { path: cacheDir, fromCache: false, unreachable: false, source: fullSource };
5635
5795
  } catch (error) {
5636
5796
  if (error instanceof MarketplaceUnreachableError) throw error;
@@ -5639,11 +5799,22 @@ async function downloadAnsweredFor(cacheDir, source, fullSource, differingCopies
5639
5799
  await remove(staged);
5640
5800
  }
5641
5801
  }
5802
+ function downloadStaged(source, fullSource, staged) {
5803
+ return gigetDownload(source, fullSource, {
5804
+ dir: staged,
5805
+ force: true,
5806
+ forceClean: true,
5807
+ offline: false
5808
+ });
5809
+ }
5642
5810
  async function downloadPast(savedTarball, record, download, source) {
5643
5811
  if (!savedUnder(savedTarball, record)) {
5644
5812
  throw new MarketplaceUnreachableError(source, ONLY_A_SAVED_COPY_CAME_BACK);
5645
5813
  }
5646
- await withTarballSetAside(savedTarball.path, download);
5814
+ await holdingGigetFolder(
5815
+ gigetProviderDir(source),
5816
+ () => withTarballSetAside(savedTarball.path, download)
5817
+ );
5647
5818
  }
5648
5819
  async function withTarballSetAside(tarball, download) {
5649
5820
  const record = gigetRecordOf(tarball);
@@ -5679,44 +5850,170 @@ function everyFileIn(dir) {
5679
5850
  return glob(EVERY_FILE, dir, { dot: true });
5680
5851
  }
5681
5852
  function sameBytesAt(file, left, right) {
5682
- return readFileSync(path17.join(left, file)).equals(readFileSync(path17.join(right, file)));
5853
+ return readFileSync(path16.join(left, file)).equals(readFileSync(path16.join(right, file)));
5683
5854
  }
5684
5855
  async function replaceCopy(cacheDir, staged, record) {
5856
+ await moveIntoPlace(cacheDir, staged);
5857
+ await writeFile(fetchRecordPath(cacheDir), JSON.stringify(record));
5858
+ }
5859
+ async function moveIntoPlace(cacheDir, staged) {
5685
5860
  await remove(cacheDir);
5686
5861
  await rename(staged, cacheDir);
5687
- await writeFile(fetchRecordPath(cacheDir), JSON.stringify(record));
5688
5862
  }
5689
5863
  var GIGET_TARBALLS = "**/*.tar.gz";
5690
5864
  var gigetTarballRecordSchema = z4.object({ etag: z4.string().min(1).optional() });
5691
5865
  async function gigetDownload(source, fullSource, options) {
5692
5866
  const providerDir = gigetProviderDir(source);
5693
- const timesBefore = await tarballTimes(providerDir);
5694
- const result = await downloadTemplate(fullSource, options);
5695
- const tarball = path17.join(providerDir, gigetTarballOf(result));
5696
- const writtenByThisDownload = timesBefore.get(tarball) !== await modifiedAt(tarball);
5697
- if (writtenByThisDownload) return { result };
5698
- verbose(`giget extracted the tarball it saved earlier, not a download: ${tarball}`);
5699
- return { result, savedTarball: { path: tarball, etag: await etagSavedWith(tarball) } };
5867
+ return holdingGigetFolder(
5868
+ providerDir,
5869
+ () => downloadOverMarkedRecords(providerDir, fullSource, options)
5870
+ );
5871
+ }
5872
+ async function downloadOverMarkedRecords(providerDir, fullSource, options) {
5873
+ const marks = await markSavedRecords(providerDir);
5874
+ try {
5875
+ const result = await downloadTemplate(fullSource, options);
5876
+ const tarball = path16.join(providerDir, gigetTarballOf(result));
5877
+ if (await writtenByThisDownload(marks.get(tarball))) return { result };
5878
+ verbose(`giget extracted the tarball it saved earlier, not a download: ${tarball}`);
5879
+ return { result, savedTarball: { path: tarball, etag: await etagSavedWith(tarball) } };
5880
+ } finally {
5881
+ await Promise.all([...marks.values()].map(unmark));
5882
+ }
5700
5883
  }
5701
5884
  function gigetProviderDir(source) {
5702
5885
  const provider = SOURCE_PROTO_RE.exec(source)?.[1];
5703
5886
  if (provider === void 0) throw new Error(`No provider named in '${source}'`);
5704
- return path17.join(gigetCacheRoot(), provider);
5887
+ return path16.join(gigetCacheRoot(), provider);
5888
+ }
5889
+ var GIGET_FOLDER_LOCK = "agents-inc.lock";
5890
+ var EXCLUSIVE_CREATE = "wx";
5891
+ var LOCK_RETRY_MS = 100;
5892
+ var LOCK_ABANDONED_MS = 10 * 60 * 1e3;
5893
+ var gigetFolderHeld = new AsyncLocalStorage3();
5894
+ var holdersInThisProcess = /* @__PURE__ */ new Set();
5895
+ async function holdingGigetFolder(folder, body) {
5896
+ if (gigetFolderHeld.getStore() === folder) return body();
5897
+ const lock = lockFor(folder);
5898
+ try {
5899
+ await lockGigetFolder(folder, lock);
5900
+ return await gigetFolderHeld.run(folder, body);
5901
+ } finally {
5902
+ await letGo(lock);
5903
+ }
5904
+ }
5905
+ function lockFor(folder) {
5906
+ const holder = `${process.pid} ${randomUUID()}`;
5907
+ holdersInThisProcess.add(holder);
5908
+ return { path: path16.join(folder, GIGET_FOLDER_LOCK), holder };
5705
5909
  }
5706
- async function tarballTimes(providerDir) {
5910
+ async function letGo({ path: lockPath, holder }) {
5911
+ try {
5912
+ await removeLockHeldBy(lockPath, holder);
5913
+ } finally {
5914
+ holdersInThisProcess.delete(holder);
5915
+ }
5916
+ }
5917
+ async function lockGigetFolder(folder, lock) {
5918
+ await ensureDir(folder);
5919
+ while (!await created(lock)) {
5920
+ const holder = await readFileOptional(lock.path);
5921
+ if (await abandoned(lock.path, holder)) {
5922
+ verbose(`Taking over the lock on ${folder}: its run ended, or held it past any download`);
5923
+ await removeLockHeldBy(lock.path, holder);
5924
+ } else {
5925
+ await sleep(LOCK_RETRY_MS);
5926
+ }
5927
+ }
5928
+ }
5929
+ async function created({ path: lockPath, holder }) {
5930
+ try {
5931
+ await (await open(lockPath, EXCLUSIVE_CREATE)).close();
5932
+ } catch (error) {
5933
+ if (failedWith(error, "EEXIST")) return false;
5934
+ throw error;
5935
+ }
5936
+ await writeFile(lockPath, holder);
5937
+ return true;
5938
+ }
5939
+ async function abandoned(lockPath, holder) {
5940
+ return holderEnded(holder) || await outlastedAnyDownload(lockPath);
5941
+ }
5942
+ function holderEnded(holder) {
5943
+ const pid = Number.parseInt(holder, 10);
5944
+ if (pid === process.pid) return !holdersInThisProcess.has(holder);
5945
+ return pid > 0 && !processRunning(pid);
5946
+ }
5947
+ async function outlastedAnyDownload(lockPath) {
5948
+ const age = await ageOf(lockPath);
5949
+ return age !== void 0 && age > LOCK_ABANDONED_MS;
5950
+ }
5951
+ function processRunning(pid) {
5952
+ try {
5953
+ process.kill(pid, 0);
5954
+ return true;
5955
+ } catch (error) {
5956
+ return !failedWith(error, "ESRCH");
5957
+ }
5958
+ }
5959
+ async function ageOf(file) {
5960
+ try {
5961
+ return Date.now() - (await stat(file)).mtimeMs;
5962
+ } catch (error) {
5963
+ if (failedWith(error, "ENOENT")) return void 0;
5964
+ throw error;
5965
+ }
5966
+ }
5967
+ async function removeLockHeldBy(lockPath, holder) {
5968
+ if (await readFileOptional(lockPath) === holder) await remove(lockPath);
5969
+ }
5970
+ function failedWith(error, code) {
5971
+ return isRecord(error) && error.code === code;
5972
+ }
5973
+ var RECORD_MARK = "\n";
5974
+ var NO_RECORD = "{}";
5975
+ var UNMARKED_SUFFIX = ".unmarked";
5976
+ async function markSavedRecords(providerDir) {
5707
5977
  const tarballs = await glob(GIGET_TARBALLS, providerDir);
5708
- const paths = tarballs.map((tarball) => path17.join(providerDir, tarball));
5709
- return new Map(await Promise.all(paths.map(async (at) => [at, await modifiedAt(at)])));
5978
+ const paths = tarballs.map((tarball) => path16.join(providerDir, tarball));
5979
+ return new Map(await Promise.all(paths.map(async (at) => [at, await markRecordOf(at)])));
5980
+ }
5981
+ async function markRecordOf(tarball) {
5982
+ const record = gigetRecordOf(tarball);
5983
+ await putBackLeftUnmarked(record);
5984
+ const held = await fileExists(record);
5985
+ const marked = `${RECORD_MARK}${await readFileOptional(record, NO_RECORD)}`;
5986
+ if (held) await rename(record, unmarkedOf(record));
5987
+ await writeFile(record, marked);
5988
+ return { record, marked, held };
5710
5989
  }
5711
- async function modifiedAt(file) {
5712
- return await fileExists(file) ? (await stat(file)).mtimeMs : void 0;
5990
+ async function putBackLeftUnmarked(record) {
5991
+ if (await fileExists(unmarkedOf(record))) await rename(unmarkedOf(record), record);
5992
+ }
5993
+ function unmarkedOf(record) {
5994
+ return `${record}${UNMARKED_SUFFIX}`;
5995
+ }
5996
+ async function writtenByThisDownload(mark) {
5997
+ return mark === void 0 || await rewritten(mark);
5998
+ }
5999
+ async function rewritten({ record, marked }) {
6000
+ return await readFileOptional(record, marked) !== marked;
6001
+ }
6002
+ async function unmark(mark) {
6003
+ const { record, held } = mark;
6004
+ if (await rewritten(mark)) {
6005
+ if (held) await remove(unmarkedOf(record));
6006
+ return;
6007
+ }
6008
+ if (held) await rename(unmarkedOf(record), record);
6009
+ else await remove(record);
5713
6010
  }
5714
6011
  function gigetTarballOf(result) {
5715
6012
  const name = result.name;
5716
6013
  const version = result.version;
5717
6014
  if (typeof name !== "string") throw new Error("the download named no template");
5718
6015
  const stem = typeof version === "string" && version !== "" ? version : name;
5719
- return path17.join(name, `${stem}.tar.gz`);
6016
+ return path16.join(name, `${stem}.tar.gz`);
5720
6017
  }
5721
6018
  function gigetRecordOf(tarball) {
5722
6019
  return `${tarball}.json`;
@@ -5730,7 +6027,7 @@ function savedUnder(savedTarball, record) {
5730
6027
  return savedTarball.etag !== void 0 && savedTarball.etag === record.etag;
5731
6028
  }
5732
6029
  function keepsTheCopy(verdict) {
5733
- return verdict === "current" || verdict === "unreachable" || verdict === "refused";
6030
+ return verdict === "current" || verdict === "unreachable" || verdict === "refused" || verdict === "outdated";
5734
6031
  }
5735
6032
  var REVALIDATION_TIMEOUT_MS = 5e3;
5736
6033
  var FETCH_RECORD_SUFFIX = ".etag.json";
@@ -5752,16 +6049,12 @@ function revalidateCachedCopy(cacheDir, source) {
5752
6049
  verdicts.set(cacheDir, verdict);
5753
6050
  return verdict;
5754
6051
  }
5755
- function markCopyCurrentForThisRun(cacheDir) {
5756
- verdictsThisRun().set(cacheDir, Promise.resolve("current"));
6052
+ function settleForThisRun(cacheDir, verdict) {
6053
+ verdictsThisRun().set(cacheDir, Promise.resolve(verdict));
5757
6054
  }
5758
6055
  async function classifyCachedCopy(cacheDir, source) {
5759
6056
  const record = await readFetchRecord(cacheDir);
5760
6057
  if (!record) return "unrecorded";
5761
- if (record.etag === void 0 && !mustAnswer()) {
5762
- verbose(`No ETag was recorded for ${record.tar} \u2014 keeping the cached copy`);
5763
- return "current";
5764
- }
5765
6058
  try {
5766
6059
  return judgeAnswer(record, await fetchEtag(record.tar));
5767
6060
  } catch (error) {
@@ -5780,6 +6073,7 @@ function stopOrKeepTheCopy(refusal2, source) {
5780
6073
  function judgeAnswer(record, live) {
5781
6074
  if (movedOn(record, live)) return "superseded";
5782
6075
  if (mustAnswer()) return "answered";
6076
+ if (record.etag === void 0 && live !== void 0) return "unstamped";
5783
6077
  if (live === void 0) {
5784
6078
  verbose(`${record.tar} answered without an ETag \u2014 keeping the cached copy`);
5785
6079
  }
@@ -6102,7 +6396,7 @@ function markInstalledAsPlugin(skill, primarySourceName, primarySourceType) {
6102
6396
  }
6103
6397
  async function collectPluginSkillIds(projectDir) {
6104
6398
  const projectSkillIds = await pluginSkillIdsIn(projectDir);
6105
- const globalSkillIds = isHomeDirectory(projectDir) ? [] : await pluginSkillIdsIn(os9.homedir());
6399
+ const globalSkillIds = isHomeDirectory(projectDir) ? [] : await pluginSkillIdsIn(os9.homedir(), inheritedGlobalProvider(projectDir));
6106
6400
  if (globalSkillIds.length > 0) {
6107
6401
  verbose(`Found ${globalSkillIds.length} global plugin skill(s)`);
6108
6402
  return unique3([...projectSkillIds, ...globalSkillIds]);
@@ -6112,8 +6406,8 @@ async function collectPluginSkillIds(projectDir) {
6112
6406
  }
6113
6407
  return projectSkillIds;
6114
6408
  }
6115
- async function pluginSkillIdsIn(dir) {
6116
- return typedKeys(await discoverAllPluginSkills(dir));
6409
+ async function pluginSkillIdsIn(dir, provider) {
6410
+ return typedKeys(await discoverAllPluginSkills(dir, provider));
6117
6411
  }
6118
6412
  function setActiveSources(matrix2) {
6119
6413
  for (const [, skill] of typedEntries(matrix2.skills)) {
@@ -6164,9 +6458,9 @@ function agentNotFoundError(agentName, agents) {
6164
6458
  }
6165
6459
  function describeAvailableAgents(availableAgents) {
6166
6460
  if (availableAgents.length === 0) return "No agents found in scanned directories";
6167
- const listed2 = availableAgents.slice(0, MAX_LISTED_AVAILABLE_AGENTS).join(", ");
6461
+ const listed = availableAgents.slice(0, MAX_LISTED_AVAILABLE_AGENTS).join(", ");
6168
6462
  const unlisted = availableAgents.length - MAX_LISTED_AVAILABLE_AGENTS;
6169
- return unlisted > 0 ? `Available agents: ${listed2} (and ${unlisted} more)` : `Available agents: ${listed2}`;
6463
+ return unlisted > 0 ? `Available agents: ${listed} (and ${unlisted} more)` : `Available agents: ${listed}`;
6170
6464
  }
6171
6465
  function assembleAgentConfig(agentName, definition, agentConfig, resolvedSkills) {
6172
6466
  const model = agentConfig.model ?? definition.model;
@@ -6206,14 +6500,18 @@ function resolveDescription(loadedStack, shared) {
6206
6500
  return shared ?? loadedStack?.description;
6207
6501
  }
6208
6502
  function configNameFor(projectDir) {
6209
- return isHomeDirectory(projectDir) ? DEFAULT_PLUGIN_NAME : path18.basename(projectDir);
6503
+ return isHomeDirectory(projectDir) ? DEFAULT_PLUGIN_NAME : path17.basename(projectDir);
6210
6504
  }
6211
6505
  async function loadCuratedStack(projectDir, projectStack) {
6212
- const globalStack = isHomeDirectory(projectDir) ? void 0 : await loadGlobalStack();
6506
+ const globalStack = isHomeDirectory(projectDir) ? void 0 : await loadGlobalStack(projectDir);
6213
6507
  return { ...globalStack, ...projectStack };
6214
6508
  }
6215
- async function loadGlobalStack() {
6216
- return (await loadInstalledConfig(os10.homedir()))?.config.stack;
6509
+ async function loadGlobalStack(projectDir) {
6510
+ const inherited = await loadProjectConfigFromDir(
6511
+ os10.homedir(),
6512
+ inheritedGlobalProvider(projectDir)
6513
+ );
6514
+ return inherited?.config.stack;
6217
6515
  }
6218
6516
  async function buildInstallConfig(selection, sourceResult, projectDir) {
6219
6517
  const skillIds = unique4(selection.skills.map((s) => s.id));
@@ -6252,7 +6550,10 @@ async function loadSelectedStack(stackId, sourceResult) {
6252
6550
  verbose(
6253
6551
  `buildInstallConfig: loadedStack=${loadedStack ? `found (id='${loadedStack.id}')` : "NOT FOUND"}`
6254
6552
  );
6255
- if (!loadedStack) throw new Error(stackNotOfferedMessage(stackId, source));
6553
+ if (!loadedStack) {
6554
+ const shippedStackIds = sourceResult.matrix.suggestedStacks.map((stack) => stack.id);
6555
+ throw new Error(stackNotOfferedMessage(stackId, source, shippedStackIds));
6556
+ }
6256
6557
  return loadedStack;
6257
6558
  }
6258
6559
  function stackToPreserve(loadedStack, curatedStack) {
@@ -6360,31 +6661,61 @@ async function describeScope(kind, root, provider) {
6360
6661
 
6361
6662
  // src/cli/lib/installation/detect-installations.ts
6362
6663
  async function detectInstallations(cwd) {
6664
+ const configs = await configsRead(cwd);
6665
+ return configs.filter((config) => !config.declaresNothingInAProject).map(asInstallation);
6666
+ }
6667
+ async function configsDeclaringNothing(cwd) {
6668
+ const configs = await configsRead(cwd);
6669
+ return configs.filter((config) => config.declaresNothingInAProject).map(asInstallation);
6670
+ }
6671
+ async function holdsAConfigDeclaringNothing(cwd) {
6672
+ return (await configsDeclaringNothing(cwd)).length > 0;
6673
+ }
6674
+ async function configsRead(cwd) {
6363
6675
  const everywhereOneCouldBe = scopeRootsInPlay(cwd).flatMap(everyProviderUnder);
6364
- const found = await Promise.all(everywhereOneCouldBe.map(installationIfPresent));
6365
- return found.filter(
6366
- (installation) => installation !== null
6367
- );
6676
+ const found = await Promise.all(everywhereOneCouldBe.map(configIfPresent));
6677
+ const onDisk = found.filter((config) => config !== null);
6678
+ return Promise.all(onDisk.map(withWhetherItDeclaresNothing));
6368
6679
  }
6369
6680
  function everyProviderUnder({ kind, root }) {
6370
6681
  return PROVIDERS.map((provider) => ({
6371
6682
  provider,
6372
6683
  scope: kind,
6684
+ root,
6373
6685
  configPath: getProjectConfigPath(root, provider)
6374
6686
  }));
6375
6687
  }
6376
- async function installationIfPresent(candidate) {
6688
+ async function configIfPresent(candidate) {
6377
6689
  return await fileExists(candidate.configPath) ? candidate : null;
6378
6690
  }
6691
+ async function withWhetherItDeclaresNothing(config) {
6692
+ return { ...config, declaresNothingInAProject: await declaresNothingInAProject(config) };
6693
+ }
6694
+ async function declaresNothingInAProject(config) {
6695
+ if (config.scope === "global") return false;
6696
+ const asRead = declaresNothingAsLastRead(config.configPath);
6697
+ if (asRead !== void 0) return asRead;
6698
+ try {
6699
+ const declaresNothing = await configDeclaresNothing(config.root, config.provider);
6700
+ recordWhetherItDeclaresNothing(config.configPath, declaresNothing);
6701
+ return declaresNothing;
6702
+ } catch (error) {
6703
+ if (error instanceof ConfigLoadError) return false;
6704
+ throw error;
6705
+ }
6706
+ }
6707
+ function asInstallation({ provider, scope, configPath }) {
6708
+ return { provider, scope, configPath };
6709
+ }
6379
6710
 
6380
6711
  // src/cli/lib/hosts/claude-host.ts
6381
6712
  import os12 from "os";
6382
- import path21 from "path";
6713
+ import path20 from "path";
6383
6714
  import { z as z6 } from "zod";
6384
6715
 
6385
6716
  // src/cli/utils/exec.ts
6386
6717
  import { spawn } from "child_process";
6387
- import path19 from "path";
6718
+ import path18 from "path";
6388
6719
  var MAX_PLUGIN_PATH_LENGTH = 1024;
6389
6720
  var MAX_PLUGIN_NAME_LENGTH = 256;
6390
6721
  var MAX_MARKETPLACE_SOURCE_LENGTH = 1024;
@@ -6392,7 +6723,7 @@ var SAFE_NAME_PATTERN2 = /^[a-zA-Z0-9._@/-]+$/;
6392
6723
  var SAFE_PLUGIN_PATH_PATTERN = /^[a-zA-Z0-9._@/:~-]+$/;
6393
6724
  var OPENS_A_RELATIVE_PATH = /^\.\.?[/\\]/;
6394
6725
  function namesADirectoryOnThisMachine(source) {
6395
- return path19.isAbsolute(source) || OPENS_A_RELATIVE_PATH.test(source);
6726
+ return path18.isAbsolute(source) || OPENS_A_RELATIVE_PATH.test(source);
6396
6727
  }
6397
6728
  var CONTROL_CHAR_PATTERN = /[\x00-\x08\x0E-\x1F\x7F]/u;
6398
6729
  function validatePluginPath(pluginPath) {
@@ -6482,7 +6813,7 @@ async function execCommand(command, args, options) {
6482
6813
  }
6483
6814
 
6484
6815
  // src/cli/lib/plugins/plugin-settings.ts
6485
- import path20 from "path";
6816
+ import path19 from "path";
6486
6817
  import { z as z5 } from "zod";
6487
6818
  var pluginSettingsSchema = z5.object({
6488
6819
  // Absent when no plugin is enabled.
@@ -6503,7 +6834,7 @@ var installedPluginsSchema = z5.object({
6503
6834
  plugins: z5.record(z5.string(), z5.array(pluginInstallationSchema))
6504
6835
  }).passthrough();
6505
6836
  function getInstalledPluginsRegistryPath(pluginsDir2) {
6506
- return path20.join(pluginsDir2, INSTALLED_PLUGINS_FILE);
6837
+ return path19.join(pluginsDir2, INSTALLED_PLUGINS_FILE);
6507
6838
  }
6508
6839
  async function readRegisteredInstallations(pluginsDir2) {
6509
6840
  const registryPath = getInstalledPluginsRegistryPath(pluginsDir2);
@@ -6536,7 +6867,7 @@ async function listPluginInstallsForProject(pluginsDir2, projectDir) {
6536
6867
  });
6537
6868
  }
6538
6869
  async function getEnabledPluginKeys(projectDir) {
6539
- const settingsPath = path20.join(projectDir, CLAUDE_DIR, STANDARD_FILES.SETTINGS_JSON);
6870
+ const settingsPath = path19.join(projectDir, CLAUDE_DIR, STANDARD_FILES.SETTINGS_JSON);
6540
6871
  if (!await fileExists(settingsPath)) {
6541
6872
  verbose(`No settings.json found at '${settingsPath}'`);
6542
6873
  return [];
@@ -6596,6 +6927,13 @@ async function claudePluginInstall(pluginPath, scope, projectDir, options) {
6596
6927
  throw new Error(`Plugin installation failed: ${whyClaudeFailed(result)}`);
6597
6928
  }
6598
6929
  }
6930
+ async function claudePluginUpdate(pluginRef, scope, projectDir, options) {
6931
+ validatePluginName(pluginRef);
6932
+ const cwd = resolvePluginCwd(scope, projectDir);
6933
+ const args = ["plugin", "update", pluginRef, "--scope", scope];
6934
+ const result = await execCommand("claude", args, { cwd, ...configDirEnv(options) });
6935
+ if (result.exitCode !== 0) throw new Error(whyClaudeFailed(result));
6936
+ }
6599
6937
  async function isClaudeCLIAvailable(options) {
6600
6938
  try {
6601
6939
  const result = await execCommand("claude", ["--version"], configDirEnv(options));
@@ -6684,7 +7022,7 @@ function namesNothingThere(message) {
6684
7022
  return NOT_INSTALLED_MESSAGES.some((phrase) => message.includes(phrase));
6685
7023
  }
6686
7024
  function claudePluginsRoot(projectDir, options) {
6687
- if (options?.configDir !== void 0) return path21.join(options.configDir, PLUGINS_SUBDIR);
7025
+ if (options?.configDir !== void 0) return path20.join(options.configDir, PLUGINS_SUBDIR);
6688
7026
  return pluginsDir(CLAUDE, "global", projectDir);
6689
7027
  }
6690
7028
  async function claudeListPlugins(projectDir, options) {
@@ -6712,6 +7050,7 @@ function claudeHost() {
6712
7050
  addMarketplace: claudePluginMarketplaceAdd,
6713
7051
  refreshMarketplace: claudePluginMarketplaceUpdate,
6714
7052
  installPlugin: (pluginRef, scope, projectDir, options) => claudePluginInstall(pluginRef, toClaudePluginScope(scope), projectDir, options),
7053
+ updatePlugin: (pluginRef, scope, projectDir, options) => claudePluginUpdate(pluginRef, toClaudePluginScope(scope), projectDir, options),
6715
7054
  uninstallPlugin: (pluginRef, scope, projectDir, options) => claudePluginUninstall(pluginRef, toClaudePluginScope(scope), projectDir, options),
6716
7055
  listPlugins: claudeListPlugins
6717
7056
  };
@@ -6719,8 +7058,89 @@ function claudeHost() {
6719
7058
 
6720
7059
  // src/cli/lib/hosts/codex-host.ts
6721
7060
  import os13 from "os";
6722
- import path22 from "path";
7061
+ import path21 from "path";
6723
7062
  import { z as z7 } from "zod";
7063
+
7064
+ // src/cli/lib/hosts/codex-plugin-switch.ts
7065
+ import { partition as partition5 } from "remeda";
7066
+ function sayWhichAreLeftOn(leftOn) {
7067
+ return leftOn.map(({ pluginKey, reason }) => `Codex left ${pluginKey} switched on: ${reason}`).join("\n");
7068
+ }
7069
+ var PluginsLeftSwitchedOnError = class extends Error {
7070
+ constructor(leftOn) {
7071
+ super(sayWhichAreLeftOn(leftOn));
7072
+ this.leftOn = leftOn;
7073
+ }
7074
+ leftOn;
7075
+ };
7076
+ var FailedLeavingPluginsSwitchedOnError = class extends Error {
7077
+ constructor(failure, leftOn) {
7078
+ super(`${getErrorMessage(failure)}
7079
+ ${sayWhichAreLeftOn(leftOn)}`, { cause: failure });
7080
+ this.failure = failure;
7081
+ this.leftOn = leftOn;
7082
+ }
7083
+ failure;
7084
+ leftOn;
7085
+ };
7086
+ var SWITCH = /^(\s*enabled\s*=\s*)(true|false)(\s*)$/;
7087
+ var SWITCHED_OFF = "false";
7088
+ var NOT_FOUND = -1;
7089
+ async function switchCodexPluginsOff(configFile, pluginKeys) {
7090
+ if (pluginKeys.length === 0) return;
7091
+ const leftOn = await pluginsStillSwitchedOn(configFile, pluginKeys);
7092
+ if (leftOn.length > 0) throw new PluginsLeftSwitchedOnError(leftOn);
7093
+ }
7094
+ async function pluginsStillSwitchedOn(configFile, pluginKeys) {
7095
+ const leftOn = (pluginKey, reason) => ({ pluginKey, configFile, reason });
7096
+ try {
7097
+ const config = await readFile(configFile);
7098
+ const switched = withSwitchesOff(config, pluginKeys);
7099
+ if (switched.config !== config) await writeFile(configFile, switched.config);
7100
+ return switched.unfound.map(
7101
+ (pluginKey) => leftOn(pluginKey, notSpelledAsCodexWritesIt(pluginKey))
7102
+ );
7103
+ } catch (error) {
7104
+ return pluginKeys.map((pluginKey) => leftOn(pluginKey, getErrorMessage(error)));
7105
+ }
7106
+ }
7107
+ function withSwitchesOff(config, pluginKeys) {
7108
+ const lines = config.split("\n");
7109
+ const located = pluginKeys.map((pluginKey) => ({
7110
+ pluginKey,
7111
+ at: switchLineOf(lines, pluginKey)
7112
+ }));
7113
+ const [found, unfound] = partition5(located, ({ at }) => at !== NOT_FOUND);
7114
+ const toSwitch = new Set(found.map(({ at }) => at));
7115
+ return {
7116
+ config: lines.map((line2, at) => toSwitch.has(at) ? switchedOff(line2) : line2).join("\n"),
7117
+ unfound: unfound.map(({ pluginKey }) => pluginKey)
7118
+ };
7119
+ }
7120
+ function switchLineOf(lines, pluginKey) {
7121
+ const header = lines.findIndex((line2) => line2.trim() === pluginTableHeader(pluginKey));
7122
+ if (header === NOT_FOUND) return NOT_FOUND;
7123
+ const end = endOfTable(lines, header);
7124
+ return lines.findIndex((line2, at) => at > header && at < end && SWITCH.test(line2));
7125
+ }
7126
+ function endOfTable(lines, header) {
7127
+ const nextTable = lines.findIndex((line2, at) => at > header && opensATable(line2));
7128
+ return nextTable === NOT_FOUND ? lines.length : nextTable;
7129
+ }
7130
+ function switchedOff(line2) {
7131
+ return line2.replace(SWITCH, `$1${SWITCHED_OFF}$3`);
7132
+ }
7133
+ function opensATable(line2) {
7134
+ return line2.trim().startsWith("[");
7135
+ }
7136
+ function pluginTableHeader(pluginKey) {
7137
+ return `[plugins."${pluginKey}"]`;
7138
+ }
7139
+ function notSpelledAsCodexWritesIt(pluginKey) {
7140
+ return `it has no ${pluginTableHeader(pluginKey)} table with an 'enabled' line`;
7141
+ }
7142
+
7143
+ // src/cli/lib/hosts/codex-host.ts
6724
7144
  var CODEX = "codex";
6725
7145
  var PLUGIN_CACHE_SUBDIR = "cache";
6726
7146
  var STANDING_WARNING = /could not create PATH aliases/;
@@ -6803,7 +7223,7 @@ var pluginListingSchema = z7.object({
6803
7223
  )
6804
7224
  });
6805
7225
  function codexPluginsRoot(projectDir, options) {
6806
- if (options?.configDir !== void 0) return path22.join(options.configDir, PLUGINS_SUBDIR);
7226
+ if (options?.configDir !== void 0) return path21.join(options.configDir, PLUGINS_SUBDIR);
6807
7227
  return pluginsDir(CODEX, "global", projectDir);
6808
7228
  }
6809
7229
  async function isCodexCLIAvailable(options) {
@@ -6877,23 +7297,56 @@ async function upgradeGitMarketplace(name, options) {
6877
7297
  );
6878
7298
  }
6879
7299
  async function reinstallEachPluginOf(name, options) {
6880
- const installed = await codexPluginListing(options, codexPluginCwd());
6881
- for (const plugin of installed.filter((entry) => entry.marketplaceName === name)) {
6882
- await installCodexPlugin(plugin.pluginId, options);
7300
+ const ofTheMarketplace = (await codexPluginListing(options, codexPluginCwd())).filter(
7301
+ (plugin) => plugin.marketplaceName === name
7302
+ );
7303
+ for (const [at, plugin] of ofTheMarketplace.entries()) {
7304
+ try {
7305
+ await installCodexPlugin(plugin.pluginId, options);
7306
+ } catch (refusal2) {
7307
+ await switchBackOffThenRethrow(refusal2, ofTheMarketplace.slice(0, at), options);
7308
+ }
6883
7309
  }
7310
+ await switchBackOff(ofTheMarketplace, options);
7311
+ }
7312
+ async function switchBackOffThenRethrow(refusal2, added, options) {
7313
+ try {
7314
+ await switchBackOff(added, options);
7315
+ } catch (error) {
7316
+ if (!(error instanceof PluginsLeftSwitchedOnError)) throw error;
7317
+ throw new FailedLeavingPluginsSwitchedOnError(refusal2, error.leftOn);
7318
+ }
7319
+ throw refusal2;
7320
+ }
7321
+ async function switchBackOff(heldBefore, options) {
7322
+ await switchCodexPluginsOff(
7323
+ codexConfigFile(options),
7324
+ heldBefore.filter((plugin) => !plugin.enabled).map((plugin) => plugin.pluginId)
7325
+ );
7326
+ }
7327
+ function codexConfigFile(options) {
7328
+ return path21.join(codexStateRoot(options, codexPluginCwd()), CODEX_CONFIG_FILE);
6884
7329
  }
6885
7330
  async function codexInstallPlugin(pluginRef, _scope, _projectDir, options) {
6886
7331
  await installCodexPlugin(pluginRef, options);
6887
7332
  }
6888
- async function installCodexPlugin(pluginRef, options) {
7333
+ async function installCodexPlugin(pluginRef, options, attempt = "install") {
6889
7334
  validatePluginPath(pluginRef);
6890
7335
  await runCodexOrThrow(
6891
7336
  ["plugin", "add", pluginRef, "--json"],
6892
7337
  options,
6893
- `install ${pluginRef}`,
7338
+ `${attempt} ${pluginRef}`,
6894
7339
  codexPluginCwd()
6895
7340
  );
6896
7341
  }
7342
+ async function codexUpdatePlugin(pluginRef, _scope, _projectDir, options) {
7343
+ const held = await heldPlugin(pluginRef, options);
7344
+ if (held === void 0) {
7345
+ throw new Error(`Failed to update ${pluginRef}: codex has not installed it`);
7346
+ }
7347
+ await installCodexPlugin(pluginRef, options, "update");
7348
+ await switchBackOff([held], options);
7349
+ }
6897
7350
  async function codexUninstallPlugin(pluginRef, _scope, _projectDir, options) {
6898
7351
  validatePluginName(pluginRef);
6899
7352
  const wasInstalled = await isListed(pluginRef, options);
@@ -6911,8 +7364,11 @@ async function codexUninstallPlugin(pluginRef, _scope, _projectDir, options) {
6911
7364
  return wasInstalled ? "removed" : "absent";
6912
7365
  }
6913
7366
  async function isListed(pluginRef, options) {
7367
+ return await heldPlugin(pluginRef, options) !== void 0;
7368
+ }
7369
+ async function heldPlugin(pluginRef, options) {
6914
7370
  const installed = await codexPluginListing(options, codexPluginCwd());
6915
- return installed.some((plugin) => plugin.pluginId === pluginRef);
7371
+ return installed.find((plugin) => plugin.pluginId === pluginRef);
6916
7372
  }
6917
7373
  async function codexPluginListing(options, askedFrom) {
6918
7374
  const listing = await readJsonFromCodex(
@@ -6933,7 +7389,7 @@ async function codexListPlugins(projectDir, options) {
6933
7389
  async function codexPluginsOrUnreadable(projectDir, options) {
6934
7390
  const pluginsRoot = codexPluginsRoot(projectDir, options);
6935
7391
  if (pluginsRoot === null) return { kind: "listed", plugins: [] };
6936
- const cacheRoot2 = path22.join(pluginsRoot, PLUGIN_CACHE_SUBDIR);
7392
+ const cacheRoot2 = path21.join(pluginsRoot, PLUGIN_CACHE_SUBDIR);
6937
7393
  let installed;
6938
7394
  try {
6939
7395
  installed = await codexPluginListing(options, projectDir);
@@ -6945,7 +7401,7 @@ async function codexPluginsOrUnreadable(projectDir, options) {
6945
7401
  kind: "listed",
6946
7402
  plugins: installed.map((plugin) => ({
6947
7403
  pluginKey: plugin.pluginId,
6948
- installPath: path22.join(cacheRoot2, plugin.marketplaceName, plugin.name, plugin.version),
7404
+ installPath: path21.join(cacheRoot2, plugin.marketplaceName, plugin.name, plugin.version),
6949
7405
  enabled: plugin.enabled
6950
7406
  }))
6951
7407
  };
@@ -6960,6 +7416,7 @@ function codexHost() {
6960
7416
  addMarketplace: codexMarketplaceAdd,
6961
7417
  refreshMarketplace: refreshCodexMarketplace,
6962
7418
  installPlugin: codexInstallPlugin,
7419
+ updatePlugin: codexUpdatePlugin,
6963
7420
  uninstallPlugin: codexUninstallPlugin,
6964
7421
  listPlugins: codexListPlugins
6965
7422
  };
@@ -7225,10 +7682,11 @@ function sumCounts(scopeCounts) {
7225
7682
  async function countCompiledAgentsPerScope(installation, scopes) {
7226
7683
  return Promise.all(
7227
7684
  scopes.map(async (scope) => {
7228
- const dir = resolveInstallPaths(installation.projectDir, scope).agentsDir;
7685
+ const provider = providerAtScope(installation.projectDir, scope);
7686
+ const dir = agentsDir(provider, scope, installation.projectDir);
7229
7687
  return {
7230
7688
  dir,
7231
- count: await countDirEntries(dir, isCompiledAgentFileIn(agentCodec("claude").extension))
7689
+ count: await countDirEntries(dir, isCompiledAgentFileIn(agentCodec(provider).extension))
7232
7690
  };
7233
7691
  })
7234
7692
  );
@@ -7257,7 +7715,7 @@ function formatInstallationDisplay(info, skillLines = []) {
7257
7715
  }
7258
7716
 
7259
7717
  // src/cli/lib/plugins/plugin-validator.ts
7260
- import path23 from "path";
7718
+ import path22 from "path";
7261
7719
 
7262
7720
  // src/cli/lib/validation-result.ts
7263
7721
  function validResult() {
@@ -7289,15 +7747,15 @@ async function validatePluginStructure(pluginPath) {
7289
7747
  if (!await directoryExists2(pluginPath)) {
7290
7748
  return invalidResult(`Plugin directory does not exist: ${pluginPath}`);
7291
7749
  }
7292
- const pluginDir = path23.join(pluginPath, PLUGIN_MANIFEST_DIR);
7750
+ const pluginDir = path22.join(pluginPath, PLUGIN_MANIFEST_DIR);
7293
7751
  if (!await directoryExists2(pluginDir)) {
7294
7752
  errors.push(`Missing ${PLUGIN_MANIFEST_DIR}/ directory`);
7295
7753
  }
7296
- const manifestPath = path23.join(pluginDir, STANDARD_FILES.PLUGIN_JSON);
7754
+ const manifestPath = path22.join(pluginDir, STANDARD_FILES.PLUGIN_JSON);
7297
7755
  if (!await fileExists(manifestPath)) {
7298
7756
  errors.push(`Missing ${PLUGIN_MANIFEST_DIR}/${STANDARD_FILES.PLUGIN_JSON}`);
7299
7757
  }
7300
- const readmePath = path23.join(pluginPath, STANDARD_FILES.README_MD);
7758
+ const readmePath = path22.join(pluginPath, STANDARD_FILES.README_MD);
7301
7759
  if (!await fileExists(readmePath)) {
7302
7760
  warnings.push("Missing README.md (recommended for documentation)");
7303
7761
  }
@@ -7340,15 +7798,15 @@ async function validatePluginManifest(manifestPath) {
7340
7798
  if (!manifest.description) {
7341
7799
  warnings.push("Missing description field (recommended for discoverability)");
7342
7800
  }
7343
- const pluginDir = path23.dirname(path23.dirname(manifestPath));
7801
+ const pluginDir = path22.dirname(path22.dirname(manifestPath));
7344
7802
  if (isNonEmptyString(manifest.skills)) {
7345
- const skillsPath = path23.join(pluginDir, manifest.skills);
7803
+ const skillsPath = path22.join(pluginDir, manifest.skills);
7346
7804
  if (!await directoryExists2(skillsPath)) {
7347
7805
  errors.push(`Skills path does not exist: ${manifest.skills}`);
7348
7806
  }
7349
7807
  }
7350
7808
  if (isNonEmptyString(manifest.agents)) {
7351
- const agentsPath = path23.join(pluginDir, manifest.agents);
7809
+ const agentsPath = path22.join(pluginDir, manifest.agents);
7352
7810
  if (!await directoryExists2(agentsPath)) {
7353
7811
  errors.push(`Agents path does not exist: ${manifest.agents}`);
7354
7812
  }
@@ -7417,14 +7875,14 @@ function prefixResult(result, prefix) {
7417
7875
  async function validateEachFile(pluginPath, scannedDir, files, validate) {
7418
7876
  const results = await Promise.all(
7419
7877
  files.map(async (file) => {
7420
- const filePath = path23.join(scannedDir, file);
7421
- return prefixResult(await validate(filePath), path23.relative(pluginPath, filePath));
7878
+ const filePath = path22.join(scannedDir, file);
7879
+ return prefixResult(await validate(filePath), path22.relative(pluginPath, filePath));
7422
7880
  })
7423
7881
  );
7424
7882
  return mergeValidationResults(results);
7425
7883
  }
7426
7884
  async function validatePluginSkillFiles(pluginPath, skillsRelPath) {
7427
- const skillsDir2 = path23.join(pluginPath, skillsRelPath);
7885
+ const skillsDir2 = path22.join(pluginPath, skillsRelPath);
7428
7886
  if (!await directoryExists2(skillsDir2)) return validResult();
7429
7887
  const files = await glob(`**/${STANDARD_FILES.SKILL_MD}`, skillsDir2);
7430
7888
  if (files.length === 0) {
@@ -7437,7 +7895,7 @@ async function validatePluginSkillFiles(pluginPath, skillsRelPath) {
7437
7895
  return validateEachFile(pluginPath, skillsDir2, files, validateSkillFrontmatter);
7438
7896
  }
7439
7897
  async function validatePluginAgentFiles(pluginPath, agentsRelPath) {
7440
- const agentsDir2 = path23.join(pluginPath, agentsRelPath);
7898
+ const agentsDir2 = path22.join(pluginPath, agentsRelPath);
7441
7899
  if (!await directoryExists2(agentsDir2)) return validResult();
7442
7900
  const files = await glob(agentCodec("claude").listGlob, agentsDir2);
7443
7901
  if (files.length === 0) {
@@ -7508,7 +7966,7 @@ async function listPluginDirNames(pluginsDir2) {
7508
7966
  const dirChecks = await Promise.all(
7509
7967
  allDirs.map(async (dirName) => ({
7510
7968
  dirName,
7511
- isPlugin: await directoryExists2(path23.join(pluginsDir2, dirName, PLUGIN_MANIFEST_DIR))
7969
+ isPlugin: await directoryExists2(path22.join(pluginsDir2, dirName, PLUGIN_MANIFEST_DIR))
7512
7970
  }))
7513
7971
  );
7514
7972
  return dirChecks.filter((c) => c.isPlugin).map((c) => c.dirName);
@@ -7516,7 +7974,7 @@ async function listPluginDirNames(pluginsDir2) {
7516
7974
  async function validateEachPlugin(pluginsDir2, pluginDirs) {
7517
7975
  const results = [];
7518
7976
  for (const pluginName of pluginDirs) {
7519
- const result = await validatePlugin(path23.join(pluginsDir2, pluginName));
7977
+ const result = await validatePlugin(path22.join(pluginsDir2, pluginName));
7520
7978
  results.push({ name: pluginName, result });
7521
7979
  }
7522
7980
  return results;
@@ -7537,10 +7995,10 @@ function summarizePluginValidations(results) {
7537
7995
  }
7538
7996
 
7539
7997
  // src/cli/lib/plugins/plugin-discovery.ts
7540
- async function getVerifiedPluginInstallPaths(projectDir) {
7998
+ async function getVerifiedPluginInstallPaths(projectDir, provider = providerInUse(projectDir)) {
7541
7999
  let enabled;
7542
8000
  try {
7543
- const installed = await hostAt(projectDir).listPlugins(projectDir);
8001
+ const installed = await hostFor(provider).listPlugins(projectDir);
7544
8002
  enabled = installed.filter((plugin) => plugin.enabled).map(({ pluginKey, installPath }) => ({ pluginKey, installPath }));
7545
8003
  } catch (error) {
7546
8004
  verbose(`Failed to list installed plugins: ${getErrorMessage(error)}`);
@@ -7559,8 +8017,8 @@ async function hasManifestOnDisk({ pluginKey, installPath }) {
7559
8017
  }
7560
8018
  return manifestExists;
7561
8019
  }
7562
- async function discoverAllPluginSkills(projectDir) {
7563
- const pluginPaths = await getVerifiedPluginInstallPaths(projectDir);
8020
+ async function discoverAllPluginSkills(projectDir, provider = providerInUse(projectDir)) {
8021
+ const pluginPaths = await getVerifiedPluginInstallPaths(projectDir, provider);
7564
8022
  if (pluginPaths.length === 0) {
7565
8023
  verbose(`No enabled plugins found for '${projectDir}'`);
7566
8024
  return {};
@@ -7599,14 +8057,14 @@ function sanitizeSkillName(name) {
7599
8057
  return name.replace(/\+/g, "-");
7600
8058
  }
7601
8059
  function isSingleFolderName(name) {
7602
- return name !== "" && name !== "." && name !== ".." && path24.basename(name) === name;
8060
+ return name !== "" && name !== "." && name !== ".." && path23.basename(name) === name;
7603
8061
  }
7604
- async function assertClearable(pluginDir, skillPath, skillName) {
7605
- const skill = `Skill '${path24.basename(skillPath)}'`;
7606
- const file = `File: ${path24.join(skillPath, STANDARD_FILES.SKILL_MD)}`;
7607
- if (!isSingleFolderName(skillName)) {
8062
+ async function assertClearable(pluginDir, skillPath, skillName2) {
8063
+ const skill = `Skill '${path23.basename(skillPath)}'`;
8064
+ const file = `File: ${path23.join(skillPath, STANDARD_FILES.SKILL_MD)}`;
8065
+ if (!isSingleFolderName(skillName2)) {
7608
8066
  throw new Error(
7609
- `${skill} is named '${skillName}', which is not a single folder name, so its plugin cannot be built inside the output directory. ${file}`
8067
+ `${skill} is named '${skillName2}', which is not a single folder name, so its plugin cannot be built inside the output directory. ${file}`
7610
8068
  );
7611
8069
  }
7612
8070
  if (isPathWithin(skillPath, pluginDir)) {
@@ -7616,12 +8074,12 @@ async function assertClearable(pluginDir, skillPath, skillName) {
7616
8074
  }
7617
8075
  if (await holdsContentNoBuildMade(pluginDir)) {
7618
8076
  throw new Error(
7619
- `${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}`
8077
+ `${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}`
7620
8078
  );
7621
8079
  }
7622
8080
  }
7623
8081
  async function readPluginSkillMetadata(skillPath) {
7624
- const metadataPath = path24.join(skillPath, STANDARD_FILES.METADATA_YAML);
8082
+ const metadataPath = path23.join(skillPath, STANDARD_FILES.METADATA_YAML);
7625
8083
  if (!await fileExists(metadataPath)) {
7626
8084
  return null;
7627
8085
  }
@@ -7639,8 +8097,8 @@ async function readPluginSkillMetadata(skillPath) {
7639
8097
  return null;
7640
8098
  }
7641
8099
  }
7642
- function generateReadme(skillName, frontmatter) {
7643
- return `# ${skillName}
8100
+ function generateReadme(skillName2, frontmatter) {
8101
+ return `# ${skillName2}
7644
8102
 
7645
8103
  ${frontmatter.description}
7646
8104
 
@@ -7650,7 +8108,7 @@ Add this plugin to your Claude Code configuration:
7650
8108
 
7651
8109
  \`\`\`json
7652
8110
  {
7653
- "plugins": ["${skillName}"]
8111
+ "plugins": ["${skillName2}"]
7654
8112
  }
7655
8113
  \`\`\`
7656
8114
 
@@ -7665,28 +8123,28 @@ This skill is automatically available when installed.
7665
8123
  }
7666
8124
  async function writeSkillFiles(skillPath, pluginSkillDir, skillMdContent) {
7667
8125
  await ensureDir(pluginSkillDir);
7668
- await writeFile(path24.join(pluginSkillDir, STANDARD_FILES.SKILL_MD), skillMdContent);
8126
+ await writeFile(path23.join(pluginSkillDir, STANDARD_FILES.SKILL_MD), skillMdContent);
7669
8127
  verbose(` Copied ${STANDARD_FILES.SKILL_MD}`);
7670
8128
  for (const fileName of SKILL_CONTENT_FILES) {
7671
8129
  if (fileName === STANDARD_FILES.SKILL_MD) continue;
7672
- const sourcePath = path24.join(skillPath, fileName);
8130
+ const sourcePath = path23.join(skillPath, fileName);
7673
8131
  if (await fileExists(sourcePath)) {
7674
8132
  const content = await readFile(sourcePath);
7675
- await writeFile(path24.join(pluginSkillDir, fileName), content);
8133
+ await writeFile(path23.join(pluginSkillDir, fileName), content);
7676
8134
  verbose(` Copied ${fileName}`);
7677
8135
  }
7678
8136
  }
7679
8137
  for (const dirName of SKILL_CONTENT_DIRS) {
7680
- const sourceDir2 = path24.join(skillPath, dirName);
8138
+ const sourceDir2 = path23.join(skillPath, dirName);
7681
8139
  if (await fileExists(sourceDir2)) {
7682
- await copy(sourceDir2, path24.join(pluginSkillDir, dirName));
8140
+ await copy(sourceDir2, path23.join(pluginSkillDir, dirName));
7683
8141
  verbose(` Copied ${dirName}/`);
7684
8142
  }
7685
8143
  }
7686
8144
  }
7687
8145
  async function readSkillMdToBuild(skillPath) {
7688
- const dirBasename = path24.basename(skillPath);
7689
- const skillMdPath = path24.join(skillPath, STANDARD_FILES.SKILL_MD);
8146
+ const dirBasename = path23.basename(skillPath);
8147
+ const skillMdPath = path23.join(skillPath, STANDARD_FILES.SKILL_MD);
7690
8148
  if (!await fileExists(skillMdPath)) {
7691
8149
  throw new Error(
7692
8150
  `Skill '${dirBasename}' is missing required ${STANDARD_FILES.SKILL_MD} file. Expected at: ${skillMdPath}`
@@ -7704,13 +8162,13 @@ async function readSkillMdToBuild(skillPath) {
7704
8162
  async function compileSkillPlugin(options) {
7705
8163
  const { skillPath, outputDir, skillName: overrideName } = options;
7706
8164
  const { skillMdContent, frontmatter } = await readSkillMdToBuild(skillPath);
7707
- const skillName = overrideName ?? sanitizeSkillName(frontmatter.name);
7708
- const pluginDir = path24.join(outputDir, skillName);
7709
- await assertClearable(pluginDir, skillPath, skillName);
7710
- verbose(`Compiling skill plugin: ${skillName} from ${skillPath}`);
8165
+ const skillName2 = overrideName ?? sanitizeSkillName(frontmatter.name);
8166
+ const pluginDir = path23.join(outputDir, skillName2);
8167
+ await assertClearable(pluginDir, skillPath, skillName2);
8168
+ verbose(`Compiling skill plugin: ${skillName2} from ${skillPath}`);
7711
8169
  const metadata = await readPluginSkillMetadata(skillPath);
7712
8170
  const manifestAt = (version) => generateSkillPluginManifest({
7713
- skillName,
8171
+ skillName: skillName2,
7714
8172
  description: frontmatter.description,
7715
8173
  ...metadata?.author !== void 0 && { author: metadata.author },
7716
8174
  ...metadata?.category !== void 0 && { category: metadata.category },
@@ -7722,21 +8180,21 @@ async function compileSkillPlugin(options) {
7722
8180
  await writeContentHash(pluginDir, await computeSkillFolderHash(skillPath), getPluginManifestPath);
7723
8181
  await writeSkillFiles(
7724
8182
  skillPath,
7725
- path24.join(pluginDir, STANDARD_DIRS.SKILLS, skillName),
8183
+ path23.join(pluginDir, STANDARD_DIRS.SKILLS, skillName2),
7726
8184
  skillMdContent
7727
8185
  );
7728
- const readme = generateReadme(skillName, frontmatter);
7729
- await writeFile(path24.join(pluginDir, STANDARD_FILES.README_MD), readme);
8186
+ const readme = generateReadme(skillName2, frontmatter);
8187
+ await writeFile(path23.join(pluginDir, STANDARD_FILES.README_MD), readme);
7730
8188
  verbose(" Generated README.md");
7731
8189
  const manifest = manifestAt(
7732
8190
  versionAfterRebuild(earlierBuild, await computeShippedHash(pluginDir))
7733
8191
  );
7734
8192
  await writePluginManifest(pluginDir, manifest);
7735
- verbose(` Wrote plugin.json for ${skillName} (v${manifest.version})`);
8193
+ verbose(` Wrote plugin.json for ${skillName2} (v${manifest.version})`);
7736
8194
  return {
7737
8195
  pluginPath: pluginDir,
7738
8196
  manifest,
7739
- skillName
8197
+ skillName: skillName2
7740
8198
  };
7741
8199
  }
7742
8200
  async function compileAllSkillPlugins(skillsDir2, outputDir) {
@@ -7744,7 +8202,7 @@ async function compileAllSkillPlugins(skillsDir2, outputDir) {
7744
8202
  const failed = [];
7745
8203
  const skillMdFiles = await glob(`**/${STANDARD_FILES.SKILL_MD}`, skillsDir2);
7746
8204
  for (const skillMdFile of skillMdFiles) {
7747
- const skillPath = path24.join(skillsDir2, path24.dirname(skillMdFile));
8205
+ const skillPath = path23.join(skillsDir2, path23.dirname(skillMdFile));
7748
8206
  try {
7749
8207
  const result = await compileSkillPlugin({
7750
8208
  skillPath,
@@ -7754,7 +8212,7 @@ async function compileAllSkillPlugins(skillsDir2, outputDir) {
7754
8212
  log(` [OK] ${result.skillName}`);
7755
8213
  } catch (error) {
7756
8214
  const errorMessage = getErrorMessage(error);
7757
- const dirBasename = path24.basename(skillPath);
8215
+ const dirBasename = path23.basename(skillPath);
7758
8216
  failed.push(dirBasename);
7759
8217
  warn(`Failed to compile skill from '${dirBasename}': ${errorMessage}`);
7760
8218
  }
@@ -7770,7 +8228,7 @@ Compiled ${plural(results.length, "skill plugin")}:`);
7770
8228
  }
7771
8229
 
7772
8230
  // src/cli/lib/skills/local-skill-loader.ts
7773
- import path25 from "path";
8231
+ import path24 from "path";
7774
8232
  function copiedInSkillsAt(root, provider) {
7775
8233
  const scope = isHomeDirectory(root) ? "global" : "project";
7776
8234
  return {
@@ -7796,9 +8254,9 @@ async function discoverLocalSkills(root, provider = providerInUse(root)) {
7796
8254
  };
7797
8255
  }
7798
8256
  async function extractLocalSkill(localSkillsPath, pathPrefix, skillDirName) {
7799
- const skillDir = path25.join(localSkillsPath, skillDirName);
7800
- const metadataPath = path25.join(skillDir, STANDARD_FILES.METADATA_YAML);
7801
- const skillMdPath = path25.join(skillDir, STANDARD_FILES.SKILL_MD);
8257
+ const skillDir = path24.join(localSkillsPath, skillDirName);
8258
+ const metadataPath = path24.join(skillDir, STANDARD_FILES.METADATA_YAML);
8259
+ const skillMdPath = path24.join(skillDir, STANDARD_FILES.SKILL_MD);
7802
8260
  if (!await fileExists(metadataPath)) {
7803
8261
  verbose(`Skipping local skill '${skillDirName}': No metadata.yaml found`);
7804
8262
  return null;
@@ -7828,7 +8286,7 @@ async function extractLocalSkill(localSkillsPath, pathPrefix, skillDirName) {
7828
8286
  return null;
7829
8287
  }
7830
8288
  const relativePath = `${pathPrefix}/${skillDirName}/`;
7831
- const absolutePath = path25.join(localSkillsPath, skillDirName) + path25.sep;
8289
+ const absolutePath = path24.join(localSkillsPath, skillDirName) + path24.sep;
7832
8290
  const skillId = frontmatter.name;
7833
8291
  const extracted = {
7834
8292
  id: skillId,
@@ -7851,7 +8309,7 @@ async function extractLocalSkill(localSkillsPath, pathPrefix, skillDirName) {
7851
8309
  }
7852
8310
 
7853
8311
  // src/cli/lib/skills/unresolved-skill-entries.ts
7854
- import path26 from "path";
8312
+ import path25 from "path";
7855
8313
  async function findUnusableSavedSkillMetadata(savedSkills, projectDir) {
7856
8314
  const verdicts = await Promise.all(
7857
8315
  savedSkills.map((saved) => unusableMetadataOf(saved, projectDir))
@@ -7870,11 +8328,11 @@ function claimsLocalCopy(saved) {
7870
8328
  }
7871
8329
  function installedMetadataPath(saved, projectDir) {
7872
8330
  const { skillsDir: skillsDir2 } = resolveInstallPaths(projectDir, saved.scope);
7873
- return path26.join(skillsDir2, saved.id, STANDARD_FILES.METADATA_YAML);
8331
+ return path25.join(skillsDir2, saved.id, STANDARD_FILES.METADATA_YAML);
7874
8332
  }
7875
8333
 
7876
8334
  // src/cli/lib/skills/local-skill-mover.ts
7877
- import path27 from "path";
8335
+ import path26 from "path";
7878
8336
  var THE_DIRECTORY_ITSELF = ".";
7879
8337
  function validateSkillId(skillId) {
7880
8338
  return !(skillId.length === 0 || skillId === THE_DIRECTORY_ITSELF || skillId.includes("\0") || skillId.includes("..") || skillId.includes("/") || skillId.includes("\\"));
@@ -7884,8 +8342,8 @@ async function deleteLocalSkill(projectDir, skillId, scope = "project") {
7884
8342
  warn(`Invalid skill ID for deletion: '${skillId}'`);
7885
8343
  return;
7886
8344
  }
7887
- const skillsDir2 = path27.resolve(resolveInstallPaths(projectDir, scope).skillsDir);
7888
- const skillPath = path27.resolve(skillsDir2, skillId);
8345
+ const skillsDir2 = path26.resolve(resolveInstallPaths(projectDir, scope).skillsDir);
8346
+ const skillPath = path26.resolve(skillsDir2, skillId);
7889
8347
  if (!isPathWithin(skillPath, skillsDir2)) {
7890
8348
  warn(`Skill ID '${skillId}' resolves outside the skills directory.`);
7891
8349
  return;
@@ -7903,10 +8361,10 @@ async function migrateLocalSkillScope(skillId, fromScope, projectDir) {
7903
8361
  return;
7904
8362
  }
7905
8363
  const toScope = fromScope === "global" ? "project" : "global";
7906
- const fromSkillsDir = path27.resolve(resolveInstallPaths(projectDir, fromScope).skillsDir);
7907
- const toSkillsDir = path27.resolve(resolveInstallPaths(projectDir, toScope).skillsDir);
7908
- const fromPath = path27.resolve(fromSkillsDir, skillId);
7909
- const toPath = path27.resolve(toSkillsDir, skillId);
8364
+ const fromSkillsDir = path26.resolve(resolveInstallPaths(projectDir, fromScope).skillsDir);
8365
+ const toSkillsDir = path26.resolve(resolveInstallPaths(projectDir, toScope).skillsDir);
8366
+ const fromPath = path26.resolve(fromSkillsDir, skillId);
8367
+ const toPath = path26.resolve(toSkillsDir, skillId);
7910
8368
  if (!isPathWithin(fromPath, fromSkillsDir)) {
7911
8369
  warn(`Skill ID '${skillId}' resolves outside the source skills directory.`);
7912
8370
  return;
@@ -7933,7 +8391,7 @@ async function migrateLocalSkillScope(skillId, fromScope, projectDir) {
7933
8391
 
7934
8392
  // src/cli/lib/matrix/matrix-loader.ts
7935
8393
  import { parse as parseYaml5 } from "yaml";
7936
- import path28 from "path";
8394
+ import path27 from "path";
7937
8395
  async function loadSkillCategories(configPath) {
7938
8396
  const data = await loadConfig(configPath, skillCategoriesFileSchema);
7939
8397
  if (!data) {
@@ -7942,10 +8400,18 @@ async function loadSkillCategories(configPath) {
7942
8400
  verbose(`Loaded skill categories: ${configPath}`);
7943
8401
  return data.categories;
7944
8402
  }
8403
+ var NoSkillRulesError = class extends Error {
8404
+ constructor(configPath) {
8405
+ super(`Invalid skill rules at '${configPath}': failed to load or validate`);
8406
+ this.configPath = configPath;
8407
+ this.name = "NoSkillRulesError";
8408
+ }
8409
+ configPath;
8410
+ };
7945
8411
  async function loadSkillRules(configPath) {
7946
8412
  const data = await loadConfig(configPath, skillRulesFileSchema);
7947
8413
  if (!data) {
7948
- throw new Error(`Invalid skill rules at '${configPath}': failed to load or validate`);
8414
+ throw new NoSkillRulesError(configPath);
7949
8415
  }
7950
8416
  const config = {
7951
8417
  version: data.version,
@@ -7961,29 +8427,33 @@ async function loadSkillRules(configPath) {
7961
8427
  }
7962
8428
  async function extractAllSkills(skillsDir2) {
7963
8429
  const metadataFiles = await glob(`**/${STANDARD_FILES.METADATA_YAML}`, skillsDir2);
7964
- const skills = [];
8430
+ const folders = [];
7965
8431
  for (const metadataFile of metadataFiles) {
7966
- const skill = await extractSkill(skillsDir2, metadataFile);
7967
- if (skill) skills.push(skill);
8432
+ folders.push(await readSkillFolder(skillsDir2, metadataFile));
7968
8433
  }
7969
- return skills;
8434
+ return {
8435
+ skills: folders.flatMap((folder) => folder.kind === "loaded" ? [folder.skill] : []),
8436
+ unloadedSkillIds: folders.flatMap(
8437
+ (folder) => folder.kind === "unloaded" ? [folder.skillId] : []
8438
+ )
8439
+ };
7970
8440
  }
7971
- async function extractSkill(skillsDir2, metadataFile) {
7972
- const skillDir = path28.dirname(metadataFile);
7973
- const skillMdPath = path28.join(skillsDir2, skillDir, STANDARD_FILES.SKILL_MD);
8441
+ async function readSkillFolder(skillsDir2, metadataFile) {
8442
+ const skillDir = path27.dirname(metadataFile);
8443
+ const skillMdPath = path27.join(skillsDir2, skillDir, STANDARD_FILES.SKILL_MD);
7974
8444
  if (!await fileExists(skillMdPath)) {
7975
- verbose(`Skipping ${metadataFile}: No ${STANDARD_FILES.SKILL_MD} found`);
7976
- return null;
8445
+ warn(`Skipping '${metadataFile}': No ${STANDARD_FILES.SKILL_MD} found`);
8446
+ return { kind: "unnamed" };
7977
8447
  }
7978
- const metadata = await readMatrixMetadataOrWarn(metadataFile, path28.join(skillsDir2, metadataFile));
7979
- if (!metadata) return null;
8448
+ const metadata = await readMatrixMetadataOrWarn(metadataFile, path27.join(skillsDir2, metadataFile));
8449
+ if (!metadata) return unloadedSkillNamedBy(skillMdPath);
7980
8450
  const frontmatter = parseFrontmatterOrWarn(skillDir, skillMdPath, await readFile(skillMdPath));
7981
- if (!frontmatter) return null;
8451
+ if (!frontmatter) return { kind: "unnamed" };
7982
8452
  if (!metadata.displayName) {
7983
8453
  warn(
7984
8454
  `Skipping '${metadataFile}': invalid ${STANDARD_FILES.METADATA_YAML} \u2014 missing required field: ${METADATA_KEYS.DISPLAY_NAME}`
7985
8455
  );
7986
- return null;
8456
+ return { kind: "unloaded", skillId: frontmatter.name };
7987
8457
  }
7988
8458
  const skillId = frontmatter.name;
7989
8459
  const extracted = {
@@ -8001,7 +8471,16 @@ async function extractSkill(skillsDir2, metadataFile) {
8001
8471
  ...metadata.custom === true ? { custom: true } : {}
8002
8472
  };
8003
8473
  verbose(`Extracted skill: ${skillId}`);
8004
- return extracted;
8474
+ return { kind: "loaded", skill: extracted };
8475
+ }
8476
+ async function unloadedSkillNamedBy(skillMdPath) {
8477
+ try {
8478
+ const frontmatter = parseFrontmatter(await readFile(skillMdPath), skillMdPath);
8479
+ return frontmatter ? { kind: "unloaded", skillId: frontmatter.name } : { kind: "unnamed" };
8480
+ } catch (error) {
8481
+ verbose(`No skill named by '${skillMdPath}' \u2014 ${getErrorMessage(error)}`);
8482
+ return { kind: "unnamed" };
8483
+ }
8005
8484
  }
8006
8485
  async function readMatrixMetadataOrWarn(metadataFile, metadataPath) {
8007
8486
  const metadataContent = await readFile(metadataPath);
@@ -8029,7 +8508,7 @@ function parseFrontmatterOrWarn(skillDir, skillMdPath, skillMdContent) {
8029
8508
  const unreadable = unreadableFrontmatter(skillMdContent);
8030
8509
  if (unreadable !== void 0) {
8031
8510
  warn(
8032
- `Skipping '${path28.join(skillDir, STANDARD_FILES.SKILL_MD)}': unparseable ${STANDARD_FILES.SKILL_MD} frontmatter at ${skillMdPath} \u2014 ${unreadable}`
8511
+ `Skipping '${path27.join(skillDir, STANDARD_FILES.SKILL_MD)}': unparseable ${STANDARD_FILES.SKILL_MD} frontmatter at ${skillMdPath} \u2014 ${unreadable}`
8033
8512
  );
8034
8513
  }
8035
8514
  return null;
@@ -9911,17 +10390,29 @@ async function loadCatalogue(options) {
9911
10390
  devMode = false,
9912
10391
  matrixOnly = false
9913
10392
  } = options;
9914
- const sourceConfig = await resolveSource({ caller, flag: sourceFlag, projectDir, provider });
10393
+ const resolvedProjectDir = projectDir || process.cwd();
10394
+ const sourceConfig = await resolveSource({
10395
+ caller,
10396
+ flag: sourceFlag,
10397
+ projectDir: resolvedProjectDir,
10398
+ provider
10399
+ });
9915
10400
  const { source } = sourceConfig;
9916
10401
  verbose(`Loading skills from source: ${source}`);
9917
10402
  const catalogue = await resolveBaseResult(sourceConfig, { purpose, devMode, matrixOnly });
9918
- const resolvedProjectDir = projectDir || process.cwd();
9919
10403
  const result = {
9920
10404
  ...catalogue,
9921
- marketplaceSkillIds: new Set(typedKeys(catalogue.matrix.skills))
10405
+ marketplaceSkillIds: new Set(typedKeys(catalogue.matrix.skills)),
10406
+ marketplaceSkills: { ...catalogue.matrix.skills }
9922
10407
  };
9923
- result.matrix = await mergeLocalSkills(result.matrix, resolvedProjectDir, provider);
9924
- await seatGlobalInstallationSkills(result.matrix, source, purpose);
10408
+ const globalProvider = provider ?? inheritedGlobalProvider(resolvedProjectDir);
10409
+ result.matrix = await mergeLocalSkills(
10410
+ result.matrix,
10411
+ resolvedProjectDir,
10412
+ provider,
10413
+ globalProvider
10414
+ );
10415
+ await seatGlobalInstallationSkills(result.matrix, source, purpose, globalProvider);
9925
10416
  if (!options.skipExtraSources) {
9926
10417
  await loadSkillsFromAllSources(
9927
10418
  result.matrix,
@@ -9935,16 +10426,20 @@ async function loadCatalogue(options) {
9935
10426
  initializeMatrix(result.matrix);
9936
10427
  return result;
9937
10428
  }
9938
- async function seatGlobalInstallationSkills(matrix2, loadedSource, purpose) {
9939
- const uncarried = await globalPluginSkillsUncarried(matrix2);
10429
+ async function seatGlobalInstallationSkills(matrix2, loadedSource, purpose, globalProvider) {
10430
+ const uncarried = await globalPluginSkillsUncarried(matrix2, globalProvider);
9940
10431
  if (uncarried.length === 0) return;
9941
- const globalCatalogue = await loadGlobalInstallationCatalogue(loadedSource, purpose);
10432
+ const globalCatalogue = await loadGlobalInstallationCatalogue(
10433
+ loadedSource,
10434
+ purpose,
10435
+ globalProvider
10436
+ );
9942
10437
  if (globalCatalogue === null) return;
9943
10438
  for (const id of uncarried) seatFromCatalogue(matrix2, globalCatalogue, id);
9944
10439
  }
9945
- async function globalPluginSkillsUncarried(matrix2) {
10440
+ async function globalPluginSkillsUncarried(matrix2, globalProvider) {
9946
10441
  try {
9947
- const globalConfig = await loadGlobalSourceConfig();
10442
+ const globalConfig = await loadGlobalSourceConfig(globalProvider);
9948
10443
  return (globalConfig?.skills ?? []).filter(isActivePlugin).filter((skill) => matrix2.skills[skill.id] === void 0).map((skill) => skill.id);
9949
10444
  } catch (error) {
9950
10445
  verbose(`Global installation's skills not seated \u2014 ${getErrorMessage(error)}`);
@@ -9954,9 +10449,13 @@ async function globalPluginSkillsUncarried(matrix2) {
9954
10449
  function isActivePlugin(skill) {
9955
10450
  return !skill.excluded && skill.origin !== EJECT_SOURCE;
9956
10451
  }
9957
- async function loadGlobalInstallationCatalogue(loadedSource, purpose) {
10452
+ async function loadGlobalInstallationCatalogue(loadedSource, purpose, globalProvider) {
9958
10453
  try {
9959
- const globalSourceConfig = await resolveSource({ caller: "stored", projectDir: os14.homedir() });
10454
+ const globalSourceConfig = await resolveSource({
10455
+ caller: "stored",
10456
+ projectDir: os14.homedir(),
10457
+ provider: globalProvider
10458
+ });
9960
10459
  if (globalSourceConfig.source === loadedSource) return null;
9961
10460
  return await resolveBaseResult(globalSourceConfig, {
9962
10461
  purpose,
@@ -10012,6 +10511,8 @@ async function resolveBaseResult(sourceConfig, { purpose, devMode, matrixOnly })
10012
10511
  const fetched = matrixOnly ? NOTHING_FETCHED : await fetchFromSource(source);
10013
10512
  return {
10014
10513
  matrix: copyOfBuiltInMatrix(),
10514
+ // Checked when it was generated, so nothing in it failed to load.
10515
+ unloadedSkillIds: /* @__PURE__ */ new Set(),
10015
10516
  sourceConfig,
10016
10517
  sourcePath: fetched.path,
10017
10518
  isLocal: false,
@@ -10082,8 +10583,8 @@ function goesOnWithoutManifest(source, purpose) {
10082
10583
  function configuredLabel(sourceConfig) {
10083
10584
  return sourceConfig.marketplace === void 0 ? {} : { marketplace: sourceConfig.marketplace };
10084
10585
  }
10085
- async function mergeLocalSkills(matrix2, projectDir, provider) {
10086
- const withGlobal = isHomeDirectory(projectDir) ? matrix2 : await mergeDiscoveredLocalSkills(matrix2, os14.homedir(), "global", provider);
10586
+ async function mergeLocalSkills(matrix2, projectDir, provider, globalProvider) {
10587
+ const withGlobal = isHomeDirectory(projectDir) ? matrix2 : await mergeDiscoveredLocalSkills(matrix2, os14.homedir(), "global", globalProvider);
10087
10588
  return mergeDiscoveredLocalSkills(withGlobal, projectDir, "project", provider);
10088
10589
  }
10089
10590
  async function mergeDiscoveredLocalSkills(matrix2, dir, label, provider) {
@@ -10097,10 +10598,11 @@ async function mergeDiscoveredLocalSkills(matrix2, dir, label, provider) {
10097
10598
  async function loadFromLocal(source, sourceConfig, purpose) {
10098
10599
  const skillsPath = isLocalSource(source) ? (await fetchFromSource(source)).path : PROJECT_ROOT;
10099
10600
  verbose(`Loading skills from local path: ${skillsPath}`);
10100
- const mergedMatrix = await loadAndMergeFromBasePath(skillsPath, source);
10601
+ const { matrix: matrix2, unloadedSkillIds } = await loadAndMergeFromBasePath(skillsPath, source);
10101
10602
  const labels = await resolveMarketplaceLabels(skillsPath, sourceConfig, purpose);
10102
10603
  return {
10103
- matrix: mergedMatrix,
10604
+ matrix: matrix2,
10605
+ unloadedSkillIds,
10104
10606
  sourceConfig,
10105
10607
  sourcePath: skillsPath,
10106
10608
  isLocal: true,
@@ -10112,10 +10614,11 @@ async function loadFromRemote(source, sourceConfig, purpose) {
10112
10614
  verbose(`Fetching skills from remote source: ${source}`);
10113
10615
  const fetchResult = await fetchFromSource(source);
10114
10616
  verbose(`Fetched to: ${fetchResult.path}`);
10115
- const mergedMatrix = await loadAndMergeFromBasePath(fetchResult.path, source);
10617
+ const { matrix: matrix2, unloadedSkillIds } = await loadAndMergeFromBasePath(fetchResult.path, source);
10116
10618
  const labels = await resolveMarketplaceLabels(source, sourceConfig, purpose);
10117
10619
  return {
10118
- matrix: mergedMatrix,
10620
+ matrix: matrix2,
10621
+ unloadedSkillIds,
10119
10622
  sourceConfig,
10120
10623
  sourcePath: fetchResult.path,
10121
10624
  isLocal: false,
@@ -10124,7 +10627,7 @@ async function loadFromRemote(source, sourceConfig, purpose) {
10124
10627
  };
10125
10628
  }
10126
10629
  async function loadMarketplaceMatrix(marketplaceDir) {
10127
- const matrix2 = await loadAndMergeFromBasePath(marketplaceDir, marketplaceDir);
10630
+ const { matrix: matrix2 } = await loadAndMergeFromBasePath(marketplaceDir, marketplaceDir);
10128
10631
  return { ...matrix2, categories: categoriesTheseSkillsAreIn(matrix2) };
10129
10632
  }
10130
10633
  function categoriesTheseSkillsAreIn(matrix2) {
@@ -10140,16 +10643,17 @@ async function loadAndMergeFromBasePath(basePath, source) {
10140
10643
  const skillsDirRelPath = sourceProjectConfig?.skillsDir ?? SKILLS_DIR_PATH;
10141
10644
  const stacksRelFile = sourceProjectConfig?.stacksFile;
10142
10645
  const { categories, sourceRules } = await loadSourceTaxonomy(basePath);
10143
- const skillsDir2 = path29.join(basePath, skillsDirRelPath);
10646
+ const skillsDir2 = path28.join(basePath, skillsDirRelPath);
10144
10647
  verbose(`Skills from source: ${skillsDir2}`);
10145
- const skills = await extractAllSkills(skillsDir2);
10648
+ const { skills, unloadedSkillIds } = await extractAllSkills(skillsDir2);
10146
10649
  await refuseCatalogueCollisions(basePath, source, skills);
10147
10650
  const relationships = relationshipsForSource(skills, sourceRules);
10148
10651
  const mergedMatrix = mergeMatrixWithSkills(categories, relationships, skills);
10149
- initializeMatrix(mergedMatrix);
10150
10652
  const stacks = await resolveOfferedStacks(basePath, stacksRelFile, source);
10151
- mergedMatrix.suggestedStacks = stacks.map((stack) => convertStackToResolvedStack(stack));
10152
- return mergedMatrix;
10653
+ mergedMatrix.suggestedStacks = stacks.map(
10654
+ (stack) => convertStackToResolvedStack(stack, mergedMatrix)
10655
+ );
10656
+ return { matrix: mergedMatrix, unloadedSkillIds: new Set(unloadedSkillIds) };
10153
10657
  }
10154
10658
  async function loadSourceTaxonomy(basePath) {
10155
10659
  const sourceCategories = await loadSourceCategories(basePath);
@@ -10163,7 +10667,7 @@ async function loadSourceTaxonomy(basePath) {
10163
10667
  return { categories, sourceRules };
10164
10668
  }
10165
10669
  async function loadSourceCategories(basePath) {
10166
- const categoriesPath = path29.join(basePath, SKILL_CATEGORIES_PATH);
10670
+ const categoriesPath = path28.join(basePath, SKILL_CATEGORIES_PATH);
10167
10671
  if (!await fileExists(categoriesPath)) return void 0;
10168
10672
  const categories = await loadSkillCategories(categoriesPath);
10169
10673
  verbose(
@@ -10172,7 +10676,7 @@ async function loadSourceCategories(basePath) {
10172
10676
  return categories;
10173
10677
  }
10174
10678
  async function loadSourceRules(basePath) {
10175
- const rulesPath = path29.join(basePath, SKILL_RULES_PATH);
10679
+ const rulesPath = path28.join(basePath, SKILL_RULES_PATH);
10176
10680
  if (!await fileExists(rulesPath)) return void 0;
10177
10681
  const rules = await loadSkillRules(rulesPath);
10178
10682
  verbose(`Loaded source rules: ${rulesPath}`);
@@ -10187,11 +10691,11 @@ async function refuseCatalogueCollisions(basePath, source, skills) {
10187
10691
  throw new Error(catalogueCollisionError(collidingIds, source));
10188
10692
  }
10189
10693
  function catalogueCollisionError(collidingIds, source) {
10190
- const listed2 = collidingIds.slice(0, MAX_REPORTED_COLLISIONS).map((id) => ` ${id}`);
10191
- const unlisted = collidingIds.length - listed2.length;
10694
+ const listed = collidingIds.slice(0, MAX_REPORTED_COLLISIONS).map((id) => ` ${id}`);
10695
+ const unlisted = collidingIds.length - listed.length;
10192
10696
  return [
10193
10697
  `Marketplace '${source}' ships ${collidingIds.length} skill id(s) the public catalogue already owns:`,
10194
- ...listed2,
10698
+ ...listed,
10195
10699
  ...unlisted > 0 ? [` ... and ${unlisted} more`] : [],
10196
10700
  `A skill id is the directory the skill installs into, so these would shadow the public catalogue's own skills. Every id must carry its marketplace's name as a namespace \u2014 '<marketplace>-<id>'. Rename each skill and the 'name' in its SKILL.md, then re-run 'build marketplace', which refuses the same ids before they are published.`
10197
10701
  ].join("\n");
@@ -10226,11 +10730,12 @@ function withdrawnSkillsWarning(stack, withdrawn) {
10226
10730
  const named = withdrawn.map((id) => `'${id}'`).join(", ");
10227
10731
  return `Stack '${stack.id}' names ${withdrawn.length} skill(s) this marketplace's catalogue does not carry: ${named}. Left out of the stack \u2014 selecting it would install nothing.`;
10228
10732
  }
10229
- function convertStackToResolvedStack(stack) {
10733
+ function convertStackToResolvedStack(stack, catalogue) {
10230
10734
  const { declared, undeclared } = declaredAgentsIn(stack);
10231
10735
  if (undeclared.length > 0) warn(undeclaredAgentsWarning(stack, undeclared));
10736
+ const isCarried = carriedBy(catalogue);
10232
10737
  const agentConfigs = agentConfigsFor(stack, declared);
10233
- const [allSkillIds, withdrawn] = partition4(namedSkillIds(agentConfigs), isInCurrentCatalogue);
10738
+ const [allSkillIds, withdrawn] = partition6(namedSkillIds(agentConfigs), isCarried);
10234
10739
  if (withdrawn.length > 0) warn(withdrawnSkillsWarning(stack, withdrawn));
10235
10740
  verbose(
10236
10741
  `Stack '${stack.id}' has ${allSkillIds.length} skills from ${agentConfigs.length} agents`
@@ -10239,7 +10744,7 @@ function convertStackToResolvedStack(stack) {
10239
10744
  id: stack.id,
10240
10745
  name: stack.name,
10241
10746
  description: stack.description,
10242
- skills: skillsByAgent(agentConfigs),
10747
+ skills: skillsByAgent(agentConfigs, isCarried),
10243
10748
  allSkillIds,
10244
10749
  philosophy: stack.philosophy || ""
10245
10750
  };
@@ -10257,21 +10762,21 @@ function namedSkillIds(agentConfigs) {
10257
10762
  )
10258
10763
  );
10259
10764
  }
10260
- function isInCurrentCatalogue(id) {
10261
- return id in matrix.skills;
10765
+ function carriedBy(catalogue) {
10766
+ return (id) => id in catalogue.skills;
10262
10767
  }
10263
- function skillsByAgent(agentConfigs) {
10768
+ function skillsByAgent(agentConfigs, isCarried) {
10264
10769
  return typedFromEntries(
10265
10770
  agentConfigs.map(
10266
- ({ agentId, agentConfig }) => [agentId, resolveStackAgentSkills(agentConfig)]
10771
+ ({ agentId, agentConfig }) => [agentId, resolveStackAgentSkills(agentConfig, isCarried)]
10267
10772
  )
10268
10773
  );
10269
10774
  }
10270
- function resolveStackAgentSkills(agentConfig) {
10775
+ function resolveStackAgentSkills(agentConfig, isCarried) {
10271
10776
  const carriedByCategory = typedEntries(agentConfig).map(
10272
10777
  ([category, assignments]) => (
10273
10778
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Object.entries drops the `| undefined` a Partial<Record> admits
10274
- [category, (assignments ?? []).map((a) => a.id).filter(isInCurrentCatalogue)]
10779
+ [category, (assignments ?? []).map((a) => a.id).filter(isCarried)]
10275
10780
  )
10276
10781
  );
10277
10782
  return typedFromEntries(carriedByCategory.filter(([, ids]) => ids.length > 0));
@@ -10280,7 +10785,10 @@ function declaresCategory(matrix2, category) {
10280
10785
  return category !== LOCAL_PSEUDO_CATEGORY && matrix2.categories[category] !== void 0;
10281
10786
  }
10282
10787
  function undeclaredCategoryRefusal(metadata, category) {
10283
- const metadataPath = path29.join(metadata.path, STANDARD_FILES.METADATA_YAML);
10788
+ if (metadata.localPath === void 0) {
10789
+ throw new Error(`Local skill '${metadata.id}' was discovered without its directory`);
10790
+ }
10791
+ const metadataPath = path28.join(metadata.localPath, STANDARD_FILES.METADATA_YAML);
10284
10792
  return `Skipping local skill '${metadata.id}': ${METADATA_KEYS.CATEGORY} '${category}' is not one this installation declares, so the skill belongs in no grid tab and can be given to no sub-agent. Set ${METADATA_KEYS.CATEGORY} in ${metadataPath} to a category that already exists \u2014 a skill is placed in the taxonomy, it does not extend it.`;
10285
10793
  }
10286
10794
  function mergeLocalSkillsIntoMatrix(matrix2, localResult) {
@@ -10357,7 +10865,10 @@ export {
10357
10865
  rootsHoldingTheRetiredSourceFolder,
10358
10866
  chooseProviderForThisRun,
10359
10867
  forgetTheProviderChosenForThisRun,
10868
+ declaresNoContent,
10360
10869
  providerInUse,
10870
+ inheritedGlobalProvider,
10871
+ providerAtScope,
10361
10872
  sourceFolderInUse,
10362
10873
  relativeConfigPath,
10363
10874
  sourceFolderName,
@@ -10390,9 +10901,10 @@ export {
10390
10901
  skillAssignedToNoAgent,
10391
10902
  scopeBlockedStackAssignment,
10392
10903
  globalScopedAgentsHint,
10393
- marketplacesRefreshed,
10394
10904
  marketplaceRefreshFailed,
10395
10905
  marketplacesRefreshFailed,
10906
+ codexPluginLeftSwitchedOn,
10907
+ pluginsLeftSwitchedOn,
10396
10908
  marketplaceOwnerHasNoName,
10397
10909
  marketplaceHasNoVersion,
10398
10910
  marketplaceNameNotPublishable,
@@ -10414,10 +10926,13 @@ export {
10414
10926
  sharedSkillsLeftBehind,
10415
10927
  sharedConfigExistingInstall,
10416
10928
  sharedConfigProjectScopeAtHome,
10417
- skippedUnknownSkills,
10929
+ skippedSkills,
10418
10930
  skippedUnknownAgents,
10419
10931
  carriedSkillsWritten,
10932
+ copiedSkillsRemovedAfterRefusedSave,
10933
+ carriedSkillsRemovedAfterRefusedSave,
10420
10934
  SHARED_CONFIG_APPLY,
10935
+ idNamesAnotherMarketplace,
10421
10936
  registeredProjectsRecompiled,
10422
10937
  sharedConfigNeedsTerminal,
10423
10938
  nothingInstalledToApplyTo,
@@ -10475,7 +10990,9 @@ export {
10475
10990
  formatZodIssues,
10476
10991
  validateSkillMetadata,
10477
10992
  splitMetadataValidationIssues,
10993
+ ConfigSchemaError,
10478
10994
  ConfigDefaultExportError,
10995
+ ConfigEvaluationError,
10479
10996
  NO_VALID_DEFAULT_EXPORT,
10480
10997
  configLoadFailure,
10481
10998
  loadConfig,
@@ -10494,21 +11011,23 @@ export {
10494
11011
  MarketplaceRefusedError,
10495
11012
  isLocalSource,
10496
11013
  normalizeStackRecord,
11014
+ StacksLoadError,
10497
11015
  loadStackById,
10498
11016
  defaultUsageGuidance,
10499
11017
  resolveAgentConfigToSkills,
10500
11018
  getStackSkillIds,
10501
11019
  ConfigLoadError,
11020
+ configDeclaresNothing,
10502
11021
  loadProjectConfigFromDir,
10503
11022
  loadProjectConfigAt,
10504
11023
  loadInstalledConfig,
10505
- configDirsInPlay,
11024
+ configsInPlay,
10506
11025
  findConfigLoadFailures,
10507
11026
  loadProjectConfig,
11027
+ loadConfigInPlay,
10508
11028
  validateProjectConfig,
10509
11029
  INSTALL_MODE_LABELS,
10510
11030
  INSTALL_MODE_DESCRIPTIONS,
10511
- declaresNoContent,
10512
11031
  deriveInstallMode,
10513
11032
  detectProjectInstallation,
10514
11033
  holdsItsOwnInstallation,
@@ -10550,6 +11069,7 @@ export {
10550
11069
  readLocalSkillMetadata,
10551
11070
  validateSkillPath,
10552
11071
  copySkillsToLocalFlattened,
11072
+ copyMarketplaceSkillsToLocalFlattened,
10553
11073
  getUserPluginsDir,
10554
11074
  getProjectPluginsDir,
10555
11075
  getPluginAgentsDir,
@@ -10576,9 +11096,12 @@ export {
10576
11096
  execCommand,
10577
11097
  getInstalledPluginsRegistryPath,
10578
11098
  listRegisteredPluginInstalls,
11099
+ PluginsLeftSwitchedOnError,
11100
+ FailedLeavingPluginsSwitchedOnError,
10579
11101
  refuseUnofferedPlacements,
10580
11102
  hostFor,
10581
11103
  hostAt,
11104
+ getVerifiedPluginInstallPaths,
10582
11105
  discoverAllPluginSkills,
10583
11106
  listPluginNames,
10584
11107
  compileSkillPlugin,
@@ -10587,6 +11110,7 @@ export {
10587
11110
  findUnusableSavedSkillMetadata,
10588
11111
  deleteLocalSkill,
10589
11112
  migrateLocalSkillScope,
11113
+ NoSkillRulesError,
10590
11114
  claimSlug,
10591
11115
  matrixHealthIssues,
10592
11116
  generateMarketplace,
@@ -10613,6 +11137,8 @@ export {
10613
11137
  sourceScopesInPlay,
10614
11138
  scopeRootsInPlay,
10615
11139
  detectInstallations,
11140
+ configsDeclaringNothing,
11141
+ holdsAConfigDeclaringNothing,
10616
11142
  ejectCopyFailureError,
10617
11143
  copyingBesideGlobalPlugins,
10618
11144
  installingBesideGlobalCopies,
@@ -10620,4 +11146,4 @@ export {
10620
11146
  executeMigration,
10621
11147
  keepsGlobalInstall
10622
11148
  };
10623
- //# sourceMappingURL=chunk-E45IT3HC.js.map
11149
+ //# sourceMappingURL=chunk-ENET4IF6.js.map