agents-inc 0.170.0 → 0.170.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/{chunk-BMRUNMPV.js → chunk-2YA4DDRM.js} +35 -35
  3. package/dist/chunk-2YA4DDRM.js.map +1 -0
  4. package/dist/{chunk-44TE3RKH.js → chunk-3ED3KHHD.js} +5 -4
  5. package/dist/chunk-3ED3KHHD.js.map +1 -0
  6. package/dist/{chunk-3G7X2WKI.js → chunk-5BNX73OU.js} +18 -38
  7. package/dist/chunk-5BNX73OU.js.map +1 -0
  8. package/dist/{chunk-7RDMTR64.js → chunk-66AEVERM.js} +785 -256
  9. package/dist/chunk-66AEVERM.js.map +1 -0
  10. package/dist/{chunk-PJWGTNOX.js → chunk-7C3PFRBU.js} +144 -186
  11. package/dist/chunk-7C3PFRBU.js.map +1 -0
  12. package/dist/{chunk-52HDHJ7I.js → chunk-AUNRHWH4.js} +32 -21
  13. package/dist/chunk-AUNRHWH4.js.map +1 -0
  14. package/dist/{chunk-KOLDW6SF.js → chunk-BBDGYJHX.js} +2 -2
  15. package/dist/chunk-BBDGYJHX.js.map +1 -0
  16. package/dist/{chunk-SZYQISA2.js → chunk-ELV5WDS3.js} +43 -29
  17. package/dist/chunk-ELV5WDS3.js.map +1 -0
  18. package/dist/{chunk-MQ6CGZNJ.js → chunk-GAKVZNTX.js} +1879 -2019
  19. package/dist/chunk-GAKVZNTX.js.map +1 -0
  20. package/dist/chunk-GEMW5RQ2.js +1169 -0
  21. package/dist/chunk-GEMW5RQ2.js.map +1 -0
  22. package/dist/{chunk-NQW25DCF.js → chunk-GHHJYNAN.js} +1 -7
  23. package/dist/chunk-GHHJYNAN.js.map +1 -0
  24. package/dist/{chunk-WTWMGH2C.js → chunk-HACRR422.js} +2 -2
  25. package/dist/chunk-HACRR422.js.map +1 -0
  26. package/dist/{chunk-F2BHT5C4.js → chunk-IWY5IR4F.js} +129 -151
  27. package/dist/chunk-IWY5IR4F.js.map +1 -0
  28. package/dist/{chunk-JFLVEFFP.js → chunk-KIK5EZYD.js} +1 -1
  29. package/dist/chunk-KIK5EZYD.js.map +1 -0
  30. package/dist/{chunk-USVBYQSQ.js → chunk-O4LN3HFI.js} +16 -6
  31. package/dist/chunk-O4LN3HFI.js.map +1 -0
  32. package/dist/{chunk-RIPSGNLU.js → chunk-OCAVW6N5.js} +3 -3
  33. package/dist/chunk-OCAVW6N5.js.map +1 -0
  34. package/dist/{chunk-4AAOOAIN.js → chunk-P6CYS4JK.js} +2 -2
  35. package/dist/chunk-P6CYS4JK.js.map +1 -0
  36. package/dist/chunk-PLFANHRZ.js +35 -0
  37. package/dist/chunk-PLFANHRZ.js.map +1 -0
  38. package/dist/{chunk-6ZRH5RHC.js → chunk-PMUZR4XO.js} +4 -4
  39. package/dist/chunk-PMUZR4XO.js.map +1 -0
  40. package/dist/{chunk-F5EBEATU.js → chunk-PSUTNOQ2.js} +34 -46
  41. package/dist/chunk-PSUTNOQ2.js.map +1 -0
  42. package/dist/chunk-RIK45Q4I.js +103 -0
  43. package/dist/chunk-RIK45Q4I.js.map +1 -0
  44. package/dist/{chunk-HED2DAXZ.js → chunk-THO7LO5B.js} +5 -5
  45. package/dist/chunk-THO7LO5B.js.map +1 -0
  46. package/dist/{chunk-OYJQUA3D.js → chunk-YGDWHDOY.js} +31 -13
  47. package/dist/chunk-YGDWHDOY.js.map +1 -0
  48. package/dist/{chunk-NCWDK6L7.js → chunk-YXKTY5WM.js} +254 -379
  49. package/dist/chunk-YXKTY5WM.js.map +1 -0
  50. package/dist/{chunk-NEZ4OAOL.js → chunk-ZEGM3MVR.js} +3 -5
  51. package/dist/chunk-ZEGM3MVR.js.map +1 -0
  52. package/dist/commands/build/marketplace.js +46 -46
  53. package/dist/commands/build/marketplace.js.map +1 -1
  54. package/dist/commands/build/plugins.js +25 -28
  55. package/dist/commands/build/plugins.js.map +1 -1
  56. package/dist/commands/compile.js +87 -178
  57. package/dist/commands/compile.js.map +1 -1
  58. package/dist/commands/doctor.js +510 -360
  59. package/dist/commands/doctor.js.map +1 -1
  60. package/dist/commands/edit.js +451 -357
  61. package/dist/commands/edit.js.map +1 -1
  62. package/dist/commands/eject.js +144 -125
  63. package/dist/commands/eject.js.map +1 -1
  64. package/dist/commands/init.js +20 -27
  65. package/dist/commands/list.js +39 -55
  66. package/dist/commands/list.js.map +1 -1
  67. package/dist/commands/new/marketplace.js +6 -11
  68. package/dist/commands/new/marketplace.js.map +1 -1
  69. package/dist/commands/search.js +35 -32
  70. package/dist/commands/search.js.map +1 -1
  71. package/dist/commands/share.js +17 -77
  72. package/dist/commands/share.js.map +1 -1
  73. package/dist/commands/uninstall.js +159 -222
  74. package/dist/commands/uninstall.js.map +1 -1
  75. package/dist/commands/update.js +9 -32
  76. package/dist/commands/update.js.map +1 -1
  77. package/dist/components/common/prompt-confirm.js +2 -2
  78. package/dist/components/common/removal-plan-confirm.js +2 -2
  79. package/dist/components/common/scroll-affordance.js +2 -2
  80. package/dist/components/common/select-list.js +2 -2
  81. package/dist/components/common/skill-agent-summary.js +4 -4
  82. package/dist/components/common/spinner.js +2 -4
  83. package/dist/components/common/summary-panel.js +5 -5
  84. package/dist/components/common/summary-panel.js.map +1 -1
  85. package/dist/config-exports.js +7 -3
  86. package/dist/config-exports.js.map +1 -1
  87. package/dist/hooks/init.js +18 -19
  88. package/dist/hooks/init.js.map +1 -1
  89. package/dist/index.js.map +1 -1
  90. package/dist/load-agent-defs-6QUUMGHK.js +14 -0
  91. package/dist/recompile-project-agents-I33KQJFB.js +65 -0
  92. package/dist/recompile-project-agents-I33KQJFB.js.map +1 -0
  93. package/dist/{source-loader-C5SQXZXI.js → source-loader-TTJVW2ER.js} +5 -5
  94. package/dist/src/agents/developer/ai-developer/playbook.md +2 -5
  95. package/dist/src/agents/developer/api-developer/playbook.md +2 -5
  96. package/dist/src/agents/developer/cli-developer/playbook.md +2 -5
  97. package/dist/src/agents/developer/web-developer/playbook.md +2 -5
  98. package/dist/src/agents/meta/agent-summoner/output.md +2 -2
  99. package/dist/src/agents/meta/agent-summoner/playbook.md +1 -13
  100. package/dist/src/agents/meta/codex-keeper/critical-requirements.md +2 -5
  101. package/dist/src/agents/meta/convention-keeper/critical-requirements.md +2 -4
  102. package/dist/src/agents/tester/ai-tester/critical-requirements.md +1 -2
  103. package/dist/src/agents/tester/ai-tester/playbook.md +2 -0
  104. package/dist/src/agents/tester/api-tester/critical-requirements.md +1 -2
  105. package/dist/src/agents/tester/api-tester/playbook.md +2 -0
  106. package/dist/src/agents/tester/cli-tester/critical-requirements.md +1 -2
  107. package/dist/src/agents/tester/cli-tester/playbook.md +2 -0
  108. package/dist/src/agents/tester/web-tester/critical-requirements.md +1 -2
  109. package/dist/src/agents/tester/web-tester/playbook.md +2 -0
  110. package/package.json +9 -8
  111. package/src/agents/developer/ai-developer/playbook.md +2 -5
  112. package/src/agents/developer/api-developer/playbook.md +2 -5
  113. package/src/agents/developer/cli-developer/playbook.md +2 -5
  114. package/src/agents/developer/web-developer/playbook.md +2 -5
  115. package/src/agents/meta/agent-summoner/output.md +2 -2
  116. package/src/agents/meta/agent-summoner/playbook.md +1 -13
  117. package/src/agents/meta/codex-keeper/critical-requirements.md +2 -5
  118. package/src/agents/meta/convention-keeper/critical-requirements.md +2 -4
  119. package/src/agents/tester/ai-tester/critical-requirements.md +1 -2
  120. package/src/agents/tester/ai-tester/playbook.md +2 -0
  121. package/src/agents/tester/api-tester/critical-requirements.md +1 -2
  122. package/src/agents/tester/api-tester/playbook.md +2 -0
  123. package/src/agents/tester/cli-tester/critical-requirements.md +1 -2
  124. package/src/agents/tester/cli-tester/playbook.md +2 -0
  125. package/src/agents/tester/web-tester/critical-requirements.md +1 -2
  126. package/src/agents/tester/web-tester/playbook.md +2 -0
  127. package/src/schemas/project-config.schema.json +0 -3
  128. package/src/schemas/project-source-config.schema.json +0 -6
  129. package/dist/chunk-3G7X2WKI.js.map +0 -1
  130. package/dist/chunk-44TE3RKH.js.map +0 -1
  131. package/dist/chunk-4AAOOAIN.js.map +0 -1
  132. package/dist/chunk-52HDHJ7I.js.map +0 -1
  133. package/dist/chunk-6HNXYYVF.js +0 -373
  134. package/dist/chunk-6HNXYYVF.js.map +0 -1
  135. package/dist/chunk-6ZRH5RHC.js.map +0 -1
  136. package/dist/chunk-7RDMTR64.js.map +0 -1
  137. package/dist/chunk-BMRUNMPV.js.map +0 -1
  138. package/dist/chunk-CLPUWOSA.js +0 -46
  139. package/dist/chunk-CLPUWOSA.js.map +0 -1
  140. package/dist/chunk-F2BHT5C4.js.map +0 -1
  141. package/dist/chunk-F5EBEATU.js.map +0 -1
  142. package/dist/chunk-HED2DAXZ.js.map +0 -1
  143. package/dist/chunk-JFLVEFFP.js.map +0 -1
  144. package/dist/chunk-KOLDW6SF.js.map +0 -1
  145. package/dist/chunk-MQ6CGZNJ.js.map +0 -1
  146. package/dist/chunk-NCWDK6L7.js.map +0 -1
  147. package/dist/chunk-NEZ4OAOL.js.map +0 -1
  148. package/dist/chunk-NIHUOWE4.js +0 -106
  149. package/dist/chunk-NIHUOWE4.js.map +0 -1
  150. package/dist/chunk-NQW25DCF.js.map +0 -1
  151. package/dist/chunk-OYJQUA3D.js.map +0 -1
  152. package/dist/chunk-PJWGTNOX.js.map +0 -1
  153. package/dist/chunk-RIPSGNLU.js.map +0 -1
  154. package/dist/chunk-SZYQISA2.js.map +0 -1
  155. package/dist/chunk-USVBYQSQ.js.map +0 -1
  156. package/dist/chunk-V3KTSP3Q.js +0 -940
  157. package/dist/chunk-V3KTSP3Q.js.map +0 -1
  158. package/dist/chunk-WTWMGH2C.js.map +0 -1
  159. package/dist/load-agent-defs-YMDWBFDP.js +0 -14
  160. package/dist/recompile-project-agents-SZ76WNTW.js +0 -18
  161. package/dist/source-loader-C5SQXZXI.js.map +0 -1
  162. /package/dist/{load-agent-defs-YMDWBFDP.js.map → load-agent-defs-6QUUMGHK.js.map} +0 -0
  163. /package/dist/{recompile-project-agents-SZ76WNTW.js.map → source-loader-TTJVW2ER.js.map} +0 -0
@@ -1,940 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- installPluginSkills,
4
- pluginInstallFailureError,
5
- unbackedPluginInstallError,
6
- unbackedPluginSkillIds
7
- } from "./chunk-CLPUWOSA.js";
8
- import {
9
- STATUS_MESSAGES,
10
- claimSlug,
11
- completedWithFailures,
12
- computeFileHash,
13
- copy,
14
- defaultUsageGuidance,
15
- directoryExists,
16
- fetchMarketplace,
17
- findConfigLoadFailures,
18
- findUnusableSavedSkillMetadata,
19
- getErrorMessage,
20
- getStackSkillIds,
21
- glob,
22
- hostAt,
23
- injectForkedFromMetadata,
24
- installationConfigsUnreadable,
25
- isHomeDirectory,
26
- parseFrontmatter,
27
- pluginsInstalled,
28
- propagatedRecompileSummary,
29
- readFile,
30
- readForkedFromMetadata,
31
- readLocalSkillMetadata,
32
- readSkillMetadata,
33
- refuseUnofferedPlacements,
34
- refuseUnofferedStack,
35
- refuseUnwritableStack,
36
- registeredProjectConfigUnreadable,
37
- registeredProjectGone,
38
- registeredProjectOnAnotherProvider,
39
- remove,
40
- resolveBranding,
41
- resolveInstallPaths,
42
- savedSkillMetadataUnusableError,
43
- scopeBlockedStackAssignment,
44
- sharedConfigProjectScopeAtHome,
45
- skillAssignedToNoAgent,
46
- skillMetadataUnusableDetail,
47
- validateSkillPath,
48
- verbose,
49
- warn,
50
- writeFile,
51
- writeMetadataYaml
52
- } from "./chunk-MQ6CGZNJ.js";
53
- import {
54
- BUILT_IN_MATRIX,
55
- getCategoryDomain,
56
- typedEntries,
57
- typedKeys
58
- } from "./chunk-52HDHJ7I.js";
59
- import {
60
- CLI_COLORS,
61
- DEFAULT_BRANDING,
62
- GITHUB_SOURCE,
63
- LOCAL_SKILLS_PATH,
64
- MIN_TERMINAL_SIZE,
65
- STANDARD_FILES,
66
- isActiveAt,
67
- seedExternalSkillSchema
68
- } from "./chunk-PJWGTNOX.js";
69
-
70
- // src/cli/utils/terminal.ts
71
- function clearTerminalScreen() {
72
- process.stdout.write("\x1B[H\x1B[2J\x1B[3J");
73
- }
74
- function isTerminalLargeEnough(columns, rows) {
75
- return columns >= MIN_TERMINAL_SIZE.COLS && rows >= MIN_TERMINAL_SIZE.ROWS;
76
- }
77
- function formatTerminalTooSmallMessage(columns) {
78
- const issue = columns < MIN_TERMINAL_SIZE.COLS ? `too narrow (need ${MIN_TERMINAL_SIZE.COLS})` : `too short (need ${MIN_TERMINAL_SIZE.ROWS})`;
79
- return `Terminal ${issue}. Please resize.`;
80
- }
81
-
82
- // src/cli/lib/exit-codes.ts
83
- var EXIT_CODES = {
84
- SUCCESS: 0,
85
- ERROR: 1,
86
- INVALID_ARGS: 2,
87
- NETWORK_ERROR: 3,
88
- CANCELLED: 4,
89
- /**
90
- * The command ran to the end and part of what it set out to do did not happen.
91
- *
92
- * Distinct from {@link EXIT_CODES.ERROR} because the two ask opposite things of whoever reads
93
- * them: an `ERROR` aborted, so nothing landed and the run can be repeated; this one landed and
94
- * the repeat is the wrong move — what is owed is the remedy the output names, per failure.
95
- * Collapsing it into `ERROR` would re-make the defect it exists to close one value along, and
96
- * a spec asserting `not.toBe(0)` cannot tell a refusal from a partial apply either.
97
- *
98
- * It is raised at the END of a command, never mid-flight: the work completes first, which is
99
- * the whole difference between this and `this.error`.
100
- */
101
- COMPLETED_WITH_FAILURES: 5
102
- };
103
-
104
- // src/cli/lib/seed/external-skills.ts
105
- import os from "os";
106
- import path from "path";
107
- import { mkdtemp } from "fs/promises";
108
- import { isDeepEqual, omit } from "remeda";
109
-
110
- // ../compile/src/carried-skill.ts
111
- import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
112
- var FRONTMATTER_BLOCK = /^---\r?\n([\s\S]*?)\r?\n---/;
113
- var NAME_FIELD = /^name:.*$/m;
114
- function withInstalledName(manifest, { id, description }) {
115
- const named = `name: ${id}`;
116
- const block = FRONTMATTER_BLOCK.exec(manifest);
117
- if (!block) {
118
- return `---
119
- ${named}
120
- description: ${description}
121
- ---
122
-
123
- ${manifest}`;
124
- }
125
- const [whole, fields = ""] = block;
126
- const renamed = NAME_FIELD.test(fields) ? fields.replace(NAME_FIELD, named) : `${named}
127
- ${fields}`;
128
- return `---
129
- ${renamed}
130
- ---${manifest.slice(whole.length)}`;
131
- }
132
- function carriedSkillAuthor(repo) {
133
- const [owner = repo] = repo.split("/");
134
- return `@${owner}`;
135
- }
136
- function carriedSkillMetadata({
137
- id,
138
- skill,
139
- domain,
140
- usageGuidance,
141
- shipped
142
- }) {
143
- return {
144
- author: carriedSkillAuthor(skill.repo),
145
- usageGuidance,
146
- ...shipped,
147
- displayName: skill.displayName,
148
- slug: id,
149
- category: skill.categoryId,
150
- domain,
151
- cliDescription: shortLabel(skill.description),
152
- custom: true
153
- };
154
- }
155
- var CLI_DESCRIPTION_MAX_LENGTH = 60;
156
- var ELLIPSIS = "\u2026";
157
- function shortLabel(description) {
158
- if (description.length <= CLI_DESCRIPTION_MAX_LENGTH) return description;
159
- const room = description.slice(
160
- 0,
161
- CLI_DESCRIPTION_MAX_LENGTH - ELLIPSIS.length
162
- );
163
- return `${toLastWholeWord(room)}${ELLIPSIS}`;
164
- }
165
- function toLastWholeWord(text) {
166
- const lastBreak = text.lastIndexOf(" ");
167
- return (lastBreak > 0 ? text.slice(0, lastBreak) : text).trimEnd();
168
- }
169
-
170
- // src/cli/lib/seed/external-skills.ts
171
- function pluginInstallError(ids) {
172
- return [
173
- "This configuration cannot be installed: these skills travel inside it, so no marketplace serves them and they cannot be installed as plugins:",
174
- ...ids.map((id) => ` ${id}`),
175
- "Re-share it with each skill above set to eject, which is the only way a carried skill installs."
176
- ].join("\n");
177
- }
178
- function selectedExternals(payload) {
179
- return typedEntries(payload.external ?? {}).flatMap(([id, skill]) => {
180
- const entry = payload.skills[id];
181
- return entry ? [{ id, skill, entry }] : [];
182
- });
183
- }
184
- function askedForAsPlugin(externals) {
185
- return externals.filter(({ entry }) => entry.install !== "eject").map(({ id }) => id);
186
- }
187
- function claimingACatalogueId(externals, matrix) {
188
- return externals.filter(({ id }) => heldByEitherCatalogue(id, matrix)).map(({ id }) => id);
189
- }
190
- var CATALOGUE_SKILL_IDS = new Set(typedKeys(BUILT_IN_MATRIX.skills));
191
- function heldByEitherCatalogue(id, matrix) {
192
- return heldByLoadedCatalogue(matrix.skills[id]) || CATALOGUE_SKILL_IDS.has(id);
193
- }
194
- function heldByLoadedCatalogue(incumbent) {
195
- return incumbent !== void 0 && incumbent.local !== true;
196
- }
197
- function catalogueIdCollisionError(ids) {
198
- return [
199
- "This configuration cannot be installed: these skills travel inside it under ids a catalogue already owns:",
200
- ...ids.map((id) => ` ${id}`),
201
- "A skill id is the directory the skill installs into, so each would be written over the catalogue's own copy of that skill. Re-share it with each skill above added again, which mints an id \u2014 'external-<category>-<name>' \u2014 that no catalogue owns."
202
- ].join("\n");
203
- }
204
- function externalSlug(id) {
205
- return id;
206
- }
207
- function repoRef(repo) {
208
- return `${GITHUB_SOURCE.GITHUB_PREFIX}${repo}`;
209
- }
210
- function externalSkillDir(id, scope, projectDir) {
211
- return path.join(resolveInstallPaths(projectDir, scope).skillsDir, id);
212
- }
213
- function externalCatalogueEntry(install) {
214
- return {
215
- id: install.id,
216
- slug: externalSlug(install.id),
217
- displayName: install.skill.displayName,
218
- description: install.skill.description,
219
- // Boundary cast held by the guard above it: the category was confirmed against the catalogue
220
- // the payload names, and this one has just been shown to declare it too.
221
- category: install.skill.categoryId,
222
- author: carriedSkillAuthor(install.skill.repo),
223
- path: `${LOCAL_SKILLS_PATH}/${install.id}/`,
224
- conflictsWith: [],
225
- requires: [],
226
- alternatives: [],
227
- discourages: [],
228
- // Local and custom for the same reason: the bytes are on this machine and nowhere else, and
229
- // the skill was authored outside the vocabulary this catalogue ships.
230
- local: true,
231
- custom: true,
232
- localPath: install.skillDir
233
- };
234
- }
235
- function seatExternalSkill({ id, skill, entry }, matrix, projectDir) {
236
- const domain = getCategoryDomain(skill.categoryId);
237
- if (domain === void 0) return [];
238
- const install = {
239
- id,
240
- skill,
241
- domain,
242
- scope: entry.scope,
243
- skillDir: externalSkillDir(id, entry.scope, projectDir)
244
- };
245
- matrix.skills[id] = externalCatalogueEntry(install);
246
- claimSlug(matrix.slugMap, externalSlug(id), id);
247
- return [install];
248
- }
249
- function registerExternalSkills(payload, matrix, projectDir) {
250
- const externals = selectedExternals(payload);
251
- const asPlugin = askedForAsPlugin(externals);
252
- const colliding = claimingACatalogueId(externals, matrix);
253
- if (asPlugin.length > 0) throw new Error(pluginInstallError(asPlugin));
254
- if (colliding.length > 0) throw new Error(catalogueIdCollisionError(colliding));
255
- return externals.flatMap((external) => seatExternalSkill(external, matrix, projectDir));
256
- }
257
- async function writeExternalSkills(installs) {
258
- await refuseUncarriedDestinations(installs);
259
- const written = await Promise.all(installs.map(writeExternalSkill));
260
- return changedIds(written);
261
- }
262
- var SCRATCH_DIR_PREFIX = "agents-inc-carried-";
263
- async function carriedSkillsThatDiffer(installs) {
264
- if (installs.length === 0) return [];
265
- const scratch = await mkdtemp(path.join(os.tmpdir(), SCRATCH_DIR_PREFIX));
266
- try {
267
- const written = await Promise.all(
268
- installs.map(async (install) => writeExternalSkill(await scratchCopyOf(install, scratch)))
269
- );
270
- return changedIds(written);
271
- } finally {
272
- await remove(scratch);
273
- }
274
- }
275
- async function scratchCopyOf(install, scratch) {
276
- const skillDir = path.join(scratch, install.id);
277
- if (await directoryExists(install.skillDir)) await copy(install.skillDir, skillDir);
278
- return { ...install, skillDir };
279
- }
280
- function changedIds(written) {
281
- return written.filter(({ changed }) => changed).map(({ id }) => id);
282
- }
283
- async function writeExternalSkill(install) {
284
- const before = await readSkillDir(install.skillDir);
285
- await writeSkillTree(install);
286
- await writeSkillManifest(install);
287
- await registerSkillOnDisk(install);
288
- return { id: install.id, changed: !isDeepEqual(before, await readSkillDir(install.skillDir)) };
289
- }
290
- async function readSkillDir(skillDir) {
291
- if (!await directoryExists(skillDir)) return null;
292
- const files = omit(await readSkillTree(skillDir), [STANDARD_FILES.METADATA_YAML]);
293
- return { files, metadata: undated(await readLocalSkillMetadata(skillDir)) };
294
- }
295
- function undated(metadata) {
296
- if (metadata?.forkedFrom === void 0) return metadata;
297
- return { ...metadata, forkedFrom: omit(metadata.forkedFrom, ["date"]) };
298
- }
299
- async function refuseUncarriedDestinations(installs) {
300
- const judged = await Promise.all(installs.map(judgeDestination));
301
- const blocked = judged.filter((destination) => destination !== null);
302
- if (blocked.length > 0) throw new Error(uncarriedDestinationError(blocked));
303
- }
304
- async function judgeDestination(install) {
305
- const { id, skillDir } = install;
306
- if (!await directoryExists(skillDir)) return null;
307
- if ((await readForkedFromMetadata(skillDir))?.path !== void 0) return null;
308
- return { id, skillDir };
309
- }
310
- function uncarriedDestinationError(blocked) {
311
- return [
312
- "This configuration cannot be installed: these skills travel inside it under ids this installation already keeps a skill directory for, and no shared configuration put those directories there:",
313
- ...blocked.map(({ id, skillDir }) => ` ${id} -> ${skillDir}`),
314
- "A skill id is the directory the skill installs into, so each would be written over a skill written here by hand or ejected from a marketplace. Move or rename the directory above, or re-share the configuration with each skill added again, which mints an id \u2014 'external-<category>-<name>' \u2014 that nothing here holds."
315
- ].join("\n");
316
- }
317
- async function writeSkillTree(install) {
318
- for (const [relativePath, contents] of Object.entries(install.skill.files)) {
319
- const filePath = path.join(install.skillDir, relativePath);
320
- validateSkillPath(filePath, install.skillDir, relativePath);
321
- await writeFile(filePath, contents);
322
- }
323
- }
324
- async function writeSkillManifest(install) {
325
- const manifestPath = path.join(install.skillDir, STANDARD_FILES.SKILL_MD);
326
- await writeFile(
327
- manifestPath,
328
- withInstalledName(await readFile(manifestPath), {
329
- id: install.id,
330
- description: install.skill.description
331
- })
332
- );
333
- }
334
- async function registerSkillOnDisk(install) {
335
- const metadataPath = path.join(install.skillDir, STANDARD_FILES.METADATA_YAML);
336
- const shipped = await readLocalSkillMetadata(install.skillDir);
337
- await writeMetadataYaml(
338
- metadataPath,
339
- carriedSkillMetadata({
340
- id: install.id,
341
- skill: install.skill,
342
- domain: install.domain,
343
- usageGuidance: defaultUsageGuidance(install.skill.categoryId),
344
- shipped
345
- })
346
- );
347
- await injectForkedFromMetadata(
348
- install.skillDir,
349
- install.id,
350
- await computeFileHash(path.join(install.skillDir, STANDARD_FILES.SKILL_MD)),
351
- { source: repoRef(install.skill.repo), path: install.skill.path }
352
- );
353
- }
354
- function unnameableRepoMessage(id) {
355
- return `${id} travels inside a shared configuration, and this installation does not record the repository its content came from`;
356
- }
357
- function uncarryableContentMessage(id, reason) {
358
- return `${id} travels inside a shared configuration, and ${reason}`;
359
- }
360
- function repoFromRef(ref) {
361
- if (ref === void 0 || !ref.startsWith(GITHUB_SOURCE.GITHUB_PREFIX)) return void 0;
362
- return ref.slice(GITHUB_SOURCE.GITHUB_PREFIX.length);
363
- }
364
- var NO_MANIFEST_DESCRIPTION = `no description can be read from its ${STANDARD_FILES.SKILL_MD}`;
365
- function manifestDescription(files, skillDir) {
366
- const manifest = files[STANDARD_FILES.SKILL_MD];
367
- if (manifest === void 0) return null;
368
- const frontmatter = parseFrontmatter(manifest, path.join(skillDir, STANDARD_FILES.SKILL_MD));
369
- return frontmatter === null ? null : frontmatter.description;
370
- }
371
- async function readSkillTree(skillDir) {
372
- const files = await glob("**/*", skillDir, { dot: true });
373
- const entries = await Promise.all(
374
- files.map(async (file) => [file, await readFile(path.join(skillDir, file))])
375
- );
376
- return Object.fromEntries(entries);
377
- }
378
- async function readCarriedSkill({ id, skillDir, provenance }) {
379
- if (provenance.path === void 0) return { carries: "nothing" };
380
- const repo = repoFromRef(provenance.source);
381
- if (repo === void 0) return { carries: "unshareable", line: unnameableRepoMessage(id) };
382
- const described = await readSkillMetadata(path.join(skillDir, STANDARD_FILES.METADATA_YAML));
383
- if (!described.usable) {
384
- return { carries: "unshareable", line: uncarryableContentMessage(id, described.reason) };
385
- }
386
- const files = await readSkillTree(skillDir);
387
- const description = manifestDescription(files, skillDir);
388
- if (description === null) {
389
- return { carries: "unshareable", line: uncarryableContentMessage(id, NO_MANIFEST_DESCRIPTION) };
390
- }
391
- const parsed = seedExternalSkillSchema.safeParse({
392
- displayName: described.metadata.displayName,
393
- description,
394
- categoryId: described.metadata.category,
395
- repo,
396
- path: provenance.path,
397
- files
398
- });
399
- if (!parsed.success) {
400
- const reason = parsed.error.issues.map((issue) => issue.message).join("; ");
401
- return { carries: "unshareable", line: uncarryableContentMessage(id, reason) };
402
- }
403
- return { carries: "content", id, skill: parsed.data };
404
- }
405
- async function readCarriedSkills(owned) {
406
- const reads = await Promise.all(owned.map(readCarriedSkill));
407
- return {
408
- external: Object.fromEntries(
409
- reads.flatMap((read) => read.carries === "content" ? [[read.id, read.skill]] : [])
410
- ),
411
- uncarryable: reads.flatMap((read) => read.carries === "unshareable" ? [read.line] : [])
412
- };
413
- }
414
-
415
- // src/cli/base-command.ts
416
- import { Command } from "@oclif/core";
417
- import chalk from "chalk";
418
- import { unique } from "remeda";
419
-
420
- // src/cli/lib/operations/source/ensure-marketplace.ts
421
- async function ensureMarketplace(sourceResult, host) {
422
- const source = sourceResult.sourceConfig.source;
423
- if (!sourceResult.marketplace) {
424
- try {
425
- const marketplaceResult = await fetchMarketplace(source);
426
- sourceResult.marketplace = marketplaceResult.marketplace.name;
427
- } catch (error) {
428
- warn(`Could not resolve a marketplace from '${source}': ${getErrorMessage(error)}`);
429
- return { marketplace: null, registered: false };
430
- }
431
- }
432
- const marketplace = sourceResult.marketplace;
433
- const exists = await host.marketplaceExists(marketplace);
434
- if (!exists) {
435
- const marketplaceSource = source.replace(/^github:/, "");
436
- await host.addMarketplace(marketplaceSource);
437
- return { marketplace, registered: true };
438
- }
439
- try {
440
- await host.refreshMarketplace(marketplace);
441
- } catch {
442
- warn("Could not update marketplace \u2014 continuing with cached version");
443
- }
444
- return { marketplace, registered: false };
445
- }
446
-
447
- // src/cli/lib/operations/source/require-marketplace.ts
448
- async function requireMarketplace(sourceResult, purpose, host) {
449
- const mpResult = await ensureMarketplace(sourceResult, host);
450
- if (!mpResult.marketplace) {
451
- return {
452
- ok: false,
453
- error: `Cannot ${purpose}: marketplace could not be resolved from '${sourceResult.sourceConfig.source}'. Plugin install mode requires a marketplace \u2014 fix the marketplace or switch the affected skills to eject mode.`
454
- };
455
- }
456
- return { ok: true, marketplace: mpResult.marketplace, registered: mpResult.registered };
457
- }
458
-
459
- // src/cli/base-command.ts
460
- var TERMINAL_RESIZE_POLL_MS = 500;
461
- var terminalColumns = () => process.stdout.columns ?? MIN_TERMINAL_SIZE.COLS;
462
- var terminalRows = () => process.stdout.rows ?? MIN_TERMINAL_SIZE.ROWS;
463
- function isTerminalBigEnough() {
464
- return isTerminalLargeEnough(terminalColumns(), terminalRows());
465
- }
466
- function waitForTerminal(isSatisfied) {
467
- return new Promise((resolve) => {
468
- const check = () => {
469
- if (!isSatisfied()) return;
470
- clearInterval(interval);
471
- process.stdout.off("resize", check);
472
- resolve();
473
- };
474
- const interval = setInterval(check, TERMINAL_RESIZE_POLL_MS);
475
- process.stdout.on("resize", check);
476
- });
477
- }
478
- function findUnassignedSkills(config, selected) {
479
- const assignedIds = new Set(config.stack ? getStackSkillIds(config.stack) : []);
480
- const selectedIds = new Set(selected.map((skill) => skill.id));
481
- const globalAgentNames = config.agents.filter((agent) => isActiveAt(agent, "global")).map((agent) => agent.name);
482
- const unassignedIds = unique(
483
- config.skills.filter((skill) => !skill.excluded && selectedIds.has(skill.id)).map((skill) => skill.id).filter((skillId) => !assignedIds.has(skillId))
484
- );
485
- return unassignedIds.map((skillId) => ({
486
- skillId,
487
- blockedBy: hasActiveProjectEntry(config.skills, skillId) ? globalAgentNames : []
488
- }));
489
- }
490
- function hasActiveProjectEntry(skills, skillId) {
491
- return skills.some((skill) => skill.id === skillId && isActiveAt(skill, "project"));
492
- }
493
- function projectScopedContent(result) {
494
- return {
495
- skillIds: result.skills.filter((skill) => isActiveAt(skill, "project")).map((skill) => skill.id),
496
- agentNames: result.agentConfigs.filter((agent) => isActiveAt(agent, "project")).map((agent) => agent.name)
497
- };
498
- }
499
- var BaseCommand = class extends Command {
500
- /**
501
- * Everything this run set out to do and did not, in the order it hit them.
502
- *
503
- * Non-empty is the whole of the exit-code decision — see {@link exitIfWorkIncomplete}. It is
504
- * deliberately not a boolean: a non-zero exit whose output does not say what failed and what
505
- * finishes it is worse than the silent success it replaces.
506
- *
507
- * Private, with {@link hasIncompleteWork} the only reading a subclass gets. A command records
508
- * through the two methods below and asks whether anything is owed; the list itself is this
509
- * class's, because it is this class that answers for it.
510
- */
511
- incompleteWork = [];
512
- async init() {
513
- await super.init();
514
- await this.ensureTerminalSize();
515
- }
516
- /**
517
- * Blocks the command from launching in a terminal below {@link MIN_TERMINAL_SIZE}. This runs
518
- * once, before Ink mounts.
519
- */
520
- async ensureTerminalSize() {
521
- if (isTerminalBigEnough()) return;
522
- this.clearTerminal();
523
- this.log(chalk.hex(CLI_COLORS.WARNING)(formatTerminalTooSmallMessage(terminalColumns())));
524
- await waitForTerminal(isTerminalBigEnough);
525
- }
526
- handleError(error) {
527
- const message = getErrorMessage(error);
528
- this.error(message, { exit: EXIT_CODES.ERROR });
529
- }
530
- /**
531
- * The name this run prints itself under: `branding.name` where a config supplies one, and
532
- * {@link DEFAULT_BRANDING.NAME} everywhere else.
533
- *
534
- * **DEGRADE is the posture, and it is chosen here for every command that prints a name.**
535
- * `loadSourceConfig` raises for a config that exists and cannot be evaluated, and two of the
536
- * four callers must survive precisely that state: `doctor`'s whole job is naming it, and
537
- * `uninstall` is the only way out of it. A heading is decoration — nothing about a display name
538
- * is worth refusing a command over, and refusing here would turn the branding wiring into a new
539
- * way for an unreadable config to abort two commands that survive it today. `init` and `eject`
540
- * cannot reach the catch at all: {@link ensureConfigReadable} has already refused every config
541
- * they would read.
542
- *
543
- * The cause is not swallowed: it goes to `verbose`, and the command's own config reader reports
544
- * it in full where the user needs to act on it. Reporting it twice would put a warning about
545
- * the configuration above a heading, which is the one line that has to be the first thing on
546
- * screen.
547
- */
548
- async resolveBrandingName(projectDir) {
549
- try {
550
- const branding = await resolveBranding(projectDir);
551
- return branding.name;
552
- } catch (error) {
553
- verbose(`Could not read branding from the config: ${getErrorMessage(error)}`);
554
- return DEFAULT_BRANDING.NAME;
555
- }
556
- }
557
- logSuccess(message) {
558
- this.log(`\u2713 ${message}`);
559
- }
560
- logWarning(message) {
561
- this.warn(message);
562
- }
563
- logInfo(message) {
564
- this.log(message);
565
- }
566
- clearTerminal() {
567
- clearTerminalScreen();
568
- }
569
- /**
570
- * Resolves the marketplace required for a plugin operation, or hard-exits with
571
- * an actionable error when it cannot be resolved. `onRegistered` is invoked
572
- * (when provided) if the marketplace had to be registered — init uses it to log.
573
- *
574
- * `host` is the caller's because a marketplace registration is handed no directory of its own:
575
- * every caller here holds the install root the run is about, and `hostAt` turns one into the
576
- * host whose registry the marketplace has to land in.
577
- */
578
- async requireMarketplaceOrExit(sourceResult, purpose, host, onRegistered) {
579
- const required = await requireMarketplace(sourceResult, purpose, host);
580
- if (!required.ok) {
581
- this.error(required.error, { exit: EXIT_CODES.ERROR });
582
- }
583
- if (required.registered) {
584
- onRegistered?.(required.marketplace);
585
- }
586
- return required.marketplace;
587
- }
588
- /**
589
- * Reports every constraint a selection breaks — a conflicting pair, an unmet
590
- * requirement, an exclusive category with two picks.
591
- *
592
- * Shared by `init` and `edit` so one selection cannot be reported two ways. Both commands ask
593
- * the same question of the same matrix, so a roster that draws a warning through one must draw
594
- * the same warning through the other; `init` used to compute the answer and drop it, which is
595
- * the whole reason this lives here rather than on either command.
596
- *
597
- * These are advisory (`ValidationError` in types/matrix.ts): the selection is installed either
598
- * way and neither command's exit code turns on them, so they are warnings and not errors. The
599
- * loop is its own guard — a selection that breaks none says nothing.
600
- */
601
- reportValidationErrors(validation) {
602
- for (const error of validation.errors) {
603
- this.warn(error.message);
604
- }
605
- }
606
- /**
607
- * Installs skill plugins and narrates the operation.
608
- *
609
- * Shared by `init` and `edit` for the same reason as {@link reportValidationErrors}:
610
- * both run the SAME install against the same marketplace, so a line one prints about
611
- * it the other owes. `edit` used to run it in silence — a real `claude plugin install`
612
- * was indistinguishable in the output from nothing having happened.
613
- *
614
- * The skills nothing carries are refused FIRST, by name: an install no marketplace
615
- * could serve is knowable before the host's CLI is asked, and the generic
616
- * install-failure advice is unfollowable for a skill the user wrote themselves.
617
- */
618
- async installPluginSkillsReported(skills, marketplace, projectDir, matrix) {
619
- const unbacked = unbackedPluginSkillIds(skills, matrix);
620
- if (unbacked.length > 0) {
621
- this.error(unbackedPluginInstallError(unbacked), { exit: EXIT_CODES.ERROR });
622
- }
623
- this.announcePluginInstall();
624
- const result = await installPluginSkills(skills, marketplace, projectDir);
625
- this.reportPluginInstalls(result);
626
- return result;
627
- }
628
- /**
629
- * The opening half of the narration above, exposed for the one caller that cannot
630
- * use the whole of it: an eject→plugin migration installs each plugin and deletes
631
- * that skill's working copy in the same step (see `executeMigration`), so the
632
- * install it performs is not this command's to run.
633
- */
634
- announcePluginInstall() {
635
- this.log(STATUS_MESSAGES.INSTALLING_PLUGINS);
636
- }
637
- /**
638
- * The closing half: what was installed, what was not, and the refusal to continue
639
- * past a failure.
640
- *
641
- * The hard-error is part of the report rather than each caller's own postscript
642
- * because it is the same rule everywhere — plugin install intent is inviolable, so
643
- * a run that could not honor it must stop BEFORE any config records a marketplace
644
- * `origin` for a skill with no plugin registration.
645
- */
646
- reportPluginInstalls(result) {
647
- for (const item of result.installed) {
648
- this.log(` Installed ${item.ref}`);
649
- }
650
- for (const item of result.failed) {
651
- this.warn(`Failed to install plugin ${item.id}: ${item.error}`);
652
- }
653
- if (result.failed.length > 0) {
654
- this.error(pluginInstallFailureError(result.failed.length), { exit: EXIT_CODES.ERROR });
655
- }
656
- this.log(`${pluginsInstalled(result.installed.length)}
657
- `);
658
- }
659
- /**
660
- * Renders what a gated write's fan-out did to the OTHER registered projects: one line for each
661
- * it left alone because its config.ts cannot be loaded, then the recompile it performed.
662
- *
663
- * `uninstall` alone calls {@link reportPropagatedRecompile} directly, since it words the projects
664
- * whose config it could not prune in its own terms.
665
- */
666
- reportFanOut(report) {
667
- for (const projectPath of report.propagated.unreadable) {
668
- this.warn(registeredProjectConfigUnreadable(projectPath));
669
- }
670
- this.reportPropagatedRecompile(report);
671
- }
672
- /**
673
- * One line for each registered project a fan-out left alone because nothing of this
674
- * installation is there to rewrite: nothing is installed at its path any more, or it is another
675
- * provider's installation. `compile` and `uninstall` print it — neither project's config can
676
- * hold what either run changed.
677
- */
678
- reportProjectsOutOfReach(propagated) {
679
- for (const projectPath of propagated.gone) {
680
- this.warn(registeredProjectGone(projectPath));
681
- }
682
- for (const projectPath of propagated.notOurs) {
683
- this.warn(registeredProjectOnAnotherProvider(projectPath));
684
- }
685
- }
686
- /**
687
- * Renders the recompile a gated write already performed on every OTHER registered
688
- * project this run's global change was propagated into.
689
- *
690
- * Shared by all four commands that fan out — the count and its wording are one
691
- * statement about one operation, and four copies of it had already drifted into two
692
- * spellings of the same sentence.
693
- */
694
- reportPropagatedRecompile(report) {
695
- if (report.propagated.updated.length === 0) return;
696
- const { rewrittenCount, unchangedCount, failedCount, warnings } = report.recompile;
697
- for (const warning of warnings) {
698
- this.warn(warning);
699
- }
700
- this.log(propagatedRecompileSummary(rewrittenCount, unchangedCount, failedCount));
701
- }
702
- /**
703
- * Names every selected skill this save left in no sub-agent's stack, and — where the
704
- * scope rule is why — every sub-agent it was kept away from.
705
- *
706
- * Shared by `init` and `edit` for the reason {@link reportValidationErrors} is: both
707
- * build the stack from the same rules over the same selection, so a skill that reaches
708
- * nothing through one reaches nothing through the other. The rule being reported is
709
- * CORRECT and nothing here changes it — a project-scoped skill installs, is written to
710
- * `config.ts`, and is loaded by nobody, which every other surface reports as a clean
711
- * install.
712
- *
713
- * `compile` says the same thing about the same shape one layer over, for a config
714
- * hand-edited into it — see `Compile.warnScopeDroppedStackPairs`.
715
- */
716
- reportUnassignedSkills(config, selected) {
717
- for (const { skillId, blockedBy } of findUnassignedSkills(config, selected)) {
718
- this.warn(skillAssignedToNoAgent(skillId));
719
- if (blockedBy.length > 0) {
720
- this.warn(scopeBlockedStackAssignment(blockedBy, skillId));
721
- }
722
- }
723
- }
724
- /**
725
- * Refuses to run when a config file exists but cannot be loaded.
726
- *
727
- * Both configs this run would read are checked, because both are read on the way through: a
728
- * project's own `config.ts` and the global one every project write inlines. Checking up
729
- * front is the whole point — an unreadable GLOBAL config otherwise surfaced only at the
730
- * config write, after the install had copied skills and installed plugins, as a warning that
731
- * the write never happened.
732
- *
733
- * Every command that reads an installation's config calls it before anything else reads one:
734
- * the writes (`init`, `edit`, `compile`, `update`, `share`, `eject`) and the two that only
735
- * report (`search`, `list`). `doctor` reports the same file instead, and `uninstall` is the way
736
- * out of it, so neither does.
737
- *
738
- * A MISSING config is not a corrupt one and passes here untouched: it is the legitimate
739
- * state `init` exists for, and the state `edit` reports as "no installation".
740
- */
741
- async ensureConfigReadable(projectDir) {
742
- const failures = await findConfigLoadFailures(projectDir);
743
- if (failures.length > 0) {
744
- this.error(installationConfigsUnreadable(failures), { exit: EXIT_CODES.ERROR });
745
- }
746
- }
747
- /**
748
- * Refuses to run when a saved skill entry names a local skill that IS installed and whose
749
- * metadata.yaml describes no skill.
750
- *
751
- * One layer below {@link ensureConfigReadable} and for the same reason: the config parses,
752
- * and a file it points at does not.
753
- *
754
- * Only entries the catalogue does not carry are looked at, so a healthy install costs no
755
- * filesystem read. An entry whose files are simply GONE is not refused — there
756
- * is no file to repair.
757
- *
758
- * Runs before anything is asked or written, which is the whole point: past that, everything
759
- * this protects has been rewritten.
760
- */
761
- async ensureSavedSkillsReadable(savedSkills, matrix, projectDir) {
762
- const unresolved = savedSkills.filter((saved) => !matrix.skills[saved.id]);
763
- await this.refuseUnusableSavedSkillMetadata(
764
- unresolved,
765
- projectDir,
766
- savedSkillMetadataUnusableError
767
- );
768
- }
769
- /**
770
- * Refuses to run when an installed entry of `savedSkills` has a metadata.yaml that describes no
771
- * skill, with `refusal` saying what the file would cost the command that met it.
772
- */
773
- async refuseUnusableSavedSkillMetadata(savedSkills, projectDir, refusal) {
774
- const unusable = await findUnusableSavedSkillMetadata(savedSkills, projectDir);
775
- if (unusable.length === 0) return;
776
- for (const entry of unusable) {
777
- this.log(skillMetadataUnusableDetail(entry));
778
- }
779
- this.error(refusal(unusable), { exit: EXIT_CODES.ERROR });
780
- }
781
- /**
782
- * Refuses a shared configuration carrying project-scoped content at the home directory.
783
- *
784
- * The home directory IS the global scope, and a global installation holds only global-scoped
785
- * content. Both scopes read one config file there, and on Claude one skills directory and one
786
- * agents directory as well — so a project-scoped entry lands in the global config wearing a
787
- * label that contradicts the file it is in, and on Claude `toClaudePluginScope` maps that
788
- * declared scope onward, registering the skill against `$HOME` as a project. Nothing
789
- * below this point reads the scope again, so whatever the boundary lets through becomes the
790
- * truth.
791
- *
792
- * Shared by `init --from` and `edit --from` for the reason {@link reportValidationErrors} is
793
- * shared: it is a fact about the payload and the directory, identical whichever command asked,
794
- * and an invariant enforced on one producer and not the other is enforced nowhere. It matters
795
- * more on the destructive one — `edit --from` reaches the same contradiction through the other
796
- * door and can remove global entries on the way.
797
- *
798
- * It takes the DECODED selection, so it must run after the decode and can run no earlier: an
799
- * all-global configuration is exactly what a global installation is for, and only the decode
800
- * says which entries survived this catalogue and what scope each rests at. Every caller runs it
801
- * before it reports a skip and before it writes anything, so a run about to be refused neither
802
- * narrates nor mutates.
803
- */
804
- refuseProjectScopedContentAtHome(result, projectDir) {
805
- if (!isHomeDirectory(projectDir)) return;
806
- const { skillIds, agentNames } = projectScopedContent(result);
807
- if (skillIds.length === 0 && agentNames.length === 0) return;
808
- this.error(sharedConfigProjectScopeAtHome(skillIds, agentNames), { exit: EXIT_CODES.ERROR });
809
- }
810
- /**
811
- * The refusals a `--from` producer can decide only once it holds what it would install — the
812
- * selection and the skills the configuration carries — asked before it lists any of it or asks
813
- * whether to install it.
814
- *
815
- * A run that lists, asks, and is told yes has promised that a yes installs what was listed, so a
816
- * refusal still to come after the question would ask a person to confirm an install that was
817
- * never going to happen. Every other
818
- * `--from` refusal is already decided above the lists — a carried skill asked for as a plugin or
819
- * under a catalogue's id, project content at the home directory, an installation already here —
820
- * and these four are the ones that need the decoded selection to be asked of:
821
- *
822
- * - **A placement the host does not offer.** Read off the selection and the host the run
823
- * installs through, nothing else, so nothing makes it wait for the yes.
824
- * - **A stack the marketplace does not ship.** The stack supplies only the configuration's
825
- * description, yet the install refuses an id it cannot resolve — after the plugins are
826
- * installed and the skills copied. So it is asked here, through the lookup the install makes,
827
- * of the marketplace the install reads.
828
- * - **A carried skill over a directory no shared configuration put there** — the user's own
829
- * skill at the id the configuration minted for it. Judged on the same set the producer writes,
830
- * so the skills already installed globally, which it does not write, are not asked about.
831
- * - **A stack the config writer would refuse** — two skills in a category that holds one, as a
832
- * hand-written row or the kept skills an apply puts back can make. Asked through the writer's
833
- * own rule, because `edit --from` writes the config after its removals have landed.
834
- *
835
- * Shared by `init --from` and `edit --from` for the reason {@link refuseProjectScopedContentAtHome}
836
- * is: an invariant enforced on one producer and not the other is enforced nowhere.
837
- */
838
- async refuseSharedConfigBeforeAsking(selection, carried, root, sourceResult) {
839
- this.refuseUnofferablePlacementsBeforeWriting(selection.skills, root);
840
- try {
841
- await refuseUnofferedStack(selection.selectedStackId, sourceResult);
842
- await refuseUncarriedDestinations(carried);
843
- refuseUnwritableStack(selection.assignedStack, sourceResult.matrix);
844
- } catch (error) {
845
- this.handleError(error);
846
- }
847
- }
848
- /**
849
- * Refuses a SELECTION asking for a mode/scope cell this host does not offer, before the run has
850
- * touched anything.
851
- *
852
- * **`bindsItsOfferedPlacements` is not a pre-flight and cannot be made into one.** It refuses
853
- * at `installPlugin`, which `installPluginSkills` calls once per skill inside a loop that
854
- * collects per-skill failures — so a payload whose first plugin row is offerable and whose
855
- * second is not installed a real plugin into the user's Codex registry and only then stopped,
856
- * on a message ending "Nothing has been changed." Nothing removed it afterwards: no `config.ts`
857
- * was written, so `uninstall` had no row naming it and `doctor` had no configuration to check
858
- * it against — an orphan in the host's own registry that no command in this CLI can see. The
859
- * single-row case cannot show it, because with one unofferable row the loop's first iteration
860
- * IS the refusal. `edit --from` had no pre-flight at all until it came here, and met exactly
861
- * that per-plugin guard after its own yes.
862
- *
863
- * It reads the whole selection — ejected rows included — because the roster is about CELLS
864
- * rather than about plugins: a host that stopped offering `eject+project` would owe the same
865
- * refusal, and a guard that only looked at plugin rows would go on writing files for it.
866
- *
867
- * The same function the read path calls (`refuseUnofferedPlacements`, off the same host
868
- * roster), so `init`'s sentence and `compile`'s are one sentence rather than two that agree
869
- * today. The per-call guard stays where it is: it is the backstop for a caller that did not
870
- * come through here.
871
- */
872
- refuseUnofferablePlacementsBeforeWriting(skills, root) {
873
- try {
874
- refuseUnofferedPlacements(skills, hostAt(root));
875
- } catch (error) {
876
- this.error(getErrorMessage(error), { exit: EXIT_CODES.ERROR });
877
- }
878
- }
879
- /** Whether this run owes an account — the one thing a command may ask about the list. */
880
- get hasIncompleteWork() {
881
- return this.incompleteWork.length > 0;
882
- }
883
- /**
884
- * Warns about work this run attempted and did not complete, and records it for the ending.
885
- *
886
- * Both halves, always. The warning stays at the point of failure because that is where it is
887
- * explicable and where the surrounding output says what was being attempted; the record is
888
- * what decides the exit code, and a failure the user is told about while the process reports
889
- * success is exactly the state this closes.
890
- *
891
- * What it must never do is abort. Every caller sits past the point where the mutation it is
892
- * reporting on has already happened, so stopping would relabel a state already committed to
893
- * disk rather than prevent it — and would abandon the steps after it that can still land.
894
- *
895
- * The `recovery` sentences live in `INCOMPLETE_WORK_RECOVERY` in `utils/messages.ts`. One
896
- * spelling per remedy is what matters; three spellings of "run compile" read as three
897
- * different instructions.
898
- */
899
- reportIncompleteWork(what, recovery) {
900
- this.warn(what);
901
- this.recordIncompleteWork(what, recovery);
902
- }
903
- /**
904
- * The recording half alone, for a caller that has already said its piece: a recompile warns
905
- * once per failed sub-agent with the compiler's own reason, so the ending names the roster
906
- * those reasons belong to instead of repeating any of them.
907
- */
908
- recordIncompleteWork(what, recovery) {
909
- this.incompleteWork.push({ what, recovery });
910
- }
911
- /**
912
- * The command's last word: what did not happen, what finishes it, and a non-zero exit.
913
- *
914
- * `this.exit` raises oclif's `ExitError`, which the framework carries to the process exit
915
- * code and prints nothing for — so the account printed here is the whole of the output, with
916
- * none of the `Error:` framing `this.error` would add to a command that in fact completed.
917
- *
918
- * A command that records must call this, or it files failures into a list nothing reads —
919
- * which is the original silence wearing the mechanism built to close it, and is what
920
- * `failure-reporting-classification.test.ts` holds every command in `src/cli/commands/` to.
921
- */
922
- exitIfWorkIncomplete() {
923
- if (!this.hasIncompleteWork) return;
924
- this.log(`
925
- ${chalk.hex(CLI_COLORS.WARNING)(completedWithFailures(this.incompleteWork))}
926
- `);
927
- this.exit(EXIT_CODES.COMPLETED_WITH_FAILURES);
928
- }
929
- };
930
-
931
- export {
932
- clearTerminalScreen,
933
- EXIT_CODES,
934
- registerExternalSkills,
935
- writeExternalSkills,
936
- carriedSkillsThatDiffer,
937
- readCarriedSkills,
938
- BaseCommand
939
- };
940
- //# sourceMappingURL=chunk-V3KTSP3Q.js.map