agents-inc 0.168.0 → 0.170.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 (200) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +18 -15
  3. package/dist/{chunk-JIBCVXXL.js → chunk-3G7X2WKI.js} +4 -4
  4. package/dist/{chunk-JOEA52NR.js → chunk-44TE3RKH.js} +46 -2
  5. package/dist/chunk-44TE3RKH.js.map +1 -0
  6. package/dist/{chunk-NU2K5G54.js → chunk-52HDHJ7I.js} +11 -19
  7. package/dist/chunk-52HDHJ7I.js.map +1 -0
  8. package/dist/{chunk-EH2PGPLP.js → chunk-6HNXYYVF.js} +39 -22
  9. package/dist/chunk-6HNXYYVF.js.map +1 -0
  10. package/dist/{chunk-4UKMQUAU.js → chunk-6ZRH5RHC.js} +10 -11
  11. package/dist/chunk-6ZRH5RHC.js.map +1 -0
  12. package/dist/{chunk-5JUQKGCP.js → chunk-7RDMTR64.js} +14 -34
  13. package/dist/chunk-7RDMTR64.js.map +1 -0
  14. package/dist/{chunk-3LZNY46H.js → chunk-BMRUNMPV.js} +4 -4
  15. package/dist/{chunk-VVRI35SK.js → chunk-CLPUWOSA.js} +4 -4
  16. package/dist/{chunk-VVRI35SK.js.map → chunk-CLPUWOSA.js.map} +1 -1
  17. package/dist/{chunk-TW35AEB4.js → chunk-F2BHT5C4.js} +9 -7
  18. package/dist/chunk-F2BHT5C4.js.map +1 -0
  19. package/dist/{chunk-27AVGFXH.js → chunk-F5EBEATU.js} +2 -2
  20. package/dist/{chunk-GE7NUPMI.js → chunk-HED2DAXZ.js} +3 -3
  21. package/dist/{chunk-7PYXS2QU.js → chunk-KOLDW6SF.js} +3 -3
  22. package/dist/{chunk-7PYXS2QU.js.map → chunk-KOLDW6SF.js.map} +1 -1
  23. package/dist/{chunk-C2UISKIM.js → chunk-MQ6CGZNJ.js} +109 -422
  24. package/dist/chunk-MQ6CGZNJ.js.map +1 -0
  25. package/dist/{chunk-P6I72PR3.js → chunk-NCWDK6L7.js} +115 -196
  26. package/dist/chunk-NCWDK6L7.js.map +1 -0
  27. package/dist/{chunk-VRXX4D6K.js → chunk-NEZ4OAOL.js} +1 -1
  28. package/dist/chunk-NEZ4OAOL.js.map +1 -0
  29. package/dist/chunk-NIHUOWE4.js +106 -0
  30. package/dist/chunk-NIHUOWE4.js.map +1 -0
  31. package/dist/{chunk-OI2H6M5I.js → chunk-NQW25DCF.js} +2 -2
  32. package/dist/chunk-NQW25DCF.js.map +1 -0
  33. package/dist/{chunk-ZUGH4MQG.js → chunk-OYJQUA3D.js} +3 -3
  34. package/dist/{chunk-ZUGH4MQG.js.map → chunk-OYJQUA3D.js.map} +1 -1
  35. package/dist/{chunk-AG3ZVM3B.js → chunk-PJWGTNOX.js} +186 -210
  36. package/dist/chunk-PJWGTNOX.js.map +1 -0
  37. package/dist/{chunk-HR7TK2XW.js → chunk-RIPSGNLU.js} +3 -3
  38. package/dist/chunk-RIPSGNLU.js.map +1 -0
  39. package/dist/{chunk-4DRFEYCE.js → chunk-SZYQISA2.js} +7 -7
  40. package/dist/chunk-SZYQISA2.js.map +1 -0
  41. package/dist/{chunk-W6GO2CYX.js → chunk-USVBYQSQ.js} +2 -2
  42. package/dist/{chunk-H4ZKGGTE.js → chunk-V3KTSP3Q.js} +50 -42
  43. package/dist/chunk-V3KTSP3Q.js.map +1 -0
  44. package/dist/{chunk-KTLKR4YY.js → chunk-WTWMGH2C.js} +2 -2
  45. package/dist/commands/build/marketplace.js +23 -22
  46. package/dist/commands/build/marketplace.js.map +1 -1
  47. package/dist/commands/build/plugins.js +8 -9
  48. package/dist/commands/build/plugins.js.map +1 -1
  49. package/dist/commands/compile.js +19 -20
  50. package/dist/commands/compile.js.map +1 -1
  51. package/dist/commands/doctor.js +17 -18
  52. package/dist/commands/doctor.js.map +1 -1
  53. package/dist/commands/edit.js +118 -297
  54. package/dist/commands/edit.js.map +1 -1
  55. package/dist/commands/eject.js +21 -33
  56. package/dist/commands/eject.js.map +1 -1
  57. package/dist/commands/init.js +17 -45
  58. package/dist/commands/list.js +19 -17
  59. package/dist/commands/list.js.map +1 -1
  60. package/dist/commands/new/marketplace.js +11 -11
  61. package/dist/commands/new/marketplace.js.map +1 -1
  62. package/dist/commands/search.js +12 -13
  63. package/dist/commands/search.js.map +1 -1
  64. package/dist/commands/share.js +11 -12
  65. package/dist/commands/share.js.map +1 -1
  66. package/dist/commands/uninstall.js +18 -20
  67. package/dist/commands/uninstall.js.map +1 -1
  68. package/dist/commands/update.js +8 -9
  69. package/dist/commands/update.js.map +1 -1
  70. package/dist/components/common/removal-plan-confirm.js +2 -2
  71. package/dist/components/common/scroll-affordance.js +9 -0
  72. package/dist/components/common/select-list.js +2 -3
  73. package/dist/components/common/skill-agent-summary.js +11 -0
  74. package/dist/components/common/spinner.js +1 -1
  75. package/dist/{chunk-QWFG7Y3R.js → components/common/summary-panel.js} +57 -31
  76. package/dist/components/common/summary-panel.js.map +1 -0
  77. package/dist/config-exports.js +1 -1
  78. package/dist/hooks/init.js +18 -46
  79. package/dist/hooks/init.js.map +1 -1
  80. package/dist/load-agent-defs-YMDWBFDP.js +14 -0
  81. package/dist/recompile-project-agents-SZ76WNTW.js +18 -0
  82. package/dist/{source-loader-FRJTCSDL.js → source-loader-C5SQXZXI.js} +5 -5
  83. package/dist/src/agents/meta/codex-keeper/playbook.md +2 -3
  84. package/package.json +2 -3
  85. package/src/agents/meta/codex-keeper/playbook.md +2 -3
  86. package/src/schemas/project-config.schema.json +1 -1
  87. package/assets/demo.tape +0 -266
  88. package/dist/chunk-23DNVNFP.js +0 -292
  89. package/dist/chunk-23DNVNFP.js.map +0 -1
  90. package/dist/chunk-3DIDV6H3.js +0 -268
  91. package/dist/chunk-3DIDV6H3.js.map +0 -1
  92. package/dist/chunk-4DRFEYCE.js.map +0 -1
  93. package/dist/chunk-4HWOYYY7.js +0 -92
  94. package/dist/chunk-4HWOYYY7.js.map +0 -1
  95. package/dist/chunk-4UKMQUAU.js.map +0 -1
  96. package/dist/chunk-5JUQKGCP.js.map +0 -1
  97. package/dist/chunk-6WGXTB2L.js +0 -28
  98. package/dist/chunk-6WGXTB2L.js.map +0 -1
  99. package/dist/chunk-AG3ZVM3B.js.map +0 -1
  100. package/dist/chunk-C2UISKIM.js.map +0 -1
  101. package/dist/chunk-CMJBKQUH.js +0 -23
  102. package/dist/chunk-CMJBKQUH.js.map +0 -1
  103. package/dist/chunk-DJEO3F2H.js +0 -230
  104. package/dist/chunk-DJEO3F2H.js.map +0 -1
  105. package/dist/chunk-EH2PGPLP.js.map +0 -1
  106. package/dist/chunk-GRL73YPF.js +0 -197
  107. package/dist/chunk-GRL73YPF.js.map +0 -1
  108. package/dist/chunk-H4ZKGGTE.js.map +0 -1
  109. package/dist/chunk-HI6YB7XP.js +0 -59
  110. package/dist/chunk-HI6YB7XP.js.map +0 -1
  111. package/dist/chunk-HL3GYRMR.js +0 -17
  112. package/dist/chunk-HL3GYRMR.js.map +0 -1
  113. package/dist/chunk-HR7TK2XW.js.map +0 -1
  114. package/dist/chunk-JOEA52NR.js.map +0 -1
  115. package/dist/chunk-JPMV4E4E.js +0 -118
  116. package/dist/chunk-JPMV4E4E.js.map +0 -1
  117. package/dist/chunk-JVUN6AOO.js +0 -237
  118. package/dist/chunk-JVUN6AOO.js.map +0 -1
  119. package/dist/chunk-KQ3PDARZ.js +0 -25
  120. package/dist/chunk-KQ3PDARZ.js.map +0 -1
  121. package/dist/chunk-L3W2JWBA.js +0 -168
  122. package/dist/chunk-L3W2JWBA.js.map +0 -1
  123. package/dist/chunk-LFUJCRCT.js +0 -27
  124. package/dist/chunk-LFUJCRCT.js.map +0 -1
  125. package/dist/chunk-NU2K5G54.js.map +0 -1
  126. package/dist/chunk-OI2H6M5I.js.map +0 -1
  127. package/dist/chunk-P6I72PR3.js.map +0 -1
  128. package/dist/chunk-PCFUXQJA.js +0 -317
  129. package/dist/chunk-PCFUXQJA.js.map +0 -1
  130. package/dist/chunk-PLX2FLEQ.js +0 -48
  131. package/dist/chunk-PLX2FLEQ.js.map +0 -1
  132. package/dist/chunk-QWFG7Y3R.js.map +0 -1
  133. package/dist/chunk-SLBL3WIP.js +0 -121
  134. package/dist/chunk-SLBL3WIP.js.map +0 -1
  135. package/dist/chunk-TSIU7DME.js +0 -43
  136. package/dist/chunk-TSIU7DME.js.map +0 -1
  137. package/dist/chunk-TW35AEB4.js.map +0 -1
  138. package/dist/chunk-VRXX4D6K.js.map +0 -1
  139. package/dist/chunk-W5KEYSOL.js +0 -51
  140. package/dist/chunk-W5KEYSOL.js.map +0 -1
  141. package/dist/chunk-WNLF5EDZ.js +0 -1170
  142. package/dist/chunk-WNLF5EDZ.js.map +0 -1
  143. package/dist/chunk-WTJ3AY33.js +0 -31
  144. package/dist/chunk-WTJ3AY33.js.map +0 -1
  145. package/dist/chunk-X6ZZEYO3.js +0 -53
  146. package/dist/chunk-X6ZZEYO3.js.map +0 -1
  147. package/dist/chunk-XQHZPINL.js +0 -89
  148. package/dist/chunk-XQHZPINL.js.map +0 -1
  149. package/dist/chunk-YN3SWF53.js +0 -35
  150. package/dist/chunk-YN3SWF53.js.map +0 -1
  151. package/dist/components/wizard/category-grid.js +0 -13
  152. package/dist/components/wizard/checkbox-grid.js +0 -12
  153. package/dist/components/wizard/domain-selection.js +0 -18
  154. package/dist/components/wizard/run-wizard-session.js +0 -40
  155. package/dist/components/wizard/run-wizard-session.js.map +0 -1
  156. package/dist/components/wizard/scroll-affordance.js +0 -9
  157. package/dist/components/wizard/skill-agent-summary.js +0 -13
  158. package/dist/components/wizard/source-grid.js +0 -16
  159. package/dist/components/wizard/source-grid.js.map +0 -1
  160. package/dist/components/wizard/stack-selection.js +0 -17
  161. package/dist/components/wizard/stack-selection.js.map +0 -1
  162. package/dist/components/wizard/step-agents.js +0 -18
  163. package/dist/components/wizard/step-agents.js.map +0 -1
  164. package/dist/components/wizard/step-build.js +0 -18
  165. package/dist/components/wizard/step-build.js.map +0 -1
  166. package/dist/components/wizard/step-confirm.js +0 -18
  167. package/dist/components/wizard/step-confirm.js.map +0 -1
  168. package/dist/components/wizard/step-sources.js +0 -19
  169. package/dist/components/wizard/step-sources.js.map +0 -1
  170. package/dist/components/wizard/step-stack.js +0 -18
  171. package/dist/components/wizard/step-stack.js.map +0 -1
  172. package/dist/components/wizard/summary-panel.js +0 -17
  173. package/dist/components/wizard/summary-panel.js.map +0 -1
  174. package/dist/components/wizard/toast.js +0 -9
  175. package/dist/components/wizard/toast.js.map +0 -1
  176. package/dist/components/wizard/wizard-layout.js +0 -22
  177. package/dist/components/wizard/wizard-layout.js.map +0 -1
  178. package/dist/components/wizard/wizard-tabs.js +0 -11
  179. package/dist/components/wizard/wizard-tabs.js.map +0 -1
  180. package/dist/components/wizard/wizard.js +0 -39
  181. package/dist/components/wizard/wizard.js.map +0 -1
  182. package/dist/load-agent-defs-5XMVB3MU.js +0 -14
  183. package/dist/load-agent-defs-5XMVB3MU.js.map +0 -1
  184. package/dist/recompile-project-agents-7PQWYQ6B.js +0 -18
  185. package/dist/recompile-project-agents-7PQWYQ6B.js.map +0 -1
  186. package/dist/source-loader-FRJTCSDL.js.map +0 -1
  187. package/dist/stores/wizard-store.js +0 -24
  188. package/dist/stores/wizard-store.js.map +0 -1
  189. /package/assets/{demo.gif → legacy-wizard.gif} +0 -0
  190. /package/dist/{chunk-JIBCVXXL.js.map → chunk-3G7X2WKI.js.map} +0 -0
  191. /package/dist/{chunk-3LZNY46H.js.map → chunk-BMRUNMPV.js.map} +0 -0
  192. /package/dist/{chunk-27AVGFXH.js.map → chunk-F5EBEATU.js.map} +0 -0
  193. /package/dist/{chunk-GE7NUPMI.js.map → chunk-HED2DAXZ.js.map} +0 -0
  194. /package/dist/{chunk-W6GO2CYX.js.map → chunk-USVBYQSQ.js.map} +0 -0
  195. /package/dist/{chunk-KTLKR4YY.js.map → chunk-WTWMGH2C.js.map} +0 -0
  196. /package/dist/components/{wizard → common}/scroll-affordance.js.map +0 -0
  197. /package/dist/components/{wizard → common}/skill-agent-summary.js.map +0 -0
  198. /package/dist/{components/wizard/category-grid.js.map → load-agent-defs-YMDWBFDP.js.map} +0 -0
  199. /package/dist/{components/wizard/checkbox-grid.js.map → recompile-project-agents-SZ76WNTW.js.map} +0 -0
  200. /package/dist/{components/wizard/domain-selection.js.map → source-loader-C5SQXZXI.js.map} +0 -0
@@ -2,23 +2,22 @@
2
2
  import {
3
3
  publishSeedConfig,
4
4
  seedPayloadForInstallation
5
- } from "../chunk-4DRFEYCE.js";
5
+ } from "../chunk-SZYQISA2.js";
6
6
  import {
7
7
  unofferablePlacementsFound
8
- } from "../chunk-KTLKR4YY.js";
9
- import "../chunk-ZUGH4MQG.js";
10
- import "../chunk-3LZNY46H.js";
8
+ } from "../chunk-WTWMGH2C.js";
9
+ import "../chunk-OYJQUA3D.js";
10
+ import "../chunk-BMRUNMPV.js";
11
11
  import {
12
12
  providerFlag,
13
13
  providerNamedBy,
14
14
  refuseAnAmbiguousInstallation
15
- } from "../chunk-HR7TK2XW.js";
15
+ } from "../chunk-RIPSGNLU.js";
16
16
  import {
17
17
  BaseCommand,
18
18
  EXIT_CODES
19
- } from "../chunk-H4ZKGGTE.js";
20
- import "../chunk-VVRI35SK.js";
21
- import "../chunk-CMJBKQUH.js";
19
+ } from "../chunk-V3KTSP3Q.js";
20
+ import "../chunk-CLPUWOSA.js";
22
21
  import {
23
22
  formatZodErrors,
24
23
  getErrorMessage,
@@ -30,14 +29,14 @@ import {
30
29
  stripTerminalControls,
31
30
  truncateText,
32
31
  warn
33
- } from "../chunk-C2UISKIM.js";
32
+ } from "../chunk-MQ6CGZNJ.js";
34
33
  import {
35
34
  typedEntries
36
- } from "../chunk-NU2K5G54.js";
35
+ } from "../chunk-52HDHJ7I.js";
37
36
  import {
38
37
  installableSeedPayloadSchema
39
- } from "../chunk-AG3ZVM3B.js";
40
- import "../chunk-OI2H6M5I.js";
38
+ } from "../chunk-PJWGTNOX.js";
39
+ import "../chunk-NQW25DCF.js";
41
40
 
42
41
  // src/cli/commands/share.ts
43
42
  import { Flags } from "@oclif/core";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/commands/share.ts","../../src/cli/lib/seed/read-piped-payload.ts","../../src/cli/utils/read-stream.ts"],"sourcesContent":["import { Flags } from \"@oclif/core\";\n\nimport { BaseCommand } from \"../base-command.js\";\nimport { isLocalSource } from \"../lib/configuration/config.js\";\nimport { EXIT_CODES } from \"../lib/exit-codes.js\";\nimport { unofferablePlacementsFound } from \"../lib/hosts/configured-placements.js\";\nimport { seedPayloadForInstallation } from \"../lib/seed/installation-payload.js\";\nimport { publishSeedConfig } from \"../lib/seed/publish-seed.js\";\nimport {\n readPipedPayload,\n STDIN_IS_A_TERMINAL,\n type PipedPayload,\n} from \"../lib/seed/read-piped-payload.js\";\nimport {\n providerFlag,\n providerNamedBy,\n refuseAnAmbiguousInstallation,\n} from \"../lib/installation/provider-flag.js\";\nimport { warn } from \"../utils/logger.js\";\nimport { readAllOf } from \"../utils/read-stream.js\";\nimport {\n sharedConfigDestinations,\n sharedIdNamesAFolder,\n sharedSkillsLeftBehind,\n shareRefusesEjectedSkills,\n} from \"../utils/messages.js\";\nimport { typedEntries } from \"../utils/typed-object.js\";\nimport type { SkillId } from \"../types/index.js\";\nimport type { SeedPayload } from \"@workspace/matrix/seed\";\n\n/** A payload to publish, and what is owed about it once it has an id. */\ntype ShareablePayload = { payload: SeedPayload; caveats: string[] };\n\n/**\n * What an id minted from this installation will not do everywhere, said after the id rather than\n * refused: it still installs everything else.\n *\n * A payload names one marketplace, by the ref `--marketplace` took. A folder is a ref only this\n * machine can follow, and a plugin from any other marketplace is skipped wherever the id is\n * installed.\n */\nfunction caveatsAbout(payload: SeedPayload, leftBehind: readonly SkillId[]): string[] {\n const { marketplace } = payload;\n return [\n ...(isAFolder(marketplace) ? [sharedIdNamesAFolder(marketplace)] : []),\n ...(leftBehind.length > 0 ? [sharedSkillsLeftBehind(leftBehind)] : []),\n ];\n}\n\n/** Whether a payload's marketplace ref is a folder on this machine rather than a repository. */\nfunction isAFolder(marketplace: string | undefined): marketplace is string {\n return marketplace !== undefined && isLocalSource(marketplace);\n}\n\n/** The skills a payload asks the receiver to eject — local copies, editor-added ones included. */\nfunction ejectedSkillsIn(payload: SeedPayload): string[] {\n return typedEntries(payload.skills)\n .filter(([, skill]) => skill.install === \"eject\")\n .map(([id]) => id);\n}\n\nexport default class Share extends BaseCommand {\n static summary = \"Share this installation as an id anyone can install\";\n\n static description =\n \"Turns the skills, sub-agents and per-agent curation installed here into a configuration the agentsinc.sh store holds, and prints the id it was given. Install it elsewhere with 'init --from <id>', or open it in the editor. The id is the configuration's own hash, so sharing an unchanged installation returns the id it already had.\";\n\n static flags = {\n stdin: Flags.boolean({\n description:\n \"Share a configuration piped in on standard input instead of the one installed here\",\n default: false,\n }),\n provider: providerFlag(),\n };\n\n static examples = [\n \"<%= config.bin %> <%= command.id %>\",\n \"cat proposal.json | <%= config.bin %> <%= command.id %> --stdin\",\n ];\n\n /**\n * Read, map, refuse, publish, then say what the id will not do everywhere.\n *\n * Everything that can fail locally fails before the POST. The store's free tier allows a\n * thousand writes a day and reads a hundred times that, so a write is the scarce half — and one\n * spent on a configuration that cannot be installed buys a dead link.\n *\n * The first three steps are `seedPayloadForInstallation`, shared with `edit --ui`: the two\n * commands mint the same id from the same directory and differ only in what they do with it.\n * The one refusal `edit --ui` does not share is of ejected skills — a share carries plugins\n * only, and the editor is where a user goes to turn ejected skills into plugins.\n */\n async run(): Promise<void> {\n const { flags } = await this.parse(Share);\n\n // Only the installation path reads a directory. A piped payload is the CALLER's configuration\n // and no folder on this machine decides anything about it, so neither a scope holding two\n // installations nor a placement this host cannot fill is this run's problem — asking anyway\n // would refuse a `--stdin` share for the state of a directory it never opens.\n if (!flags.stdin) {\n await refuseAnAmbiguousInstallation(\n process.cwd(),\n \"share\",\n providerNamedBy(flags.provider),\n (message) => this.error(message, { exit: EXIT_CODES.INVALID_ARGS }),\n );\n await this.ensureConfigReadable(process.cwd());\n await this.refuseUnofferablePlacements(process.cwd());\n }\n\n const { payload, caveats } = flags.stdin\n ? await this.payloadFromPipe()\n : await this.payloadFromInstallation();\n\n const published = await publishSeedConfig(payload);\n if (!published.ok) {\n this.error(published.error, { exit: EXIT_CODES.ERROR });\n }\n\n this.reportShared(published.id);\n // Through `warn()` from `utils/logger.ts` rather than `this.warn`: oclif hard-wraps at the\n // terminal width, and a caveat names a folder path that does not survive being broken.\n for (const caveat of caveats) warn(caveat);\n }\n\n /**\n * Refuses a configuration asking for a mode/scope cell its own host does not offer, before the\n * payload is built and long before the POST.\n *\n * **`share` is a read path, and the roster that names them missed it.** The list this refusal\n * was built against reads \"`compile`, `edit`, `update` and `doctor`\" and claims to be every\n * command that acts on a configuration it did not just write — while `share` sits beside them\n * reading the same file through `seedPayloadForInstallation`. What it publishes is described as\n * \"the skills, sub-agents and per-agent curation installed HERE\", and a row the host could not\n * place was never installed here at all, so the id would describe an installation that does not\n * exist. `refuseAnAmbiguousInstallation` above is this finding's sibling and was already asked;\n * the two are the pair `doctor`'s Placements row reports together.\n *\n * A refusal rather than a row, like the other three writers, and for a reason of its own: the\n * store's write is the scarce half, and one spent on an installation nothing on this machine\n * will act on buys a link to a misdescription.\n *\n * `uninstall` reads the same file and is deliberately NOT on this roster: a refusal there would\n * make an unofferable installation unremovable, which is a guard that has swallowed its domain.\n */\n private async refuseUnofferablePlacements(cwd: string): Promise<void> {\n const [finding] = await unofferablePlacementsFound(cwd);\n if (finding !== undefined) this.error(finding, { exit: EXIT_CODES.ERROR });\n }\n\n /** The installation in this directory: mapped, refused if it holds ejected skills, announced. */\n private async payloadFromInstallation(): Promise<ShareablePayload> {\n const prepared = await seedPayloadForInstallation(process.cwd());\n if (!prepared.ok) {\n this.error(prepared.error, { exit: EXIT_CODES.ERROR });\n }\n\n this.refuseEjectedSkills(prepared.payload);\n this.log(`Sharing ${prepared.skills} skill(s) across ${prepared.agents} sub-agent(s)...`);\n\n return {\n payload: prepared.payload,\n caveats: caveatsAbout(prepared.payload, prepared.leftBehind),\n };\n }\n\n /**\n * Refuses a share while anything it would send is an ejected (Local) skill.\n *\n * An ejected skill is a copy on this machine, which the user may have edited and an\n * editor-added skill always is, so a share carries plugins only — from a project, that covers\n * the ejected skills it inherits from the global installation as well as its own. A skill the\n * user wrote by hand is never in the payload, so it neither travels nor refuses.\n */\n private refuseEjectedSkills(payload: SeedPayload): void {\n const ejected = ejectedSkillsIn(payload);\n if (ejected.length > 0) {\n this.error(shareRefusesEjectedSkills(ejected), { exit: EXIT_CODES.ERROR });\n }\n }\n\n /**\n * A configuration the CALLER holds, which is why nothing here reads an installation.\n *\n * That is the whole distinction the flag draws, and it is not cosmetic: a bare `share` resolves\n * an installation the way every other command does — this project, then the global one — so\n * without this branch, sharing a piped payload from a directory with nothing in it would\n * publish whatever the machine happens to have installed globally.\n *\n * The producer this exists for is not this CLI. `meta-config-stack-detect` walks a repository\n * and emits a `SeedPayload` it is forbidden to write or apply, and an id is the only door into\n * the editor, which reads `?fromId=` and nothing else. Publishing from here rather than from\n * the producer keeps `SEED_VERSION`, the `AGENTS_INC_API_URL` override and the caller's\n * user-agent in the one place that owns them.\n *\n * It owes no caveats: those describe an installation, and none was read.\n */\n private async payloadFromPipe(): Promise<ShareablePayload> {\n if (process.stdin.isTTY) {\n this.error(STDIN_IS_A_TERMINAL, { exit: EXIT_CODES.ERROR });\n }\n\n const read: PipedPayload = readPipedPayload(await readAllOf(process.stdin));\n if (!read.ok) {\n this.error(read.error, { exit: EXIT_CODES.ERROR });\n }\n\n return { payload: read.payload, caveats: [] };\n }\n\n /**\n * Both destinations, because an id nobody can act on is not a share and there are exactly two\n * things that read one: this CLI, and the editor the configuration can be reopened in. The\n * lines themselves are `sharedConfigDestinations`, shared with `edit --ui` for the same reason\n * the mint above is.\n */\n private reportShared(id: string): void {\n this.logSuccess(`Shared as ${id}`);\n for (const destination of sharedConfigDestinations(id)) {\n this.log(destination);\n }\n }\n}\n","import { installableSeedPayloadSchema, type SeedPayload } from \"@workspace/matrix/seed\";\n\nimport { formatZodErrors } from \"../schema-validator.js\";\nimport { getErrorMessage } from \"../../utils/errors.js\";\nimport { stripTerminalControls, truncateText } from \"../../utils/string.js\";\n\n/** A payload read from a pipe, or the reason it could not be. */\nexport type PipedPayload =\n | { readonly ok: true; readonly payload: SeedPayload }\n | { readonly ok: false; readonly error: string };\n\nexport const NOTHING_PIPED =\n \"--stdin expects a configuration on standard input and none arrived. Pipe one in, as 'cat proposal.json | agents-inc share --stdin'.\";\n\nexport const STDIN_IS_A_TERMINAL =\n \"--stdin expects a configuration on standard input, but standard input is a terminal. Pipe one in rather than typing it.\";\n\n/**\n * How much of a body to quote back when it is not JSON, so a refusal names what it read.\n *\n * A CHARACTER bound, because what it clips is arbitrary bytes a producer piped by mistake: any\n * cut is as good as any other, and the point is only that the caller recognises what they sent.\n */\nconst EXCERPT = 80;\n\n/**\n * How many problems to name before the rest are counted instead.\n *\n * The scope rule raises one issue per (skill, sub-agent) PAIR rather than one per payload, and\n * each renders at roughly 150 characters — the same sentence every time, with two names changed.\n * A repository scan is 20-40 skills across 3-6 sub-agents, so an unbounded list is 60-240 of them\n * and hundreds of lines: the refusal becomes the wall of text it is complaining about.\n *\n * Four, so the whole message lands near twice the 300-character `EXPLANATION_BUDGET` in\n * `publish-seed.ts`, which bounds the store's account of this same refusal arriving over the\n * wire. Twice, because that one quotes back a remote explanation of a payload already sent, where\n * these are the caller's own pairs and each name is somewhere they have to go and edit — and four\n * of them is enough to show the mistake is a pattern rather than a one-off.\n *\n * A COUNT rather than a `truncateText` over the joined list, which is the one place this file's\n * two bounds differ. A character cut lands mid-sentence, and mid-sentence here means a half-named\n * skill or sub-agent — worse than one left out and counted, because it reads as a name.\n */\nconst PROBLEMS_NAMED = 4;\n\n/**\n * Everything that can fail locally, failing before the caller spends a write.\n *\n * The store's free tier allows a thousand writes a day against a hundred times that in reads, so\n * a write is the scarce half — and one spent on a payload the decoder cannot read buys a dead\n * link. Each of the three refusals below is a different mistake and says which: nothing arrived,\n * what arrived is not JSON, or it is JSON the contract does not accept.\n *\n * `installableSeedPayloadSchema` rather than a shape written here, and that is the point of\n * publishing from the CLI at all. `SEED_VERSION` is a `z.literal`, so a producer that hardcodes\n * the wire format emits payloads the store refuses the day it moves; the schema travels with the\n * code that posts.\n *\n * THE *INSTALLABLE* SCHEMA, AND THE DIFFERENCE IS THE WHOLE BUG THIS CLOSED. The worker validates\n * a POST with `installableSeedPayloadSchema` and a GET with the base `seedPayloadSchema` — write\n * strict, read lenient, deliberately, so links already in the wild stay repairable in the editor.\n * This gate read the BASE schema, so the one rule the two differ by — a project-scoped skill\n * assigned to a sub-agent resting at global, which has nowhere to be written — was the single\n * payload that passed locally and failed at the edge. It arrived as a bare `HTTP 400`.\n *\n * That was not an exotic shape. `agents` is sparse and an absent entry rests on the shared default\n * of `global`, so \"assign a skill and say nothing else\" is already the unwritable pair;\n * `meta-config-stack-detect` emitted it on every run. Reading the same schema the store writes\n * with is what makes this file's promise — everything that can fail locally, failing before the\n * caller spends a write — true rather than nearly true.\n */\nexport function readPipedPayload(body: string): PipedPayload {\n if (body.trim() === \"\") return { ok: false, error: NOTHING_PIPED };\n\n const read = readJson(body);\n if (!read.parsed) return { ok: false, error: notJsonMessage(body, read.reason) };\n\n const result = installableSeedPayloadSchema.safeParse(read.value);\n if (!result.success) {\n return { ok: false, error: refusedByContract(formatZodErrors(result.error)) };\n }\n\n return { ok: true, payload: result.data };\n}\n\n/** Whether a body is JSON at all, separated from whether it is a payload. */\ntype JsonRead =\n | { readonly parsed: true; readonly value: unknown }\n | { readonly parsed: false; readonly reason: string };\n\n/**\n * `JSON.parse` behind a verdict, so the caller reads as three guards rather than as a `let`\n * assigned inside a `try`. The same shape `readYaml` takes in `lib/skills/skill-metadata.ts`,\n * for the same reason: the failure is one of several a reader has to tell apart, and a thrown\n * exception is the one form that cannot sit beside the others.\n */\nfunction readJson(body: string): JsonRead {\n try {\n return { parsed: true, value: JSON.parse(body) };\n } catch (error) {\n return { parsed: false, reason: getErrorMessage(error) };\n }\n}\n\n/**\n * Two quotes of the same untrusted body, and both have to be made inert.\n *\n * The excerpt is the obvious one. The REASON is not, and it is the one that got through: V8 writes\n * `Unexpected token 'h', \"here is...\" is not valid JSON`, quoting the offending input verbatim, so\n * a parser's own account of a failure carries whatever caused it. `truncateText` covers the\n * excerpt; nothing covered the reason, because nothing looked like it came from outside.\n */\nfunction notJsonMessage(body: string, reason: string): string {\n const opening = truncateText(body.trim(), EXCERPT);\n const said = stripTerminalControls(reason);\n return `What arrived on standard input is not JSON: ${said}. It began: ${opening}`;\n}\n\nfunction refusedByContract(problems: string[]): string {\n return `The configuration on standard input is not one this store accepts: ${namedProblems(problems)}`;\n}\n\n/**\n * The first few problems in full, and a count of however many were left out.\n *\n * The count is what keeps a clipped list honest. Without it a caller who fixed the pairs named\n * here and ran again would have no way to tell this from a complete list — so the second refusal\n * would read as a new fault rather than as the rest of the one they were already fixing.\n */\nfunction namedProblems(problems: string[]): string {\n const named = problems.slice(0, PROBLEMS_NAMED).join(\"; \");\n const elided = problems.length - PROBLEMS_NAMED;\n return elided > 0 ? `${named} (and ${elided} more)` : named;\n}\n","import type { Readable } from \"node:stream\";\n\n/**\n * Every chunk of a readable, as text.\n *\n * A stream rather than `readFileSync(\"/dev/stdin\")`: that path does not exist on every platform\n * this CLI runs on, and it reads a pipe the operating system may still be filling. Async\n * iteration is the one form that is correct on all of them.\n */\nexport async function readAllOf(stream: Readable): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of stream) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));\n }\n\n return Buffer.concat(chunks).toString(\"utf-8\");\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/cli/commands/share.ts","../../src/cli/lib/seed/read-piped-payload.ts","../../src/cli/utils/read-stream.ts"],"sourcesContent":["import { Flags } from \"@oclif/core\";\n\nimport { BaseCommand } from \"../base-command.js\";\nimport { isLocalSource } from \"../lib/configuration/config.js\";\nimport { EXIT_CODES } from \"../lib/exit-codes.js\";\nimport { unofferablePlacementsFound } from \"../lib/hosts/configured-placements.js\";\nimport { seedPayloadForInstallation } from \"../lib/seed/installation-payload.js\";\nimport { publishSeedConfig } from \"../lib/seed/publish-seed.js\";\nimport {\n readPipedPayload,\n STDIN_IS_A_TERMINAL,\n type PipedPayload,\n} from \"../lib/seed/read-piped-payload.js\";\nimport {\n providerFlag,\n providerNamedBy,\n refuseAnAmbiguousInstallation,\n} from \"../lib/installation/provider-flag.js\";\nimport { warn } from \"../utils/logger.js\";\nimport { readAllOf } from \"../utils/read-stream.js\";\nimport {\n sharedConfigDestinations,\n sharedIdNamesAFolder,\n sharedSkillsLeftBehind,\n shareRefusesEjectedSkills,\n} from \"../utils/messages.js\";\nimport { typedEntries } from \"../utils/typed-object.js\";\nimport type { SkillId } from \"../types/index.js\";\nimport type { SeedPayload } from \"@workspace/matrix/seed\";\n\n/** A payload to publish, and what is owed about it once it has an id. */\ntype ShareablePayload = { payload: SeedPayload; caveats: string[] };\n\n/**\n * What an id minted from this installation will not do everywhere, said after the id rather than\n * refused: it still installs everything else.\n *\n * A payload names one marketplace, by the ref `--marketplace` took. A folder is a ref only this\n * machine can follow, and a plugin from any other marketplace is skipped wherever the id is\n * installed.\n */\nfunction caveatsAbout(payload: SeedPayload, leftBehind: readonly SkillId[]): string[] {\n const { marketplace } = payload;\n return [\n ...(isAFolder(marketplace) ? [sharedIdNamesAFolder(marketplace)] : []),\n ...(leftBehind.length > 0 ? [sharedSkillsLeftBehind(leftBehind)] : []),\n ];\n}\n\n/** Whether a payload's marketplace ref is a folder on this machine rather than a repository. */\nfunction isAFolder(marketplace: string | undefined): marketplace is string {\n return marketplace !== undefined && isLocalSource(marketplace);\n}\n\n/** The skills a payload asks the receiver to eject — local copies, editor-added ones included. */\nfunction ejectedSkillsIn(payload: SeedPayload): string[] {\n return typedEntries(payload.skills)\n .filter(([, skill]) => skill.install === \"eject\")\n .map(([id]) => id);\n}\n\nexport default class Share extends BaseCommand {\n static summary = \"Share this installation as an id anyone can install\";\n\n static description =\n \"Turns the skills, sub-agents and per-agent curation installed here into a configuration the agentsinc.sh store holds, and prints the id it was given. Install it elsewhere with 'init --from <id>', or open it in the editor. The id is the configuration's own hash, so sharing an unchanged installation returns the id it already had.\";\n\n static flags = {\n stdin: Flags.boolean({\n description:\n \"Share a configuration piped in on standard input instead of the one installed here\",\n default: false,\n }),\n provider: providerFlag(),\n };\n\n static examples = [\n \"<%= config.bin %> <%= command.id %>\",\n \"cat proposal.json | <%= config.bin %> <%= command.id %> --stdin\",\n ];\n\n /**\n * Read, map, refuse, publish, then say what the id will not do everywhere.\n *\n * Everything that can fail locally fails before the POST. The store's free tier allows a\n * thousand writes a day and reads a hundred times that, so a write is the scarce half — and one\n * spent on a configuration that cannot be installed buys a dead link.\n *\n * The first three steps are `seedPayloadForInstallation`, shared with `edit --ui`: the two\n * commands mint the same id from the same directory and differ only in what they do with it.\n * The one refusal `edit --ui` does not share is of ejected skills — a share carries plugins\n * only, and the editor is where a user goes to turn ejected skills into plugins.\n */\n async run(): Promise<void> {\n const { flags } = await this.parse(Share);\n\n // Only the installation path reads a directory. A piped payload is the CALLER's configuration\n // and no folder on this machine decides anything about it, so neither a scope holding two\n // installations nor a placement this host cannot fill is this run's problem — asking anyway\n // would refuse a `--stdin` share for the state of a directory it never opens.\n if (!flags.stdin) {\n await refuseAnAmbiguousInstallation(\n process.cwd(),\n \"share\",\n providerNamedBy(flags.provider),\n (message) => this.error(message, { exit: EXIT_CODES.INVALID_ARGS }),\n );\n await this.ensureConfigReadable(process.cwd());\n await this.refuseUnofferablePlacements(process.cwd());\n }\n\n const { payload, caveats } = flags.stdin\n ? await this.payloadFromPipe()\n : await this.payloadFromInstallation();\n\n const published = await publishSeedConfig(payload);\n if (!published.ok) {\n this.error(published.error, { exit: EXIT_CODES.ERROR });\n }\n\n this.reportShared(published.id);\n // Through `warn()` from `utils/logger.ts` rather than `this.warn`: oclif hard-wraps at the\n // terminal width, and a caveat names a folder path that does not survive being broken.\n for (const caveat of caveats) warn(caveat);\n }\n\n /**\n * Refuses a configuration asking for a mode/scope cell its own host does not offer, before the\n * payload is built and long before the POST.\n *\n * **`share` is a read path, and the roster that names them missed it.** The list this refusal\n * was built against reads \"`compile`, `edit`, `update` and `doctor`\" and claims to be every\n * command that acts on a configuration it did not just write — while `share` sits beside them\n * reading the same file through `seedPayloadForInstallation`. What it publishes is described as\n * \"the skills, sub-agents and per-agent curation installed HERE\", and a row the host could not\n * place was never installed here at all, so the id would describe an installation that does not\n * exist. `refuseAnAmbiguousInstallation` above is this finding's sibling and was already asked;\n * the two are the pair `doctor`'s Placements row reports together.\n *\n * A refusal rather than a row, like the other three writers, and for a reason of its own: the\n * store's write is the scarce half, and one spent on an installation nothing on this machine\n * will act on buys a link to a misdescription.\n *\n * `uninstall` reads the same file and is deliberately NOT on this roster: a refusal there would\n * make an unofferable installation unremovable, which is a guard that has swallowed its domain.\n */\n private async refuseUnofferablePlacements(cwd: string): Promise<void> {\n const [finding] = await unofferablePlacementsFound(cwd);\n if (finding !== undefined) this.error(finding, { exit: EXIT_CODES.ERROR });\n }\n\n /** The installation in this directory: mapped, refused if it holds ejected skills, announced. */\n private async payloadFromInstallation(): Promise<ShareablePayload> {\n const prepared = await seedPayloadForInstallation(process.cwd());\n if (!prepared.ok) {\n this.error(prepared.error, { exit: EXIT_CODES.ERROR });\n }\n\n this.refuseEjectedSkills(prepared.payload);\n this.log(`Sharing ${prepared.skills} skill(s) across ${prepared.agents} sub-agent(s)...`);\n\n return {\n payload: prepared.payload,\n caveats: caveatsAbout(prepared.payload, prepared.leftBehind),\n };\n }\n\n /**\n * Refuses a share while anything it would send is an ejected (Local) skill.\n *\n * An ejected skill is a copy on this machine, which the user may have edited and an\n * editor-added skill always is, so a share carries plugins only — from a project, that covers\n * the ejected skills it inherits from the global installation as well as its own. A skill the\n * user wrote by hand is never in the payload, so it neither travels nor refuses.\n */\n private refuseEjectedSkills(payload: SeedPayload): void {\n const ejected = ejectedSkillsIn(payload);\n if (ejected.length > 0) {\n this.error(shareRefusesEjectedSkills(ejected), { exit: EXIT_CODES.ERROR });\n }\n }\n\n /**\n * A configuration the CALLER holds, which is why nothing here reads an installation.\n *\n * That is the whole distinction the flag draws, and it is not cosmetic: a bare `share` resolves\n * an installation the way every other command does — this project, then the global one — so\n * without this branch, sharing a piped payload from a directory with nothing in it would\n * publish whatever the machine happens to have installed globally.\n *\n * The producer this exists for is not this CLI. `meta-config-stack-detect` walks a repository\n * and emits a `SeedPayload` it is forbidden to write or apply, and an id is the only door into\n * the editor, which reads `?fromId=` and nothing else. Publishing from here rather than from\n * the producer keeps `SEED_VERSION`, the `AGENTS_INC_API_URL` override and the caller's\n * user-agent in the one place that owns them.\n *\n * It owes no caveats: those describe an installation, and none was read.\n */\n private async payloadFromPipe(): Promise<ShareablePayload> {\n if (process.stdin.isTTY) {\n this.error(STDIN_IS_A_TERMINAL, { exit: EXIT_CODES.ERROR });\n }\n\n const read: PipedPayload = readPipedPayload(await readAllOf(process.stdin));\n if (!read.ok) {\n this.error(read.error, { exit: EXIT_CODES.ERROR });\n }\n\n return { payload: read.payload, caveats: [] };\n }\n\n /**\n * Both destinations, because an id nobody can act on is not a share and there are exactly two\n * things that read one: this CLI, and the editor the configuration can be reopened in. The\n * lines themselves are `sharedConfigDestinations`, shared with `edit --ui` for the same reason\n * the mint above is.\n */\n private reportShared(id: string): void {\n this.logSuccess(`Shared as ${id}`);\n for (const destination of sharedConfigDestinations(id)) {\n this.log(destination);\n }\n }\n}\n","import { installableSeedPayloadSchema, type SeedPayload } from \"@workspace/matrix/seed\";\n\nimport { formatZodErrors } from \"../schema-validator.js\";\nimport { getErrorMessage } from \"../../utils/errors.js\";\nimport { stripTerminalControls, truncateText } from \"../../utils/string.js\";\n\n/** A payload read from a pipe, or the reason it could not be. */\nexport type PipedPayload =\n | { readonly ok: true; readonly payload: SeedPayload }\n | { readonly ok: false; readonly error: string };\n\nexport const NOTHING_PIPED =\n \"--stdin expects a configuration on standard input and none arrived. Pipe one in, as 'cat proposal.json | agents-inc share --stdin'.\";\n\nexport const STDIN_IS_A_TERMINAL =\n \"--stdin expects a configuration on standard input, but standard input is a terminal. Pipe one in rather than typing it.\";\n\n/**\n * How much of a body to quote back when it is not JSON, so a refusal names what it read.\n *\n * A CHARACTER bound, because what it clips is arbitrary bytes a producer piped by mistake: any\n * cut is as good as any other, and the point is only that the caller recognises what they sent.\n */\nconst EXCERPT = 80;\n\n/**\n * How many problems to name before the rest are counted instead.\n *\n * The scope rule raises one issue per (skill, sub-agent) PAIR rather than one per payload, and\n * each renders at roughly 150 characters — the same sentence every time, with two names changed.\n * A repository scan is 20-40 skills across 3-6 sub-agents, so an unbounded list is 60-240 of them\n * and hundreds of lines: the refusal becomes the wall of text it is complaining about.\n *\n * Four, so the whole message lands near twice the 300-character `EXPLANATION_BUDGET` in\n * `publish-seed.ts`, which bounds the store's account of this same refusal arriving over the\n * wire. Twice, because that one quotes back a remote explanation of a payload already sent, where\n * these are the caller's own pairs and each name is somewhere they have to go and edit — and four\n * of them is enough to show the mistake is a pattern rather than a one-off.\n *\n * A COUNT rather than a `truncateText` over the joined list, which is the one place this file's\n * two bounds differ. A character cut lands mid-sentence, and mid-sentence here means a half-named\n * skill or sub-agent — worse than one left out and counted, because it reads as a name.\n */\nconst PROBLEMS_NAMED = 4;\n\n/**\n * Everything that can fail locally, failing before the caller spends a write.\n *\n * The store's free tier allows a thousand writes a day against a hundred times that in reads, so\n * a write is the scarce half — and one spent on a payload the decoder cannot read buys a dead\n * link. Each of the three refusals below is a different mistake and says which: nothing arrived,\n * what arrived is not JSON, or it is JSON the contract does not accept.\n *\n * `installableSeedPayloadSchema` rather than a shape written here, and that is the point of\n * publishing from the CLI at all. `SEED_VERSION` is a `z.literal`, so a producer that hardcodes\n * the wire format emits payloads the store refuses the day it moves; the schema travels with the\n * code that posts.\n *\n * THE *INSTALLABLE* SCHEMA, AND THE DIFFERENCE IS THE WHOLE BUG THIS CLOSED. The worker validates\n * a POST with `installableSeedPayloadSchema` and a GET with the base `seedPayloadSchema` — write\n * strict, read lenient, deliberately, so links already in the wild stay repairable in the editor.\n * This gate read the BASE schema, so the one rule the two differ by — a project-scoped skill\n * assigned to a sub-agent resting at global, which has nowhere to be written — was the single\n * payload that passed locally and failed at the edge. It arrived as a bare `HTTP 400`.\n *\n * That was not an exotic shape. `agents` is sparse and an absent entry rests on the shared default\n * of `global`, so \"assign a skill and say nothing else\" is already the unwritable pair;\n * `meta-config-stack-detect` emitted it on every run. Reading the same schema the store writes\n * with is what makes this file's promise — everything that can fail locally, failing before the\n * caller spends a write — true rather than nearly true.\n */\nexport function readPipedPayload(body: string): PipedPayload {\n if (body.trim() === \"\") return { ok: false, error: NOTHING_PIPED };\n\n const read = readJson(body);\n if (!read.parsed) return { ok: false, error: notJsonMessage(body, read.reason) };\n\n const result = installableSeedPayloadSchema.safeParse(read.value);\n if (!result.success) {\n return { ok: false, error: refusedByContract(formatZodErrors(result.error)) };\n }\n\n return { ok: true, payload: result.data };\n}\n\n/** Whether a body is JSON at all, separated from whether it is a payload. */\ntype JsonRead =\n | { readonly parsed: true; readonly value: unknown }\n | { readonly parsed: false; readonly reason: string };\n\n/**\n * `JSON.parse` behind a verdict, so the caller reads as three guards rather than as a `let`\n * assigned inside a `try`. The same shape `readYaml` takes in `lib/skills/skill-metadata.ts`,\n * for the same reason: the failure is one of several a reader has to tell apart, and a thrown\n * exception is the one form that cannot sit beside the others.\n */\nfunction readJson(body: string): JsonRead {\n try {\n return { parsed: true, value: JSON.parse(body) };\n } catch (error) {\n return { parsed: false, reason: getErrorMessage(error) };\n }\n}\n\n/**\n * Two quotes of the same untrusted body, and both have to be made inert.\n *\n * The excerpt is the obvious one. The REASON is not, and it is the one that got through: V8 writes\n * `Unexpected token 'h', \"here is...\" is not valid JSON`, quoting the offending input verbatim, so\n * a parser's own account of a failure carries whatever caused it. `truncateText` covers the\n * excerpt; nothing covered the reason, because nothing looked like it came from outside.\n */\nfunction notJsonMessage(body: string, reason: string): string {\n const opening = truncateText(body.trim(), EXCERPT);\n const said = stripTerminalControls(reason);\n return `What arrived on standard input is not JSON: ${said}. It began: ${opening}`;\n}\n\nfunction refusedByContract(problems: string[]): string {\n return `The configuration on standard input is not one this store accepts: ${namedProblems(problems)}`;\n}\n\n/**\n * The first few problems in full, and a count of however many were left out.\n *\n * The count is what keeps a clipped list honest. Without it a caller who fixed the pairs named\n * here and ran again would have no way to tell this from a complete list — so the second refusal\n * would read as a new fault rather than as the rest of the one they were already fixing.\n */\nfunction namedProblems(problems: string[]): string {\n const named = problems.slice(0, PROBLEMS_NAMED).join(\"; \");\n const elided = problems.length - PROBLEMS_NAMED;\n return elided > 0 ? `${named} (and ${elided} more)` : named;\n}\n","import type { Readable } from \"node:stream\";\n\n/**\n * Every chunk of a readable, as text.\n *\n * A stream rather than `readFileSync(\"/dev/stdin\")`: that path does not exist on every platform\n * this CLI runs on, and it reads a pipe the operating system may still be filling. Async\n * iteration is the one form that is correct on all of them.\n */\nexport async function readAllOf(stream: Readable): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of stream) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));\n }\n\n return Buffer.concat(chunks).toString(\"utf-8\");\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  RemovalPlanConfirm
4
- } from "../chunk-W6GO2CYX.js";
4
+ } from "../chunk-USVBYQSQ.js";
5
5
  import {
6
6
  promptConfirm
7
7
  } from "../chunk-4AAOOAIN.js";
@@ -10,28 +10,27 @@ import {
10
10
  lazyGateDeps,
11
11
  mutateGlobal,
12
12
  propagateGlobalRemoval
13
- } from "../chunk-5JUQKGCP.js";
14
- import "../chunk-TW35AEB4.js";
13
+ } from "../chunk-7RDMTR64.js";
14
+ import "../chunk-F2BHT5C4.js";
15
15
  import {
16
16
  loadAgentDefs
17
- } from "../chunk-GE7NUPMI.js";
18
- import "../chunk-JIBCVXXL.js";
17
+ } from "../chunk-HED2DAXZ.js";
18
+ import "../chunk-3G7X2WKI.js";
19
19
  import {
20
20
  listAgentFilesOf,
21
21
  splitAgentsByProvenance
22
- } from "../chunk-3LZNY46H.js";
22
+ } from "../chunk-BMRUNMPV.js";
23
23
  import "../chunk-JFLVEFFP.js";
24
24
  import {
25
25
  providerFlag,
26
26
  providerNamedBy,
27
27
  refuseAnAmbiguousInstallation
28
- } from "../chunk-HR7TK2XW.js";
28
+ } from "../chunk-RIPSGNLU.js";
29
29
  import {
30
30
  BaseCommand,
31
31
  EXIT_CODES
32
- } from "../chunk-H4ZKGGTE.js";
33
- import "../chunk-VVRI35SK.js";
34
- import "../chunk-CMJBKQUH.js";
32
+ } from "../chunk-V3KTSP3Q.js";
33
+ import "../chunk-CLPUWOSA.js";
35
34
  import {
36
35
  ConfigLoadError,
37
36
  INFO_MESSAGES,
@@ -66,15 +65,15 @@ import {
66
65
  uninstallConfigUnreadable,
67
66
  uninstallNeedsTerminal,
68
67
  unmarkedAgentsKept
69
- } from "../chunk-C2UISKIM.js";
70
- import "../chunk-NU2K5G54.js";
68
+ } from "../chunk-MQ6CGZNJ.js";
69
+ import "../chunk-52HDHJ7I.js";
71
70
  import {
72
71
  CLAUDE_DIR,
73
72
  DEFAULT_BRANDING,
74
73
  EJECT_SOURCE,
75
74
  STANDARD_FILES
76
- } from "../chunk-AG3ZVM3B.js";
77
- import "../chunk-OI2H6M5I.js";
75
+ } from "../chunk-PJWGTNOX.js";
76
+ import "../chunk-NQW25DCF.js";
78
77
 
79
78
  // src/cli/commands/uninstall.tsx
80
79
  import "react";
@@ -208,9 +207,8 @@ var Uninstall = class _Uninstall extends BaseCommand {
208
207
  provider: providerFlag()
209
208
  };
210
209
  /**
211
- * Starts at any terminal size. The size gate exists for the wizard's layout, and this command
212
- * draws none — a plan and a one-line confirm fit any terminal — while it is the way out of every
213
- * state, so a window too small to hold a wizard must not hold up an uninstall.
210
+ * Starts at any terminal size: a plan and a one-line confirm fit any terminal, and this command
211
+ * is the way out of every state, so a small window must not hold up an uninstall.
214
212
  */
215
213
  async ensureTerminalSize() {
216
214
  }
@@ -319,9 +317,9 @@ var Uninstall = class _Uninstall extends BaseCommand {
319
317
  * Returns null when nothing is registered or loading fails; a load failure
320
318
  * warns and never aborts the uninstall.
321
319
  *
322
- * `skipExtraSources: true` is NOT a divergence from the wizard's full
323
- * multi-source load: extra-source loading only annotates each skill's
324
- * `availableSources`/`activeSource` for wizard UI tagging — it never adds
320
+ * `skipExtraSources: true` is NOT a divergence from a full multi-source
321
+ * load: extra-source loading only annotates each skill's
322
+ * `availableSources`/`activeSource` — it never adds
325
323
  * skills or categories to the matrix, and the config-types writer never reads
326
324
  * those annotations, so the regenerated project types are byte-identical
327
325
  * either way (pinned by the skipExtraSources parity test in
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/commands/uninstall.tsx"],"sourcesContent":["import React from \"react\";\nimport { partition } from \"remeda\";\nimport path from \"path\";\n\nimport { Flags } from \"@oclif/core\";\n\nimport { BaseCommand } from \"../base-command\";\nimport {\n RemovalPlanConfirm,\n type RemovalPlanSection,\n} from \"../components/common/removal-plan-confirm.js\";\nimport { promptConfirm } from \"../components/common/prompt-confirm.js\";\nimport { getErrorMessage } from \"../utils/errors\";\nimport {\n directoryExists,\n fileExists,\n listDirectories,\n remove,\n removeDirIfEmpty,\n} from \"../utils/fs\";\nimport { listAgentFilesOf, splitAgentsByProvenance } from \"../lib/agents\";\nimport { hostAt } from \"../lib/hosts/host-for.js\";\nimport type { PluginHost, PluginRemovalOutcome } from \"../lib/hosts/plugin-host.js\";\nimport {\n listPluginNames,\n getProjectPluginsDir,\n buildMarketplacePluginRef,\n parseMarketplacePluginRef,\n} from \"../lib/plugins/index\";\nimport { readForkedFromMetadata } from \"../lib/skills/index\";\nimport { isHomeDirectory, resolveInstallPaths } from \"../lib/installation/index\";\nimport { lazyGateDeps, mutateGlobal, propagateGlobalRemoval } from \"../lib/config-gate/index.js\";\nimport { loadSkillsMatrixFromSource } from \"../lib/loading\";\nimport { loadAgentDefs } from \"../lib/operations\";\nimport { ConfigLoadError, loadProjectConfigAt } from \"../lib/configuration/project-config\";\nimport type { Provider } from \"../consts\";\nimport { CLAUDE_DIR, DEFAULT_BRANDING, EJECT_SOURCE, STANDARD_FILES } from \"../consts\";\nimport {\n providerInUse,\n agentCodec,\n type AgentCodec,\n sourceFolderInUse,\n sourceRootOf,\n type SourceRoot,\n} from \"../lib/installation/install-layout.js\";\nimport {\n providerFlag,\n providerNamedBy,\n refuseAnAmbiguousInstallation,\n} from \"../lib/installation/provider-flag.js\";\nimport { EXIT_CODES } from \"../lib/exit-codes\";\nimport {\n SUCCESS_MESSAGES,\n INFO_MESSAGES,\n UNINSTALL_PLAN,\n compiledAgentsRemoval,\n localSkillsRemoval,\n notInstalledHere,\n unmarkedAgentsKept,\n registeredProjectsUpdated,\n registeredProjectUpdateSkipped,\n registeredProjectsUpdateFailed,\n uninstallConfigUnreadable,\n uninstallNeedsTerminal,\n} from \"../utils/messages\";\nimport type { AgentDefinition, AgentName, MergedSkillsMatrix, ProjectConfig } from \"../types/index\";\nimport type { SkillScope } from \"../types/config\";\n\n/**\n * One removal this run promises AND makes. Each entry carries what its remover needs\n * — paths and names, never display strings — so the preview and the removal are two\n * readings of one value rather than two derivations of the same target.\n */\ntype RemovalEntry =\n | { kind: \"plugins\"; pluginsDir: string; names: string[] }\n | { kind: \"skills\"; skillsDir: string }\n | { kind: \"agents\"; agentsDir: string; agentNames: string[]; codec: AgentCodec }\n | { kind: \"config\"; sourceDir: string; fileNames: string[] };\n\n/** The single entry of a kind, for the remover that consumes it. */\ntype PluginRemoval = Extract<RemovalEntry, { kind: \"plugins\" }>;\ntype SkillsRemoval = Extract<RemovalEntry, { kind: \"skills\" }>;\ntype AgentsRemoval = Extract<RemovalEntry, { kind: \"agents\" }>;\ntype ConfigManifestRemoval = Extract<RemovalEntry, { kind: \"config\" }>;\n\n/**\n * The uninstall removal plan: what this run removes, and what it deliberately leaves\n * behind. Built once per run — the confirm UI renders it, the executor consumes the\n * same value, so the plan cannot name a removal the run declines to make.\n */\ntype RemovalPlan = {\n entries: RemovalEntry[];\n /** Statements naming content that stays, and why — see {@link keptStatements}. */\n kept: string[];\n};\n\n/**\n * Pure builder for the uninstall removal plan — the one place that decides what this\n * run removes. Every removal downstream is driven by an entry from here, and nothing\n * downstream re-reads the target to decide whether to act.\n */\nfunction buildRemovalPlan(target: UninstallTarget): RemovalPlan {\n const agents = compiledAgentsEntry(target);\n\n return {\n entries: [\n ...pluginsEntry(target),\n ...localSkillsEntry(target),\n ...agents,\n ...configManifestEntries(target),\n ],\n kept: keptStatements(target, agents),\n };\n}\n\n/** The plan carries at least one removal — the only thing that makes this run an uninstall. */\nfunction hasAnythingToRemove(plan: RemovalPlan): boolean {\n return plan.entries.length > 0;\n}\n\nfunction pluginsEntry(target: UninstallTarget): PluginRemoval[] {\n if (!target.hasPlugins) return [];\n return [{ kind: \"plugins\", pluginsDir: target.pluginsDir, names: [...target.cliPluginNames] }];\n}\n\nfunction localSkillsEntry(target: UninstallTarget): SkillsRemoval[] {\n if (!target.hasLocalSkills) return [];\n return [{ kind: \"skills\", skillsDir: target.skillsDir }];\n}\n\nfunction compiledAgentsEntry(target: UninstallTarget): AgentsRemoval[] {\n if (!target.hasLocalAgents) return [];\n\n const agentNames = identifiableAgents(target);\n if (agentNames.length === 0) return [];\n\n return [{ kind: \"agents\", agentsDir: target.agentsDir, agentNames, codec: target.agentCodec }];\n}\n\n/**\n * The agent files this run can say are its own, and on whose authority.\n *\n * The configuration is the first: it names the agents this install compiled, and a marker-less\n * file it names is still this CLI's — every install predating the marker is in exactly that\n * state. The provenance marker the compiler stamps into every agent is the second, reached\n * when there is no configuration left to read: provably ours, whoever lost the config. With\n * neither, nothing here is identifiable and the plan carries no removal for it.\n */\nfunction identifiableAgents(target: UninstallTarget): string[] {\n if (target.configuredAgents.length > 0) return target.configuredAgents;\n return target.markedAgents;\n}\n\n/** The CLI config manifest (config.ts + config-types.ts) is always removed from the source folder. */\nfunction configManifestEntries(target: UninstallTarget): ConfigManifestRemoval[] {\n const { dir, manifestFiles } = target.sourceFolder;\n if (manifestFiles.length === 0) return [];\n return [{ kind: \"config\", sourceDir: dir, fileNames: manifestFiles }];\n}\n\n/**\n * The plan's kept half: the agent files carrying no provenance marker that the removal above\n * does not claim anyway. Derived FROM the removal rather than from a second reading of the\n * target, so the two halves of the plan cannot contradict each other — an agent named in both\n * lists would be a promise to remove a file the same plan calls kept.\n */\nfunction keptStatements(target: UninstallTarget, agentRemovals: AgentsRemoval[]): string[] {\n const removing = new Set(agentRemovals.flatMap((entry) => entry.agentNames));\n const kept = target.unmarkedAgents.filter((name) => !removing.has(name));\n if (kept.length === 0) return [];\n return [unmarkedAgentsKept(target.agentsDir, kept.length)];\n}\n\n/** Every entry of a kind — the config kind has one per source folder. */\nfunction planEntries<K extends RemovalEntry[\"kind\"]>(\n plan: RemovalPlan,\n kind: K,\n): Extract<RemovalEntry, { kind: K }>[] {\n return plan.entries.filter(\n (entry): entry is Extract<RemovalEntry, { kind: K }> => entry.kind === kind,\n );\n}\n\n/** The plan's entry of a kind, for the remover that consumes it. At most one exists. */\nfunction planEntry<K extends RemovalEntry[\"kind\"]>(\n plan: RemovalPlan,\n kind: K,\n): Extract<RemovalEntry, { kind: K }> | undefined {\n return plan.entries.find(\n (entry): entry is Extract<RemovalEntry, { kind: K }> => entry.kind === kind,\n );\n}\n\n/** The heading an entry prints under, and the lines it contributes beneath it. */\nfunction describeEntry(entry: RemovalEntry): RemovalPlanSection {\n switch (entry.kind) {\n case \"plugins\":\n return { label: UNINSTALL_PLAN.PLUGINS_HEADING, items: entry.names };\n case \"skills\":\n return {\n label: UNINSTALL_PLAN.CLI_MANAGED_FILES_HEADING,\n items: [localSkillsRemoval(entry.skillsDir)],\n };\n case \"agents\":\n return {\n label: UNINSTALL_PLAN.CLI_MANAGED_FILES_HEADING,\n items: [compiledAgentsRemoval(entry.agentsDir)],\n };\n case \"config\":\n return {\n label: UNINSTALL_PLAN.CONFIG_HEADING,\n items: entry.fileNames.map((fileName) => `${entry.sourceDir}/${fileName}`),\n };\n default: {\n const _exhaustive: never = entry;\n return _exhaustive;\n }\n }\n}\n\nconst PLAN_SECTION_ORDER = [\n UNINSTALL_PLAN.PLUGINS_HEADING,\n UNINSTALL_PLAN.CLI_MANAGED_FILES_HEADING,\n UNINSTALL_PLAN.CONFIG_HEADING,\n] as const;\n\n/**\n * The plan's display half: entries grouped under their heading, in printing order —\n * skills and compiled agents share one. A heading is a promise about the lines beneath\n * it, so a heading no entry contributed to is not printed at all. The single source of\n * the strings shared by printRemovalPlan (plain text) and the UninstallConfirm Ink\n * component; each renderer only adds its own indentation and styling.\n */\nfunction planSections(entries: readonly RemovalEntry[]): RemovalPlanSection[] {\n const described = entries.map(describeEntry);\n\n return PLAN_SECTION_ORDER.flatMap((label) =>\n sectionWithItems(label, itemsUnder(described, label)),\n );\n}\n\n/** Every line the described entries contributed under one heading, in plan order. */\nfunction itemsUnder(described: readonly RemovalPlanSection[], label: string): string[] {\n return described.filter((section) => section.label === label).flatMap((section) => section.items);\n}\n\nfunction sectionWithItems(label: string, items: string[]): RemovalPlanSection[] {\n if (items.length === 0) return [];\n return [{ label, items }];\n}\n\nconst UNINSTALL_CONFIRM_MESSAGE = \"Are you sure you want to uninstall?\";\n\nexport default class Uninstall extends BaseCommand {\n /**\n * The name this run prints itself under, resolved once in {@link run} because three separate\n * lines carry it: the heading, the sign-off, and the warning naming the tool a preserved\n * directory was not created by. The last of those sits three calls below the resolution, and\n * threading a display string through `executeUninstall` -> `removeLocalFiles` ->\n * `removePlannedSkills` would widen three signatures to deliver one word.\n *\n * A plain `string` holding the shipped default rather than an optional every reader would have\n * to answer for. `run` replaces it before the first line is printed, so what a user sees is\n * always what their configuration says.\n */\n private brandingName: string = DEFAULT_BRANDING.NAME;\n\n static summary = `Remove ${DEFAULT_BRANDING.NAME} from this project`;\n\n static description = `Uninstall ${DEFAULT_BRANDING.NAME} from this project. Removes CLI-managed skills (matched by marketplace), compiled agents, plugins, and the config manifest (config.ts + config-types.ts) this project keeps. User-created content is preserved.`;\n\n static examples = [\n \"<%= config.bin %> <%= command.id %>\",\n \"<%= config.bin %> <%= command.id %> --yes\",\n ];\n\n static flags = {\n yes: Flags.boolean({\n char: \"y\",\n description: \"Skip confirmation prompt\",\n default: false,\n }),\n provider: providerFlag(),\n };\n\n /**\n * Starts at any terminal size. The size gate exists for the wizard's layout, and this command\n * draws none — a plan and a one-line confirm fit any terminal — while it is the way out of every\n * state, so a window too small to hold a wizard must not hold up an uninstall.\n */\n protected async ensureTerminalSize(): Promise<void> {\n // Nothing to wait for — see above.\n }\n\n async run(): Promise<void> {\n const { flags } = await this.parse(Uninstall);\n const projectDir = process.cwd();\n\n // Before the branding read and before anything is detected: this run is destructive, and a\n // scope holding two installations has two answers to \"which one\". Removing the roster's first\n // is removing a user's other installation with nothing on screen to say so.\n await refuseAnAmbiguousInstallation(\n projectDir,\n \"uninstall\",\n providerNamedBy(flags.provider),\n (message) => this.error(message, { exit: EXIT_CODES.INVALID_ARGS }),\n );\n\n this.brandingName = await this.resolveBrandingName(projectDir);\n\n this.printHeader();\n\n const target = await detectUninstallTarget(projectDir, (failure) =>\n this.warn(uninstallConfigUnreadable(failure)),\n );\n const plan = buildRemovalPlan(target);\n if (!hasAnythingToRemove(plan)) {\n this.reportNothingToUninstall();\n return;\n }\n\n const confirmed = flags.yes ? this.printRemovalPlan(plan) : await this.confirmRemoval(plan);\n if (!confirmed) {\n this.log(\"\");\n this.log(\"Uninstall cancelled\");\n this.exit(EXIT_CODES.CANCELLED);\n }\n\n await this.executeUninstall(plan, target, projectDir);\n this.reportSuccess();\n }\n\n private printHeader(): void {\n this.log(\"\");\n this.log(`${this.brandingName} Uninstall`);\n this.log(\"\");\n }\n\n private reportNothingToUninstall(): void {\n this.warn(\"Nothing to uninstall.\");\n this.log(\"\");\n this.log(notInstalledHere(this.brandingName));\n this.log(\"\");\n this.log(INFO_MESSAGES.NO_CHANGES_MADE);\n }\n\n private printRemovalPlan(plan: RemovalPlan): true {\n this.log(UNINSTALL_PLAN.PREVIEW_HEADING);\n this.log(\"\");\n\n for (const section of planSections(plan.entries)) {\n this.log(` ${section.label}`);\n for (const item of section.items) {\n this.log(` ${item}`);\n }\n }\n\n for (const statement of plan.kept) {\n this.log(\"\");\n this.log(statement);\n }\n\n this.log(\"\");\n return true;\n }\n\n private async confirmRemoval(plan: RemovalPlan): Promise<boolean> {\n // A confirm nobody can answer must never become a yes, and mounting it here died on Ink's\n // raw-mode error — so a run with no terminal is refused before it, naming `--yes`.\n if (!process.stdin.isTTY) {\n this.error(uninstallNeedsTerminal(), { exit: EXIT_CODES.ERROR });\n }\n\n const outcome = await promptConfirm(({ onConfirm, onCancel }) => (\n <RemovalPlanConfirm\n heading={UNINSTALL_PLAN.PREVIEW_HEADING}\n sections={planSections(plan.entries)}\n statements={plan.kept}\n message={UNINSTALL_CONFIRM_MESSAGE}\n onConfirm={onConfirm}\n onCancel={onCancel}\n />\n ));\n return outcome === \"confirmed\";\n }\n\n private async executeUninstall(\n plan: RemovalPlan,\n target: UninstallTarget,\n projectDir: string,\n ): Promise<void> {\n const isGlobalUninstall = isHomeDirectory(projectDir);\n // Prepared BEFORE any removal: the projects[] registry and the source used\n // to regenerate each project's config-types.ts both live in the global\n // config this uninstall is about to delete.\n const propagation = isGlobalUninstall\n ? await this.prepareGlobalPropagation(target, projectDir)\n : null;\n\n await this.removePlannedPlugins(planEntry(plan, \"plugins\"), target.config, projectDir);\n\n try {\n await this.removeLocalFiles(plan, target);\n } catch (error) {\n this.log(\"Failed to remove local files\");\n this.error(getErrorMessage(error), {\n exit: EXIT_CODES.ERROR,\n });\n }\n\n if (isGlobalUninstall) {\n // The global manifest is gone — prune the inlined global-scoped entries the\n // registered projects still carry so they stop referencing removed content.\n if (propagation) {\n await this.updateRegisteredProjects(propagation);\n }\n return;\n }\n\n // Deregister this project from the global config's tracked projects so future\n // global edits stop propagating back into it. Best-effort by nature: a missing,\n // project-less, or corrupt global config (ConfigLoadError) must never fail the\n // uninstall — warn and move on.\n try {\n // Registration bookkeeping only: nothing inlines the `projects[]` list into a\n // project config, so this loads neither the matrix nor the agent definitions\n // and the uninstall stays offline.\n await mutateGlobal({ kind: \"deregister-project\", projectDir }, lazyGateDeps(projectDir));\n } catch (error) {\n this.warn(`Could not update the global project registry: ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Loads what updating registered projects needs (global config with its\n * projects[] registry, skills matrix, agent definitions) BEFORE the global\n * the config manifest is deleted — source resolution reads that config.\n * Returns null when nothing is registered or loading fails; a load failure\n * warns and never aborts the uninstall.\n *\n * `skipExtraSources: true` is NOT a divergence from the wizard's full\n * multi-source load: extra-source loading only annotates each skill's\n * `availableSources`/`activeSource` for wizard UI tagging — it never adds\n * skills or categories to the matrix, and the config-types writer never reads\n * those annotations, so the regenerated project types are byte-identical\n * either way (pinned by the skipExtraSources parity test in\n * local-installer.test.ts). Skipping avoids fetching every registered extra\n * source (network on a cold cache, plus unreachable-remote warnings) during\n * an uninstall that must never hang or noise-fail on remote state.\n *\n * `purpose: \"upkeep\"` because the prune only rewrites projects that are already installed: a\n * marketplace that has since lost its manifest must not leave every registered project naming\n * the global content this uninstall deletes.\n */\n private async prepareGlobalPropagation(\n target: UninstallTarget,\n projectDir: string,\n ): Promise<GlobalPropagationData | null> {\n if (target.config === null || target.registeredProjects.length === 0) return null;\n const globalConfig = target.config;\n\n try {\n const [sourceResult, agentDefs] = await Promise.all([\n loadSkillsMatrixFromSource({\n projectDir,\n purpose: \"upkeep\",\n skipExtraSources: true,\n matrixOnly: true,\n }),\n loadAgentDefs(),\n ]);\n return {\n globalConfig,\n matrix: sourceResult.matrix,\n agents: agentDefs.agents,\n provider: providerInUse(projectDir),\n };\n } catch (error) {\n this.warn(registeredProjectsUpdateFailed(getErrorMessage(error)));\n return null;\n }\n }\n\n /**\n * Prunes the inlined global-scoped config entries from every registered\n * project and regenerates its config-types.ts. Runs AFTER the global manifest\n * removal so the regenerated project types fall back to the standalone form\n * instead of importing from the deleted global config-types.ts. Best-effort:\n * unreachable projects are warned and skipped, and no failure here may abort\n * the uninstall.\n */\n private async updateRegisteredProjects(propagation: GlobalPropagationData): Promise<void> {\n try {\n const report = await propagateGlobalRemoval(\n propagation.globalConfig,\n { matrix: propagation.matrix, agents: propagation.agents },\n propagation.provider,\n );\n const { updated, unreadable, failed } = report.propagated;\n this.reportProjectsOutOfReach(report.propagated);\n // Uninstall's own sentence, only where it is true: the config is there and was not pruned.\n for (const skippedPath of [...unreadable, ...failed]) {\n this.warn(registeredProjectUpdateSkipped(skippedPath));\n }\n if (updated.length === 0) return;\n\n this.logSuccess(registeredProjectsUpdated(updated.length));\n\n // The pruned projects' compiled agents were built from the global rows this\n // uninstall just removed, so the prune owes them a recompile too.\n this.reportPropagatedRecompile(report);\n } catch (error) {\n this.warn(registeredProjectsUpdateFailed(getErrorMessage(error)));\n }\n }\n\n private reportSuccess(): void {\n this.log(\"\");\n this.log(`${this.brandingName} has been uninstalled.`);\n this.log(\"\");\n this.logSuccess(SUCCESS_MESSAGES.UNINSTALL_COMPLETE);\n this.log(\"\");\n }\n\n /**\n * Each step acts only when the plan carries its entry, so what the user was shown and\n * what this run removes are the same list read twice.\n */\n private async removeLocalFiles(plan: RemovalPlan, target: UninstallTarget): Promise<void> {\n await this.removePlannedSkills(planEntry(plan, \"skills\"));\n await this.removePlannedAgents(planEntry(plan, \"agents\"));\n\n // The same statements the preview showed, from the same plan: what the user approved and\n // what this run reports are one value read twice, never two derivations that agree today.\n for (const statement of plan.kept) {\n this.log(statement);\n }\n\n const cleanup = await cleanupEmptyDirs(target, planEntries(plan, \"config\"));\n\n this.reportSourceFolderCleanup(cleanup.sourceFolder);\n\n this.reportSourceRootCleanup(target.sourceRoot, cleanup);\n\n if (cleanup.claudeDirRemoved) {\n this.logSuccess(`Removed ${CLAUDE_DIR}/`);\n } else if (cleanup.claudeDirKept) {\n this.log(`Kept ${CLAUDE_DIR}/ (contains user content)`);\n }\n }\n\n /** What became of one source folder: gone, emptied of the CLI's files, or kept for the user's. */\n private reportSourceFolderCleanup(folder: SourceFolderCleanup): void {\n if (folder.dirRemoved) {\n this.logSuccess(`Removed ${folder.relName}/`);\n } else if (folder.manifestRemoved) {\n this.logSuccess(`Removed CLI config from ${folder.relName}/`);\n } else if (folder.kept) {\n this.log(`Kept ${folder.relName}/ (contains user content)`);\n }\n }\n\n /** What became of the `.agents-inc/` parent, said out loud in both directions. */\n private reportSourceRootCleanup(sourceRoot: SourceRoot, cleanup: CleanupResult): void {\n if (cleanup.sourceRootRemoved) {\n this.logSuccess(`Removed ${sourceRoot.relName}/`);\n return;\n }\n if (cleanup.sourceRootKept) {\n this.log(`Kept ${sourceRoot.relName}/ (contains user content)`);\n }\n }\n\n private async removePlannedPlugins(\n entry: PluginRemoval | undefined,\n config: ProjectConfig | null,\n projectDir: string,\n ): Promise<void> {\n if (!entry) return;\n\n this.log(\"Uninstalling plugins...\");\n\n try {\n const pluginResult = await uninstallPlugins(entry, config, projectDir, (name) =>\n this.log(` Uninstalled plugin '${name}'`),\n );\n\n // Silent on zero rather than \"Uninstalled 0 plugins\": the line's job is to account for\n // removals, and a run that observed none has nothing to account for — the plan preview has\n // already said what it expected to find. Saying \"0\" beside a plan that named one reads as a\n // failure, which it is not: a plugin a user removed themselves is the ordinary case.\n if (pluginResult.totalUninstalled > 0) {\n this.logSuccess(\n `Uninstalled ${pluginResult.totalUninstalled} ${pluginResult.totalUninstalled === 1 ? \"plugin\" : \"plugins\"}`,\n );\n }\n } catch (error) {\n this.log(\"Plugin uninstall failed\");\n this.error(getErrorMessage(error), {\n exit: EXIT_CODES.ERROR,\n });\n }\n }\n\n private async removePlannedSkills(entry: SkillsRemoval | undefined): Promise<void> {\n if (!entry) return;\n\n const result = await removeMatchingSkills(\n entry,\n (dirName) => this.log(` Uninstalled skill '${dirName}'`),\n (dirName) => this.warn(`Skipping '${dirName}': not created by ${this.brandingName} CLI`),\n );\n if (result.removedCount === 0) return;\n\n this.logSuccess(\n `Removed ${result.removedCount} CLI-installed ${result.removedCount === 1 ? \"skill\" : \"skills\"}`,\n );\n }\n\n private async removePlannedAgents(entry: AgentsRemoval | undefined): Promise<void> {\n if (!entry) return;\n\n const result = await removeMatchingAgents(entry, (agentName) =>\n this.log(` Uninstalled agent '${agentName}'`),\n );\n if (result.removedCount === 0) return;\n\n this.logSuccess(\n `Removed ${result.removedCount} compiled ${result.removedCount === 1 ? \"agent\" : \"agents\"}`,\n );\n }\n}\n\n/** @internal Exported for testing */\nexport type UninstallTarget = {\n hasPlugins: boolean;\n pluginNames: string[];\n /** Plugin names filtered to only those installed by this CLI (matched against config skills) */\n cliPluginNames: string[];\n hasLocalSkills: boolean;\n hasLocalAgents: boolean;\n hasClaudeDir: boolean;\n pluginsDir: string;\n skillsDir: string;\n agentsDir: string;\n /**\n * How this installation's host names and marks a compiled agent — `*.md` on Claude, `*.toml` on\n * Codex. Everything the plan reads or removes in `agentsDir` goes through it (CLI-896).\n */\n agentCodec: AgentCodec;\n claudeDir: string;\n /** This scope's source folder: what this run reads and removes in it. */\n sourceFolder: ScopeSourceFolder;\n /** The `.agents-inc/` parent the source folder sits under. */\n sourceRoot: SourceRoot;\n /** The source folder's config, when it is readable. */\n config: ProjectConfig | null;\n /** Every project the config registers — only a global config registers any. */\n registeredProjects: string[];\n /** Agent names the config names (e.g., [\"web-developer\"]) */\n configuredAgents: AgentName[];\n /** On-disk agent basenames carrying this CLI's provenance marker. */\n markedAgents: string[];\n /** On-disk agent basenames carrying no marker — the user's, unless the config claims them. */\n unmarkedAgents: string[];\n};\n\n/** The source folder of the scope being uninstalled, and what this run reads and removes in it. */\ntype ScopeSourceFolder = {\n dir: string;\n /** The folder as a user writes it: `.agents-inc/claude`. */\n relName: string;\n /** The manifest files on disk in it — `config.ts`, `config-types.ts` — which this run removes. */\n manifestFiles: string[];\n /** Its config, or `null` when the file is absent or unreadable — see {@link loadUninstallConfig}. */\n config: ProjectConfig | null;\n};\n\n/**\n * Everything a global uninstall needs to update the registered projects after\n * the global manifest is removed — captured before removal because it is\n * sourced from the config being deleted.\n */\ntype GlobalPropagationData = {\n globalConfig: ProjectConfig;\n matrix: MergedSkillsMatrix;\n agents: Partial<Record<AgentName, AgentDefinition>>;\n /**\n * Which installation is being removed.\n *\n * Captured here for the same reason everything else on this type is: by the time the prune\n * runs, the folder that says which provider this was has been deleted, so reading it then\n * would answer about whatever is left rather than about what was removed.\n */\n provider: Provider;\n};\n\ntype SkillRemovalResult = {\n removedCount: number;\n skippedCount: number;\n removedNames: string[];\n skippedNames: string[];\n /** Whether the skills directory was cleaned up (empty after removal) */\n dirCleaned: boolean;\n};\n\ntype AgentRemovalResult = {\n removedCount: number;\n removedNames: string[];\n /** Whether the agents directory was cleaned up (empty after removal) */\n dirCleaned: boolean;\n};\n\ntype UninstallPluginsResult = {\n uninstalledNames: string[];\n totalUninstalled: number;\n};\n\n/** What a cleanup pass did with the source folder. */\ntype SourceFolderCleanup = {\n relName: string;\n /** Whether config.ts/config-types.ts were removed from it */\n manifestRemoved: boolean;\n /** Whether the emptied folder itself was removed */\n dirRemoved: boolean;\n /** Whether it is still there because something this CLI never wrote lives in it */\n kept: boolean;\n};\n\ntype CleanupResult = {\n claudeDirRemoved: boolean;\n sourceFolder: SourceFolderCleanup;\n /** Whether the emptied `.agents-inc/` parent went with the last provider folder under it */\n sourceRootRemoved: boolean;\n /** Whether `.agents-inc/` still exists because something this CLI never wrote lives in it */\n sourceRootKept: boolean;\n /** Whether .claude/ still exists with user content after cleanup */\n claudeDirKept: boolean;\n};\n\nfunction collectConfiguredAgents(config: Partial<ProjectConfig> | null): AgentName[] {\n if (!config?.agents) return [];\n return config.agents.map((a) => a.name);\n}\n\n/** @internal Exported for testing */\nexport function getCliInstalledPluginKeys(config: Partial<ProjectConfig> | null): Set<string> {\n if (!config?.skills) return new Set();\n const { marketplaceName } = config;\n return new Set(\n config.skills.flatMap((skill) => [\n // Primary key: skill.id@skill.origin\n buildMarketplacePluginRef(skill.id, skill.origin),\n // Marketplace variant for plugins installed via marketplace where\n // skill.origin may differ (e.g., \"eject\" vs the marketplace name)\n ...(marketplaceName && skill.origin !== marketplaceName && skill.origin !== EJECT_SOURCE\n ? [buildMarketplacePluginRef(skill.id, marketplaceName)]\n : []),\n ]),\n );\n}\n\n/**\n * Loads the config that drives the removal plan. A config file that exists but\n * cannot be parsed (`ConfigLoadError`) is handed to `onLoadFailed` and\n * then treated exactly like a missing one — an unreadable config is precisely\n * when a user needs to uninstall, so it must never fail the run. Same posture as\n * the `deregister-project` mutation's call site above. Only the plan degrades: the plugins and\n * compiled agents the config named can no longer be identified, while file\n * removal proceeds. Any other failure is a real fault and still propagates.\n */\nasync function loadUninstallConfig(\n configPath: string,\n projectDir: string,\n provider: Provider,\n onLoadFailed: (failure: ConfigLoadError) => void,\n): Promise<ProjectConfig | null> {\n try {\n const result = await loadProjectConfigAt(configPath, projectDir, provider);\n return result?.config ?? null;\n } catch (error) {\n if (!(error instanceof ConfigLoadError)) throw error;\n onLoadFailed(error);\n return null;\n }\n}\n\n/** The folder's manifest files on disk, and its config read under uninstall's degrade posture. */\nasync function readSourceFolder(\n folder: Pick<ScopeSourceFolder, \"dir\" | \"relName\">,\n projectDir: string,\n provider: Provider,\n onConfigLoadFailed: (failure: ConfigLoadError) => void,\n): Promise<ScopeSourceFolder> {\n const [manifestFiles, config] = await Promise.all([\n manifestFilesIn(folder.dir),\n loadUninstallConfig(\n path.join(folder.dir, STANDARD_FILES.CONFIG_TS),\n projectDir,\n provider,\n onConfigLoadFailed,\n ),\n ]);\n return { ...folder, manifestFiles, config };\n}\n\n/** The files of the CLI config manifest — `config.ts`, `config-types.ts` — that are in `dir`. */\nasync function manifestFilesIn(dir: string): Promise<string[]> {\n const manifest = [STANDARD_FILES.CONFIG_TS, STANDARD_FILES.CONFIG_TYPES_TS];\n const present = await Promise.all(\n manifest.map((fileName) => fileExists(path.join(dir, fileName))),\n );\n return manifest.filter((_, index) => present[index]);\n}\n\n/** A config with its excluded rows dropped — the entries an uninstall acts on. */\nfunction activeConfigOf(config: ProjectConfig): ProjectConfig {\n return {\n ...config,\n skills: config.skills.filter((s) => !s.excluded),\n agents: config.agents.filter((a) => !a.excluded),\n };\n}\n\n/**\n * Detects what's installed in a project directory for uninstallation.\n *\n * Checks for plugins, local skills, agents, config directories, and\n * resolves which plugins were installed by this CLI.\n */\nasync function detectUninstallTarget(\n projectDir: string,\n onConfigLoadFailed: (failure: ConfigLoadError) => void,\n): Promise<UninstallTarget> {\n const pluginsDir = getProjectPluginsDir(projectDir);\n const { skillsDir, agentsDir } = resolveInstallPaths(projectDir);\n const claudeDir = path.join(projectDir, CLAUDE_DIR);\n const provider = providerInUse(projectDir);\n\n const [hasLocalSkills, hasLocalAgents, hasClaudeDir, sourceFolder, agentProvenance] =\n await Promise.all([\n directoryExists(skillsDir),\n directoryExists(agentsDir),\n directoryExists(claudeDir),\n readSourceFolder(\n sourceFolderInUse(projectDir, provider),\n projectDir,\n provider,\n onConfigLoadFailed,\n ),\n splitAgentsByProvenance(agentsDir, agentCodec(provider)),\n ]);\n\n const { config } = sourceFolder;\n\n let pluginNames: string[] = [];\n try {\n pluginNames = await listPluginNames(projectDir);\n } catch {\n // Best-effort: plugin detection may fail\n }\n\n const activeConfig = config === null ? null : activeConfigOf(config);\n const configuredAgents = collectConfiguredAgents(activeConfig);\n const cliInstalledKeys = getCliInstalledPluginKeys(activeConfig);\n const cliPluginNames = thisRunOwnsAnyPlugin(projectDir)\n ? pluginNames.filter((name) => cliInstalledKeys.has(name))\n : [];\n\n return {\n hasPlugins: cliPluginNames.length > 0,\n pluginNames,\n cliPluginNames,\n hasLocalSkills,\n hasLocalAgents,\n hasClaudeDir,\n pluginsDir,\n skillsDir,\n agentsDir,\n claudeDir,\n sourceFolder,\n sourceRoot: sourceRootOf(projectDir),\n config,\n registeredProjects: config?.projects ?? [],\n configuredAgents,\n agentCodec: agentCodec(provider),\n markedAgents: agentProvenance.marked,\n unmarkedAgents: agentProvenance.unmarked,\n };\n}\n\n/**\n * Whether this run has any plugin of its own to remove.\n *\n * **A PROJECT uninstall on a host that installs no project-scoped plugin has none, and asking\n * anyway is the one operation in this step that can destroy work nobody asked about.** Codex\n * installs plugins for a MACHINE: no subcommand takes a scope, `plugin add` run inside a project\n * writes the switch to the global config and silently un-scopes it, and `plugin list` answers the\n * same set from every directory. So every plugin a Codex project can see belongs to every other\n * project on that machine, and a project uninstall that swept them would take them all away.\n *\n * Read off {@link PluginHost.installsProjectScopedPlugins} rather than written as a provider\n * check, so a host added later is covered by the flag it already has to set. Claude's answer is\n * `true`, so its sweep is unchanged at both scopes — which is deliberate: whether a Claude project\n * uninstall should drop a user-scoped registration is a separate question about a host that CAN\n * hold both, and nothing here is the place to settle it.\n *\n * A GLOBAL uninstall owns the machine's plugins whatever the host, which is the case the other\n * half of this pair rests on.\n */\nfunction thisRunOwnsAnyPlugin(projectDir: string): boolean {\n if (isHomeDirectory(projectDir)) return true;\n return hostAt(projectDir).installsProjectScopedPlugins;\n}\n\nfunction shouldRemoveSkill(forkedFrom: { source?: string } | null): boolean {\n return forkedFrom !== null;\n}\n\nasync function removeMatchingSkills(\n entry: SkillsRemoval,\n onRemoved?: (dirName: string) => void,\n onSkipped?: (dirName: string) => void,\n): Promise<SkillRemovalResult> {\n const classified = await classifySkillDirs(entry.skillsDir);\n const removedNames = await removeClassifiedSkills(\n classified.toRemove,\n entry.skillsDir,\n onRemoved,\n );\n classified.toSkip.forEach((name) => onSkipped?.(name));\n const dirCleaned = await cleanupSkillsDir(entry.skillsDir, classified.toSkip.length === 0);\n\n return {\n removedCount: removedNames.length,\n skippedCount: classified.toSkip.length,\n removedNames,\n skippedNames: classified.toSkip,\n dirCleaned,\n };\n}\n\nasync function classifySkillDirs(\n skillsDir: string,\n): Promise<{ toRemove: string[]; toSkip: string[] }> {\n const dirNames = await listDirectories(skillsDir);\n const entries = await Promise.all(\n dirNames.map(async (name) => ({\n name,\n forkedFrom: await readForkedFromMetadata(path.join(skillsDir, name)),\n })),\n );\n const [removable, skippable] = partition(entries, (entry) => shouldRemoveSkill(entry.forkedFrom));\n return { toRemove: removable.map((e) => e.name), toSkip: skippable.map((e) => e.name) };\n}\n\nasync function removeClassifiedSkills(\n names: string[],\n skillsDir: string,\n onRemoved?: (name: string) => void,\n): Promise<string[]> {\n for (const name of names) {\n await remove(path.join(skillsDir, name));\n onRemoved?.(name);\n }\n return names;\n}\n\nasync function cleanupSkillsDir(dir: string, allRemoved: boolean): Promise<boolean> {\n if (!allRemoved) return false;\n return removeDirIfEmpty(dir);\n}\n\n/**\n * Removes the compiled agent files the plan's agents entry names — `.md` on Claude, `.toml` on\n * Codex, as the entry's codec says.\n *\n * A file is removed only when its basename is one the entry names; every other agent file\n * is preserved. Cleans up the agents directory if empty after removal. Which names those are\n * — the configured roster, or the files carrying the provenance marker — is the plan's\n * decision, not this function's, and so is whether to act at all: no entry, no call.\n *\n * @param onRemoved - Called for each removed agent name (for logging)\n */\nasync function removeMatchingAgents(\n entry: AgentsRemoval,\n onRemoved?: (agentName: string) => void,\n): Promise<AgentRemovalResult> {\n const agentFiles = await listAgentFiles(entry.agentsDir, entry.codec);\n const removedNames = agentFiles\n .map((agentFile) => path.basename(agentFile, entry.codec.extension))\n .filter((agentName) => entry.agentNames.includes(agentName));\n\n for (const agentName of removedNames) {\n await remove(path.join(entry.agentsDir, `${agentName}${entry.codec.extension}`));\n onRemoved?.(agentName);\n }\n\n const dirCleaned = await removeDirIfEmpty(entry.agentsDir);\n\n return {\n removedCount: removedNames.length,\n removedNames,\n dirCleaned,\n };\n}\n\n/**\n * Where a plugin is looked for FIRST when the config has no entry naming its skill.\n *\n * The scope translation swallowed this until C3 — an absent scope and a project one mapped to\n * the same Claude scope word — so the order is unchanged and only the silence is gone.\n */\nconst SCOPE_OF_AN_UNRECORDED_SKILL: SkillScope = \"project\";\n\n/**\n * Every scope this host installs plugins at, the config's own answer first.\n *\n * A plugin's registered scope is genuinely ambiguous here — a skill re-scoped after install is\n * filed under the scope it was installed at, not the one the config now names — so a removal\n * asks at each scope rather than trusting one. The best-effort sweep this replaces did it with\n * two hard-coded Claude scope words and no way to answer for another host; reading the roster off\n * {@link PluginHost.offeredPlacements} is the same sweep with the host deciding its width, which\n * for a host that installs plugins globally only is one scope rather than two.\n */\nfunction pluginScopesToSweep(host: PluginHost, primary: SkillScope): SkillScope[] {\n const offered = host.offeredPlacements\n .filter((placement) => placement.mode === \"plugin\")\n .map((placement) => placement.scope);\n\n if (!offered.includes(primary)) return offered;\n return [primary, ...offered.filter((scope) => scope !== primary)];\n}\n\n/**\n * Drops a plugin's registration wherever the host filed it, and says whether one was there.\n *\n * Best-effort on the FAILURE side and unchanged by C3: the directory is deleted either way, so a\n * registration that cannot be dropped is untidy rather than wrong, and a thrown error here would\n * abort an uninstall that has already removed files. What changed is that it no longer reports\n * NOTHING — {@link PluginHost.uninstallPlugin} answers `removed` or `absent`, and that answer is\n * the only thing on this path that knows the difference.\n *\n * `removed` if ANY swept scope had one, because the sweep exists precisely for a plugin filed\n * under a scope the config no longer names: one registration dropped at either scope is one\n * removal, not two, and not none.\n */\nasync function removePluginWhereverItIsFiled(\n host: PluginHost,\n pluginRef: string,\n primaryScope: SkillScope,\n projectDir: string,\n): Promise<PluginRemovalOutcome> {\n let outcome: PluginRemovalOutcome = \"absent\";\n\n for (const scope of pluginScopesToSweep(host, primaryScope)) {\n try {\n const swept = await host.uninstallPlugin(pluginRef, scope, projectDir);\n if (swept === \"removed\") outcome = \"removed\";\n } catch {\n // Best-effort: the plugin may not be registered at this scope. A throw is neither outcome\n // — the host reserves it for a failure that is not \"there was nothing there\" — so it leaves\n // `outcome` exactly as the other scopes found it rather than claiming an absence.\n }\n }\n\n return outcome;\n}\n\n/**\n * Uninstalls the plugins the plan's plugins entry names, by removing them from the\n * host and deleting their local directories. The entry decides WHICH plugins go;\n * `config` only answers HOW to ask the host — the scope its registry filed each\n * one under, per-skill where the config says, project-level otherwise.\n *\n * @param onUninstalled - Called for each successfully uninstalled plugin name (for logging)\n * @internal Exported for testing\n */\nexport async function uninstallPlugins(\n entry: PluginRemoval,\n config: ProjectConfig | null,\n projectDir: string,\n onUninstalled?: (pluginName: string) => void,\n): Promise<UninstallPluginsResult> {\n const host = hostAt(projectDir);\n const hostAvailable = await host.isAvailable();\n const observed: string[] = [];\n\n for (const pluginName of entry.names) {\n const removal = hostAvailable\n ? await removePluginWhereverItIsFiled(\n host,\n pluginName,\n scopeThePluginIsFiledUnder(config, pluginName),\n projectDir,\n )\n : nothingWasObserved();\n\n await remove(path.join(entry.pluginsDir, pluginName));\n\n if (removal === \"removed\") {\n observed.push(pluginName);\n onUninstalled?.(pluginName);\n }\n }\n\n // What was OBSERVED, never what was asked for — D11(b), and the one Claude-visible change this\n // step signs off. On Codex `plugin remove` exits 0 and prints the same document for a plugin\n // that was never installed, one that really went and one removed twice, so a count built from\n // the plan is a count of intentions: it says \"Uninstalled 1 plugin\" for a plugin this command\n // did not touch. The listing the host takes BEFORE its removal is the only thing that knows.\n return {\n uninstalledNames: observed,\n totalUninstalled: observed.length,\n };\n}\n\n/**\n * The scope the host's registry most likely filed this plugin under, from the config's own row.\n *\n * The sweep beside it asks at every scope the host installs plugins at, so this is a PREFERENCE\n * rather than an answer: a skill re-scoped after install is filed under the scope it was installed\n * at, not the one the config now names.\n */\nfunction scopeThePluginIsFiledUnder(config: ProjectConfig | null, pluginName: string): SkillScope {\n const skillId = parseMarketplacePluginRef(pluginName);\n return (\n config?.skills.find((skill) => skill.id === skillId)?.scope ?? SCOPE_OF_AN_UNRECORDED_SKILL\n );\n}\n\n/**\n * What a run with no host binary observed, which is nothing — the OTHER half of D11(b).\n *\n * This command used to count every plugin it MEANT to remove, so a machine where the host is not\n * installed reported a clean sweep of registrations nothing had touched. An unreachable host is\n * not a failure — the directory is still deleted and the uninstall still completes — so it is an\n * outcome rather than a throw, and the outcome is that nothing was seen to go.\n */\nfunction nothingWasObserved(): PluginRemovalOutcome {\n return \"absent\";\n}\n\n/**\n * Removes exactly the manifest files the plan's config entry names from the source folder,\n * then the source folder itself when it has nothing else left. User-owned content in it\n * (e.g. ejected templates) keeps the directory alive. No entry\n * means the plan promised no manifest removal, so none is made.\n */\nasync function removeConfigManifest(\n entry: ConfigManifestRemoval | undefined,\n): Promise<{ manifestRemoved: boolean; dirRemoved: boolean }> {\n if (!entry) return { manifestRemoved: false, dirRemoved: false };\n\n await Promise.all(\n entry.fileNames.map((fileName) => remove(path.join(entry.sourceDir, fileName))),\n );\n\n return { manifestRemoved: true, dirRemoved: await removeDirIfEmpty(entry.sourceDir) };\n}\n\n/**\n * Removes the CLI config manifest the plan named, then cleans up the emptied .claude/\n * directory and the emptied source folder.\n *\n * The directories themselves are not plan entries — they are the user's, and are removed\n * only when nothing of theirs is left in them once the CLI-managed contents are gone.\n */\nasync function cleanupEmptyDirs(\n target: Pick<UninstallTarget, \"hasClaudeDir\" | \"claudeDir\" | \"sourceRoot\" | \"sourceFolder\">,\n manifestEntries: readonly ConfigManifestRemoval[],\n): Promise<CleanupResult> {\n const sourceFolder = await cleanupSourceFolder(\n target.sourceFolder,\n manifestEntries.find((entry) => entry.sourceDir === target.sourceFolder.dir),\n );\n const sourceRoot = await cleanupSourceRoot(target.sourceRoot, sourceFolder.dirRemoved);\n\n const claudeDirRemoved = target.hasClaudeDir && (await removeDirIfEmpty(target.claudeDir));\n // Nothing else removes .claude itself, so \"kept\" is exactly \"present but not removed\".\n const claudeDirKept =\n !claudeDirRemoved && target.hasClaudeDir && (await directoryExists(target.claudeDir));\n\n return {\n claudeDirRemoved,\n sourceFolder,\n sourceRootRemoved: sourceRoot.removed,\n sourceRootKept: sourceRoot.kept,\n claudeDirKept,\n };\n}\n\n/**\n * The source folder, once the plan's manifest files are out of it: removed when nothing is left,\n * kept when the user's own content is. A folder the plan named no file in is still removed when it\n * is empty — it is the product's name on a folder holding nothing.\n */\nasync function cleanupSourceFolder(\n folder: ScopeSourceFolder,\n entry: ConfigManifestRemoval | undefined,\n): Promise<SourceFolderCleanup> {\n const manifest = await removeConfigManifest(entry);\n const dirRemoved = manifest.dirRemoved || (await removeDirIfEmpty(folder.dir));\n const kept = !dirRemoved && (await directoryExists(folder.dir));\n return {\n relName: folder.relName,\n manifestRemoved: manifest.manifestRemoved,\n dirRemoved,\n kept,\n };\n}\n\n/** What a cleanup pass did with the `.agents-inc/` parent: took it away, or deliberately left it. */\ntype SourceRootCleanup = { removed: boolean; kept: boolean };\n\n/**\n * The provider folder's `.agents-inc/` parent, once the folder under it has gone.\n *\n * Cleanup above is leaf-first — `removeDirIfEmpty` on the folder the config was in — so nothing\n * looks one level up, and `.agents-inc/` left standing is a directory naming this product with\n * nothing installed under it. A later `init` and `doctor` both read that as a half-built layout.\n *\n * Only when it is empty, and only through the same `removeDirIfEmpty` the leaf uses: a consuming\n * repository keeps its own state under this parent — the gate's `baseline.json` is the live case\n * — and that state is not this CLI's to remove. Keeping it is then SAID rather than done in\n * silence, because a directory left where an install was reads as one the uninstall forgot.\n *\n * Attempted only when the provider folder itself went: while that folder is still there the\n * parent is not empty for a reason the user has already been told about, and a second line about\n * it would name the wrong cause.\n */\nasync function cleanupSourceRoot(\n sourceRoot: SourceRoot,\n providerDirRemoved: boolean,\n): Promise<SourceRootCleanup> {\n if (!providerDirRemoved) return { removed: false, kept: false };\n\n const removed = await removeDirIfEmpty(sourceRoot.dir);\n return { removed, kept: !removed && (await directoryExists(sourceRoot.dir)) };\n}\n\nasync function listAgentFiles(agentsDir: string, codec: AgentCodec): Promise<string[]> {\n try {\n return await listAgentFilesOf(agentsDir, codec);\n } catch {\n return [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAkB;AAClB,SAAS,iBAAiB;AAC1B,OAAO,UAAU;AAEjB,SAAS,aAAa;AAkXhB;AAjRN,SAAS,iBAAiB,QAAsC;AAC9D,QAAM,SAAS,oBAAoB,MAAM;AAEzC,SAAO;AAAA,IACL,SAAS;AAAA,MACP,GAAG,aAAa,MAAM;AAAA,MACtB,GAAG,iBAAiB,MAAM;AAAA,MAC1B,GAAG;AAAA,MACH,GAAG,sBAAsB,MAAM;AAAA,IACjC;AAAA,IACA,MAAM,eAAe,QAAQ,MAAM;AAAA,EACrC;AACF;AAGA,SAAS,oBAAoB,MAA4B;AACvD,SAAO,KAAK,QAAQ,SAAS;AAC/B;AAEA,SAAS,aAAa,QAA0C;AAC9D,MAAI,CAAC,OAAO,WAAY,QAAO,CAAC;AAChC,SAAO,CAAC,EAAE,MAAM,WAAW,YAAY,OAAO,YAAY,OAAO,CAAC,GAAG,OAAO,cAAc,EAAE,CAAC;AAC/F;AAEA,SAAS,iBAAiB,QAA0C;AAClE,MAAI,CAAC,OAAO,eAAgB,QAAO,CAAC;AACpC,SAAO,CAAC,EAAE,MAAM,UAAU,WAAW,OAAO,UAAU,CAAC;AACzD;AAEA,SAAS,oBAAoB,QAA0C;AACrE,MAAI,CAAC,OAAO,eAAgB,QAAO,CAAC;AAEpC,QAAM,aAAa,mBAAmB,MAAM;AAC5C,MAAI,WAAW,WAAW,EAAG,QAAO,CAAC;AAErC,SAAO,CAAC,EAAE,MAAM,UAAU,WAAW,OAAO,WAAW,YAAY,OAAO,OAAO,WAAW,CAAC;AAC/F;AAWA,SAAS,mBAAmB,QAAmC;AAC7D,MAAI,OAAO,iBAAiB,SAAS,EAAG,QAAO,OAAO;AACtD,SAAO,OAAO;AAChB;AAGA,SAAS,sBAAsB,QAAkD;AAC/E,QAAM,EAAE,KAAK,cAAc,IAAI,OAAO;AACtC,MAAI,cAAc,WAAW,EAAG,QAAO,CAAC;AACxC,SAAO,CAAC,EAAE,MAAM,UAAU,WAAW,KAAK,WAAW,cAAc,CAAC;AACtE;AAQA,SAAS,eAAe,QAAyB,eAA0C;AACzF,QAAM,WAAW,IAAI,IAAI,cAAc,QAAQ,CAAC,UAAU,MAAM,UAAU,CAAC;AAC3E,QAAM,OAAO,OAAO,eAAe,OAAO,CAAC,SAAS,CAAC,SAAS,IAAI,IAAI,CAAC;AACvE,MAAI,KAAK,WAAW,EAAG,QAAO,CAAC;AAC/B,SAAO,CAAC,mBAAmB,OAAO,WAAW,KAAK,MAAM,CAAC;AAC3D;AAGA,SAAS,YACP,MACA,MACsC;AACtC,SAAO,KAAK,QAAQ;AAAA,IAClB,CAAC,UAAuD,MAAM,SAAS;AAAA,EACzE;AACF;AAGA,SAAS,UACP,MACA,MACgD;AAChD,SAAO,KAAK,QAAQ;AAAA,IAClB,CAAC,UAAuD,MAAM,SAAS;AAAA,EACzE;AACF;AAGA,SAAS,cAAc,OAAyC;AAC9D,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,EAAE,OAAO,eAAe,iBAAiB,OAAO,MAAM,MAAM;AAAA,IACrE,KAAK;AACH,aAAO;AAAA,QACL,OAAO,eAAe;AAAA,QACtB,OAAO,CAAC,mBAAmB,MAAM,SAAS,CAAC;AAAA,MAC7C;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,OAAO,eAAe;AAAA,QACtB,OAAO,CAAC,sBAAsB,MAAM,SAAS,CAAC;AAAA,MAChD;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,OAAO,eAAe;AAAA,QACtB,OAAO,MAAM,UAAU,IAAI,CAAC,aAAa,GAAG,MAAM,SAAS,IAAI,QAAQ,EAAE;AAAA,MAC3E;AAAA,IACF,SAAS;AACP,YAAM,cAAqB;AAC3B,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEA,IAAM,qBAAqB;AAAA,EACzB,eAAe;AAAA,EACf,eAAe;AAAA,EACf,eAAe;AACjB;AASA,SAAS,aAAa,SAAwD;AAC5E,QAAM,YAAY,QAAQ,IAAI,aAAa;AAE3C,SAAO,mBAAmB;AAAA,IAAQ,CAAC,UACjC,iBAAiB,OAAO,WAAW,WAAW,KAAK,CAAC;AAAA,EACtD;AACF;AAGA,SAAS,WAAW,WAA0C,OAAyB;AACrF,SAAO,UAAU,OAAO,CAAC,YAAY,QAAQ,UAAU,KAAK,EAAE,QAAQ,CAAC,YAAY,QAAQ,KAAK;AAClG;AAEA,SAAS,iBAAiB,OAAe,OAAuC;AAC9E,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,SAAO,CAAC,EAAE,OAAO,MAAM,CAAC;AAC1B;AAEA,IAAM,4BAA4B;AAElC,IAAqB,YAArB,MAAqB,mBAAkB,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYzC,eAAuB,iBAAiB;AAAA,EAEhD,OAAO,UAAU,UAAU,iBAAiB,IAAI;AAAA,EAEhD,OAAO,cAAc,aAAa,iBAAiB,IAAI;AAAA,EAEvD,OAAO,WAAW;AAAA,IAChB;AAAA,IACA;AAAA,EACF;AAAA,EAEA,OAAO,QAAQ;AAAA,IACb,KAAK,MAAM,QAAQ;AAAA,MACjB,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AAAA,IACD,UAAU,aAAa;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAgB,qBAAoC;AAAA,EAEpD;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,EAAE,MAAM,IAAI,MAAM,KAAK,MAAM,UAAS;AAC5C,UAAM,aAAa,QAAQ,IAAI;AAK/B,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,gBAAgB,MAAM,QAAQ;AAAA,MAC9B,CAAC,YAAY,KAAK,MAAM,SAAS,EAAE,MAAM,WAAW,aAAa,CAAC;AAAA,IACpE;AAEA,SAAK,eAAe,MAAM,KAAK,oBAAoB,UAAU;AAE7D,SAAK,YAAY;AAEjB,UAAM,SAAS,MAAM;AAAA,MAAsB;AAAA,MAAY,CAAC,YACtD,KAAK,KAAK,0BAA0B,OAAO,CAAC;AAAA,IAC9C;AACA,UAAM,OAAO,iBAAiB,MAAM;AACpC,QAAI,CAAC,oBAAoB,IAAI,GAAG;AAC9B,WAAK,yBAAyB;AAC9B;AAAA,IACF;AAEA,UAAM,YAAY,MAAM,MAAM,KAAK,iBAAiB,IAAI,IAAI,MAAM,KAAK,eAAe,IAAI;AAC1F,QAAI,CAAC,WAAW;AACd,WAAK,IAAI,EAAE;AACX,WAAK,IAAI,qBAAqB;AAC9B,WAAK,KAAK,WAAW,SAAS;AAAA,IAChC;AAEA,UAAM,KAAK,iBAAiB,MAAM,QAAQ,UAAU;AACpD,SAAK,cAAc;AAAA,EACrB;AAAA,EAEQ,cAAoB;AAC1B,SAAK,IAAI,EAAE;AACX,SAAK,IAAI,GAAG,KAAK,YAAY,YAAY;AACzC,SAAK,IAAI,EAAE;AAAA,EACb;AAAA,EAEQ,2BAAiC;AACvC,SAAK,KAAK,uBAAuB;AACjC,SAAK,IAAI,EAAE;AACX,SAAK,IAAI,iBAAiB,KAAK,YAAY,CAAC;AAC5C,SAAK,IAAI,EAAE;AACX,SAAK,IAAI,cAAc,eAAe;AAAA,EACxC;AAAA,EAEQ,iBAAiB,MAAyB;AAChD,SAAK,IAAI,eAAe,eAAe;AACvC,SAAK,IAAI,EAAE;AAEX,eAAW,WAAW,aAAa,KAAK,OAAO,GAAG;AAChD,WAAK,IAAI,KAAK,QAAQ,KAAK,EAAE;AAC7B,iBAAW,QAAQ,QAAQ,OAAO;AAChC,aAAK,IAAI,OAAO,IAAI,EAAE;AAAA,MACxB;AAAA,IACF;AAEA,eAAW,aAAa,KAAK,MAAM;AACjC,WAAK,IAAI,EAAE;AACX,WAAK,IAAI,SAAS;AAAA,IACpB;AAEA,SAAK,IAAI,EAAE;AACX,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,eAAe,MAAqC;AAGhE,QAAI,CAAC,QAAQ,MAAM,OAAO;AACxB,WAAK,MAAM,uBAAuB,GAAG,EAAE,MAAM,WAAW,MAAM,CAAC;AAAA,IACjE;AAEA,UAAM,UAAU,MAAM,cAAc,CAAC,EAAE,WAAW,SAAS,MACzD;AAAA,MAAC;AAAA;AAAA,QACC,SAAS,eAAe;AAAA,QACxB,UAAU,aAAa,KAAK,OAAO;AAAA,QACnC,YAAY,KAAK;AAAA,QACjB,SAAS;AAAA,QACT;AAAA,QACA;AAAA;AAAA,IACF,CACD;AACD,WAAO,YAAY;AAAA,EACrB;AAAA,EAEA,MAAc,iBACZ,MACA,QACA,YACe;AACf,UAAM,oBAAoB,gBAAgB,UAAU;AAIpD,UAAM,cAAc,oBAChB,MAAM,KAAK,yBAAyB,QAAQ,UAAU,IACtD;AAEJ,UAAM,KAAK,qBAAqB,UAAU,MAAM,SAAS,GAAG,OAAO,QAAQ,UAAU;AAErF,QAAI;AACF,YAAM,KAAK,iBAAiB,MAAM,MAAM;AAAA,IAC1C,SAAS,OAAO;AACd,WAAK,IAAI,8BAA8B;AACvC,WAAK,MAAM,gBAAgB,KAAK,GAAG;AAAA,QACjC,MAAM,WAAW;AAAA,MACnB,CAAC;AAAA,IACH;AAEA,QAAI,mBAAmB;AAGrB,UAAI,aAAa;AACf,cAAM,KAAK,yBAAyB,WAAW;AAAA,MACjD;AACA;AAAA,IACF;AAMA,QAAI;AAIF,YAAM,aAAa,EAAE,MAAM,sBAAsB,WAAW,GAAG,aAAa,UAAU,CAAC;AAAA,IACzF,SAAS,OAAO;AACd,WAAK,KAAK,iDAAiD,gBAAgB,KAAK,CAAC,EAAE;AAAA,IACrF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuBA,MAAc,yBACZ,QACA,YACuC;AACvC,QAAI,OAAO,WAAW,QAAQ,OAAO,mBAAmB,WAAW,EAAG,QAAO;AAC7E,UAAM,eAAe,OAAO;AAE5B,QAAI;AACF,YAAM,CAAC,cAAc,SAAS,IAAI,MAAM,QAAQ,IAAI;AAAA,QAClD,2BAA2B;AAAA,UACzB;AAAA,UACA,SAAS;AAAA,UACT,kBAAkB;AAAA,UAClB,YAAY;AAAA,QACd,CAAC;AAAA,QACD,cAAc;AAAA,MAChB,CAAC;AACD,aAAO;AAAA,QACL;AAAA,QACA,QAAQ,aAAa;AAAA,QACrB,QAAQ,UAAU;AAAA,QAClB,UAAU,cAAc,UAAU;AAAA,MACpC;AAAA,IACF,SAAS,OAAO;AACd,WAAK,KAAK,+BAA+B,gBAAgB,KAAK,CAAC,CAAC;AAChE,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,yBAAyB,aAAmD;AACxF,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB,YAAY;AAAA,QACZ,EAAE,QAAQ,YAAY,QAAQ,QAAQ,YAAY,OAAO;AAAA,QACzD,YAAY;AAAA,MACd;AACA,YAAM,EAAE,SAAS,YAAY,OAAO,IAAI,OAAO;AAC/C,WAAK,yBAAyB,OAAO,UAAU;AAE/C,iBAAW,eAAe,CAAC,GAAG,YAAY,GAAG,MAAM,GAAG;AACpD,aAAK,KAAK,+BAA+B,WAAW,CAAC;AAAA,MACvD;AACA,UAAI,QAAQ,WAAW,EAAG;AAE1B,WAAK,WAAW,0BAA0B,QAAQ,MAAM,CAAC;AAIzD,WAAK,0BAA0B,MAAM;AAAA,IACvC,SAAS,OAAO;AACd,WAAK,KAAK,+BAA+B,gBAAgB,KAAK,CAAC,CAAC;AAAA,IAClE;AAAA,EACF;AAAA,EAEQ,gBAAsB;AAC5B,SAAK,IAAI,EAAE;AACX,SAAK,IAAI,GAAG,KAAK,YAAY,wBAAwB;AACrD,SAAK,IAAI,EAAE;AACX,SAAK,WAAW,iBAAiB,kBAAkB;AACnD,SAAK,IAAI,EAAE;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,iBAAiB,MAAmB,QAAwC;AACxF,UAAM,KAAK,oBAAoB,UAAU,MAAM,QAAQ,CAAC;AACxD,UAAM,KAAK,oBAAoB,UAAU,MAAM,QAAQ,CAAC;AAIxD,eAAW,aAAa,KAAK,MAAM;AACjC,WAAK,IAAI,SAAS;AAAA,IACpB;AAEA,UAAM,UAAU,MAAM,iBAAiB,QAAQ,YAAY,MAAM,QAAQ,CAAC;AAE1E,SAAK,0BAA0B,QAAQ,YAAY;AAEnD,SAAK,wBAAwB,OAAO,YAAY,OAAO;AAEvD,QAAI,QAAQ,kBAAkB;AAC5B,WAAK,WAAW,WAAW,UAAU,GAAG;AAAA,IAC1C,WAAW,QAAQ,eAAe;AAChC,WAAK,IAAI,QAAQ,UAAU,2BAA2B;AAAA,IACxD;AAAA,EACF;AAAA;AAAA,EAGQ,0BAA0B,QAAmC;AACnE,QAAI,OAAO,YAAY;AACrB,WAAK,WAAW,WAAW,OAAO,OAAO,GAAG;AAAA,IAC9C,WAAW,OAAO,iBAAiB;AACjC,WAAK,WAAW,2BAA2B,OAAO,OAAO,GAAG;AAAA,IAC9D,WAAW,OAAO,MAAM;AACtB,WAAK,IAAI,QAAQ,OAAO,OAAO,2BAA2B;AAAA,IAC5D;AAAA,EACF;AAAA;AAAA,EAGQ,wBAAwB,YAAwB,SAA8B;AACpF,QAAI,QAAQ,mBAAmB;AAC7B,WAAK,WAAW,WAAW,WAAW,OAAO,GAAG;AAChD;AAAA,IACF;AACA,QAAI,QAAQ,gBAAgB;AAC1B,WAAK,IAAI,QAAQ,WAAW,OAAO,2BAA2B;AAAA,IAChE;AAAA,EACF;AAAA,EAEA,MAAc,qBACZ,OACA,QACA,YACe;AACf,QAAI,CAAC,MAAO;AAEZ,SAAK,IAAI,yBAAyB;AAElC,QAAI;AACF,YAAM,eAAe,MAAM;AAAA,QAAiB;AAAA,QAAO;AAAA,QAAQ;AAAA,QAAY,CAAC,SACtE,KAAK,IAAI,yBAAyB,IAAI,GAAG;AAAA,MAC3C;AAMA,UAAI,aAAa,mBAAmB,GAAG;AACrC,aAAK;AAAA,UACH,eAAe,aAAa,gBAAgB,IAAI,aAAa,qBAAqB,IAAI,WAAW,SAAS;AAAA,QAC5G;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,WAAK,IAAI,yBAAyB;AAClC,WAAK,MAAM,gBAAgB,KAAK,GAAG;AAAA,QACjC,MAAM,WAAW;AAAA,MACnB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAc,oBAAoB,OAAiD;AACjF,QAAI,CAAC,MAAO;AAEZ,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,MACA,CAAC,YAAY,KAAK,IAAI,wBAAwB,OAAO,GAAG;AAAA,MACxD,CAAC,YAAY,KAAK,KAAK,aAAa,OAAO,qBAAqB,KAAK,YAAY,MAAM;AAAA,IACzF;AACA,QAAI,OAAO,iBAAiB,EAAG;AAE/B,SAAK;AAAA,MACH,WAAW,OAAO,YAAY,kBAAkB,OAAO,iBAAiB,IAAI,UAAU,QAAQ;AAAA,IAChG;AAAA,EACF;AAAA,EAEA,MAAc,oBAAoB,OAAiD;AACjF,QAAI,CAAC,MAAO;AAEZ,UAAM,SAAS,MAAM;AAAA,MAAqB;AAAA,MAAO,CAAC,cAChD,KAAK,IAAI,wBAAwB,SAAS,GAAG;AAAA,IAC/C;AACA,QAAI,OAAO,iBAAiB,EAAG;AAE/B,SAAK;AAAA,MACH,WAAW,OAAO,YAAY,aAAa,OAAO,iBAAiB,IAAI,UAAU,QAAQ;AAAA,IAC3F;AAAA,EACF;AACF;AA6GA,SAAS,wBAAwB,QAAoD;AACnF,MAAI,CAAC,QAAQ,OAAQ,QAAO,CAAC;AAC7B,SAAO,OAAO,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI;AACxC;AAGO,SAAS,0BAA0B,QAAoD;AAC5F,MAAI,CAAC,QAAQ,OAAQ,QAAO,oBAAI,IAAI;AACpC,QAAM,EAAE,gBAAgB,IAAI;AAC5B,SAAO,IAAI;AAAA,IACT,OAAO,OAAO,QAAQ,CAAC,UAAU;AAAA;AAAA,MAE/B,0BAA0B,MAAM,IAAI,MAAM,MAAM;AAAA;AAAA;AAAA,MAGhD,GAAI,mBAAmB,MAAM,WAAW,mBAAmB,MAAM,WAAW,eACxE,CAAC,0BAA0B,MAAM,IAAI,eAAe,CAAC,IACrD,CAAC;AAAA,IACP,CAAC;AAAA,EACH;AACF;AAWA,eAAe,oBACb,YACA,YACA,UACA,cAC+B;AAC/B,MAAI;AACF,UAAM,SAAS,MAAM,oBAAoB,YAAY,YAAY,QAAQ;AACzE,WAAO,QAAQ,UAAU;AAAA,EAC3B,SAAS,OAAO;AACd,QAAI,EAAE,iBAAiB,iBAAkB,OAAM;AAC/C,iBAAa,KAAK;AAClB,WAAO;AAAA,EACT;AACF;AAGA,eAAe,iBACb,QACA,YACA,UACA,oBAC4B;AAC5B,QAAM,CAAC,eAAe,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,IAChD,gBAAgB,OAAO,GAAG;AAAA,IAC1B;AAAA,MACE,KAAK,KAAK,OAAO,KAAK,eAAe,SAAS;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACD,SAAO,EAAE,GAAG,QAAQ,eAAe,OAAO;AAC5C;AAGA,eAAe,gBAAgB,KAAgC;AAC7D,QAAM,WAAW,CAAC,eAAe,WAAW,eAAe,eAAe;AAC1E,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,SAAS,IAAI,CAAC,aAAa,WAAW,KAAK,KAAK,KAAK,QAAQ,CAAC,CAAC;AAAA,EACjE;AACA,SAAO,SAAS,OAAO,CAAC,GAAG,UAAU,QAAQ,KAAK,CAAC;AACrD;AAGA,SAAS,eAAe,QAAsC;AAC5D,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ,OAAO,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ;AAAA,IAC/C,QAAQ,OAAO,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ;AAAA,EACjD;AACF;AAQA,eAAe,sBACb,YACA,oBAC0B;AAC1B,QAAM,aAAa,qBAAqB,UAAU;AAClD,QAAM,EAAE,WAAW,UAAU,IAAI,oBAAoB,UAAU;AAC/D,QAAM,YAAY,KAAK,KAAK,YAAY,UAAU;AAClD,QAAM,WAAW,cAAc,UAAU;AAEzC,QAAM,CAAC,gBAAgB,gBAAgB,cAAc,cAAc,eAAe,IAChF,MAAM,QAAQ,IAAI;AAAA,IAChB,gBAAgB,SAAS;AAAA,IACzB,gBAAgB,SAAS;AAAA,IACzB,gBAAgB,SAAS;AAAA,IACzB;AAAA,MACE,kBAAkB,YAAY,QAAQ;AAAA,MACtC;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,wBAAwB,WAAW,WAAW,QAAQ,CAAC;AAAA,EACzD,CAAC;AAEH,QAAM,EAAE,OAAO,IAAI;AAEnB,MAAI,cAAwB,CAAC;AAC7B,MAAI;AACF,kBAAc,MAAM,gBAAgB,UAAU;AAAA,EAChD,QAAQ;AAAA,EAER;AAEA,QAAM,eAAe,WAAW,OAAO,OAAO,eAAe,MAAM;AACnE,QAAM,mBAAmB,wBAAwB,YAAY;AAC7D,QAAM,mBAAmB,0BAA0B,YAAY;AAC/D,QAAM,iBAAiB,qBAAqB,UAAU,IAClD,YAAY,OAAO,CAAC,SAAS,iBAAiB,IAAI,IAAI,CAAC,IACvD,CAAC;AAEL,SAAO;AAAA,IACL,YAAY,eAAe,SAAS;AAAA,IACpC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,aAAa,UAAU;AAAA,IACnC;AAAA,IACA,oBAAoB,QAAQ,YAAY,CAAC;AAAA,IACzC;AAAA,IACA,YAAY,WAAW,QAAQ;AAAA,IAC/B,cAAc,gBAAgB;AAAA,IAC9B,gBAAgB,gBAAgB;AAAA,EAClC;AACF;AAqBA,SAAS,qBAAqB,YAA6B;AACzD,MAAI,gBAAgB,UAAU,EAAG,QAAO;AACxC,SAAO,OAAO,UAAU,EAAE;AAC5B;AAEA,SAAS,kBAAkB,YAAiD;AAC1E,SAAO,eAAe;AACxB;AAEA,eAAe,qBACb,OACA,WACA,WAC6B;AAC7B,QAAM,aAAa,MAAM,kBAAkB,MAAM,SAAS;AAC1D,QAAM,eAAe,MAAM;AAAA,IACzB,WAAW;AAAA,IACX,MAAM;AAAA,IACN;AAAA,EACF;AACA,aAAW,OAAO,QAAQ,CAAC,SAAS,YAAY,IAAI,CAAC;AACrD,QAAM,aAAa,MAAM,iBAAiB,MAAM,WAAW,WAAW,OAAO,WAAW,CAAC;AAEzF,SAAO;AAAA,IACL,cAAc,aAAa;AAAA,IAC3B,cAAc,WAAW,OAAO;AAAA,IAChC;AAAA,IACA,cAAc,WAAW;AAAA,IACzB;AAAA,EACF;AACF;AAEA,eAAe,kBACb,WACmD;AACnD,QAAM,WAAW,MAAM,gBAAgB,SAAS;AAChD,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,SAAS,IAAI,OAAO,UAAU;AAAA,MAC5B;AAAA,MACA,YAAY,MAAM,uBAAuB,KAAK,KAAK,WAAW,IAAI,CAAC;AAAA,IACrE,EAAE;AAAA,EACJ;AACA,QAAM,CAAC,WAAW,SAAS,IAAI,UAAU,SAAS,CAAC,UAAU,kBAAkB,MAAM,UAAU,CAAC;AAChG,SAAO,EAAE,UAAU,UAAU,IAAI,CAAC,MAAM,EAAE,IAAI,GAAG,QAAQ,UAAU,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;AACxF;AAEA,eAAe,uBACb,OACA,WACA,WACmB;AACnB,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,KAAK,KAAK,WAAW,IAAI,CAAC;AACvC,gBAAY,IAAI;AAAA,EAClB;AACA,SAAO;AACT;AAEA,eAAe,iBAAiB,KAAa,YAAuC;AAClF,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,iBAAiB,GAAG;AAC7B;AAaA,eAAe,qBACb,OACA,WAC6B;AAC7B,QAAM,aAAa,MAAM,eAAe,MAAM,WAAW,MAAM,KAAK;AACpE,QAAM,eAAe,WAClB,IAAI,CAAC,cAAc,KAAK,SAAS,WAAW,MAAM,MAAM,SAAS,CAAC,EAClE,OAAO,CAAC,cAAc,MAAM,WAAW,SAAS,SAAS,CAAC;AAE7D,aAAW,aAAa,cAAc;AACpC,UAAM,OAAO,KAAK,KAAK,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,MAAM,SAAS,EAAE,CAAC;AAC/E,gBAAY,SAAS;AAAA,EACvB;AAEA,QAAM,aAAa,MAAM,iBAAiB,MAAM,SAAS;AAEzD,SAAO;AAAA,IACL,cAAc,aAAa;AAAA,IAC3B;AAAA,IACA;AAAA,EACF;AACF;AAQA,IAAM,+BAA2C;AAYjD,SAAS,oBAAoB,MAAkB,SAAmC;AAChF,QAAM,UAAU,KAAK,kBAClB,OAAO,CAAC,cAAc,UAAU,SAAS,QAAQ,EACjD,IAAI,CAAC,cAAc,UAAU,KAAK;AAErC,MAAI,CAAC,QAAQ,SAAS,OAAO,EAAG,QAAO;AACvC,SAAO,CAAC,SAAS,GAAG,QAAQ,OAAO,CAAC,UAAU,UAAU,OAAO,CAAC;AAClE;AAeA,eAAe,8BACb,MACA,WACA,cACA,YAC+B;AAC/B,MAAI,UAAgC;AAEpC,aAAW,SAAS,oBAAoB,MAAM,YAAY,GAAG;AAC3D,QAAI;AACF,YAAM,QAAQ,MAAM,KAAK,gBAAgB,WAAW,OAAO,UAAU;AACrE,UAAI,UAAU,UAAW,WAAU;AAAA,IACrC,QAAQ;AAAA,IAIR;AAAA,EACF;AAEA,SAAO;AACT;AAWA,eAAsB,iBACpB,OACA,QACA,YACA,eACiC;AACjC,QAAM,OAAO,OAAO,UAAU;AAC9B,QAAM,gBAAgB,MAAM,KAAK,YAAY;AAC7C,QAAM,WAAqB,CAAC;AAE5B,aAAW,cAAc,MAAM,OAAO;AACpC,UAAM,UAAU,gBACZ,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,2BAA2B,QAAQ,UAAU;AAAA,MAC7C;AAAA,IACF,IACA,mBAAmB;AAEvB,UAAM,OAAO,KAAK,KAAK,MAAM,YAAY,UAAU,CAAC;AAEpD,QAAI,YAAY,WAAW;AACzB,eAAS,KAAK,UAAU;AACxB,sBAAgB,UAAU;AAAA,IAC5B;AAAA,EACF;AAOA,SAAO;AAAA,IACL,kBAAkB;AAAA,IAClB,kBAAkB,SAAS;AAAA,EAC7B;AACF;AASA,SAAS,2BAA2B,QAA8B,YAAgC;AAChG,QAAM,UAAU,0BAA0B,UAAU;AACpD,SACE,QAAQ,OAAO,KAAK,CAAC,UAAU,MAAM,OAAO,OAAO,GAAG,SAAS;AAEnE;AAUA,SAAS,qBAA2C;AAClD,SAAO;AACT;AAQA,eAAe,qBACb,OAC4D;AAC5D,MAAI,CAAC,MAAO,QAAO,EAAE,iBAAiB,OAAO,YAAY,MAAM;AAE/D,QAAM,QAAQ;AAAA,IACZ,MAAM,UAAU,IAAI,CAAC,aAAa,OAAO,KAAK,KAAK,MAAM,WAAW,QAAQ,CAAC,CAAC;AAAA,EAChF;AAEA,SAAO,EAAE,iBAAiB,MAAM,YAAY,MAAM,iBAAiB,MAAM,SAAS,EAAE;AACtF;AASA,eAAe,iBACb,QACA,iBACwB;AACxB,QAAM,eAAe,MAAM;AAAA,IACzB,OAAO;AAAA,IACP,gBAAgB,KAAK,CAAC,UAAU,MAAM,cAAc,OAAO,aAAa,GAAG;AAAA,EAC7E;AACA,QAAM,aAAa,MAAM,kBAAkB,OAAO,YAAY,aAAa,UAAU;AAErF,QAAM,mBAAmB,OAAO,gBAAiB,MAAM,iBAAiB,OAAO,SAAS;AAExF,QAAM,gBACJ,CAAC,oBAAoB,OAAO,gBAAiB,MAAM,gBAAgB,OAAO,SAAS;AAErF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,mBAAmB,WAAW;AAAA,IAC9B,gBAAgB,WAAW;AAAA,IAC3B;AAAA,EACF;AACF;AAOA,eAAe,oBACb,QACA,OAC8B;AAC9B,QAAM,WAAW,MAAM,qBAAqB,KAAK;AACjD,QAAM,aAAa,SAAS,cAAe,MAAM,iBAAiB,OAAO,GAAG;AAC5E,QAAM,OAAO,CAAC,cAAe,MAAM,gBAAgB,OAAO,GAAG;AAC7D,SAAO;AAAA,IACL,SAAS,OAAO;AAAA,IAChB,iBAAiB,SAAS;AAAA,IAC1B;AAAA,IACA;AAAA,EACF;AACF;AAqBA,eAAe,kBACb,YACA,oBAC4B;AAC5B,MAAI,CAAC,mBAAoB,QAAO,EAAE,SAAS,OAAO,MAAM,MAAM;AAE9D,QAAM,UAAU,MAAM,iBAAiB,WAAW,GAAG;AACrD,SAAO,EAAE,SAAS,MAAM,CAAC,WAAY,MAAM,gBAAgB,WAAW,GAAG,EAAG;AAC9E;AAEA,eAAe,eAAe,WAAmB,OAAsC;AACrF,MAAI;AACF,WAAO,MAAM,iBAAiB,WAAW,KAAK;AAAA,EAChD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/cli/commands/uninstall.tsx"],"sourcesContent":["import React from \"react\";\nimport { partition } from \"remeda\";\nimport path from \"path\";\n\nimport { Flags } from \"@oclif/core\";\n\nimport { BaseCommand } from \"../base-command\";\nimport {\n RemovalPlanConfirm,\n type RemovalPlanSection,\n} from \"../components/common/removal-plan-confirm.js\";\nimport { promptConfirm } from \"../components/common/prompt-confirm.js\";\nimport { getErrorMessage } from \"../utils/errors\";\nimport {\n directoryExists,\n fileExists,\n listDirectories,\n remove,\n removeDirIfEmpty,\n} from \"../utils/fs\";\nimport { listAgentFilesOf, splitAgentsByProvenance } from \"../lib/agents\";\nimport { hostAt } from \"../lib/hosts/host-for.js\";\nimport type { PluginHost, PluginRemovalOutcome } from \"../lib/hosts/plugin-host.js\";\nimport {\n listPluginNames,\n getProjectPluginsDir,\n buildMarketplacePluginRef,\n parseMarketplacePluginRef,\n} from \"../lib/plugins/index\";\nimport { readForkedFromMetadata } from \"../lib/skills/index\";\nimport { isHomeDirectory, resolveInstallPaths } from \"../lib/installation/index\";\nimport { lazyGateDeps, mutateGlobal, propagateGlobalRemoval } from \"../lib/config-gate/index.js\";\nimport { loadSkillsMatrixFromSource } from \"../lib/loading\";\nimport { loadAgentDefs } from \"../lib/operations\";\nimport { ConfigLoadError, loadProjectConfigAt } from \"../lib/configuration/project-config\";\nimport type { Provider } from \"../consts\";\nimport { CLAUDE_DIR, DEFAULT_BRANDING, EJECT_SOURCE, STANDARD_FILES } from \"../consts\";\nimport {\n providerInUse,\n agentCodec,\n type AgentCodec,\n sourceFolderInUse,\n sourceRootOf,\n type SourceRoot,\n} from \"../lib/installation/install-layout.js\";\nimport {\n providerFlag,\n providerNamedBy,\n refuseAnAmbiguousInstallation,\n} from \"../lib/installation/provider-flag.js\";\nimport { EXIT_CODES } from \"../lib/exit-codes\";\nimport {\n SUCCESS_MESSAGES,\n INFO_MESSAGES,\n UNINSTALL_PLAN,\n compiledAgentsRemoval,\n localSkillsRemoval,\n notInstalledHere,\n unmarkedAgentsKept,\n registeredProjectsUpdated,\n registeredProjectUpdateSkipped,\n registeredProjectsUpdateFailed,\n uninstallConfigUnreadable,\n uninstallNeedsTerminal,\n} from \"../utils/messages\";\nimport type { AgentDefinition, AgentName, MergedSkillsMatrix, ProjectConfig } from \"../types/index\";\nimport type { SkillScope } from \"../types/config\";\n\n/**\n * One removal this run promises AND makes. Each entry carries what its remover needs\n * — paths and names, never display strings — so the preview and the removal are two\n * readings of one value rather than two derivations of the same target.\n */\ntype RemovalEntry =\n | { kind: \"plugins\"; pluginsDir: string; names: string[] }\n | { kind: \"skills\"; skillsDir: string }\n | { kind: \"agents\"; agentsDir: string; agentNames: string[]; codec: AgentCodec }\n | { kind: \"config\"; sourceDir: string; fileNames: string[] };\n\n/** The single entry of a kind, for the remover that consumes it. */\ntype PluginRemoval = Extract<RemovalEntry, { kind: \"plugins\" }>;\ntype SkillsRemoval = Extract<RemovalEntry, { kind: \"skills\" }>;\ntype AgentsRemoval = Extract<RemovalEntry, { kind: \"agents\" }>;\ntype ConfigManifestRemoval = Extract<RemovalEntry, { kind: \"config\" }>;\n\n/**\n * The uninstall removal plan: what this run removes, and what it deliberately leaves\n * behind. Built once per run — the confirm UI renders it, the executor consumes the\n * same value, so the plan cannot name a removal the run declines to make.\n */\ntype RemovalPlan = {\n entries: RemovalEntry[];\n /** Statements naming content that stays, and why — see {@link keptStatements}. */\n kept: string[];\n};\n\n/**\n * Pure builder for the uninstall removal plan — the one place that decides what this\n * run removes. Every removal downstream is driven by an entry from here, and nothing\n * downstream re-reads the target to decide whether to act.\n */\nfunction buildRemovalPlan(target: UninstallTarget): RemovalPlan {\n const agents = compiledAgentsEntry(target);\n\n return {\n entries: [\n ...pluginsEntry(target),\n ...localSkillsEntry(target),\n ...agents,\n ...configManifestEntries(target),\n ],\n kept: keptStatements(target, agents),\n };\n}\n\n/** The plan carries at least one removal — the only thing that makes this run an uninstall. */\nfunction hasAnythingToRemove(plan: RemovalPlan): boolean {\n return plan.entries.length > 0;\n}\n\nfunction pluginsEntry(target: UninstallTarget): PluginRemoval[] {\n if (!target.hasPlugins) return [];\n return [{ kind: \"plugins\", pluginsDir: target.pluginsDir, names: [...target.cliPluginNames] }];\n}\n\nfunction localSkillsEntry(target: UninstallTarget): SkillsRemoval[] {\n if (!target.hasLocalSkills) return [];\n return [{ kind: \"skills\", skillsDir: target.skillsDir }];\n}\n\nfunction compiledAgentsEntry(target: UninstallTarget): AgentsRemoval[] {\n if (!target.hasLocalAgents) return [];\n\n const agentNames = identifiableAgents(target);\n if (agentNames.length === 0) return [];\n\n return [{ kind: \"agents\", agentsDir: target.agentsDir, agentNames, codec: target.agentCodec }];\n}\n\n/**\n * The agent files this run can say are its own, and on whose authority.\n *\n * The configuration is the first: it names the agents this install compiled, and a marker-less\n * file it names is still this CLI's — every install predating the marker is in exactly that\n * state. The provenance marker the compiler stamps into every agent is the second, reached\n * when there is no configuration left to read: provably ours, whoever lost the config. With\n * neither, nothing here is identifiable and the plan carries no removal for it.\n */\nfunction identifiableAgents(target: UninstallTarget): string[] {\n if (target.configuredAgents.length > 0) return target.configuredAgents;\n return target.markedAgents;\n}\n\n/** The CLI config manifest (config.ts + config-types.ts) is always removed from the source folder. */\nfunction configManifestEntries(target: UninstallTarget): ConfigManifestRemoval[] {\n const { dir, manifestFiles } = target.sourceFolder;\n if (manifestFiles.length === 0) return [];\n return [{ kind: \"config\", sourceDir: dir, fileNames: manifestFiles }];\n}\n\n/**\n * The plan's kept half: the agent files carrying no provenance marker that the removal above\n * does not claim anyway. Derived FROM the removal rather than from a second reading of the\n * target, so the two halves of the plan cannot contradict each other — an agent named in both\n * lists would be a promise to remove a file the same plan calls kept.\n */\nfunction keptStatements(target: UninstallTarget, agentRemovals: AgentsRemoval[]): string[] {\n const removing = new Set(agentRemovals.flatMap((entry) => entry.agentNames));\n const kept = target.unmarkedAgents.filter((name) => !removing.has(name));\n if (kept.length === 0) return [];\n return [unmarkedAgentsKept(target.agentsDir, kept.length)];\n}\n\n/** Every entry of a kind — the config kind has one per source folder. */\nfunction planEntries<K extends RemovalEntry[\"kind\"]>(\n plan: RemovalPlan,\n kind: K,\n): Extract<RemovalEntry, { kind: K }>[] {\n return plan.entries.filter(\n (entry): entry is Extract<RemovalEntry, { kind: K }> => entry.kind === kind,\n );\n}\n\n/** The plan's entry of a kind, for the remover that consumes it. At most one exists. */\nfunction planEntry<K extends RemovalEntry[\"kind\"]>(\n plan: RemovalPlan,\n kind: K,\n): Extract<RemovalEntry, { kind: K }> | undefined {\n return plan.entries.find(\n (entry): entry is Extract<RemovalEntry, { kind: K }> => entry.kind === kind,\n );\n}\n\n/** The heading an entry prints under, and the lines it contributes beneath it. */\nfunction describeEntry(entry: RemovalEntry): RemovalPlanSection {\n switch (entry.kind) {\n case \"plugins\":\n return { label: UNINSTALL_PLAN.PLUGINS_HEADING, items: entry.names };\n case \"skills\":\n return {\n label: UNINSTALL_PLAN.CLI_MANAGED_FILES_HEADING,\n items: [localSkillsRemoval(entry.skillsDir)],\n };\n case \"agents\":\n return {\n label: UNINSTALL_PLAN.CLI_MANAGED_FILES_HEADING,\n items: [compiledAgentsRemoval(entry.agentsDir)],\n };\n case \"config\":\n return {\n label: UNINSTALL_PLAN.CONFIG_HEADING,\n items: entry.fileNames.map((fileName) => `${entry.sourceDir}/${fileName}`),\n };\n default: {\n const _exhaustive: never = entry;\n return _exhaustive;\n }\n }\n}\n\nconst PLAN_SECTION_ORDER = [\n UNINSTALL_PLAN.PLUGINS_HEADING,\n UNINSTALL_PLAN.CLI_MANAGED_FILES_HEADING,\n UNINSTALL_PLAN.CONFIG_HEADING,\n] as const;\n\n/**\n * The plan's display half: entries grouped under their heading, in printing order —\n * skills and compiled agents share one. A heading is a promise about the lines beneath\n * it, so a heading no entry contributed to is not printed at all. The single source of\n * the strings shared by printRemovalPlan (plain text) and the UninstallConfirm Ink\n * component; each renderer only adds its own indentation and styling.\n */\nfunction planSections(entries: readonly RemovalEntry[]): RemovalPlanSection[] {\n const described = entries.map(describeEntry);\n\n return PLAN_SECTION_ORDER.flatMap((label) =>\n sectionWithItems(label, itemsUnder(described, label)),\n );\n}\n\n/** Every line the described entries contributed under one heading, in plan order. */\nfunction itemsUnder(described: readonly RemovalPlanSection[], label: string): string[] {\n return described.filter((section) => section.label === label).flatMap((section) => section.items);\n}\n\nfunction sectionWithItems(label: string, items: string[]): RemovalPlanSection[] {\n if (items.length === 0) return [];\n return [{ label, items }];\n}\n\nconst UNINSTALL_CONFIRM_MESSAGE = \"Are you sure you want to uninstall?\";\n\nexport default class Uninstall extends BaseCommand {\n /**\n * The name this run prints itself under, resolved once in {@link run} because three separate\n * lines carry it: the heading, the sign-off, and the warning naming the tool a preserved\n * directory was not created by. The last of those sits three calls below the resolution, and\n * threading a display string through `executeUninstall` -> `removeLocalFiles` ->\n * `removePlannedSkills` would widen three signatures to deliver one word.\n *\n * A plain `string` holding the shipped default rather than an optional every reader would have\n * to answer for. `run` replaces it before the first line is printed, so what a user sees is\n * always what their configuration says.\n */\n private brandingName: string = DEFAULT_BRANDING.NAME;\n\n static summary = `Remove ${DEFAULT_BRANDING.NAME} from this project`;\n\n static description = `Uninstall ${DEFAULT_BRANDING.NAME} from this project. Removes CLI-managed skills (matched by marketplace), compiled agents, plugins, and the config manifest (config.ts + config-types.ts) this project keeps. User-created content is preserved.`;\n\n static examples = [\n \"<%= config.bin %> <%= command.id %>\",\n \"<%= config.bin %> <%= command.id %> --yes\",\n ];\n\n static flags = {\n yes: Flags.boolean({\n char: \"y\",\n description: \"Skip confirmation prompt\",\n default: false,\n }),\n provider: providerFlag(),\n };\n\n /**\n * Starts at any terminal size: a plan and a one-line confirm fit any terminal, and this command\n * is the way out of every state, so a small window must not hold up an uninstall.\n */\n protected async ensureTerminalSize(): Promise<void> {\n // Nothing to wait for — see above.\n }\n\n async run(): Promise<void> {\n const { flags } = await this.parse(Uninstall);\n const projectDir = process.cwd();\n\n // Before the branding read and before anything is detected: this run is destructive, and a\n // scope holding two installations has two answers to \"which one\". Removing the roster's first\n // is removing a user's other installation with nothing on screen to say so.\n await refuseAnAmbiguousInstallation(\n projectDir,\n \"uninstall\",\n providerNamedBy(flags.provider),\n (message) => this.error(message, { exit: EXIT_CODES.INVALID_ARGS }),\n );\n\n this.brandingName = await this.resolveBrandingName(projectDir);\n\n this.printHeader();\n\n const target = await detectUninstallTarget(projectDir, (failure) =>\n this.warn(uninstallConfigUnreadable(failure)),\n );\n const plan = buildRemovalPlan(target);\n if (!hasAnythingToRemove(plan)) {\n this.reportNothingToUninstall();\n return;\n }\n\n const confirmed = flags.yes ? this.printRemovalPlan(plan) : await this.confirmRemoval(plan);\n if (!confirmed) {\n this.log(\"\");\n this.log(\"Uninstall cancelled\");\n this.exit(EXIT_CODES.CANCELLED);\n }\n\n await this.executeUninstall(plan, target, projectDir);\n this.reportSuccess();\n }\n\n private printHeader(): void {\n this.log(\"\");\n this.log(`${this.brandingName} Uninstall`);\n this.log(\"\");\n }\n\n private reportNothingToUninstall(): void {\n this.warn(\"Nothing to uninstall.\");\n this.log(\"\");\n this.log(notInstalledHere(this.brandingName));\n this.log(\"\");\n this.log(INFO_MESSAGES.NO_CHANGES_MADE);\n }\n\n private printRemovalPlan(plan: RemovalPlan): true {\n this.log(UNINSTALL_PLAN.PREVIEW_HEADING);\n this.log(\"\");\n\n for (const section of planSections(plan.entries)) {\n this.log(` ${section.label}`);\n for (const item of section.items) {\n this.log(` ${item}`);\n }\n }\n\n for (const statement of plan.kept) {\n this.log(\"\");\n this.log(statement);\n }\n\n this.log(\"\");\n return true;\n }\n\n private async confirmRemoval(plan: RemovalPlan): Promise<boolean> {\n // A confirm nobody can answer must never become a yes, and mounting it here died on Ink's\n // raw-mode error — so a run with no terminal is refused before it, naming `--yes`.\n if (!process.stdin.isTTY) {\n this.error(uninstallNeedsTerminal(), { exit: EXIT_CODES.ERROR });\n }\n\n const outcome = await promptConfirm(({ onConfirm, onCancel }) => (\n <RemovalPlanConfirm\n heading={UNINSTALL_PLAN.PREVIEW_HEADING}\n sections={planSections(plan.entries)}\n statements={plan.kept}\n message={UNINSTALL_CONFIRM_MESSAGE}\n onConfirm={onConfirm}\n onCancel={onCancel}\n />\n ));\n return outcome === \"confirmed\";\n }\n\n private async executeUninstall(\n plan: RemovalPlan,\n target: UninstallTarget,\n projectDir: string,\n ): Promise<void> {\n const isGlobalUninstall = isHomeDirectory(projectDir);\n // Prepared BEFORE any removal: the projects[] registry and the source used\n // to regenerate each project's config-types.ts both live in the global\n // config this uninstall is about to delete.\n const propagation = isGlobalUninstall\n ? await this.prepareGlobalPropagation(target, projectDir)\n : null;\n\n await this.removePlannedPlugins(planEntry(plan, \"plugins\"), target.config, projectDir);\n\n try {\n await this.removeLocalFiles(plan, target);\n } catch (error) {\n this.log(\"Failed to remove local files\");\n this.error(getErrorMessage(error), {\n exit: EXIT_CODES.ERROR,\n });\n }\n\n if (isGlobalUninstall) {\n // The global manifest is gone — prune the inlined global-scoped entries the\n // registered projects still carry so they stop referencing removed content.\n if (propagation) {\n await this.updateRegisteredProjects(propagation);\n }\n return;\n }\n\n // Deregister this project from the global config's tracked projects so future\n // global edits stop propagating back into it. Best-effort by nature: a missing,\n // project-less, or corrupt global config (ConfigLoadError) must never fail the\n // uninstall — warn and move on.\n try {\n // Registration bookkeeping only: nothing inlines the `projects[]` list into a\n // project config, so this loads neither the matrix nor the agent definitions\n // and the uninstall stays offline.\n await mutateGlobal({ kind: \"deregister-project\", projectDir }, lazyGateDeps(projectDir));\n } catch (error) {\n this.warn(`Could not update the global project registry: ${getErrorMessage(error)}`);\n }\n }\n\n /**\n * Loads what updating registered projects needs (global config with its\n * projects[] registry, skills matrix, agent definitions) BEFORE the global\n * the config manifest is deleted — source resolution reads that config.\n * Returns null when nothing is registered or loading fails; a load failure\n * warns and never aborts the uninstall.\n *\n * `skipExtraSources: true` is NOT a divergence from a full multi-source\n * load: extra-source loading only annotates each skill's\n * `availableSources`/`activeSource` — it never adds\n * skills or categories to the matrix, and the config-types writer never reads\n * those annotations, so the regenerated project types are byte-identical\n * either way (pinned by the skipExtraSources parity test in\n * local-installer.test.ts). Skipping avoids fetching every registered extra\n * source (network on a cold cache, plus unreachable-remote warnings) during\n * an uninstall that must never hang or noise-fail on remote state.\n *\n * `purpose: \"upkeep\"` because the prune only rewrites projects that are already installed: a\n * marketplace that has since lost its manifest must not leave every registered project naming\n * the global content this uninstall deletes.\n */\n private async prepareGlobalPropagation(\n target: UninstallTarget,\n projectDir: string,\n ): Promise<GlobalPropagationData | null> {\n if (target.config === null || target.registeredProjects.length === 0) return null;\n const globalConfig = target.config;\n\n try {\n const [sourceResult, agentDefs] = await Promise.all([\n loadSkillsMatrixFromSource({\n projectDir,\n purpose: \"upkeep\",\n skipExtraSources: true,\n matrixOnly: true,\n }),\n loadAgentDefs(),\n ]);\n return {\n globalConfig,\n matrix: sourceResult.matrix,\n agents: agentDefs.agents,\n provider: providerInUse(projectDir),\n };\n } catch (error) {\n this.warn(registeredProjectsUpdateFailed(getErrorMessage(error)));\n return null;\n }\n }\n\n /**\n * Prunes the inlined global-scoped config entries from every registered\n * project and regenerates its config-types.ts. Runs AFTER the global manifest\n * removal so the regenerated project types fall back to the standalone form\n * instead of importing from the deleted global config-types.ts. Best-effort:\n * unreachable projects are warned and skipped, and no failure here may abort\n * the uninstall.\n */\n private async updateRegisteredProjects(propagation: GlobalPropagationData): Promise<void> {\n try {\n const report = await propagateGlobalRemoval(\n propagation.globalConfig,\n { matrix: propagation.matrix, agents: propagation.agents },\n propagation.provider,\n );\n const { updated, unreadable, failed } = report.propagated;\n this.reportProjectsOutOfReach(report.propagated);\n // Uninstall's own sentence, only where it is true: the config is there and was not pruned.\n for (const skippedPath of [...unreadable, ...failed]) {\n this.warn(registeredProjectUpdateSkipped(skippedPath));\n }\n if (updated.length === 0) return;\n\n this.logSuccess(registeredProjectsUpdated(updated.length));\n\n // The pruned projects' compiled agents were built from the global rows this\n // uninstall just removed, so the prune owes them a recompile too.\n this.reportPropagatedRecompile(report);\n } catch (error) {\n this.warn(registeredProjectsUpdateFailed(getErrorMessage(error)));\n }\n }\n\n private reportSuccess(): void {\n this.log(\"\");\n this.log(`${this.brandingName} has been uninstalled.`);\n this.log(\"\");\n this.logSuccess(SUCCESS_MESSAGES.UNINSTALL_COMPLETE);\n this.log(\"\");\n }\n\n /**\n * Each step acts only when the plan carries its entry, so what the user was shown and\n * what this run removes are the same list read twice.\n */\n private async removeLocalFiles(plan: RemovalPlan, target: UninstallTarget): Promise<void> {\n await this.removePlannedSkills(planEntry(plan, \"skills\"));\n await this.removePlannedAgents(planEntry(plan, \"agents\"));\n\n // The same statements the preview showed, from the same plan: what the user approved and\n // what this run reports are one value read twice, never two derivations that agree today.\n for (const statement of plan.kept) {\n this.log(statement);\n }\n\n const cleanup = await cleanupEmptyDirs(target, planEntries(plan, \"config\"));\n\n this.reportSourceFolderCleanup(cleanup.sourceFolder);\n\n this.reportSourceRootCleanup(target.sourceRoot, cleanup);\n\n if (cleanup.claudeDirRemoved) {\n this.logSuccess(`Removed ${CLAUDE_DIR}/`);\n } else if (cleanup.claudeDirKept) {\n this.log(`Kept ${CLAUDE_DIR}/ (contains user content)`);\n }\n }\n\n /** What became of one source folder: gone, emptied of the CLI's files, or kept for the user's. */\n private reportSourceFolderCleanup(folder: SourceFolderCleanup): void {\n if (folder.dirRemoved) {\n this.logSuccess(`Removed ${folder.relName}/`);\n } else if (folder.manifestRemoved) {\n this.logSuccess(`Removed CLI config from ${folder.relName}/`);\n } else if (folder.kept) {\n this.log(`Kept ${folder.relName}/ (contains user content)`);\n }\n }\n\n /** What became of the `.agents-inc/` parent, said out loud in both directions. */\n private reportSourceRootCleanup(sourceRoot: SourceRoot, cleanup: CleanupResult): void {\n if (cleanup.sourceRootRemoved) {\n this.logSuccess(`Removed ${sourceRoot.relName}/`);\n return;\n }\n if (cleanup.sourceRootKept) {\n this.log(`Kept ${sourceRoot.relName}/ (contains user content)`);\n }\n }\n\n private async removePlannedPlugins(\n entry: PluginRemoval | undefined,\n config: ProjectConfig | null,\n projectDir: string,\n ): Promise<void> {\n if (!entry) return;\n\n this.log(\"Uninstalling plugins...\");\n\n try {\n const pluginResult = await uninstallPlugins(entry, config, projectDir, (name) =>\n this.log(` Uninstalled plugin '${name}'`),\n );\n\n // Silent on zero rather than \"Uninstalled 0 plugins\": the line's job is to account for\n // removals, and a run that observed none has nothing to account for — the plan preview has\n // already said what it expected to find. Saying \"0\" beside a plan that named one reads as a\n // failure, which it is not: a plugin a user removed themselves is the ordinary case.\n if (pluginResult.totalUninstalled > 0) {\n this.logSuccess(\n `Uninstalled ${pluginResult.totalUninstalled} ${pluginResult.totalUninstalled === 1 ? \"plugin\" : \"plugins\"}`,\n );\n }\n } catch (error) {\n this.log(\"Plugin uninstall failed\");\n this.error(getErrorMessage(error), {\n exit: EXIT_CODES.ERROR,\n });\n }\n }\n\n private async removePlannedSkills(entry: SkillsRemoval | undefined): Promise<void> {\n if (!entry) return;\n\n const result = await removeMatchingSkills(\n entry,\n (dirName) => this.log(` Uninstalled skill '${dirName}'`),\n (dirName) => this.warn(`Skipping '${dirName}': not created by ${this.brandingName} CLI`),\n );\n if (result.removedCount === 0) return;\n\n this.logSuccess(\n `Removed ${result.removedCount} CLI-installed ${result.removedCount === 1 ? \"skill\" : \"skills\"}`,\n );\n }\n\n private async removePlannedAgents(entry: AgentsRemoval | undefined): Promise<void> {\n if (!entry) return;\n\n const result = await removeMatchingAgents(entry, (agentName) =>\n this.log(` Uninstalled agent '${agentName}'`),\n );\n if (result.removedCount === 0) return;\n\n this.logSuccess(\n `Removed ${result.removedCount} compiled ${result.removedCount === 1 ? \"agent\" : \"agents\"}`,\n );\n }\n}\n\n/** @internal Exported for testing */\nexport type UninstallTarget = {\n hasPlugins: boolean;\n pluginNames: string[];\n /** Plugin names filtered to only those installed by this CLI (matched against config skills) */\n cliPluginNames: string[];\n hasLocalSkills: boolean;\n hasLocalAgents: boolean;\n hasClaudeDir: boolean;\n pluginsDir: string;\n skillsDir: string;\n agentsDir: string;\n /**\n * How this installation's host names and marks a compiled agent — `*.md` on Claude, `*.toml` on\n * Codex. Everything the plan reads or removes in `agentsDir` goes through it (CLI-896).\n */\n agentCodec: AgentCodec;\n claudeDir: string;\n /** This scope's source folder: what this run reads and removes in it. */\n sourceFolder: ScopeSourceFolder;\n /** The `.agents-inc/` parent the source folder sits under. */\n sourceRoot: SourceRoot;\n /** The source folder's config, when it is readable. */\n config: ProjectConfig | null;\n /** Every project the config registers — only a global config registers any. */\n registeredProjects: string[];\n /** Agent names the config names (e.g., [\"web-developer\"]) */\n configuredAgents: AgentName[];\n /** On-disk agent basenames carrying this CLI's provenance marker. */\n markedAgents: string[];\n /** On-disk agent basenames carrying no marker — the user's, unless the config claims them. */\n unmarkedAgents: string[];\n};\n\n/** The source folder of the scope being uninstalled, and what this run reads and removes in it. */\ntype ScopeSourceFolder = {\n dir: string;\n /** The folder as a user writes it: `.agents-inc/claude`. */\n relName: string;\n /** The manifest files on disk in it — `config.ts`, `config-types.ts` — which this run removes. */\n manifestFiles: string[];\n /** Its config, or `null` when the file is absent or unreadable — see {@link loadUninstallConfig}. */\n config: ProjectConfig | null;\n};\n\n/**\n * Everything a global uninstall needs to update the registered projects after\n * the global manifest is removed — captured before removal because it is\n * sourced from the config being deleted.\n */\ntype GlobalPropagationData = {\n globalConfig: ProjectConfig;\n matrix: MergedSkillsMatrix;\n agents: Partial<Record<AgentName, AgentDefinition>>;\n /**\n * Which installation is being removed.\n *\n * Captured here for the same reason everything else on this type is: by the time the prune\n * runs, the folder that says which provider this was has been deleted, so reading it then\n * would answer about whatever is left rather than about what was removed.\n */\n provider: Provider;\n};\n\ntype SkillRemovalResult = {\n removedCount: number;\n skippedCount: number;\n removedNames: string[];\n skippedNames: string[];\n /** Whether the skills directory was cleaned up (empty after removal) */\n dirCleaned: boolean;\n};\n\ntype AgentRemovalResult = {\n removedCount: number;\n removedNames: string[];\n /** Whether the agents directory was cleaned up (empty after removal) */\n dirCleaned: boolean;\n};\n\ntype UninstallPluginsResult = {\n uninstalledNames: string[];\n totalUninstalled: number;\n};\n\n/** What a cleanup pass did with the source folder. */\ntype SourceFolderCleanup = {\n relName: string;\n /** Whether config.ts/config-types.ts were removed from it */\n manifestRemoved: boolean;\n /** Whether the emptied folder itself was removed */\n dirRemoved: boolean;\n /** Whether it is still there because something this CLI never wrote lives in it */\n kept: boolean;\n};\n\ntype CleanupResult = {\n claudeDirRemoved: boolean;\n sourceFolder: SourceFolderCleanup;\n /** Whether the emptied `.agents-inc/` parent went with the last provider folder under it */\n sourceRootRemoved: boolean;\n /** Whether `.agents-inc/` still exists because something this CLI never wrote lives in it */\n sourceRootKept: boolean;\n /** Whether .claude/ still exists with user content after cleanup */\n claudeDirKept: boolean;\n};\n\nfunction collectConfiguredAgents(config: Partial<ProjectConfig> | null): AgentName[] {\n if (!config?.agents) return [];\n return config.agents.map((a) => a.name);\n}\n\n/** @internal Exported for testing */\nexport function getCliInstalledPluginKeys(config: Partial<ProjectConfig> | null): Set<string> {\n if (!config?.skills) return new Set();\n const { marketplaceName } = config;\n return new Set(\n config.skills.flatMap((skill) => [\n // Primary key: skill.id@skill.origin\n buildMarketplacePluginRef(skill.id, skill.origin),\n // Marketplace variant for plugins installed via marketplace where\n // skill.origin may differ (e.g., \"eject\" vs the marketplace name)\n ...(marketplaceName && skill.origin !== marketplaceName && skill.origin !== EJECT_SOURCE\n ? [buildMarketplacePluginRef(skill.id, marketplaceName)]\n : []),\n ]),\n );\n}\n\n/**\n * Loads the config that drives the removal plan. A config file that exists but\n * cannot be parsed (`ConfigLoadError`) is handed to `onLoadFailed` and\n * then treated exactly like a missing one — an unreadable config is precisely\n * when a user needs to uninstall, so it must never fail the run. Same posture as\n * the `deregister-project` mutation's call site above. Only the plan degrades: the plugins and\n * compiled agents the config named can no longer be identified, while file\n * removal proceeds. Any other failure is a real fault and still propagates.\n */\nasync function loadUninstallConfig(\n configPath: string,\n projectDir: string,\n provider: Provider,\n onLoadFailed: (failure: ConfigLoadError) => void,\n): Promise<ProjectConfig | null> {\n try {\n const result = await loadProjectConfigAt(configPath, projectDir, provider);\n return result?.config ?? null;\n } catch (error) {\n if (!(error instanceof ConfigLoadError)) throw error;\n onLoadFailed(error);\n return null;\n }\n}\n\n/** The folder's manifest files on disk, and its config read under uninstall's degrade posture. */\nasync function readSourceFolder(\n folder: Pick<ScopeSourceFolder, \"dir\" | \"relName\">,\n projectDir: string,\n provider: Provider,\n onConfigLoadFailed: (failure: ConfigLoadError) => void,\n): Promise<ScopeSourceFolder> {\n const [manifestFiles, config] = await Promise.all([\n manifestFilesIn(folder.dir),\n loadUninstallConfig(\n path.join(folder.dir, STANDARD_FILES.CONFIG_TS),\n projectDir,\n provider,\n onConfigLoadFailed,\n ),\n ]);\n return { ...folder, manifestFiles, config };\n}\n\n/** The files of the CLI config manifest — `config.ts`, `config-types.ts` — that are in `dir`. */\nasync function manifestFilesIn(dir: string): Promise<string[]> {\n const manifest = [STANDARD_FILES.CONFIG_TS, STANDARD_FILES.CONFIG_TYPES_TS];\n const present = await Promise.all(\n manifest.map((fileName) => fileExists(path.join(dir, fileName))),\n );\n return manifest.filter((_, index) => present[index]);\n}\n\n/** A config with its excluded rows dropped — the entries an uninstall acts on. */\nfunction activeConfigOf(config: ProjectConfig): ProjectConfig {\n return {\n ...config,\n skills: config.skills.filter((s) => !s.excluded),\n agents: config.agents.filter((a) => !a.excluded),\n };\n}\n\n/**\n * Detects what's installed in a project directory for uninstallation.\n *\n * Checks for plugins, local skills, agents, config directories, and\n * resolves which plugins were installed by this CLI.\n */\nasync function detectUninstallTarget(\n projectDir: string,\n onConfigLoadFailed: (failure: ConfigLoadError) => void,\n): Promise<UninstallTarget> {\n const pluginsDir = getProjectPluginsDir(projectDir);\n const { skillsDir, agentsDir } = resolveInstallPaths(projectDir);\n const claudeDir = path.join(projectDir, CLAUDE_DIR);\n const provider = providerInUse(projectDir);\n\n const [hasLocalSkills, hasLocalAgents, hasClaudeDir, sourceFolder, agentProvenance] =\n await Promise.all([\n directoryExists(skillsDir),\n directoryExists(agentsDir),\n directoryExists(claudeDir),\n readSourceFolder(\n sourceFolderInUse(projectDir, provider),\n projectDir,\n provider,\n onConfigLoadFailed,\n ),\n splitAgentsByProvenance(agentsDir, agentCodec(provider)),\n ]);\n\n const { config } = sourceFolder;\n\n let pluginNames: string[] = [];\n try {\n pluginNames = await listPluginNames(projectDir);\n } catch {\n // Best-effort: plugin detection may fail\n }\n\n const activeConfig = config === null ? null : activeConfigOf(config);\n const configuredAgents = collectConfiguredAgents(activeConfig);\n const cliInstalledKeys = getCliInstalledPluginKeys(activeConfig);\n const cliPluginNames = thisRunOwnsAnyPlugin(projectDir)\n ? pluginNames.filter((name) => cliInstalledKeys.has(name))\n : [];\n\n return {\n hasPlugins: cliPluginNames.length > 0,\n pluginNames,\n cliPluginNames,\n hasLocalSkills,\n hasLocalAgents,\n hasClaudeDir,\n pluginsDir,\n skillsDir,\n agentsDir,\n claudeDir,\n sourceFolder,\n sourceRoot: sourceRootOf(projectDir),\n config,\n registeredProjects: config?.projects ?? [],\n configuredAgents,\n agentCodec: agentCodec(provider),\n markedAgents: agentProvenance.marked,\n unmarkedAgents: agentProvenance.unmarked,\n };\n}\n\n/**\n * Whether this run has any plugin of its own to remove.\n *\n * **A PROJECT uninstall on a host that installs no project-scoped plugin has none, and asking\n * anyway is the one operation in this step that can destroy work nobody asked about.** Codex\n * installs plugins for a MACHINE: no subcommand takes a scope, `plugin add` run inside a project\n * writes the switch to the global config and silently un-scopes it, and `plugin list` answers the\n * same set from every directory. So every plugin a Codex project can see belongs to every other\n * project on that machine, and a project uninstall that swept them would take them all away.\n *\n * Read off {@link PluginHost.installsProjectScopedPlugins} rather than written as a provider\n * check, so a host added later is covered by the flag it already has to set. Claude's answer is\n * `true`, so its sweep is unchanged at both scopes — which is deliberate: whether a Claude project\n * uninstall should drop a user-scoped registration is a separate question about a host that CAN\n * hold both, and nothing here is the place to settle it.\n *\n * A GLOBAL uninstall owns the machine's plugins whatever the host, which is the case the other\n * half of this pair rests on.\n */\nfunction thisRunOwnsAnyPlugin(projectDir: string): boolean {\n if (isHomeDirectory(projectDir)) return true;\n return hostAt(projectDir).installsProjectScopedPlugins;\n}\n\nfunction shouldRemoveSkill(forkedFrom: { source?: string } | null): boolean {\n return forkedFrom !== null;\n}\n\nasync function removeMatchingSkills(\n entry: SkillsRemoval,\n onRemoved?: (dirName: string) => void,\n onSkipped?: (dirName: string) => void,\n): Promise<SkillRemovalResult> {\n const classified = await classifySkillDirs(entry.skillsDir);\n const removedNames = await removeClassifiedSkills(\n classified.toRemove,\n entry.skillsDir,\n onRemoved,\n );\n classified.toSkip.forEach((name) => onSkipped?.(name));\n const dirCleaned = await cleanupSkillsDir(entry.skillsDir, classified.toSkip.length === 0);\n\n return {\n removedCount: removedNames.length,\n skippedCount: classified.toSkip.length,\n removedNames,\n skippedNames: classified.toSkip,\n dirCleaned,\n };\n}\n\nasync function classifySkillDirs(\n skillsDir: string,\n): Promise<{ toRemove: string[]; toSkip: string[] }> {\n const dirNames = await listDirectories(skillsDir);\n const entries = await Promise.all(\n dirNames.map(async (name) => ({\n name,\n forkedFrom: await readForkedFromMetadata(path.join(skillsDir, name)),\n })),\n );\n const [removable, skippable] = partition(entries, (entry) => shouldRemoveSkill(entry.forkedFrom));\n return { toRemove: removable.map((e) => e.name), toSkip: skippable.map((e) => e.name) };\n}\n\nasync function removeClassifiedSkills(\n names: string[],\n skillsDir: string,\n onRemoved?: (name: string) => void,\n): Promise<string[]> {\n for (const name of names) {\n await remove(path.join(skillsDir, name));\n onRemoved?.(name);\n }\n return names;\n}\n\nasync function cleanupSkillsDir(dir: string, allRemoved: boolean): Promise<boolean> {\n if (!allRemoved) return false;\n return removeDirIfEmpty(dir);\n}\n\n/**\n * Removes the compiled agent files the plan's agents entry names — `.md` on Claude, `.toml` on\n * Codex, as the entry's codec says.\n *\n * A file is removed only when its basename is one the entry names; every other agent file\n * is preserved. Cleans up the agents directory if empty after removal. Which names those are\n * — the configured roster, or the files carrying the provenance marker — is the plan's\n * decision, not this function's, and so is whether to act at all: no entry, no call.\n *\n * @param onRemoved - Called for each removed agent name (for logging)\n */\nasync function removeMatchingAgents(\n entry: AgentsRemoval,\n onRemoved?: (agentName: string) => void,\n): Promise<AgentRemovalResult> {\n const agentFiles = await listAgentFiles(entry.agentsDir, entry.codec);\n const removedNames = agentFiles\n .map((agentFile) => path.basename(agentFile, entry.codec.extension))\n .filter((agentName) => entry.agentNames.includes(agentName));\n\n for (const agentName of removedNames) {\n await remove(path.join(entry.agentsDir, `${agentName}${entry.codec.extension}`));\n onRemoved?.(agentName);\n }\n\n const dirCleaned = await removeDirIfEmpty(entry.agentsDir);\n\n return {\n removedCount: removedNames.length,\n removedNames,\n dirCleaned,\n };\n}\n\n/**\n * Where a plugin is looked for FIRST when the config has no entry naming its skill.\n *\n * The scope translation swallowed this until C3 — an absent scope and a project one mapped to\n * the same Claude scope word — so the order is unchanged and only the silence is gone.\n */\nconst SCOPE_OF_AN_UNRECORDED_SKILL: SkillScope = \"project\";\n\n/**\n * Every scope this host installs plugins at, the config's own answer first.\n *\n * A plugin's registered scope is genuinely ambiguous here — a skill re-scoped after install is\n * filed under the scope it was installed at, not the one the config now names — so a removal\n * asks at each scope rather than trusting one. The best-effort sweep this replaces did it with\n * two hard-coded Claude scope words and no way to answer for another host; reading the roster off\n * {@link PluginHost.offeredPlacements} is the same sweep with the host deciding its width, which\n * for a host that installs plugins globally only is one scope rather than two.\n */\nfunction pluginScopesToSweep(host: PluginHost, primary: SkillScope): SkillScope[] {\n const offered = host.offeredPlacements\n .filter((placement) => placement.mode === \"plugin\")\n .map((placement) => placement.scope);\n\n if (!offered.includes(primary)) return offered;\n return [primary, ...offered.filter((scope) => scope !== primary)];\n}\n\n/**\n * Drops a plugin's registration wherever the host filed it, and says whether one was there.\n *\n * Best-effort on the FAILURE side and unchanged by C3: the directory is deleted either way, so a\n * registration that cannot be dropped is untidy rather than wrong, and a thrown error here would\n * abort an uninstall that has already removed files. What changed is that it no longer reports\n * NOTHING — {@link PluginHost.uninstallPlugin} answers `removed` or `absent`, and that answer is\n * the only thing on this path that knows the difference.\n *\n * `removed` if ANY swept scope had one, because the sweep exists precisely for a plugin filed\n * under a scope the config no longer names: one registration dropped at either scope is one\n * removal, not two, and not none.\n */\nasync function removePluginWhereverItIsFiled(\n host: PluginHost,\n pluginRef: string,\n primaryScope: SkillScope,\n projectDir: string,\n): Promise<PluginRemovalOutcome> {\n let outcome: PluginRemovalOutcome = \"absent\";\n\n for (const scope of pluginScopesToSweep(host, primaryScope)) {\n try {\n const swept = await host.uninstallPlugin(pluginRef, scope, projectDir);\n if (swept === \"removed\") outcome = \"removed\";\n } catch {\n // Best-effort: the plugin may not be registered at this scope. A throw is neither outcome\n // — the host reserves it for a failure that is not \"there was nothing there\" — so it leaves\n // `outcome` exactly as the other scopes found it rather than claiming an absence.\n }\n }\n\n return outcome;\n}\n\n/**\n * Uninstalls the plugins the plan's plugins entry names, by removing them from the\n * host and deleting their local directories. The entry decides WHICH plugins go;\n * `config` only answers HOW to ask the host — the scope its registry filed each\n * one under, per-skill where the config says, project-level otherwise.\n *\n * @param onUninstalled - Called for each successfully uninstalled plugin name (for logging)\n * @internal Exported for testing\n */\nexport async function uninstallPlugins(\n entry: PluginRemoval,\n config: ProjectConfig | null,\n projectDir: string,\n onUninstalled?: (pluginName: string) => void,\n): Promise<UninstallPluginsResult> {\n const host = hostAt(projectDir);\n const hostAvailable = await host.isAvailable();\n const observed: string[] = [];\n\n for (const pluginName of entry.names) {\n const removal = hostAvailable\n ? await removePluginWhereverItIsFiled(\n host,\n pluginName,\n scopeThePluginIsFiledUnder(config, pluginName),\n projectDir,\n )\n : nothingWasObserved();\n\n await remove(path.join(entry.pluginsDir, pluginName));\n\n if (removal === \"removed\") {\n observed.push(pluginName);\n onUninstalled?.(pluginName);\n }\n }\n\n // What was OBSERVED, never what was asked for — D11(b), and the one Claude-visible change this\n // step signs off. On Codex `plugin remove` exits 0 and prints the same document for a plugin\n // that was never installed, one that really went and one removed twice, so a count built from\n // the plan is a count of intentions: it says \"Uninstalled 1 plugin\" for a plugin this command\n // did not touch. The listing the host takes BEFORE its removal is the only thing that knows.\n return {\n uninstalledNames: observed,\n totalUninstalled: observed.length,\n };\n}\n\n/**\n * The scope the host's registry most likely filed this plugin under, from the config's own row.\n *\n * The sweep beside it asks at every scope the host installs plugins at, so this is a PREFERENCE\n * rather than an answer: a skill re-scoped after install is filed under the scope it was installed\n * at, not the one the config now names.\n */\nfunction scopeThePluginIsFiledUnder(config: ProjectConfig | null, pluginName: string): SkillScope {\n const skillId = parseMarketplacePluginRef(pluginName);\n return (\n config?.skills.find((skill) => skill.id === skillId)?.scope ?? SCOPE_OF_AN_UNRECORDED_SKILL\n );\n}\n\n/**\n * What a run with no host binary observed, which is nothing — the OTHER half of D11(b).\n *\n * This command used to count every plugin it MEANT to remove, so a machine where the host is not\n * installed reported a clean sweep of registrations nothing had touched. An unreachable host is\n * not a failure — the directory is still deleted and the uninstall still completes — so it is an\n * outcome rather than a throw, and the outcome is that nothing was seen to go.\n */\nfunction nothingWasObserved(): PluginRemovalOutcome {\n return \"absent\";\n}\n\n/**\n * Removes exactly the manifest files the plan's config entry names from the source folder,\n * then the source folder itself when it has nothing else left. User-owned content in it\n * (e.g. ejected templates) keeps the directory alive. No entry\n * means the plan promised no manifest removal, so none is made.\n */\nasync function removeConfigManifest(\n entry: ConfigManifestRemoval | undefined,\n): Promise<{ manifestRemoved: boolean; dirRemoved: boolean }> {\n if (!entry) return { manifestRemoved: false, dirRemoved: false };\n\n await Promise.all(\n entry.fileNames.map((fileName) => remove(path.join(entry.sourceDir, fileName))),\n );\n\n return { manifestRemoved: true, dirRemoved: await removeDirIfEmpty(entry.sourceDir) };\n}\n\n/**\n * Removes the CLI config manifest the plan named, then cleans up the emptied .claude/\n * directory and the emptied source folder.\n *\n * The directories themselves are not plan entries — they are the user's, and are removed\n * only when nothing of theirs is left in them once the CLI-managed contents are gone.\n */\nasync function cleanupEmptyDirs(\n target: Pick<UninstallTarget, \"hasClaudeDir\" | \"claudeDir\" | \"sourceRoot\" | \"sourceFolder\">,\n manifestEntries: readonly ConfigManifestRemoval[],\n): Promise<CleanupResult> {\n const sourceFolder = await cleanupSourceFolder(\n target.sourceFolder,\n manifestEntries.find((entry) => entry.sourceDir === target.sourceFolder.dir),\n );\n const sourceRoot = await cleanupSourceRoot(target.sourceRoot, sourceFolder.dirRemoved);\n\n const claudeDirRemoved = target.hasClaudeDir && (await removeDirIfEmpty(target.claudeDir));\n // Nothing else removes .claude itself, so \"kept\" is exactly \"present but not removed\".\n const claudeDirKept =\n !claudeDirRemoved && target.hasClaudeDir && (await directoryExists(target.claudeDir));\n\n return {\n claudeDirRemoved,\n sourceFolder,\n sourceRootRemoved: sourceRoot.removed,\n sourceRootKept: sourceRoot.kept,\n claudeDirKept,\n };\n}\n\n/**\n * The source folder, once the plan's manifest files are out of it: removed when nothing is left,\n * kept when the user's own content is. A folder the plan named no file in is still removed when it\n * is empty — it is the product's name on a folder holding nothing.\n */\nasync function cleanupSourceFolder(\n folder: ScopeSourceFolder,\n entry: ConfigManifestRemoval | undefined,\n): Promise<SourceFolderCleanup> {\n const manifest = await removeConfigManifest(entry);\n const dirRemoved = manifest.dirRemoved || (await removeDirIfEmpty(folder.dir));\n const kept = !dirRemoved && (await directoryExists(folder.dir));\n return {\n relName: folder.relName,\n manifestRemoved: manifest.manifestRemoved,\n dirRemoved,\n kept,\n };\n}\n\n/** What a cleanup pass did with the `.agents-inc/` parent: took it away, or deliberately left it. */\ntype SourceRootCleanup = { removed: boolean; kept: boolean };\n\n/**\n * The provider folder's `.agents-inc/` parent, once the folder under it has gone.\n *\n * Cleanup above is leaf-first — `removeDirIfEmpty` on the folder the config was in — so nothing\n * looks one level up, and `.agents-inc/` left standing is a directory naming this product with\n * nothing installed under it. A later `init` and `doctor` both read that as a half-built layout.\n *\n * Only when it is empty, and only through the same `removeDirIfEmpty` the leaf uses: a consuming\n * repository keeps its own state under this parent — the gate's `baseline.json` is the live case\n * — and that state is not this CLI's to remove. Keeping it is then SAID rather than done in\n * silence, because a directory left where an install was reads as one the uninstall forgot.\n *\n * Attempted only when the provider folder itself went: while that folder is still there the\n * parent is not empty for a reason the user has already been told about, and a second line about\n * it would name the wrong cause.\n */\nasync function cleanupSourceRoot(\n sourceRoot: SourceRoot,\n providerDirRemoved: boolean,\n): Promise<SourceRootCleanup> {\n if (!providerDirRemoved) return { removed: false, kept: false };\n\n const removed = await removeDirIfEmpty(sourceRoot.dir);\n return { removed, kept: !removed && (await directoryExists(sourceRoot.dir)) };\n}\n\nasync function listAgentFiles(agentsDir: string, codec: AgentCodec): Promise<string[]> {\n try {\n return await listAgentFilesOf(agentsDir, codec);\n } catch {\n return [];\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,18 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  unofferablePlacementsFound
4
- } from "../chunk-KTLKR4YY.js";
4
+ } from "../chunk-WTWMGH2C.js";
5
5
  import {
6
6
  providerFlag,
7
7
  providerNamedBy,
8
8
  refuseAnAmbiguousInstallation
9
- } from "../chunk-HR7TK2XW.js";
9
+ } from "../chunk-RIPSGNLU.js";
10
10
  import {
11
11
  BaseCommand,
12
12
  EXIT_CODES
13
- } from "../chunk-H4ZKGGTE.js";
14
- import "../chunk-VVRI35SK.js";
15
- import "../chunk-CMJBKQUH.js";
13
+ } from "../chunk-V3KTSP3Q.js";
14
+ import "../chunk-CLPUWOSA.js";
16
15
  import {
17
16
  ERROR_MESSAGES,
18
17
  INFO_MESSAGES,
@@ -23,12 +22,12 @@ import {
23
22
  marketplaceRefreshFailed,
24
23
  marketplacesRefreshFailed,
25
24
  marketplacesRefreshed
26
- } from "../chunk-C2UISKIM.js";
27
- import "../chunk-NU2K5G54.js";
25
+ } from "../chunk-MQ6CGZNJ.js";
26
+ import "../chunk-52HDHJ7I.js";
28
27
  import {
29
28
  EJECT_SOURCE
30
- } from "../chunk-AG3ZVM3B.js";
31
- import "../chunk-OI2H6M5I.js";
29
+ } from "../chunk-PJWGTNOX.js";
30
+ import "../chunk-NQW25DCF.js";
32
31
 
33
32
  // src/cli/commands/update.ts
34
33
  import { unique } from "remeda";