agents-inc 0.170.0 → 0.170.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/{chunk-BMRUNMPV.js → chunk-2YA4DDRM.js} +35 -35
  3. package/dist/chunk-2YA4DDRM.js.map +1 -0
  4. package/dist/{chunk-44TE3RKH.js → chunk-3ED3KHHD.js} +5 -4
  5. package/dist/chunk-3ED3KHHD.js.map +1 -0
  6. package/dist/{chunk-3G7X2WKI.js → chunk-5BNX73OU.js} +18 -38
  7. package/dist/chunk-5BNX73OU.js.map +1 -0
  8. package/dist/{chunk-7RDMTR64.js → chunk-66AEVERM.js} +785 -256
  9. package/dist/chunk-66AEVERM.js.map +1 -0
  10. package/dist/{chunk-PJWGTNOX.js → chunk-7C3PFRBU.js} +144 -186
  11. package/dist/chunk-7C3PFRBU.js.map +1 -0
  12. package/dist/{chunk-52HDHJ7I.js → chunk-AUNRHWH4.js} +32 -21
  13. package/dist/chunk-AUNRHWH4.js.map +1 -0
  14. package/dist/{chunk-KOLDW6SF.js → chunk-BBDGYJHX.js} +2 -2
  15. package/dist/chunk-BBDGYJHX.js.map +1 -0
  16. package/dist/{chunk-SZYQISA2.js → chunk-ELV5WDS3.js} +43 -29
  17. package/dist/chunk-ELV5WDS3.js.map +1 -0
  18. package/dist/{chunk-MQ6CGZNJ.js → chunk-GAKVZNTX.js} +1879 -2019
  19. package/dist/chunk-GAKVZNTX.js.map +1 -0
  20. package/dist/chunk-GEMW5RQ2.js +1169 -0
  21. package/dist/chunk-GEMW5RQ2.js.map +1 -0
  22. package/dist/{chunk-NQW25DCF.js → chunk-GHHJYNAN.js} +1 -7
  23. package/dist/chunk-GHHJYNAN.js.map +1 -0
  24. package/dist/{chunk-WTWMGH2C.js → chunk-HACRR422.js} +2 -2
  25. package/dist/chunk-HACRR422.js.map +1 -0
  26. package/dist/{chunk-F2BHT5C4.js → chunk-IWY5IR4F.js} +129 -151
  27. package/dist/chunk-IWY5IR4F.js.map +1 -0
  28. package/dist/{chunk-JFLVEFFP.js → chunk-KIK5EZYD.js} +1 -1
  29. package/dist/chunk-KIK5EZYD.js.map +1 -0
  30. package/dist/{chunk-USVBYQSQ.js → chunk-O4LN3HFI.js} +16 -6
  31. package/dist/chunk-O4LN3HFI.js.map +1 -0
  32. package/dist/{chunk-RIPSGNLU.js → chunk-OCAVW6N5.js} +3 -3
  33. package/dist/chunk-OCAVW6N5.js.map +1 -0
  34. package/dist/{chunk-4AAOOAIN.js → chunk-P6CYS4JK.js} +2 -2
  35. package/dist/chunk-P6CYS4JK.js.map +1 -0
  36. package/dist/chunk-PLFANHRZ.js +35 -0
  37. package/dist/chunk-PLFANHRZ.js.map +1 -0
  38. package/dist/{chunk-6ZRH5RHC.js → chunk-PMUZR4XO.js} +4 -4
  39. package/dist/chunk-PMUZR4XO.js.map +1 -0
  40. package/dist/{chunk-F5EBEATU.js → chunk-PSUTNOQ2.js} +34 -46
  41. package/dist/chunk-PSUTNOQ2.js.map +1 -0
  42. package/dist/chunk-RIK45Q4I.js +103 -0
  43. package/dist/chunk-RIK45Q4I.js.map +1 -0
  44. package/dist/{chunk-HED2DAXZ.js → chunk-THO7LO5B.js} +5 -5
  45. package/dist/chunk-THO7LO5B.js.map +1 -0
  46. package/dist/{chunk-OYJQUA3D.js → chunk-YGDWHDOY.js} +31 -13
  47. package/dist/chunk-YGDWHDOY.js.map +1 -0
  48. package/dist/{chunk-NCWDK6L7.js → chunk-YXKTY5WM.js} +254 -379
  49. package/dist/chunk-YXKTY5WM.js.map +1 -0
  50. package/dist/{chunk-NEZ4OAOL.js → chunk-ZEGM3MVR.js} +3 -5
  51. package/dist/chunk-ZEGM3MVR.js.map +1 -0
  52. package/dist/commands/build/marketplace.js +46 -46
  53. package/dist/commands/build/marketplace.js.map +1 -1
  54. package/dist/commands/build/plugins.js +25 -28
  55. package/dist/commands/build/plugins.js.map +1 -1
  56. package/dist/commands/compile.js +87 -178
  57. package/dist/commands/compile.js.map +1 -1
  58. package/dist/commands/doctor.js +510 -360
  59. package/dist/commands/doctor.js.map +1 -1
  60. package/dist/commands/edit.js +451 -357
  61. package/dist/commands/edit.js.map +1 -1
  62. package/dist/commands/eject.js +144 -125
  63. package/dist/commands/eject.js.map +1 -1
  64. package/dist/commands/init.js +20 -27
  65. package/dist/commands/list.js +39 -55
  66. package/dist/commands/list.js.map +1 -1
  67. package/dist/commands/new/marketplace.js +6 -11
  68. package/dist/commands/new/marketplace.js.map +1 -1
  69. package/dist/commands/search.js +35 -32
  70. package/dist/commands/search.js.map +1 -1
  71. package/dist/commands/share.js +17 -77
  72. package/dist/commands/share.js.map +1 -1
  73. package/dist/commands/uninstall.js +159 -222
  74. package/dist/commands/uninstall.js.map +1 -1
  75. package/dist/commands/update.js +9 -32
  76. package/dist/commands/update.js.map +1 -1
  77. package/dist/components/common/prompt-confirm.js +2 -2
  78. package/dist/components/common/removal-plan-confirm.js +2 -2
  79. package/dist/components/common/scroll-affordance.js +2 -2
  80. package/dist/components/common/select-list.js +2 -2
  81. package/dist/components/common/skill-agent-summary.js +4 -4
  82. package/dist/components/common/spinner.js +2 -4
  83. package/dist/components/common/summary-panel.js +5 -5
  84. package/dist/components/common/summary-panel.js.map +1 -1
  85. package/dist/config-exports.js +7 -3
  86. package/dist/config-exports.js.map +1 -1
  87. package/dist/hooks/init.js +18 -19
  88. package/dist/hooks/init.js.map +1 -1
  89. package/dist/index.js.map +1 -1
  90. package/dist/load-agent-defs-6QUUMGHK.js +14 -0
  91. package/dist/recompile-project-agents-I33KQJFB.js +65 -0
  92. package/dist/recompile-project-agents-I33KQJFB.js.map +1 -0
  93. package/dist/{source-loader-C5SQXZXI.js → source-loader-TTJVW2ER.js} +5 -5
  94. package/dist/src/agents/developer/ai-developer/playbook.md +2 -5
  95. package/dist/src/agents/developer/api-developer/playbook.md +2 -5
  96. package/dist/src/agents/developer/cli-developer/playbook.md +2 -5
  97. package/dist/src/agents/developer/web-developer/playbook.md +2 -5
  98. package/dist/src/agents/meta/agent-summoner/output.md +2 -2
  99. package/dist/src/agents/meta/agent-summoner/playbook.md +1 -13
  100. package/dist/src/agents/meta/codex-keeper/critical-requirements.md +2 -5
  101. package/dist/src/agents/meta/convention-keeper/critical-requirements.md +2 -4
  102. package/dist/src/agents/tester/ai-tester/critical-requirements.md +1 -2
  103. package/dist/src/agents/tester/ai-tester/playbook.md +2 -0
  104. package/dist/src/agents/tester/api-tester/critical-requirements.md +1 -2
  105. package/dist/src/agents/tester/api-tester/playbook.md +2 -0
  106. package/dist/src/agents/tester/cli-tester/critical-requirements.md +1 -2
  107. package/dist/src/agents/tester/cli-tester/playbook.md +2 -0
  108. package/dist/src/agents/tester/web-tester/critical-requirements.md +1 -2
  109. package/dist/src/agents/tester/web-tester/playbook.md +2 -0
  110. package/package.json +9 -8
  111. package/src/agents/developer/ai-developer/playbook.md +2 -5
  112. package/src/agents/developer/api-developer/playbook.md +2 -5
  113. package/src/agents/developer/cli-developer/playbook.md +2 -5
  114. package/src/agents/developer/web-developer/playbook.md +2 -5
  115. package/src/agents/meta/agent-summoner/output.md +2 -2
  116. package/src/agents/meta/agent-summoner/playbook.md +1 -13
  117. package/src/agents/meta/codex-keeper/critical-requirements.md +2 -5
  118. package/src/agents/meta/convention-keeper/critical-requirements.md +2 -4
  119. package/src/agents/tester/ai-tester/critical-requirements.md +1 -2
  120. package/src/agents/tester/ai-tester/playbook.md +2 -0
  121. package/src/agents/tester/api-tester/critical-requirements.md +1 -2
  122. package/src/agents/tester/api-tester/playbook.md +2 -0
  123. package/src/agents/tester/cli-tester/critical-requirements.md +1 -2
  124. package/src/agents/tester/cli-tester/playbook.md +2 -0
  125. package/src/agents/tester/web-tester/critical-requirements.md +1 -2
  126. package/src/agents/tester/web-tester/playbook.md +2 -0
  127. package/src/schemas/project-config.schema.json +0 -3
  128. package/src/schemas/project-source-config.schema.json +0 -6
  129. package/dist/chunk-3G7X2WKI.js.map +0 -1
  130. package/dist/chunk-44TE3RKH.js.map +0 -1
  131. package/dist/chunk-4AAOOAIN.js.map +0 -1
  132. package/dist/chunk-52HDHJ7I.js.map +0 -1
  133. package/dist/chunk-6HNXYYVF.js +0 -373
  134. package/dist/chunk-6HNXYYVF.js.map +0 -1
  135. package/dist/chunk-6ZRH5RHC.js.map +0 -1
  136. package/dist/chunk-7RDMTR64.js.map +0 -1
  137. package/dist/chunk-BMRUNMPV.js.map +0 -1
  138. package/dist/chunk-CLPUWOSA.js +0 -46
  139. package/dist/chunk-CLPUWOSA.js.map +0 -1
  140. package/dist/chunk-F2BHT5C4.js.map +0 -1
  141. package/dist/chunk-F5EBEATU.js.map +0 -1
  142. package/dist/chunk-HED2DAXZ.js.map +0 -1
  143. package/dist/chunk-JFLVEFFP.js.map +0 -1
  144. package/dist/chunk-KOLDW6SF.js.map +0 -1
  145. package/dist/chunk-MQ6CGZNJ.js.map +0 -1
  146. package/dist/chunk-NCWDK6L7.js.map +0 -1
  147. package/dist/chunk-NEZ4OAOL.js.map +0 -1
  148. package/dist/chunk-NIHUOWE4.js +0 -106
  149. package/dist/chunk-NIHUOWE4.js.map +0 -1
  150. package/dist/chunk-NQW25DCF.js.map +0 -1
  151. package/dist/chunk-OYJQUA3D.js.map +0 -1
  152. package/dist/chunk-PJWGTNOX.js.map +0 -1
  153. package/dist/chunk-RIPSGNLU.js.map +0 -1
  154. package/dist/chunk-SZYQISA2.js.map +0 -1
  155. package/dist/chunk-USVBYQSQ.js.map +0 -1
  156. package/dist/chunk-V3KTSP3Q.js +0 -940
  157. package/dist/chunk-V3KTSP3Q.js.map +0 -1
  158. package/dist/chunk-WTWMGH2C.js.map +0 -1
  159. package/dist/load-agent-defs-YMDWBFDP.js +0 -14
  160. package/dist/recompile-project-agents-SZ76WNTW.js +0 -18
  161. package/dist/source-loader-C5SQXZXI.js.map +0 -1
  162. /package/dist/{load-agent-defs-YMDWBFDP.js.map → load-agent-defs-6QUUMGHK.js.map} +0 -0
  163. /package/dist/{recompile-project-agents-SZ76WNTW.js.map → source-loader-TTJVW2ER.js.map} +0 -0
@@ -1 +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: 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
+ {"version":3,"sources":["../../src/cli/commands/uninstall.tsx"],"sourcesContent":["import React from \"react\";\nimport { unique } 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 { plural } from \"../utils/string\";\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 { type ForkedFromMetadata, readForkedFromMetadata } from \"../lib/skills/index\";\nimport { isHomeDirectory, resolveInstallPaths } from \"../lib/installation/index\";\nimport {\n lazyGateDeps,\n mutateGlobal,\n propagateGlobalRemoval,\n type GateReport,\n} 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 pluginLeftRegistered,\n unmarkedAgentsKept,\n registeredProjectsUpdated,\n registeredProjectUpdateSkipped,\n registeredProjectsUpdateFailed,\n uninstallConfigUnreadable,\n uninstallNeedsTerminal,\n uninstallSkillMetadataUnreadable,\n} from \"../utils/messages\";\nimport type { AgentDefinition, AgentName, MergedSkillsMatrix, ProjectConfig } from \"../types/index\";\nimport type { SkillConfig, SkillScope } from \"../types/config\";\n\n/** Paths and names, never display strings, so the preview and the removal read one value. */\ntype RemovalEntry =\n | { kind: \"plugins\"; pluginsDir: string; names: string[] }\n | { kind: \"skills\"; skillsDir: string; skillNames: string[] }\n | { kind: \"agents\"; agentsDir: string; agentNames: string[]; codec: AgentCodec }\n | { kind: \"config\"; sourceDir: string; fileNames: string[] };\n\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\ntype RemovalPlan = {\n entries: RemovalEntry[];\n kept: string[];\n};\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\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.cliSkills.length === 0) return [];\n return [{ kind: \"skills\", skillsDir: target.skillsDir, skillNames: [...target.cliSkills] }];\n}\n\nfunction compiledAgentsEntry(target: UninstallTarget): AgentsRemoval[] {\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 * Only those with a file here, so the plan names no removal the run will not make: the config's\n * roster (pre-marker installs carry no marker) and every provenance-marked file, each once.\n */\nfunction identifiableAgents(target: UninstallTarget): string[] {\n const agentFiles = new Set([...target.markedAgents, ...target.unmarkedAgents]);\n const configuredWithAFile = target.configuredAgents.filter((name) => agentFiles.has(name));\n return unique([...configuredWithAFile, ...target.markedAgents]);\n}\n\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/** Derived from the removal, so no agent can be both removed and kept. */\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\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\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\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/** Skills and compiled agents share a heading; a heading with no items is not printed. */\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\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 /** Replaced in `run` before anything prints. */\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 protected async ensureTerminalSize(): Promise<void> {\n // Any size: uninstall is the way out of every state.\n }\n\n async run(): Promise<void> {\n const { flags } = await this.parse(Uninstall);\n const projectDir = process.cwd();\n const namedProvider = providerNamedBy(flags.provider);\n\n // Before anything is detected: removing the wrong one of two installations is destructive.\n await refuseAnAmbiguousInstallation(projectDir, \"uninstall\", namedProvider, (message) =>\n 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, {\n onConfigLoadFailed: (failure) => this.warn(uninstallConfigUnreadable(failure)),\n // Aborts: guessing would either remove the user's skill or keep this CLI's.\n onSkillMetadataUnreadable: (metadataPath) =>\n this.error(uninstallSkillMetadataUnreadable(metadataPath), { exit: EXIT_CODES.ERROR }),\n });\n const plan = buildRemovalPlan(target);\n if (!hasAnythingToRemove(plan)) {\n this.reportNothingToUninstall();\n return;\n }\n\n const approved = await this.removalApproved(plan, flags.yes, namedProvider);\n if (!approved) this.cancelUninstall();\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 async removalApproved(\n plan: RemovalPlan,\n skipConfirmation: boolean,\n namedProvider: Provider | undefined,\n ): Promise<boolean> {\n if (!skipConfirmation) return this.confirmRemoval(plan, namedProvider);\n\n this.printRemovalPlan(plan);\n return true;\n }\n\n private cancelUninstall(): never {\n this.log(\"\");\n this.log(\"Uninstall cancelled\");\n this.exit(EXIT_CODES.CANCELLED);\n }\n\n private printRemovalPlan(plan: RemovalPlan): void {\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 }\n\n private async confirmRemoval(\n plan: RemovalPlan,\n namedProvider: Provider | undefined,\n ): Promise<boolean> {\n // A confirm nobody can answer must never become a yes.\n if (!process.stdin.isTTY) {\n this.error(uninstallNeedsTerminal(namedProvider), { 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 // Before any removal: the registry and source live in the global config about to be deleted.\n const propagation = isGlobalUninstall\n ? await this.prepareGlobalPropagation(target, projectDir)\n : null;\n\n await this.removePlannedPlugins(planEntry(plan, \"plugins\"), target.config, projectDir);\n await this.removeLocalFilesOrFail(plan, target);\n\n if (!isGlobalUninstall) {\n await this.deregisterProject(projectDir);\n return;\n }\n if (propagation) await this.updateRegisteredProjects(propagation);\n }\n\n /** Degrades: a missing or corrupt global config must never fail the uninstall. */\n private async deregisterProject(projectDir: string): Promise<void> {\n try {\n // Lazy deps: deregistering inlines nothing, so 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 * A load failure warns and never aborts. `skipExtraSources` cannot change the output (extra\n * sources only annotate skills) and keeps the uninstall off the network.\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 /** Runs after the global manifest is gone, so regenerated types use the standalone form. */\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 this.reportRegisteredProjectsUpdate(report);\n } catch (error) {\n this.warn(registeredProjectsUpdateFailed(getErrorMessage(error)));\n }\n }\n\n private reportRegisteredProjectsUpdate(report: GateReport): void {\n const { updated, unreadable, failed } = report.propagated;\n this.reportProjectsOutOfReach(report.propagated);\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 this.reportPropagatedRecompile(report);\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 private async removeLocalFilesOrFail(plan: RemovalPlan, target: UninstallTarget): Promise<void> {\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\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 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 this.reportDirCleanup(target.sourceRoot.relName, cleanup.sourceRoot);\n this.reportDirCleanup(CLAUDE_DIR, cleanup.claudeDir);\n }\n\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 private reportDirCleanup(relName: string, cleanup: DirCleanup): void {\n if (cleanup.removed) {\n this.logSuccess(`Removed ${relName}/`);\n return;\n }\n if (cleanup.kept) {\n this.log(`Kept ${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, {\n onUninstalled: (name) => this.log(` Uninstalled plugin '${name}'`),\n onLeftRegistered: (name, provider) => this.warn(pluginLeftRegistered(name, provider)),\n });\n\n // Silent on zero: \"0\" beside a plan that named one reads as a failure, and is not one.\n if (pluginResult.totalUninstalled > 0) {\n this.logSuccess(`Uninstalled ${plural(pluginResult.totalUninstalled, \"plugin\")}`);\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 removedNames = await removeMatchingSkills(entry, (dirName) =>\n this.log(` Uninstalled skill '${dirName}'`),\n );\n if (removedNames.length === 0) return;\n\n this.logSuccess(`Removed ${plural(removedNames.length, \"CLI-installed skill\")}`);\n }\n\n private async removePlannedAgents(entry: AgentsRemoval | undefined): Promise<void> {\n if (!entry) return;\n\n const removedNames = await removeMatchingAgents(entry, (agentName) =>\n this.log(` Uninstalled agent '${agentName}'`),\n );\n if (removedNames.length === 0) return;\n\n this.logSuccess(`Removed ${plural(removedNames.length, \"compiled agent\")}`);\n }\n}\n\ntype UninstallTarget = {\n hasPlugins: boolean;\n /** Only those this CLI installed, matched against config skills. */\n cliPluginNames: string[];\n /** Carrying `forkedFrom`: this CLI installed them. The rest are the user's, never removed. */\n cliSkills: string[];\n hasClaudeDir: boolean;\n pluginsDir: string;\n skillsDir: string;\n agentsDir: string;\n agentCodec: AgentCodec;\n claudeDir: string;\n sourceFolder: ScopeSourceFolder;\n sourceRoot: SourceRoot;\n config: ProjectConfig | null;\n registeredProjects: string[];\n configuredAgents: AgentName[];\n markedAgents: string[];\n /** The user's, unless the config claims them. */\n unmarkedAgents: string[];\n};\n\ntype ScopeSourceFolder = {\n dir: string;\n relName: string;\n manifestFiles: string[];\n /** `null` when absent or unreadable. */\n config: ProjectConfig | null;\n};\n\n/** Captured before removal: all of it comes from the config and folder being deleted. */\ntype GlobalPropagationData = {\n globalConfig: ProjectConfig;\n matrix: MergedSkillsMatrix;\n agents: Partial<Record<AgentName, AgentDefinition>>;\n provider: Provider;\n};\n\ntype UninstallPluginsResult = {\n totalUninstalled: number;\n};\n\ntype PluginRemovalReporter = {\n onUninstalled?: (pluginName: string) => void;\n /** No host CLI to ask, so the plugin's registration outlives its folder. */\n onLeftRegistered?: (pluginName: string, provider: Provider) => void;\n};\n\ntype SourceFolderCleanup = {\n relName: string;\n manifestRemoved: boolean;\n dirRemoved: boolean;\n /** Still there because of content this CLI never wrote. */\n kept: boolean;\n};\n\ntype DirCleanup = { removed: boolean; kept: boolean };\n\ntype CleanupResult = {\n sourceFolder: SourceFolderCleanup;\n sourceRoot: DirCleanup;\n claudeDir: DirCleanup;\n};\n\nfunction collectConfiguredAgents(config: Partial<ProjectConfig> | null): AgentName[] {\n if (!config?.agents) return [];\n return config.agents.map((a) => a.name);\n}\n\nfunction getCliInstalledPluginKeys(config: Partial<ProjectConfig> | null): Set<string> {\n if (!config?.skills) return new Set();\n const { marketplaceName } = config;\n return new Set(config.skills.flatMap((skill) => pluginRefsOf(skill, marketplaceName)));\n}\n\nfunction pluginRefsOf(skill: SkillConfig, marketplaceName: string | undefined): string[] {\n const ownRef = buildMarketplacePluginRef(skill.id, skill.origin);\n if (!marketplaceName || skill.origin === marketplaceName || skill.origin === EJECT_SOURCE) {\n return [ownRef];\n }\n // Installed via the marketplace while `origin` names something else.\n return [ownRef, buildMarketplacePluginRef(skill.id, marketplaceName)];\n}\n\n/**\n * Degrades: a `ConfigLoadError` is reported and treated as a missing config, since that is when a\n * user needs to uninstall. Any other failure 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/** Its config is read under {@link loadUninstallConfig}'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\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\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\ntype DetectionFailureHandlers = {\n /** Degrades: see {@link loadUninstallConfig}. */\n onConfigLoadFailed: (failure: ConfigLoadError) => void;\n onSkillMetadataUnreadable: (metadataPath: string) => never;\n};\n\nasync function detectUninstallTarget(\n projectDir: string,\n handlers: DetectionFailureHandlers,\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 const codec = agentCodec(provider);\n\n const [cliSkills, hasClaudeDir, sourceFolder, agentProvenance] = await Promise.all([\n skillsThisCliInstalled(skillsDir, handlers.onSkillMetadataUnreadable),\n directoryExists(claudeDir),\n readSourceFolder(\n sourceFolderInUse(projectDir, provider),\n projectDir,\n provider,\n handlers.onConfigLoadFailed,\n ),\n splitAgentsByProvenance(agentsDir, codec),\n ]);\n\n const { config } = sourceFolder;\n const activeConfig = config === null ? null : activeConfigOf(config);\n const cliPluginNames = await pluginsThisRunRemoves(projectDir, activeConfig);\n\n return {\n hasPlugins: cliPluginNames.length > 0,\n cliPluginNames,\n cliSkills,\n hasClaudeDir,\n pluginsDir,\n skillsDir,\n agentsDir,\n claudeDir,\n sourceFolder,\n sourceRoot: sourceRootOf(projectDir),\n config,\n registeredProjects: config?.projects ?? [],\n configuredAgents: collectConfiguredAgents(activeConfig),\n agentCodec: codec,\n markedAgents: agentProvenance.marked,\n unmarkedAgents: agentProvenance.unmarked,\n };\n}\n\nasync function pluginsThisRunRemoves(\n projectDir: string,\n activeConfig: ProjectConfig | null,\n): Promise<string[]> {\n const installed = await listPluginNames(projectDir);\n if (!thisRunOwnsAnyPlugin(projectDir)) return [];\n\n const cliInstalledKeys = getCliInstalledPluginKeys(activeConfig);\n return installed.filter((name) => cliInstalledKeys.has(name));\n}\n\n/**\n * A host without project-scoped plugins (Codex) shares them machine-wide, so a project uninstall\n * there must not sweep them. A global uninstall owns them on any host.\n */\nfunction thisRunOwnsAnyPlugin(projectDir: string): boolean {\n if (isHomeDirectory(projectDir)) return true;\n return hostAt(projectDir).installsProjectScopedPlugins;\n}\n\n/** Read before the plan is built, so the plan and the removal read one classification. */\nasync function skillsThisCliInstalled(\n skillsDir: string,\n onMetadataUnreadable: (metadataPath: string) => never,\n): Promise<string[]> {\n const dirNames = await listDirectories(skillsDir);\n const skills = await Promise.all(\n dirNames.map(async (name) => ({\n name,\n forkedFrom: await readSkillProvenance(skillsDir, name, onMetadataUnreadable),\n })),\n );\n return skills.filter((skill) => skill.forkedFrom !== null).map((skill) => skill.name);\n}\n\n/**\n * A metadata.yaml that reads but does not parse is the user's (warned); one found but unreadable\n * aborts. A skill folder that cannot be opened hides its metadata.yaml (`fileExists` answers\n * false on EACCES), so that skill reads as the user's and is kept, even when this CLI made it.\n */\nasync function readSkillProvenance(\n skillsDir: string,\n dirName: string,\n onUnreadable: (metadataPath: string) => never,\n): Promise<ForkedFromMetadata | null> {\n const skillDir = path.join(skillsDir, dirName);\n try {\n return await readForkedFromMetadata(skillDir);\n } catch {\n // Cause dropped on purpose: the stop names the file and the fix only, and uninstall has no\n // --verbose to carry it.\n return onUnreadable(path.join(skillDir, STANDARD_FILES.METADATA_YAML));\n }\n}\n\n/** The planned skills still there: the count is what the run removed, not what it planned. */\nasync function removeMatchingSkills(\n entry: SkillsRemoval,\n onRemoved: (dirName: string) => void,\n): Promise<string[]> {\n const present = await listDirectories(entry.skillsDir);\n const removedNames = present.filter((name) => entry.skillNames.includes(name));\n\n for (const name of removedNames) {\n await remove(path.join(entry.skillsDir, name));\n onRemoved(name);\n }\n return removedNames;\n}\n\nasync function removeMatchingAgents(\n entry: AgentsRemoval,\n onRemoved: (agentName: string) => void,\n): Promise<string[]> {\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 return removedNames;\n}\n\nconst SCOPE_OF_AN_UNRECORDED_SKILL: SkillScope = \"project\";\n\n/** Every offered plugin scope, the config's first: a re-scoped skill stays where installed. */\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/** Failures are swallowed: the directory is deleted either way and files are already gone. */\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 // A throw is neither outcome, so `outcome` stays as the other scopes left it.\n }\n }\n\n return outcome;\n}\n\n/** `config` only supplies the scope each plugin is filed under; the entry decides which go. */\nasync function uninstallPlugins(\n entry: PluginRemoval,\n config: ProjectConfig | null,\n projectDir: string,\n { onUninstalled, onLeftRegistered }: PluginRemovalReporter = {},\n): Promise<UninstallPluginsResult> {\n const host = hostAt(projectDir);\n const hostAvailable = await host.isAvailable();\n const observed: string[] = [];\n\n if (!hostAvailable) {\n for (const pluginName of entry.names) onLeftRegistered?.(pluginName, host.provider);\n }\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 // Observed, not intended: Codex `plugin remove` exits 0 whether or not the plugin existed.\n return { totalUninstalled: observed.length };\n}\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\nfunction nothingWasObserved(): PluginRemovalOutcome {\n return \"absent\";\n}\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/** Directories are not plan entries: each goes only once nothing of the user's is left in it. */\nasync function cleanupEmptyDirs(\n target: Pick<\n UninstallTarget,\n \"skillsDir\" | \"agentsDir\" | \"hasClaudeDir\" | \"claudeDir\" | \"sourceRoot\" | \"sourceFolder\"\n >,\n manifestEntries: readonly ConfigManifestRemoval[],\n): Promise<CleanupResult> {\n // Whether or not the plan named a removal there: a folder holding nothing is no one's.\n await removeDirIfEmpty(target.skillsDir);\n await removeDirIfEmpty(target.agentsDir);\n\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 const claudeDir = await cleanupClaudeDir(target);\n\n return { sourceFolder, sourceRoot, claudeDir };\n}\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/**\n * Only once the provider folder went, and only when empty: consuming repositories keep their own\n * state under `.agents-inc/`.\n */\nasync function cleanupSourceRoot(\n sourceRoot: SourceRoot,\n providerDirRemoved: boolean,\n): Promise<DirCleanup> {\n if (!providerDirRemoved) return { removed: false, kept: false };\n return removeDirUnlessUserContent(sourceRoot.dir);\n}\n\nasync function cleanupClaudeDir(\n target: Pick<UninstallTarget, \"hasClaudeDir\" | \"claudeDir\">,\n): Promise<DirCleanup> {\n if (!target.hasClaudeDir) return { removed: false, kept: false };\n return removeDirUnlessUserContent(target.claudeDir);\n}\n\n/** `kept` when it is still there: something of the user's is in it. */\nasync function removeDirUnlessUserContent(dir: string): Promise<DirCleanup> {\n const removed = await removeDirIfEmpty(dir);\n return { removed, kept: !removed && (await directoryExists(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,cAAc;AACvB,OAAO,UAAU;AAEjB,SAAS,aAAa;AAiVhB;AAxPN,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;AAEA,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,OAAO,UAAU,WAAW,EAAG,QAAO,CAAC;AAC3C,SAAO,CAAC,EAAE,MAAM,UAAU,WAAW,OAAO,WAAW,YAAY,CAAC,GAAG,OAAO,SAAS,EAAE,CAAC;AAC5F;AAEA,SAAS,oBAAoB,QAA0C;AACrE,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;AAMA,SAAS,mBAAmB,QAAmC;AAC7D,QAAM,aAAa,oBAAI,IAAI,CAAC,GAAG,OAAO,cAAc,GAAG,OAAO,cAAc,CAAC;AAC7E,QAAM,sBAAsB,OAAO,iBAAiB,OAAO,CAAC,SAAS,WAAW,IAAI,IAAI,CAAC;AACzF,SAAO,OAAO,CAAC,GAAG,qBAAqB,GAAG,OAAO,YAAY,CAAC;AAChE;AAEA,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;AAGA,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;AAEA,SAAS,YACP,MACA,MACsC;AACtC,SAAO,KAAK,QAAQ;AAAA,IAClB,CAAC,UAAuD,MAAM,SAAS;AAAA,EACzE;AACF;AAEA,SAAS,UACP,MACA,MACgD;AAChD,SAAO,KAAK,QAAQ;AAAA,IAClB,CAAC,UAAuD,MAAM,SAAS;AAAA,EACzE;AACF;AAEA,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;AAGA,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;AAEA,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,EAEzC,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,EAEA,MAAgB,qBAAoC;AAAA,EAEpD;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,EAAE,MAAM,IAAI,MAAM,KAAK,MAAM,UAAS;AAC5C,UAAM,aAAa,QAAQ,IAAI;AAC/B,UAAM,gBAAgB,gBAAgB,MAAM,QAAQ;AAGpD,UAAM;AAAA,MAA8B;AAAA,MAAY;AAAA,MAAa;AAAA,MAAe,CAAC,YAC3E,KAAK,MAAM,SAAS,EAAE,MAAM,WAAW,aAAa,CAAC;AAAA,IACvD;AAEA,SAAK,eAAe,MAAM,KAAK,oBAAoB,UAAU;AAE7D,SAAK,YAAY;AAEjB,UAAM,SAAS,MAAM,sBAAsB,YAAY;AAAA,MACrD,oBAAoB,CAAC,YAAY,KAAK,KAAK,0BAA0B,OAAO,CAAC;AAAA;AAAA,MAE7E,2BAA2B,CAAC,iBAC1B,KAAK,MAAM,iCAAiC,YAAY,GAAG,EAAE,MAAM,WAAW,MAAM,CAAC;AAAA,IACzF,CAAC;AACD,UAAM,OAAO,iBAAiB,MAAM;AACpC,QAAI,CAAC,oBAAoB,IAAI,GAAG;AAC9B,WAAK,yBAAyB;AAC9B;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,KAAK,gBAAgB,MAAM,MAAM,KAAK,aAAa;AAC1E,QAAI,CAAC,SAAU,MAAK,gBAAgB;AAEpC,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,EAEA,MAAc,gBACZ,MACA,kBACA,eACkB;AAClB,QAAI,CAAC,iBAAkB,QAAO,KAAK,eAAe,MAAM,aAAa;AAErE,SAAK,iBAAiB,IAAI;AAC1B,WAAO;AAAA,EACT;AAAA,EAEQ,kBAAyB;AAC/B,SAAK,IAAI,EAAE;AACX,SAAK,IAAI,qBAAqB;AAC9B,SAAK,KAAK,WAAW,SAAS;AAAA,EAChC;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;AAAA,EACb;AAAA,EAEA,MAAc,eACZ,MACA,eACkB;AAElB,QAAI,CAAC,QAAQ,MAAM,OAAO;AACxB,WAAK,MAAM,uBAAuB,aAAa,GAAG,EAAE,MAAM,WAAW,MAAM,CAAC;AAAA,IAC9E;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;AAEpD,UAAM,cAAc,oBAChB,MAAM,KAAK,yBAAyB,QAAQ,UAAU,IACtD;AAEJ,UAAM,KAAK,qBAAqB,UAAU,MAAM,SAAS,GAAG,OAAO,QAAQ,UAAU;AACrF,UAAM,KAAK,uBAAuB,MAAM,MAAM;AAE9C,QAAI,CAAC,mBAAmB;AACtB,YAAM,KAAK,kBAAkB,UAAU;AACvC;AAAA,IACF;AACA,QAAI,YAAa,OAAM,KAAK,yBAAyB,WAAW;AAAA,EAClE;AAAA;AAAA,EAGA,MAAc,kBAAkB,YAAmC;AACjE,QAAI;AAEF,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,EAMA,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,EAGA,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,WAAK,+BAA+B,MAAM;AAAA,IAC5C,SAAS,OAAO;AACd,WAAK,KAAK,+BAA+B,gBAAgB,KAAK,CAAC,CAAC;AAAA,IAClE;AAAA,EACF;AAAA,EAEQ,+BAA+B,QAA0B;AAC/D,UAAM,EAAE,SAAS,YAAY,OAAO,IAAI,OAAO;AAC/C,SAAK,yBAAyB,OAAO,UAAU;AAC/C,eAAW,eAAe,CAAC,GAAG,YAAY,GAAG,MAAM,GAAG;AACpD,WAAK,KAAK,+BAA+B,WAAW,CAAC;AAAA,IACvD;AACA,QAAI,QAAQ,WAAW,EAAG;AAE1B,SAAK,WAAW,0BAA0B,QAAQ,MAAM,CAAC;AACzD,SAAK,0BAA0B,MAAM;AAAA,EACvC;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,EAEA,MAAc,uBAAuB,MAAmB,QAAwC;AAC9F,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;AAAA,EACF;AAAA,EAEA,MAAc,iBAAiB,MAAmB,QAAwC;AACxF,UAAM,KAAK,oBAAoB,UAAU,MAAM,QAAQ,CAAC;AACxD,UAAM,KAAK,oBAAoB,UAAU,MAAM,QAAQ,CAAC;AAExD,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;AACnD,SAAK,iBAAiB,OAAO,WAAW,SAAS,QAAQ,UAAU;AACnE,SAAK,iBAAiB,YAAY,QAAQ,SAAS;AAAA,EACrD;AAAA,EAEQ,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,EAEQ,iBAAiB,SAAiB,SAA2B;AACnE,QAAI,QAAQ,SAAS;AACnB,WAAK,WAAW,WAAW,OAAO,GAAG;AACrC;AAAA,IACF;AACA,QAAI,QAAQ,MAAM;AAChB,WAAK,IAAI,QAAQ,OAAO,2BAA2B;AAAA,IACrD;AAAA,EACF;AAAA,EAEA,MAAc,qBACZ,OACA,QACA,YACe;AACf,QAAI,CAAC,MAAO;AAEZ,SAAK,IAAI,yBAAyB;AAElC,QAAI;AACF,YAAM,eAAe,MAAM,iBAAiB,OAAO,QAAQ,YAAY;AAAA,QACrE,eAAe,CAAC,SAAS,KAAK,IAAI,yBAAyB,IAAI,GAAG;AAAA,QAClE,kBAAkB,CAAC,MAAM,aAAa,KAAK,KAAK,qBAAqB,MAAM,QAAQ,CAAC;AAAA,MACtF,CAAC;AAGD,UAAI,aAAa,mBAAmB,GAAG;AACrC,aAAK,WAAW,eAAe,OAAO,aAAa,kBAAkB,QAAQ,CAAC,EAAE;AAAA,MAClF;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,eAAe,MAAM;AAAA,MAAqB;AAAA,MAAO,CAAC,YACtD,KAAK,IAAI,wBAAwB,OAAO,GAAG;AAAA,IAC7C;AACA,QAAI,aAAa,WAAW,EAAG;AAE/B,SAAK,WAAW,WAAW,OAAO,aAAa,QAAQ,qBAAqB,CAAC,EAAE;AAAA,EACjF;AAAA,EAEA,MAAc,oBAAoB,OAAiD;AACjF,QAAI,CAAC,MAAO;AAEZ,UAAM,eAAe,MAAM;AAAA,MAAqB;AAAA,MAAO,CAAC,cACtD,KAAK,IAAI,wBAAwB,SAAS,GAAG;AAAA,IAC/C;AACA,QAAI,aAAa,WAAW,EAAG;AAE/B,SAAK,WAAW,WAAW,OAAO,aAAa,QAAQ,gBAAgB,CAAC,EAAE;AAAA,EAC5E;AACF;AAkEA,SAAS,wBAAwB,QAAoD;AACnF,MAAI,CAAC,QAAQ,OAAQ,QAAO,CAAC;AAC7B,SAAO,OAAO,OAAO,IAAI,CAAC,MAAM,EAAE,IAAI;AACxC;AAEA,SAAS,0BAA0B,QAAoD;AACrF,MAAI,CAAC,QAAQ,OAAQ,QAAO,oBAAI,IAAI;AACpC,QAAM,EAAE,gBAAgB,IAAI;AAC5B,SAAO,IAAI,IAAI,OAAO,OAAO,QAAQ,CAAC,UAAU,aAAa,OAAO,eAAe,CAAC,CAAC;AACvF;AAEA,SAAS,aAAa,OAAoB,iBAA+C;AACvF,QAAM,SAAS,0BAA0B,MAAM,IAAI,MAAM,MAAM;AAC/D,MAAI,CAAC,mBAAmB,MAAM,WAAW,mBAAmB,MAAM,WAAW,cAAc;AACzF,WAAO,CAAC,MAAM;AAAA,EAChB;AAEA,SAAO,CAAC,QAAQ,0BAA0B,MAAM,IAAI,eAAe,CAAC;AACtE;AAMA,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;AAEA,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;AAEA,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,UAC0B;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;AACzC,QAAM,QAAQ,WAAW,QAAQ;AAEjC,QAAM,CAAC,WAAW,cAAc,cAAc,eAAe,IAAI,MAAM,QAAQ,IAAI;AAAA,IACjF,uBAAuB,WAAW,SAAS,yBAAyB;AAAA,IACpE,gBAAgB,SAAS;AAAA,IACzB;AAAA,MACE,kBAAkB,YAAY,QAAQ;AAAA,MACtC;AAAA,MACA;AAAA,MACA,SAAS;AAAA,IACX;AAAA,IACA,wBAAwB,WAAW,KAAK;AAAA,EAC1C,CAAC;AAED,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,eAAe,WAAW,OAAO,OAAO,eAAe,MAAM;AACnE,QAAM,iBAAiB,MAAM,sBAAsB,YAAY,YAAY;AAE3E,SAAO;AAAA,IACL,YAAY,eAAe,SAAS;AAAA,IACpC;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,kBAAkB,wBAAwB,YAAY;AAAA,IACtD,YAAY;AAAA,IACZ,cAAc,gBAAgB;AAAA,IAC9B,gBAAgB,gBAAgB;AAAA,EAClC;AACF;AAEA,eAAe,sBACb,YACA,cACmB;AACnB,QAAM,YAAY,MAAM,gBAAgB,UAAU;AAClD,MAAI,CAAC,qBAAqB,UAAU,EAAG,QAAO,CAAC;AAE/C,QAAM,mBAAmB,0BAA0B,YAAY;AAC/D,SAAO,UAAU,OAAO,CAAC,SAAS,iBAAiB,IAAI,IAAI,CAAC;AAC9D;AAMA,SAAS,qBAAqB,YAA6B;AACzD,MAAI,gBAAgB,UAAU,EAAG,QAAO;AACxC,SAAO,OAAO,UAAU,EAAE;AAC5B;AAGA,eAAe,uBACb,WACA,sBACmB;AACnB,QAAM,WAAW,MAAM,gBAAgB,SAAS;AAChD,QAAM,SAAS,MAAM,QAAQ;AAAA,IAC3B,SAAS,IAAI,OAAO,UAAU;AAAA,MAC5B;AAAA,MACA,YAAY,MAAM,oBAAoB,WAAW,MAAM,oBAAoB;AAAA,IAC7E,EAAE;AAAA,EACJ;AACA,SAAO,OAAO,OAAO,CAAC,UAAU,MAAM,eAAe,IAAI,EAAE,IAAI,CAAC,UAAU,MAAM,IAAI;AACtF;AAOA,eAAe,oBACb,WACA,SACA,cACoC;AACpC,QAAM,WAAW,KAAK,KAAK,WAAW,OAAO;AAC7C,MAAI;AACF,WAAO,MAAM,uBAAuB,QAAQ;AAAA,EAC9C,QAAQ;AAGN,WAAO,aAAa,KAAK,KAAK,UAAU,eAAe,aAAa,CAAC;AAAA,EACvE;AACF;AAGA,eAAe,qBACb,OACA,WACmB;AACnB,QAAM,UAAU,MAAM,gBAAgB,MAAM,SAAS;AACrD,QAAM,eAAe,QAAQ,OAAO,CAAC,SAAS,MAAM,WAAW,SAAS,IAAI,CAAC;AAE7E,aAAW,QAAQ,cAAc;AAC/B,UAAM,OAAO,KAAK,KAAK,MAAM,WAAW,IAAI,CAAC;AAC7C,cAAU,IAAI;AAAA,EAChB;AACA,SAAO;AACT;AAEA,eAAe,qBACb,OACA,WACmB;AACnB,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,cAAU,SAAS;AAAA,EACrB;AACA,SAAO;AACT;AAEA,IAAM,+BAA2C;AAGjD,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;AAGA,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,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAGA,eAAe,iBACb,OACA,QACA,YACA,EAAE,eAAe,iBAAiB,IAA2B,CAAC,GAC7B;AACjC,QAAM,OAAO,OAAO,UAAU;AAC9B,QAAM,gBAAgB,MAAM,KAAK,YAAY;AAC7C,QAAM,WAAqB,CAAC;AAE5B,MAAI,CAAC,eAAe;AAClB,eAAW,cAAc,MAAM,MAAO,oBAAmB,YAAY,KAAK,QAAQ;AAAA,EACpF;AAEA,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;AAGA,SAAO,EAAE,kBAAkB,SAAS,OAAO;AAC7C;AAEA,SAAS,2BAA2B,QAA8B,YAAgC;AAChG,QAAM,UAAU,0BAA0B,UAAU;AACpD,SACE,QAAQ,OAAO,KAAK,CAAC,UAAU,MAAM,OAAO,OAAO,GAAG,SAAS;AAEnE;AAEA,SAAS,qBAA2C;AAClD,SAAO;AACT;AAEA,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;AAGA,eAAe,iBACb,QAIA,iBACwB;AAExB,QAAM,iBAAiB,OAAO,SAAS;AACvC,QAAM,iBAAiB,OAAO,SAAS;AAEvC,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;AACrF,QAAM,YAAY,MAAM,iBAAiB,MAAM;AAE/C,SAAO,EAAE,cAAc,YAAY,UAAU;AAC/C;AAEA,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;AAMA,eAAe,kBACb,YACA,oBACqB;AACrB,MAAI,CAAC,mBAAoB,QAAO,EAAE,SAAS,OAAO,MAAM,MAAM;AAC9D,SAAO,2BAA2B,WAAW,GAAG;AAClD;AAEA,eAAe,iBACb,QACqB;AACrB,MAAI,CAAC,OAAO,aAAc,QAAO,EAAE,SAAS,OAAO,MAAM,MAAM;AAC/D,SAAO,2BAA2B,OAAO,SAAS;AACpD;AAGA,eAAe,2BAA2B,KAAkC;AAC1E,QAAM,UAAU,MAAM,iBAAiB,GAAG;AAC1C,SAAO,EAAE,SAAS,MAAM,CAAC,WAAY,MAAM,gBAAgB,GAAG,EAAG;AACnE;AAEA,eAAe,eAAe,WAAmB,OAAsC;AACrF,MAAI;AACF,WAAO,MAAM,iBAAiB,WAAW,KAAK;AAAA,EAChD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;","names":[]}
@@ -1,17 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  unofferablePlacementsFound
4
- } from "../chunk-WTWMGH2C.js";
4
+ } from "../chunk-HACRR422.js";
5
5
  import {
6
6
  providerFlag,
7
7
  providerNamedBy,
8
8
  refuseAnAmbiguousInstallation
9
- } from "../chunk-RIPSGNLU.js";
9
+ } from "../chunk-OCAVW6N5.js";
10
10
  import {
11
11
  BaseCommand,
12
12
  EXIT_CODES
13
- } from "../chunk-V3KTSP3Q.js";
14
- import "../chunk-CLPUWOSA.js";
13
+ } from "../chunk-GEMW5RQ2.js";
15
14
  import {
16
15
  ERROR_MESSAGES,
17
16
  INFO_MESSAGES,
@@ -22,12 +21,12 @@ import {
22
21
  marketplaceRefreshFailed,
23
22
  marketplacesRefreshFailed,
24
23
  marketplacesRefreshed
25
- } from "../chunk-MQ6CGZNJ.js";
26
- import "../chunk-52HDHJ7I.js";
24
+ } from "../chunk-GAKVZNTX.js";
25
+ import "../chunk-AUNRHWH4.js";
27
26
  import {
28
27
  EJECT_SOURCE
29
- } from "../chunk-PJWGTNOX.js";
30
- import "../chunk-NQW25DCF.js";
28
+ } from "../chunk-7C3PFRBU.js";
29
+ import "../chunk-GHHJYNAN.js";
31
30
 
32
31
  // src/cli/commands/update.ts
33
32
  import { unique } from "remeda";
@@ -71,23 +70,11 @@ var Update = class _Update extends BaseCommand {
71
70
  await this.requireHost(host);
72
71
  await this.refreshMarketplaces(marketplaces, host);
73
72
  }
74
- /**
75
- * Refuses a configuration whose own host cannot place what it asks for.
76
- *
77
- * The same reading `compile` and `edit` make, off the same host roster. It matters here because
78
- * this command REFRESHES the marketplaces a configuration names — so on a configuration whose
79
- * plugin rows the host can never install, every refresh is work done for rows that will never
80
- * become an install, reported as a success.
81
- */
82
73
  async refuseUnofferablePlacements(projectDir) {
83
74
  const [finding] = await unofferablePlacementsFound(projectDir);
84
75
  if (finding !== void 0) this.error(finding, { exit: EXIT_CODES.ERROR });
85
76
  }
86
- /**
87
- * The config this directory's installation runs on — the project's own, or the
88
- * global one it inherits from. A config file that exists but cannot be loaded is
89
- * a fault, not an absence, so it is reported rather than read as "not installed".
90
- */
77
+ /** ABORT on an unreadable config: a fault, not "not installed". */
91
78
  async loadInstalledConfig(projectDir) {
92
79
  try {
93
80
  const loaded = await loadProjectConfig(projectDir);
@@ -96,25 +83,15 @@ var Update = class _Update extends BaseCommand {
96
83
  this.error(getErrorMessage(error), { exit: EXIT_CODES.ERROR });
97
84
  }
98
85
  }
99
- /** States the ownership rule once, and only where there is an ejected skill it applies to. */
100
86
  reportEjectedSkills(skills) {
101
87
  if (!skills.some(isEjected)) return;
102
88
  this.log(INFO_MESSAGES.EJECTED_SKILLS_USER_OWNED);
103
89
  }
104
- /**
105
- * Gated on there being a marketplace to refresh, so an eject-only installation
106
- * never fails on a host binary it had no reason to call.
107
- */
108
90
  async requireHost(host) {
109
91
  if (await host.isAvailable()) return;
110
92
  this.error(hostCliNotFound(host.provider), { exit: EXIT_CODES.ERROR });
111
93
  }
112
- /**
113
- * One marketplace refresh per marketplace, in sequence: they write
114
- * to the same registry, and interleaved progress lines would not say which
115
- * marketplace each belongs to. A failure is collected rather than thrown, so one
116
- * unreachable marketplace cannot hide the state of the others.
117
- */
94
+ /** Sequential, as they share one registry; failures are collected so one cannot hide the rest. */
118
95
  async refreshMarketplaces(marketplaces, host) {
119
96
  const failed = [];
120
97
  for (const marketplace of marketplaces) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/cli/commands/update.ts"],"sourcesContent":["import { unique } from \"remeda\";\n\nimport { BaseCommand } from \"../base-command.js\";\nimport { EJECT_SOURCE } from \"../consts.js\";\nimport { EXIT_CODES } from \"../lib/exit-codes.js\";\nimport { loadProjectConfig } from \"../lib/configuration/index.js\";\nimport { hostAt } from \"../lib/hosts/host-for.js\";\nimport {\n providerFlag,\n providerNamedBy,\n refuseAnAmbiguousInstallation,\n} from \"../lib/installation/provider-flag.js\";\nimport { unofferablePlacementsFound } from \"../lib/hosts/configured-placements.js\";\nimport type { PluginHost } from \"../lib/hosts/plugin-host.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport {\n ERROR_MESSAGES,\n INFO_MESSAGES,\n hostCliNotFound,\n marketplaceRefreshFailed,\n marketplacesRefreshFailed,\n marketplacesRefreshed,\n} from \"../utils/messages.js\";\nimport type { ProjectConfig, SkillConfig } from \"../types/index.js\";\n\n/** A copy the user owns rather than a plugin the marketplace serves. */\nfunction isEjected(skill: SkillConfig): boolean {\n return skill.origin === EJECT_SOURCE;\n}\n\n/** Entries the installation actually has — an excluded entry is a tombstone, not an install. */\nfunction activeSkills(skills: SkillConfig[]): SkillConfig[] {\n return skills.filter((skill) => !skill.excluded);\n}\n\n/** The marketplaces this installation actually uses, deduplicated, in config order. */\nfunction configuredMarketplaces(skills: SkillConfig[]): string[] {\n return unique(skills.filter((skill) => !isEjected(skill)).map((skill) => skill.origin));\n}\n\nexport default class Update extends BaseCommand {\n static summary = \"Refresh the marketplaces this installation uses\";\n\n static description =\n \"Runs Claude's own marketplace update for every marketplace this installation's config names. Ejected skills are copies you own and are never touched.\";\n\n static flags = { provider: providerFlag() };\n\n static examples = [\"<%= config.bin %> <%= command.id %>\"];\n\n async run(): Promise<void> {\n const { flags } = await this.parse(Update);\n\n // Before the config is loaded, because loading it is already an answer: `loadProjectConfig`\n // and `hostAt` both resolve the provider through `providerInUse`, which picks by ROSTER ORDER\n // where a scope holds one installation of each. So a refresh with no flag read one user's\n // Claude config, refreshed the marketplaces IT names, and reported them as refreshed with\n // nothing saying whose they were.\n await refuseAnAmbiguousInstallation(\n process.cwd(),\n \"update\",\n providerNamedBy(flags.provider),\n (message) => this.error(message, { exit: EXIT_CODES.INVALID_ARGS }),\n );\n // Both configs, not only the one this refresh reads: a project inherits the global config, so\n // a broken global one is a config this folder uses as surely as its own.\n await this.ensureConfigReadable(process.cwd());\n await this.refuseUnofferablePlacements(process.cwd());\n const config = await this.loadInstalledConfig(process.cwd());\n if (!config) {\n this.warn(ERROR_MESSAGES.NO_INSTALLATION);\n return;\n }\n\n const skills = activeSkills(config.skills);\n this.reportEjectedSkills(skills);\n\n const marketplaces = configuredMarketplaces(skills);\n if (marketplaces.length === 0) {\n this.log(INFO_MESSAGES.NO_PLUGIN_MARKETPLACES);\n return;\n }\n\n const host = hostAt(process.cwd());\n await this.requireHost(host);\n await this.refreshMarketplaces(marketplaces, host);\n }\n\n /**\n * Refuses a configuration whose own host cannot place what it asks for.\n *\n * The same reading `compile` and `edit` make, off the same host roster. It matters here because\n * this command REFRESHES the marketplaces a configuration names — so on a configuration whose\n * plugin rows the host can never install, every refresh is work done for rows that will never\n * become an install, reported as a success.\n */\n private async refuseUnofferablePlacements(projectDir: string): Promise<void> {\n const [finding] = await unofferablePlacementsFound(projectDir);\n if (finding !== undefined) this.error(finding, { exit: EXIT_CODES.ERROR });\n }\n\n /**\n * The config this directory's installation runs on — the project's own, or the\n * global one it inherits from. A config file that exists but cannot be loaded is\n * a fault, not an absence, so it is reported rather than read as \"not installed\".\n */\n private async loadInstalledConfig(projectDir: string): Promise<ProjectConfig | null> {\n try {\n const loaded = await loadProjectConfig(projectDir);\n return loaded?.config ?? null;\n } catch (error) {\n this.error(getErrorMessage(error), { exit: EXIT_CODES.ERROR });\n }\n }\n\n /** States the ownership rule once, and only where there is an ejected skill it applies to. */\n private reportEjectedSkills(skills: SkillConfig[]): void {\n if (!skills.some(isEjected)) return;\n this.log(INFO_MESSAGES.EJECTED_SKILLS_USER_OWNED);\n }\n\n /**\n * Gated on there being a marketplace to refresh, so an eject-only installation\n * never fails on a host binary it had no reason to call.\n */\n private async requireHost(host: PluginHost): Promise<void> {\n if (await host.isAvailable()) return;\n // The host's own provider, never a constant: this command reaches either host from C4, and a\n // fixed line tells a Codex user to install Claude Code — not the binary that is missing.\n this.error(hostCliNotFound(host.provider), { exit: EXIT_CODES.ERROR });\n }\n\n /**\n * One marketplace refresh per marketplace, in sequence: they write\n * to the same registry, and interleaved progress lines would not say which\n * marketplace each belongs to. A failure is collected rather than thrown, so one\n * unreachable marketplace cannot hide the state of the others.\n */\n private async refreshMarketplaces(marketplaces: string[], host: PluginHost): Promise<void> {\n const failed: string[] = [];\n\n for (const marketplace of marketplaces) {\n this.log(`Refreshing marketplace ${marketplace}...`);\n try {\n await host.refreshMarketplace(marketplace);\n this.log(` Updated marketplace ${marketplace}`);\n } catch (error) {\n failed.push(marketplace);\n this.warn(marketplaceRefreshFailed(marketplace, getErrorMessage(error)));\n }\n }\n\n if (failed.length > 0) {\n this.error(marketplacesRefreshFailed(failed), { exit: EXIT_CODES.ERROR });\n }\n\n this.logSuccess(marketplacesRefreshed(marketplaces.length));\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/cli/commands/update.ts"],"sourcesContent":["import { unique } from \"remeda\";\n\nimport { BaseCommand } from \"../base-command.js\";\nimport { EJECT_SOURCE } from \"../consts.js\";\nimport { EXIT_CODES } from \"../lib/exit-codes.js\";\nimport { loadProjectConfig } from \"../lib/configuration/index.js\";\nimport { hostAt } from \"../lib/hosts/host-for.js\";\nimport {\n providerFlag,\n providerNamedBy,\n refuseAnAmbiguousInstallation,\n} from \"../lib/installation/provider-flag.js\";\nimport { unofferablePlacementsFound } from \"../lib/hosts/configured-placements.js\";\nimport type { PluginHost } from \"../lib/hosts/plugin-host.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport {\n ERROR_MESSAGES,\n INFO_MESSAGES,\n hostCliNotFound,\n marketplaceRefreshFailed,\n marketplacesRefreshFailed,\n marketplacesRefreshed,\n} from \"../utils/messages.js\";\nimport type { ProjectConfig, SkillConfig } from \"../types/index.js\";\n\nfunction isEjected(skill: SkillConfig): boolean {\n return skill.origin === EJECT_SOURCE;\n}\n\n/** An excluded entry is a tombstone, not an install. */\nfunction activeSkills(skills: SkillConfig[]): SkillConfig[] {\n return skills.filter((skill) => !skill.excluded);\n}\n\nfunction configuredMarketplaces(skills: SkillConfig[]): string[] {\n return unique(skills.filter((skill) => !isEjected(skill)).map((skill) => skill.origin));\n}\n\nexport default class Update extends BaseCommand {\n static summary = \"Refresh the marketplaces this installation uses\";\n\n static description =\n \"Runs Claude's own marketplace update for every marketplace this installation's config names. Ejected skills are copies you own and are never touched.\";\n\n static flags = { provider: providerFlag() };\n\n static examples = [\"<%= config.bin %> <%= command.id %>\"];\n\n async run(): Promise<void> {\n const { flags } = await this.parse(Update);\n\n // Before the config is loaded: loading it already picks a provider by roster order.\n await refuseAnAmbiguousInstallation(\n process.cwd(),\n \"update\",\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 const config = await this.loadInstalledConfig(process.cwd());\n if (!config) {\n this.warn(ERROR_MESSAGES.NO_INSTALLATION);\n return;\n }\n\n const skills = activeSkills(config.skills);\n this.reportEjectedSkills(skills);\n\n const marketplaces = configuredMarketplaces(skills);\n if (marketplaces.length === 0) {\n this.log(INFO_MESSAGES.NO_PLUGIN_MARKETPLACES);\n return;\n }\n\n const host = hostAt(process.cwd());\n await this.requireHost(host);\n await this.refreshMarketplaces(marketplaces, host);\n }\n\n private async refuseUnofferablePlacements(projectDir: string): Promise<void> {\n const [finding] = await unofferablePlacementsFound(projectDir);\n if (finding !== undefined) this.error(finding, { exit: EXIT_CODES.ERROR });\n }\n\n /** ABORT on an unreadable config: a fault, not \"not installed\". */\n private async loadInstalledConfig(projectDir: string): Promise<ProjectConfig | null> {\n try {\n const loaded = await loadProjectConfig(projectDir);\n return loaded?.config ?? null;\n } catch (error) {\n this.error(getErrorMessage(error), { exit: EXIT_CODES.ERROR });\n }\n }\n\n private reportEjectedSkills(skills: SkillConfig[]): void {\n if (!skills.some(isEjected)) return;\n this.log(INFO_MESSAGES.EJECTED_SKILLS_USER_OWNED);\n }\n\n private async requireHost(host: PluginHost): Promise<void> {\n if (await host.isAvailable()) return;\n this.error(hostCliNotFound(host.provider), { exit: EXIT_CODES.ERROR });\n }\n\n /** Sequential, as they share one registry; failures are collected so one cannot hide the rest. */\n private async refreshMarketplaces(marketplaces: string[], host: PluginHost): Promise<void> {\n const failed: string[] = [];\n\n for (const marketplace of marketplaces) {\n this.log(`Refreshing marketplace ${marketplace}...`);\n try {\n await host.refreshMarketplace(marketplace);\n this.log(` Updated marketplace ${marketplace}`);\n } catch (error) {\n failed.push(marketplace);\n this.warn(marketplaceRefreshFailed(marketplace, getErrorMessage(error)));\n }\n }\n\n if (failed.length > 0) {\n this.error(marketplacesRefreshFailed(failed), { exit: EXIT_CODES.ERROR });\n }\n\n this.logSuccess(marketplacesRefreshed(marketplaces.length));\n }\n}\n"],"mappings":"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","names":[]}
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  promptConfirm,
4
4
  promptValue
5
- } from "../../chunk-4AAOOAIN.js";
6
- import "../../chunk-JFLVEFFP.js";
5
+ } from "../../chunk-P6CYS4JK.js";
6
+ import "../../chunk-KIK5EZYD.js";
7
7
  export {
8
8
  promptConfirm,
9
9
  promptValue
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  RemovalPlanConfirm
4
- } from "../../chunk-USVBYQSQ.js";
4
+ } from "../../chunk-O4LN3HFI.js";
5
5
  import "../../chunk-OZXAJNLA.js";
6
- import "../../chunk-PJWGTNOX.js";
6
+ import "../../chunk-7C3PFRBU.js";
7
7
  export {
8
8
  RemovalPlanConfirm
9
9
  };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ScrollAffordance
4
- } from "../../chunk-KOLDW6SF.js";
5
- import "../../chunk-PJWGTNOX.js";
4
+ } from "../../chunk-BBDGYJHX.js";
5
+ import "../../chunk-7C3PFRBU.js";
6
6
  export {
7
7
  ScrollAffordance
8
8
  };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  SelectList
4
- } from "../../chunk-44TE3RKH.js";
5
- import "../../chunk-PJWGTNOX.js";
4
+ } from "../../chunk-3ED3KHHD.js";
5
+ import "../../chunk-7C3PFRBU.js";
6
6
  export {
7
7
  SelectList
8
8
  };
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  SkillAgentSummary
4
- } from "../../chunk-6ZRH5RHC.js";
5
- import "../../chunk-NIHUOWE4.js";
6
- import "../../chunk-52HDHJ7I.js";
7
- import "../../chunk-PJWGTNOX.js";
4
+ } from "../../chunk-PMUZR4XO.js";
5
+ import "../../chunk-RIK45Q4I.js";
6
+ import "../../chunk-AUNRHWH4.js";
7
+ import "../../chunk-7C3PFRBU.js";
8
8
  export {
9
9
  SkillAgentSummary
10
10
  };
@@ -1,11 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- Spinner,
4
3
  awaitUnderSpinner
5
- } from "../../chunk-NEZ4OAOL.js";
6
- import "../../chunk-JFLVEFFP.js";
4
+ } from "../../chunk-ZEGM3MVR.js";
5
+ import "../../chunk-KIK5EZYD.js";
7
6
  export {
8
- Spinner,
9
7
  awaitUnderSpinner
10
8
  };
11
9
  //# sourceMappingURL=spinner.js.map
@@ -1,19 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ScrollAffordance
4
- } from "../../chunk-KOLDW6SF.js";
4
+ } from "../../chunk-BBDGYJHX.js";
5
5
  import {
6
6
  SkillAgentSummary
7
- } from "../../chunk-6ZRH5RHC.js";
8
- import "../../chunk-NIHUOWE4.js";
9
- import "../../chunk-52HDHJ7I.js";
7
+ } from "../../chunk-PMUZR4XO.js";
8
+ import "../../chunk-RIK45Q4I.js";
9
+ import "../../chunk-AUNRHWH4.js";
10
10
  import {
11
11
  ALL_SKILLS_EJECTED_LABEL,
12
12
  CLI_COLORS,
13
13
  DEFAULT_PUBLIC_SOURCE_NAME,
14
14
  EJECT_SOURCE,
15
15
  formatSourceDisplayName
16
- } from "../../chunk-PJWGTNOX.js";
16
+ } from "../../chunk-7C3PFRBU.js";
17
17
 
18
18
  // src/cli/components/common/summary-panel.tsx
19
19
  import { Box, Text } from "ink";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/cli/components/common/summary-panel.tsx","../../../src/cli/components/hooks/use-panel-scroll.ts","../../../src/cli/components/hooks/use-measured-height.ts"],"sourcesContent":["import { Box, Text } from \"ink\";\nimport React from \"react\";\nimport { unique } from \"remeda\";\nimport {\n ALL_SKILLS_EJECTED_LABEL,\n CLI_COLORS,\n DEFAULT_PUBLIC_SOURCE_NAME,\n EJECT_SOURCE,\n formatSourceDisplayName,\n} from \"../../consts.js\";\nimport type { SkillConfig } from \"../../types/config.js\";\nimport { usePanelScroll } from \"../hooks/use-panel-scroll.js\";\nimport { ScrollAffordance } from \"./scroll-affordance.js\";\nimport { SkillAgentSummary, type SkillAgentSummaryProps } from \"./skill-agent-summary.js\";\n\nexport type SummaryPanelProps = SkillAgentSummaryProps & {\n /** `undefined` when the configuration was built from no stack. */\n stackName: string | undefined;\n};\n\n/**\n * The Marketplace row's value: the distinct marketplaces the summarised skills come from.\n *\n * `SkillConfig.origin` is authoritative for where a skill came from, and holds exactly one\n * of two things: {@link EJECT_SOURCE}, meaning the files were copied locally, or the marketplace's\n * own name as its `marketplace.json` declares it — which is why a project initialised from a\n * different marketplace names that marketplace here without any extra plumbing.\n *\n * Three cases, and the two zero-marketplace ones are NOT the same state:\n * - **No skills at all.** Nothing is claimed yet, so the default public marketplace stands in.\n * - **Skills, but every one ejected.** \"eject\" names no marketplace, so there is none to list;\n * naming the default would assert an origin none of those skills has.\n * - **Any marketplace-sourced skill.** Its marketplace is named. Ejected siblings contribute no\n * name but do not suppress the ones that do.\n *\n * Tombstoned (`excluded`) entries count. One still records a real global install and the\n * marketplace it came from, and `computeScopeDiff` renders it as a row in the summary below, so\n * dropping it here would leave the header disowning a row it sits above.\n *\n * Sorted, so the row cannot reorder between renders on config iteration order alone.\n */\nfunction formatSkillMarketplaces(skillConfigs: SkillConfig[]): string {\n if (skillConfigs.length === 0) return formatSourceDisplayName(DEFAULT_PUBLIC_SOURCE_NAME);\n\n const marketplaceNames = unique(\n skillConfigs.map((config) => config.origin).filter((origin) => origin !== EJECT_SOURCE),\n );\n if (marketplaceNames.length === 0) return ALL_SKILLS_EJECTED_LABEL;\n\n return marketplaceNames\n .map((name) => formatSourceDisplayName(name))\n .sort()\n .join(\" · \");\n}\n\ntype PanelHeaderProps = {\n sourceNames: string;\n stackName: string | undefined;\n};\n\n/**\n * Which marketplaces and which stack the summary below was built from.\n *\n * Rendered inside the scrolled content, so its `marginBottom` scrolls away\n * rather than permanently costing the viewport a row.\n *\n * `flexShrink={0}` is load-bearing: Ink defaults every box to `flexShrink: 1`,\n * so on a short terminal Yoga squeezes this block to fit instead of letting the\n * viewport clip it, and the Marketplace and Stack rows overprint into one\n * unreadable line. Clipping is the panel's job; the header must keep its height.\n */\nconst PanelHeader: React.FC<PanelHeaderProps> = ({ sourceNames, stackName }) => (\n <Box\n flexDirection=\"column\"\n flexShrink={0}\n borderStyle=\"single\"\n borderBottom={true}\n borderTop={false}\n borderLeft={false}\n borderRight={false}\n borderColor={CLI_COLORS.NEUTRAL}\n borderBottomDimColor\n paddingBottom={1}\n marginBottom={1}\n >\n <Box flexDirection=\"row\" columnGap={1}>\n <Text color={CLI_COLORS.WARNING} bold>\n Marketplace\n </Text>\n <Text color={CLI_COLORS.NEUTRAL}>{sourceNames}</Text>\n </Box>\n <Box flexDirection=\"row\" columnGap={1}>\n <Text color={CLI_COLORS.WARNING} bold>\n Stack\n </Text>\n <Text color={CLI_COLORS.NEUTRAL}>{stackName ?? \"none\"}</Text>\n </Box>\n </Box>\n);\n\n/**\n * Marketplace/stack header above a clipped, arrow-scrollable `SkillAgentSummary`.\n *\n * It owns the `↑`/`↓` keys through `usePanelScroll`. A host that needs other\n * keys adds its own `useInput` alongside it.\n *\n * Horizontal padding only. Vertical padding is unshrinkable, so on a short\n * terminal it claims the last row the viewport has and the content measures 0\n * before it has ever been laid out — the panel then paints neither a summary\n * row nor the affordance that would say rows are missing.\n */\nexport const SummaryPanel: React.FC<SummaryPanelProps> = ({ stackName, ...summary }) => {\n const { viewportRef, contentRef, contentMarginTop, hiddenAbove, hiddenBelow } = usePanelScroll();\n\n const sourceNames = formatSkillMarketplaces(summary.skillConfigs);\n\n return (\n <Box\n flexDirection=\"column\"\n flexGrow={1}\n flexBasis={0}\n borderStyle=\"single\"\n borderColor={CLI_COLORS.NEUTRAL}\n paddingX={2}\n >\n <Box ref={viewportRef} flexDirection=\"column\" flexGrow={1} flexBasis={0} overflow=\"hidden\">\n <Box ref={contentRef} flexDirection=\"column\" flexShrink={0} marginTop={contentMarginTop}>\n <PanelHeader sourceNames={sourceNames} stackName={stackName} />\n\n <Box width=\"100%\">\n <SkillAgentSummary {...summary} />\n </Box>\n </Box>\n </Box>\n <ScrollAffordance hiddenAbove={hiddenAbove} hiddenBelow={hiddenBelow} />\n </Box>\n );\n};\n","import { useEffect, useRef, useState } from \"react\";\nimport type { Ref, RefObject } from \"react\";\nimport { type DOMElement, measureElement, useInput } from \"ink\";\nimport { useMeasuredHeight } from \"./use-measured-height.js\";\n\nexport type UsePanelScrollResult = {\n /** Attach to the clipping box — the one with `overflow=\"hidden\"`. */\n viewportRef: Ref<DOMElement>;\n /** Attach to the scrolled content box inside the viewport. */\n contentRef: RefObject<DOMElement | null>;\n /** Apply as the content box's `marginTop`; negative by the number of lines scrolled off the top. */\n contentMarginTop: number;\n /** Content lines scrolled off the top of the viewport. */\n hiddenAbove: number;\n /** Content lines clipped below the viewport. */\n hiddenBelow: number;\n};\n\n/**\n * Line scrolling for a single clipped viewport, driven by the `↑`/`↓` keys.\n *\n * The caller renders viewport, content and a `ScrollAffordance` sibling; this\n * owns the offset those three agree on. Distinct from `useSectionScroll`, which\n * moves a viewport to follow focus across variable-height sections — here there\n * is no focus, only the arrow keys.\n *\n * A host that needs other keys adds its own `useInput` alongside it, on keys\n * disjoint from `↑`/`↓`.\n */\nexport function usePanelScroll(): UsePanelScrollResult {\n // Measured on the clipping box itself, inside the border and padding, so it\n // is the height the content actually gets.\n const { ref: viewportRef, measuredHeight: viewportHeight } = useMeasuredHeight();\n const contentRef = useRef<DOMElement>(null);\n const [contentHeight, setContentHeight] = useState(0);\n const [scrollOffset, setScrollOffset] = useState(0);\n\n // Ink's measurement idiom: re-measure on every render, no dependency array\n // on purpose. The setState inside is guarded to fire only when the measured\n // height actually changed, so the chain converges instead of looping — a\n // dependency array here would skip re-measures after layout changes React\n // cannot see.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useEffect(() => {\n if (!contentRef.current) return;\n const { height } = measureElement(contentRef.current);\n // Yoga under-reports a wrapped subtree measured inside a viewport too\n // short to lay it out, and reports 0 inside a zero-height one, so the\n // tallest reading is the trustworthy one. Believing a squeezed reading\n // would shrink the hidden-line count until the affordance disappeared,\n // which hands its row back and grows the count again — a render loop.\n setContentHeight((prev) => Math.max(prev, height));\n });\n\n // contentHeight only ever sizes the scroll range and the affordance count.\n // It must never gate the clip — which is why it is not returned: a viewport\n // that stops clipping when it looks too small grows to content height, which\n // makes it look big enough to stop clipping — a stable wrong answer that\n // paints the content over the border.\n const maxScroll = Math.max(0, contentHeight - viewportHeight);\n const hiddenBelow = Math.max(0, maxScroll - scrollOffset);\n\n useInput((_input, key) => {\n if (key.upArrow) setScrollOffset((prev) => Math.max(0, prev - 1));\n if (key.downArrow) setScrollOffset((prev) => Math.min(maxScroll, prev + 1));\n });\n\n return {\n viewportRef,\n contentRef,\n contentMarginTop: scrollOffset > 0 ? -scrollOffset : 0,\n hiddenAbove: scrollOffset,\n hiddenBelow,\n };\n}\n","import { useRef, useState, useEffect } from \"react\";\nimport { type DOMElement, measureElement, useStdout } from \"ink\";\n\n/**\n * Measures the computed height of a Box element using Ink's Yoga layout engine.\n *\n * Returns a ref to attach to a Box with `flexGrow={1}` and the measured height.\n * The Box must be inside a parent chain with a constrained height (e.g., an\n * explicit `height` prop on an ancestor) so Yoga can compute the remaining space.\n *\n * Returns 0 before the first layout pass. Re-measures on terminal resize.\n */\nexport function useMeasuredHeight(): {\n ref: React.Ref<DOMElement>;\n measuredHeight: number;\n} {\n const ref = useRef<DOMElement>(null);\n const [measuredHeight, setMeasuredHeight] = useState(0);\n const { stdout } = useStdout();\n\n const measure = () => {\n if (ref.current) {\n const { height } = measureElement(ref.current);\n setMeasuredHeight((prev) => (prev !== height ? height : prev));\n }\n };\n\n // Re-measure after every render so late-mounted refs get picked up.\n // The state setter avoids unnecessary updates when height is unchanged.\n useEffect(() => {\n measure();\n });\n\n // Yoga may not have computed the layout on the first render, so\n // measureElement() returns 0. Retry with increasing delays to give\n // Yoga time to resolve the full flex constraint chain.\n useEffect(() => {\n const RETRY_DELAYS_MS = [0, 16, 50];\n const timers: ReturnType<typeof setTimeout>[] = [];\n\n for (const delay of RETRY_DELAYS_MS) {\n timers.push(setTimeout(measure, delay));\n }\n\n return () => {\n for (const timer of timers) {\n clearTimeout(timer);\n }\n };\n // Deps are deliberately empty: this is a mount-only retry ladder. `measure`\n // is re-created every render, so listing it would re-arm the timers on every\n // render instead of once.\n }, []);\n\n useEffect(() => {\n stdout.on(\"resize\", measure);\n return () => {\n stdout.off(\"resize\", measure);\n };\n }, [stdout]);\n\n return { ref, measuredHeight };\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","measureElement","useRef","useState","useEffect","measureElement"]}
1
+ {"version":3,"sources":["../../../src/cli/components/common/summary-panel.tsx","../../../src/cli/components/hooks/use-panel-scroll.ts","../../../src/cli/components/hooks/use-measured-height.ts"],"sourcesContent":["import { Box, Text } from \"ink\";\nimport React from \"react\";\nimport { unique } from \"remeda\";\nimport {\n ALL_SKILLS_EJECTED_LABEL,\n CLI_COLORS,\n DEFAULT_PUBLIC_SOURCE_NAME,\n EJECT_SOURCE,\n formatSourceDisplayName,\n} from \"../../consts.js\";\nimport type { SkillConfig } from \"../../types/config.js\";\nimport { usePanelScroll } from \"../hooks/use-panel-scroll.js\";\nimport { ScrollAffordance } from \"./scroll-affordance.js\";\nimport { SkillAgentSummary, type SkillAgentSummaryProps } from \"./skill-agent-summary.js\";\n\nexport type SummaryPanelProps = SkillAgentSummaryProps & {\n /** `undefined` when the configuration was built from no stack. */\n stackName: string | undefined;\n};\n\n/**\n * No skills shows the default marketplace; all-ejected names none. Tombstoned entries count: the\n * summary below renders them as rows.\n */\nfunction formatSkillMarketplaces(skillConfigs: SkillConfig[]): string {\n if (skillConfigs.length === 0) return formatSourceDisplayName(DEFAULT_PUBLIC_SOURCE_NAME);\n\n const marketplaceNames = unique(\n skillConfigs.map((config) => config.origin).filter((origin) => origin !== EJECT_SOURCE),\n );\n if (marketplaceNames.length === 0) return ALL_SKILLS_EJECTED_LABEL;\n\n return marketplaceNames\n .map((name) => formatSourceDisplayName(name))\n .sort()\n .join(\" · \");\n}\n\ntype PanelHeaderProps = {\n sourceNames: string;\n stackName: string | undefined;\n};\n\n/** `flexShrink={0}`: squeezed by Yoga on a short terminal, its two rows overprint. */\nconst PanelHeader: React.FC<PanelHeaderProps> = ({ sourceNames, stackName }) => (\n <Box\n flexDirection=\"column\"\n flexShrink={0}\n borderStyle=\"single\"\n borderBottom={true}\n borderTop={false}\n borderLeft={false}\n borderRight={false}\n borderColor={CLI_COLORS.NEUTRAL}\n borderBottomDimColor\n paddingBottom={1}\n marginBottom={1}\n >\n <Box flexDirection=\"row\" columnGap={1}>\n <Text color={CLI_COLORS.WARNING} bold>\n Marketplace\n </Text>\n <Text color={CLI_COLORS.NEUTRAL}>{sourceNames}</Text>\n </Box>\n <Box flexDirection=\"row\" columnGap={1}>\n <Text color={CLI_COLORS.WARNING} bold>\n Stack\n </Text>\n <Text color={CLI_COLORS.NEUTRAL}>{stackName ?? \"none\"}</Text>\n </Box>\n </Box>\n);\n\n/** Horizontal padding only: unshrinkable vertical padding starves the viewport on a short terminal. */\nexport const SummaryPanel: React.FC<SummaryPanelProps> = ({ stackName, ...summary }) => {\n const { viewportRef, contentRef, contentMarginTop, hiddenAbove, hiddenBelow } = usePanelScroll();\n\n const sourceNames = formatSkillMarketplaces(summary.skillConfigs);\n\n return (\n <Box\n flexDirection=\"column\"\n flexGrow={1}\n flexBasis={0}\n borderStyle=\"single\"\n borderColor={CLI_COLORS.NEUTRAL}\n paddingX={2}\n >\n <Box ref={viewportRef} flexDirection=\"column\" flexGrow={1} flexBasis={0} overflow=\"hidden\">\n <Box ref={contentRef} flexDirection=\"column\" flexShrink={0} marginTop={contentMarginTop}>\n <PanelHeader sourceNames={sourceNames} stackName={stackName} />\n\n <Box width=\"100%\">\n <SkillAgentSummary {...summary} />\n </Box>\n </Box>\n </Box>\n <ScrollAffordance hiddenAbove={hiddenAbove} hiddenBelow={hiddenBelow} />\n </Box>\n );\n};\n","import { useEffect, useRef, useState } from \"react\";\nimport type { Ref, RefObject } from \"react\";\nimport { type DOMElement, measureElement, useInput } from \"ink\";\nimport { useMeasuredHeight } from \"./use-measured-height.js\";\n\nexport type UsePanelScrollResult = {\n /** The `overflow=\"hidden\"` box. */\n viewportRef: Ref<DOMElement>;\n contentRef: RefObject<DOMElement | null>;\n contentMarginTop: number;\n hiddenAbove: number;\n hiddenBelow: number;\n};\n\n/** `↑`/`↓` line scrolling for one clipped viewport; hosts add their own `useInput` for other keys. */\nexport function usePanelScroll(): UsePanelScrollResult {\n const { ref: viewportRef, measuredHeight: viewportHeight } = useMeasuredHeight();\n const contentRef = useRef<DOMElement>(null);\n const [contentHeight, setContentHeight] = useState(0);\n const [scrollOffset, setScrollOffset] = useState(0);\n\n // eslint-disable-next-line react-hooks/exhaustive-deps -- re-measures every render; layout changes are invisible to React\n useEffect(() => {\n if (!contentRef.current) return;\n const { height } = measureElement(contentRef.current);\n // Yoga under-reports inside a squeezed viewport; trusting that loops the render.\n setContentHeight((prev) => Math.max(prev, height));\n });\n\n // Not returned: gating the clip on contentHeight paints content over the border.\n const maxScroll = Math.max(0, contentHeight - viewportHeight);\n const hiddenBelow = Math.max(0, maxScroll - scrollOffset);\n\n useInput((_input, key) => {\n if (key.upArrow) setScrollOffset((prev) => Math.max(0, prev - 1));\n if (key.downArrow) setScrollOffset((prev) => Math.min(maxScroll, prev + 1));\n });\n\n return {\n viewportRef,\n contentRef,\n contentMarginTop: scrollOffset > 0 ? -scrollOffset : 0,\n hiddenAbove: scrollOffset,\n hiddenBelow,\n };\n}\n","import { useRef, useState, useEffect } from \"react\";\nimport { type DOMElement, measureElement, useStdout } from \"ink\";\n\n/** Attach to a `flexGrow={1}` Box under an ancestor of constrained height; 0 before first layout. */\nexport function useMeasuredHeight(): {\n ref: React.Ref<DOMElement>;\n measuredHeight: number;\n} {\n const ref = useRef<DOMElement>(null);\n const [measuredHeight, setMeasuredHeight] = useState(0);\n const { stdout } = useStdout();\n\n const measure = () => {\n if (ref.current) {\n const { height } = measureElement(ref.current);\n setMeasuredHeight((prev) => (prev !== height ? height : prev));\n }\n };\n\n // Every render, so late-mounted refs are picked up.\n useEffect(() => {\n measure();\n });\n\n // Yoga may not have laid out yet on first render, so retry a few times.\n useEffect(() => {\n const RETRY_DELAYS_MS = [0, 16, 50];\n const timers: ReturnType<typeof setTimeout>[] = [];\n\n for (const delay of RETRY_DELAYS_MS) {\n timers.push(setTimeout(measure, delay));\n }\n\n return () => {\n for (const timer of timers) {\n clearTimeout(timer);\n }\n };\n // Mount-only: listing `measure` would re-arm the timers every render.\n }, []);\n\n useEffect(() => {\n stdout.on(\"resize\", measure);\n return () => {\n stdout.off(\"resize\", measure);\n };\n }, [stdout]);\n\n return { ref, measuredHeight };\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","measureElement","useRef","useState","useEffect","measureElement"]}
@@ -2,9 +2,13 @@
2
2
  import {
3
3
  defaultCategories,
4
4
  defaultRules,
5
- defaultStacks,
6
- defineConfig
7
- } from "./chunk-NQW25DCF.js";
5
+ defaultStacks
6
+ } from "./chunk-GHHJYNAN.js";
7
+
8
+ // src/cli/lib/configuration/define-config.ts
9
+ function defineConfig(config) {
10
+ return config;
11
+ }
8
12
  export {
9
13
  defaultCategories,
10
14
  defaultRules,
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"sources":["../src/cli/lib/configuration/define-config.ts"],"sourcesContent":["import type { ProjectConfig } from \"../../types\";\n\n/** Define the config inline: extracting it to a variable widens its literal types. */\nexport function defineConfig<const T extends ProjectConfig>(config: T): T {\n return config;\n}\n"],"mappings":";;;;;;;;AAGO,SAAS,aAA4C,QAAc;AACxE,SAAO;AACT;","names":[]}