argsbarg 4.0.4 → 4.1.1

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 (147) hide show
  1. package/CHANGELOG.md +77 -1
  2. package/README.md +91 -85
  3. package/docs/README.md +6 -6
  4. package/docs/ai-skills.md +8 -5
  5. package/docs/bundled-docs.md +1 -1
  6. package/docs/cli-program.md +9 -7
  7. package/docs/config-schema.md +37 -13
  8. package/docs/developing.md +8 -8
  9. package/docs/distribution-homebrew.md +103 -0
  10. package/docs/install.md +143 -106
  11. package/docs/mcp.md +23 -12
  12. package/docs/output-schema.md +1 -1
  13. package/examples/full-example/Formula/.gitkeep +0 -0
  14. package/examples/full-example/README.md +98 -0
  15. package/examples/full-example/biome.json +22 -0
  16. package/examples/{consumer-app → full-example}/bun.lock +2 -0
  17. package/examples/full-example/justfile +134 -0
  18. package/examples/{consumer-app → full-example}/package.json +10 -3
  19. package/examples/{consumer-app → full-example}/schemas/generated/app-config.json +1 -1
  20. package/examples/{consumer-app → full-example}/schemas/generated/status.json +1 -1
  21. package/examples/full-example/scripts/create-identity.ts +11 -0
  22. package/examples/full-example/scripts/formula-shared.ts +73 -0
  23. package/examples/full-example/scripts/gen-dev-formula.ts +26 -0
  24. package/examples/full-example/scripts/print-identity.ts +27 -0
  25. package/examples/full-example/src/commands/echo/command.ts +21 -0
  26. package/examples/full-example/src/commands/status/command.test.ts +10 -0
  27. package/examples/full-example/src/commands/status/command.ts +36 -0
  28. package/examples/{consumer-app → full-example}/src/commands/status/types.ts +1 -1
  29. package/examples/full-example/src/index.ts +10 -0
  30. package/examples/full-example/src/program.ts +57 -0
  31. package/examples/{consumer-app → full-example}/src/types.ts +1 -1
  32. package/examples/mcp-test.ts +8 -24
  33. package/examples/nested.ts +1 -3
  34. package/index.d.ts +81 -65
  35. package/package.json +2 -2
  36. package/src/builtins/builtins.test.ts +37 -22
  37. package/src/builtins/completion-group.ts +17 -15
  38. package/src/builtins/config.test.ts +31 -25
  39. package/src/builtins/config.ts +4 -3
  40. package/src/builtins/dispatch.ts +25 -1
  41. package/src/builtins/install.ts +45 -82
  42. package/src/builtins/mcp.ts +1 -1
  43. package/src/builtins/registry.ts +2 -0
  44. package/src/builtins/uninstall.ts +80 -0
  45. package/src/capabilities.ts +5 -7
  46. package/src/cli-tool/cli-smoke.test.ts +19 -0
  47. package/src/cli-tool/create.test.ts +119 -0
  48. package/src/cli-tool/create.ts +380 -0
  49. package/{examples/consumer-app/capabilities.test.ts → src/cli-tool/full-example-capabilities.test.ts} +15 -14
  50. package/src/cli-tool/main.ts +8 -0
  51. package/src/cli-tool/post-create.ts +111 -0
  52. package/src/cli-tool/program.ts +82 -0
  53. package/src/cli-tool/prompt.ts +28 -0
  54. package/src/cli-tool/run-create.ts +149 -0
  55. package/src/config/bootstrap.ts +174 -66
  56. package/src/config/context.test.ts +22 -36
  57. package/src/config/context.ts +5 -4
  58. package/src/config/file.test.ts +66 -56
  59. package/src/config/file.ts +33 -25
  60. package/src/config/resolve.test.ts +192 -1
  61. package/src/config/resolve.ts +92 -13
  62. package/src/config.integration.test.ts +17 -10
  63. package/src/docs/api-guide.test.ts +4 -5
  64. package/src/docs/docs.test.ts +2 -1
  65. package/src/docs/mcp-guide.ts +7 -8
  66. package/src/hidden-mcpb.test.ts +41 -1
  67. package/src/index.ts +7 -10
  68. package/src/install/binary-placement.test.ts +101 -0
  69. package/src/install/binary-placement.ts +47 -0
  70. package/src/install/detect-installed.ts +2 -97
  71. package/src/install/index.ts +239 -168
  72. package/src/install/install-validate.test.ts +61 -0
  73. package/src/install/install.test.ts +170 -90
  74. package/src/install/mcp-openclaw.test.ts +40 -0
  75. package/src/install/mcp-openclaw.ts +106 -0
  76. package/src/install/normalize-uninstall.ts +11 -0
  77. package/src/install/normalize.ts +20 -0
  78. package/src/install/paths.ts +18 -26
  79. package/src/install/plan.ts +40 -261
  80. package/src/install/shell.ts +0 -14
  81. package/src/install/status.test.ts +85 -0
  82. package/src/install/status.ts +22 -15
  83. package/src/install/target-base.ts +93 -0
  84. package/src/install/target-detect.ts +20 -0
  85. package/src/install/target-effective.ts +129 -0
  86. package/src/install/target-mcp-cli.ts +149 -0
  87. package/src/install/target-mcp-json.ts +130 -0
  88. package/src/install/target-plan-build.ts +67 -0
  89. package/src/install/target-registry.ts +57 -0
  90. package/src/install/target-scope.ts +253 -0
  91. package/src/install/target-skill.ts +104 -0
  92. package/src/install/target-types.ts +129 -0
  93. package/src/install/targets/app.ts +60 -0
  94. package/src/install/targets/chatgpt-mcp.ts +12 -0
  95. package/src/install/targets/claude-code-mcp.ts +15 -0
  96. package/src/install/targets/claude-desktop-mcp.ts +12 -0
  97. package/src/install/targets/claude-skill.ts +16 -0
  98. package/src/install/targets/codex-mcp.ts +25 -0
  99. package/src/install/targets/codex-skill.ts +14 -0
  100. package/src/install/targets/configure.ts +63 -0
  101. package/src/install/targets/cursor-mcp.ts +15 -0
  102. package/src/install/targets/cursor-skill.ts +16 -0
  103. package/src/install/targets/index.ts +50 -0
  104. package/src/install/targets/openclaw-mcp.ts +25 -0
  105. package/src/install/targets/openclaw-skill.ts +17 -0
  106. package/src/install/targets/opencode-mcp.ts +101 -0
  107. package/src/install/targets/opencode-skill.ts +15 -0
  108. package/src/install/targets.test.ts +118 -0
  109. package/src/install/uninstall.ts +16 -152
  110. package/src/invoke.test.ts +7 -1
  111. package/src/mcp/bundle.ts +16 -4
  112. package/src/mcp/claude.test.ts +14 -1
  113. package/src/mcp/claude.ts +11 -4
  114. package/src/mcp/env.test.ts +92 -0
  115. package/src/mcp/env.ts +15 -14
  116. package/src/mcp/zip.test.ts +17 -0
  117. package/src/mcp/zip.ts +62 -9
  118. package/src/mcp.integration.test.ts +1 -1
  119. package/src/parse.test.ts +14 -2
  120. package/src/paths/host.ts +11 -11
  121. package/src/paths/remove-empty-dir.ts +13 -0
  122. package/src/prompt.ts +10 -0
  123. package/src/schema.ts +9 -1
  124. package/src/skill/generate.ts +18 -4
  125. package/src/skill/install.ts +33 -6
  126. package/src/skill/naming.ts +28 -0
  127. package/src/types.ts +86 -7
  128. package/src/validate.ts +73 -9
  129. package/docs/templates/cursor/rules/cli-program.mdc +0 -31
  130. package/examples/config-app/main.ts +0 -20
  131. package/examples/config-app/program.ts +0 -81
  132. package/examples/config-app/schema.ts +0 -37
  133. package/examples/config-app/types.ts +0 -19
  134. package/examples/consumer-app/README.md +0 -57
  135. package/examples/consumer-app/src/main.ts +0 -15
  136. package/examples/consumer-app/src/program.ts +0 -108
  137. package/src/install/binary.ts +0 -94
  138. package/src/install/completions.ts +0 -56
  139. package/src/install/update.test.ts +0 -108
  140. package/src/install/update.ts +0 -57
  141. /package/examples/{consumer-app → full-example}/schemas/configSchemas.ts +0 -0
  142. /package/examples/{consumer-app → full-example}/schemas/outputSchemas.ts +0 -0
  143. /package/examples/{consumer-app → full-example}/scripts/schemagen/discover-schema-roots.test.ts +0 -0
  144. /package/examples/{consumer-app → full-example}/scripts/schemagen/discover-schema-roots.ts +0 -0
  145. /package/examples/{consumer-app → full-example}/scripts/schemagen/naming.ts +0 -0
  146. /package/examples/{consumer-app → full-example}/scripts/schemagen.ts +0 -0
  147. /package/examples/{consumer-app → full-example}/tsconfig.json +0 -0
@@ -0,0 +1,129 @@
1
+ import { resolveCapabilities } from "../capabilities.ts";
2
+ import type {
3
+ CliInstallConfig,
4
+ CliInstallTargets,
5
+ CliProgram,
6
+ InstallAgentIntegration,
7
+ InstallTargetSpec,
8
+ } from "../types.ts";
9
+ import type { InstallPaths } from "./paths.ts";
10
+ import { AGENT_PAIRS, INSTALL_ARTIFACT_KEYS, installTargetForKey } from "./target-registry.ts";
11
+ import type { CliInstallArtifactKey, InstallPlanMode } from "./target-types.ts";
12
+
13
+ export type { InstallTargetSpec, ResolvedInstallTarget } from "../types.ts";
14
+
15
+ export type { InstallPlanMode, InstallScope } from "./target-types.ts";
16
+
17
+ export function resolveAgentIntegration(
18
+ install?: CliInstallConfig,
19
+ mcpEnabled = false,
20
+ ): InstallAgentIntegration {
21
+ return install?.agentIntegration ?? (mcpEnabled ? "mcp" : "skill");
22
+ }
23
+
24
+ /** Resolves a boolean or object target spec against category defaults. */
25
+ export function resolveInstallTargetSpec(
26
+ spec: InstallTargetSpec | undefined,
27
+ defaults: { enabled: boolean; includedInAll: boolean },
28
+ ): { enabled: boolean; includedInAll: boolean } {
29
+ if (spec === undefined) {
30
+ return { ...defaults };
31
+ }
32
+ if (typeof spec === "boolean") {
33
+ return {
34
+ enabled: spec,
35
+ includedInAll: spec ? defaults.includedInAll : false,
36
+ };
37
+ }
38
+ return {
39
+ enabled: spec.enabled ?? defaults.enabled,
40
+ includedInAll: spec.includedInAll ?? defaults.includedInAll,
41
+ };
42
+ }
43
+
44
+ function artifactDefaults(
45
+ key: CliInstallArtifactKey,
46
+ integration: InstallAgentIntegration,
47
+ ): { enabled: boolean; includedInAll: boolean } {
48
+ const target = installTargetForKey(key);
49
+ const includedInAll = target?.defaultIncludedInAll(integration) ?? false;
50
+ return { enabled: true, includedInAll };
51
+ }
52
+
53
+ function applyAgentPairDedupe(
54
+ out: Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>,
55
+ user: CliInstallTargets | undefined,
56
+ integration: InstallAgentIntegration,
57
+ ): void {
58
+ if (integration === "both") return;
59
+
60
+ for (const [mcpKey, skillKey] of AGENT_PAIRS) {
61
+ const mcp = out[mcpKey];
62
+ const skill = out[skillKey];
63
+ if (!mcp.includedInAll || !skill.includedInAll) continue;
64
+
65
+ const mcpKeyExplicit = user?.[mcpKey] !== undefined;
66
+ const skillKeyExplicit = user?.[skillKey] !== undefined;
67
+
68
+ if (mcpKeyExplicit && !skillKeyExplicit) continue;
69
+ if (skillKeyExplicit && !mcpKeyExplicit) continue;
70
+
71
+ if (integration === "mcp") {
72
+ out[skillKey] = { ...skill, includedInAll: false };
73
+ } else {
74
+ out[mcpKey] = { ...mcp, includedInAll: false };
75
+ }
76
+ }
77
+ }
78
+
79
+ /** Effective per-artifact gates for install.targets. */
80
+ export function resolveEffectiveInstallTargets(
81
+ install?: CliInstallConfig,
82
+ program?: Pick<CliProgram, "mcpServer">,
83
+ ): Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }> {
84
+ const mcpEnabled = program?.mcpServer?.enabled === true;
85
+ const integration = resolveAgentIntegration(install, mcpEnabled);
86
+ const user = install?.targets;
87
+ const out = {} as Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>;
88
+ for (const key of INSTALL_ARTIFACT_KEYS) {
89
+ out[key] = resolveInstallTargetSpec(user?.[key], artifactDefaults(key, integration));
90
+ }
91
+ applyAgentPairDedupe(out, user, integration);
92
+ return out;
93
+ }
94
+
95
+ /** MCP enabled check for scoped uninstall/install mcp category. */
96
+ export function mcpCategoryEnabled(root: CliProgram): boolean {
97
+ return resolveCapabilities(root).mcp;
98
+ }
99
+
100
+ export interface InstallTargetPreview {
101
+ agentIntegration: InstallAgentIntegration;
102
+ all: CliInstallArtifactKey[];
103
+ mcp: CliInstallArtifactKey[];
104
+ skill: CliInstallArtifactKey[];
105
+ }
106
+
107
+ /** Derives plan mode from install CLI flags. */
108
+ export function resolveInstallPlanMode(opts: {
109
+ all?: boolean;
110
+ skill?: boolean;
111
+ mcp?: boolean;
112
+ configure?: boolean;
113
+ uninstall?: boolean;
114
+ reinstall?: boolean;
115
+ }): InstallPlanMode {
116
+ if (opts.reinstall) return "refresh";
117
+ if (opts.uninstall) return opts.all ? "uninstall-all" : "uninstall-scoped";
118
+ if (opts.all) return "install-all";
119
+ return "install-scoped";
120
+ }
121
+
122
+ /** @deprecated Use target registry. */
123
+ export function isInstallTargetAvailable(
124
+ key: CliInstallArtifactKey,
125
+ root: CliProgram,
126
+ paths: InstallPaths,
127
+ ): boolean {
128
+ return installTargetForKey(key)?.isAvailable(root, paths) ?? false;
129
+ }
@@ -0,0 +1,149 @@
1
+ import type { CliProgram } from "../types.ts";
2
+ import {
3
+ checkCodexMcpConflict,
4
+ codexMcpHasServer,
5
+ mergeCodexMcpConfig,
6
+ removeCodexMcpConfig,
7
+ } from "./mcp-codex.ts";
8
+ import { expectedMcpEntry } from "./mcp-config.ts";
9
+ import {
10
+ checkOpenclawMcpConflict,
11
+ mergeOpenclawMcpConfig,
12
+ openclawMcpHasServer,
13
+ removeOpenclawMcpConfig,
14
+ } from "./mcp-openclaw.ts";
15
+ import { displayInstallPath, type InstallPaths, userHome } from "./paths.ts";
16
+ import { InstallTarget } from "./target-base.ts";
17
+ import type {
18
+ CliInstallArtifactKey,
19
+ DetectedSnapshot,
20
+ InstallAction,
21
+ InstallActionKind,
22
+ InstalledArtifacts,
23
+ InstallStatus,
24
+ TargetPlanContext,
25
+ UninstallAction,
26
+ } from "./target-types.ts";
27
+
28
+ export interface McpCliHostSpec {
29
+ key: CliInstallArtifactKey;
30
+ actionKind: InstallActionKind;
31
+ label: string;
32
+ pairedSkillKey: CliInstallArtifactKey;
33
+ configPath: (paths: InstallPaths) => string;
34
+ detectedKey: "codexMcp" | "openclawMcp";
35
+ statusField: "codexMcp" | "openclawMcp";
36
+ isAvailable: (root: CliProgram, paths: InstallPaths) => boolean;
37
+ hasServer: (home: string, name: string) => boolean;
38
+ installMessage: string;
39
+ uninstallMessage: string;
40
+ merge: (
41
+ home: string,
42
+ name: string,
43
+ entry: ReturnType<typeof expectedMcpEntry>,
44
+ dry: boolean,
45
+ yes: boolean,
46
+ ) => string;
47
+ remove: (home: string, name: string, dry: boolean) => void;
48
+ preflight: (
49
+ home: string,
50
+ name: string,
51
+ entry: ReturnType<typeof expectedMcpEntry>,
52
+ yes: boolean,
53
+ ) => string | null;
54
+ }
55
+
56
+ /** MCP host registered via an external CLI (codex, openclaw). */
57
+ export class McpCliInstallTarget extends InstallTarget {
58
+ readonly key: CliInstallArtifactKey;
59
+ readonly actionKind: InstallActionKind;
60
+ readonly category = "mcp" as const;
61
+ readonly pairedKey: CliInstallArtifactKey;
62
+
63
+ private readonly spec: McpCliHostSpec;
64
+
65
+ constructor(spec: McpCliHostSpec) {
66
+ super();
67
+ this.spec = spec;
68
+ this.key = spec.key;
69
+ this.actionKind = spec.actionKind;
70
+ this.pairedKey = spec.pairedSkillKey;
71
+ }
72
+
73
+ isAvailable(root: CliProgram, paths: InstallPaths): boolean {
74
+ return this.spec.isAvailable(root, paths);
75
+ }
76
+
77
+ isDetected(paths: InstallPaths, _root: CliProgram): boolean {
78
+ return this.spec.hasServer(userHome(), paths.mcpName);
79
+ }
80
+
81
+ applyDetected(paths: InstallPaths, root: CliProgram, out: InstalledArtifacts): void {
82
+ out[this.spec.detectedKey] = this.isDetected(paths, root);
83
+ }
84
+
85
+ protected isDetectedFromSnapshot(detected: DetectedSnapshot): boolean {
86
+ return detected[this.spec.detectedKey] ?? false;
87
+ }
88
+
89
+ protected formatStatusLine(paths: InstallPaths, _root: CliProgram): string {
90
+ return `${displayInstallPath(this.spec.configPath(paths))} (server "${paths.mcpName}")`;
91
+ }
92
+
93
+ protected assignStatusLine(status: InstallStatus, line: string): void {
94
+ status[this.spec.statusField] = line;
95
+ }
96
+
97
+ preflight(ctx: TargetPlanContext): string | null {
98
+ const entry = expectedMcpEntry(ctx.root);
99
+ return this.spec.preflight(userHome(), ctx.paths.mcpName, entry, !!ctx.opts.yes);
100
+ }
101
+
102
+ protected buildInstallActions(ctx: TargetPlanContext): InstallAction[] {
103
+ const entry = expectedMcpEntry(ctx.root);
104
+ const configPath = this.spec.configPath(ctx.paths);
105
+ return [
106
+ {
107
+ kind: this.actionKind,
108
+ summary: `${this.spec.label}: ${displayInstallPath(configPath)}`,
109
+ message: this.spec.installMessage.replace("{name}", ctx.paths.mcpName),
110
+ run: () => {
111
+ const written = this.spec.merge(
112
+ userHome(),
113
+ ctx.paths.mcpName,
114
+ entry,
115
+ ctx.dry,
116
+ !!ctx.opts.yes,
117
+ );
118
+ return [written];
119
+ },
120
+ },
121
+ ];
122
+ }
123
+
124
+ protected buildUninstallActions(ctx: TargetPlanContext): UninstallAction[] {
125
+ const configPath = this.spec.configPath(ctx.paths);
126
+ return [
127
+ {
128
+ kind: this.actionKind,
129
+ summary: `${this.spec.label}: ${displayInstallPath(configPath)}`,
130
+ message: this.spec.uninstallMessage.replace("{name}", ctx.paths.mcpName),
131
+ run: () => {
132
+ this.spec.remove(userHome(), ctx.paths.mcpName, ctx.dry);
133
+ return [configPath];
134
+ },
135
+ },
136
+ ];
137
+ }
138
+ }
139
+
140
+ export {
141
+ checkCodexMcpConflict,
142
+ checkOpenclawMcpConflict,
143
+ codexMcpHasServer,
144
+ mergeCodexMcpConfig,
145
+ mergeOpenclawMcpConfig,
146
+ openclawMcpHasServer,
147
+ removeCodexMcpConfig,
148
+ removeOpenclawMcpConfig,
149
+ };
@@ -0,0 +1,130 @@
1
+ import type { CliProgram } from "../types.ts";
2
+ import {
3
+ checkMcpConflict,
4
+ expectedMcpEntry,
5
+ mergeMcpConfig,
6
+ readMcpServerEntry,
7
+ removeMcpConfig,
8
+ } from "./mcp-config.ts";
9
+ import { displayInstallPath, type InstallPaths } from "./paths.ts";
10
+ import { InstallTarget } from "./target-base.ts";
11
+ import type {
12
+ CliInstallArtifactKey,
13
+ DetectedSnapshot,
14
+ InstallAction,
15
+ InstallActionKind,
16
+ InstalledArtifacts,
17
+ InstallStatus,
18
+ TargetPlanContext,
19
+ UninstallAction,
20
+ } from "./target-types.ts";
21
+
22
+ export interface McpJsonHostSpec {
23
+ key: CliInstallArtifactKey;
24
+ actionKind: InstallActionKind;
25
+ label: string;
26
+ pairedSkillKey?: CliInstallArtifactKey;
27
+ configPath: (paths: InstallPaths) => string;
28
+ detectedKey: keyof Pick<
29
+ InstalledArtifacts,
30
+ "cursorMcp" | "claudeMcp" | "claudeDesktopMcp" | "chatGptMcp"
31
+ >;
32
+ statusField: keyof Pick<
33
+ InstallStatus,
34
+ "cursorMcp" | "claudeMcp" | "claudeDesktopMcp" | "chatGptMcp"
35
+ >;
36
+ isAvailable: (root: CliProgram, paths: InstallPaths) => boolean;
37
+ /** Append server name to status line (default false). */
38
+ statusIncludesServer?: boolean;
39
+ }
40
+
41
+ function mcpConfigHasServer(path: string, name: string): boolean {
42
+ return readMcpServerEntry(path, name) !== undefined;
43
+ }
44
+
45
+ /** MCP host that merges into a JSON `mcpServers` config file. */
46
+ export class McpJsonInstallTarget extends InstallTarget {
47
+ readonly key: CliInstallArtifactKey;
48
+ readonly actionKind: InstallActionKind;
49
+ readonly category = "mcp" as const;
50
+ readonly pairedKey?: CliInstallArtifactKey;
51
+
52
+ private readonly spec: McpJsonHostSpec;
53
+
54
+ constructor(spec: McpJsonHostSpec) {
55
+ super();
56
+ this.spec = spec;
57
+ this.key = spec.key;
58
+ this.actionKind = spec.actionKind;
59
+ this.pairedKey = spec.pairedSkillKey;
60
+ }
61
+
62
+ isAvailable(root: CliProgram, paths: InstallPaths): boolean {
63
+ return this.spec.isAvailable(root, paths);
64
+ }
65
+
66
+ isDetected(paths: InstallPaths, _root: CliProgram): boolean {
67
+ return mcpConfigHasServer(this.spec.configPath(paths), paths.mcpName);
68
+ }
69
+
70
+ applyDetected(paths: InstallPaths, root: CliProgram, out: InstalledArtifacts): void {
71
+ out[this.spec.detectedKey] = this.isDetected(paths, root);
72
+ }
73
+
74
+ protected isDetectedFromSnapshot(detected: DetectedSnapshot): boolean {
75
+ return detected[this.spec.detectedKey];
76
+ }
77
+
78
+ protected formatStatusLine(paths: InstallPaths, _root: CliProgram): string {
79
+ const path = displayInstallPath(this.spec.configPath(paths));
80
+ if (this.spec.statusIncludesServer) {
81
+ return `${path} (server "${paths.mcpName}")`;
82
+ }
83
+ return path;
84
+ }
85
+
86
+ protected assignStatusLine(status: InstallStatus, line: string): void {
87
+ status[this.spec.statusField] = line;
88
+ }
89
+
90
+ preflight(ctx: TargetPlanContext): string | null {
91
+ const entry = expectedMcpEntry(ctx.root);
92
+ return checkMcpConflict(
93
+ this.spec.configPath(ctx.paths),
94
+ ctx.paths.mcpName,
95
+ entry,
96
+ !!ctx.opts.yes,
97
+ );
98
+ }
99
+
100
+ protected buildInstallActions(ctx: TargetPlanContext): InstallAction[] {
101
+ const configPath = this.spec.configPath(ctx.paths);
102
+ const entry = expectedMcpEntry(ctx.root);
103
+ return [
104
+ {
105
+ kind: this.actionKind,
106
+ summary: `${this.spec.label}: ${displayInstallPath(configPath)}`,
107
+ message: `Merging MCP server "${ctx.paths.mcpName}" into ${displayInstallPath(configPath)}`,
108
+ run: () => {
109
+ mergeMcpConfig(configPath, ctx.paths.mcpName, entry, ctx.dry);
110
+ return [configPath];
111
+ },
112
+ },
113
+ ];
114
+ }
115
+
116
+ protected buildUninstallActions(ctx: TargetPlanContext): UninstallAction[] {
117
+ const configPath = this.spec.configPath(ctx.paths);
118
+ return [
119
+ {
120
+ kind: this.actionKind,
121
+ summary: `${this.spec.label}: ${displayInstallPath(configPath)}`,
122
+ message: `Removing MCP server "${ctx.paths.mcpName}" from ${displayInstallPath(configPath)}`,
123
+ run: () => {
124
+ removeMcpConfig(configPath, ctx.paths.mcpName, ctx.dry);
125
+ return [configPath];
126
+ },
127
+ },
128
+ ];
129
+ }
130
+ }
@@ -0,0 +1,67 @@
1
+ import { resolveCapabilities } from "../capabilities.ts";
2
+ import type { CliProgram } from "../types.ts";
3
+ import type { InstallPaths } from "./paths.ts";
4
+ import { mcpCategoryEnabled } from "./target-effective.ts";
5
+ import { INSTALL_TARGETS, installTargetForKey } from "./target-registry.ts";
6
+ import { buildDetectedSnapshot, buildTargetPlanContext } from "./target-scope.ts";
7
+ import type { InstallAction, InstallOpts, UninstallAction } from "./target-types.ts";
8
+
9
+ /** Builds install actions for normal mode (--all / scoped targets). */
10
+ export function buildInstallPlanFromTargets(
11
+ root: CliProgram,
12
+ paths: InstallPaths,
13
+ opts: InstallOpts,
14
+ ): InstallAction[] {
15
+ const detected = buildDetectedSnapshot(root, paths);
16
+ const ctx = buildTargetPlanContext(root, paths, opts, detected);
17
+
18
+ const actions: InstallAction[] = [];
19
+ const mcpEnabled = mcpCategoryEnabled(root);
20
+
21
+ for (const target of INSTALL_TARGETS) {
22
+ if (target.category === "mcp" && !mcpEnabled) continue;
23
+ actions.push(...target.planInstall(ctx));
24
+ }
25
+ return actions;
26
+ }
27
+
28
+ /** Builds uninstall actions for scoped targets or --all. */
29
+ export function buildUninstallPlanFromTargets(
30
+ root: CliProgram,
31
+ paths: InstallPaths,
32
+ opts: InstallOpts,
33
+ ): UninstallAction[] {
34
+ const detected = buildDetectedSnapshot(root, paths);
35
+ const ctx = buildTargetPlanContext(root, paths, opts, detected);
36
+
37
+ const actions: UninstallAction[] = [];
38
+ for (const target of INSTALL_TARGETS) {
39
+ if (target.key === "configure") continue;
40
+ actions.push(...target.planUninstall(ctx));
41
+ }
42
+ const configure = installTargetForKey("configure");
43
+ if (configure) {
44
+ actions.push(...configure.planUninstall(ctx));
45
+ }
46
+ return actions;
47
+ }
48
+
49
+ /** Runs MCP preflight for planned install actions. */
50
+ export function runTargetPreflight(
51
+ root: CliProgram,
52
+ paths: InstallPaths,
53
+ opts: InstallOpts,
54
+ actions: InstallAction[],
55
+ ): void {
56
+ if (!resolveCapabilities(root).mcp) return;
57
+
58
+ const kinds = new Set(actions.map((a) => a.kind));
59
+ const detected = buildDetectedSnapshot(root, paths);
60
+ const ctx = buildTargetPlanContext(root, paths, opts, detected);
61
+
62
+ for (const target of INSTALL_TARGETS) {
63
+ if (!target.preflight || !kinds.has(target.actionKind)) continue;
64
+ const err = target.preflight(ctx);
65
+ if (err) throw new Error(err);
66
+ }
67
+ }
@@ -0,0 +1,57 @@
1
+ import type { InstallTarget } from "./target-base.ts";
2
+ import type { SkillInstallTarget } from "./target-skill.ts";
3
+ import type { CliInstallArtifactKey, InstallActionKind } from "./target-types.ts";
4
+ import { INSTALL_TARGETS } from "./targets/index.ts";
5
+
6
+ export { INSTALL_TARGETS } from "./targets/index.ts";
7
+
8
+ export const INSTALL_ARTIFACT_KEYS: CliInstallArtifactKey[] = INSTALL_TARGETS.map((t) => t.key);
9
+
10
+ export const SKILL_KEYS: CliInstallArtifactKey[] = INSTALL_TARGETS.filter(
11
+ (t) => t.category === "skill",
12
+ ).map((t) => t.key);
13
+
14
+ export const MCP_KEYS: CliInstallArtifactKey[] = INSTALL_TARGETS.filter(
15
+ (t) => t.category === "mcp",
16
+ ).map((t) => t.key);
17
+
18
+ /** Maps plan action kinds to install.targets artifact keys. */
19
+ export const ACTION_KIND_TO_ARTIFACT: Record<InstallActionKind, CliInstallArtifactKey> =
20
+ Object.fromEntries(INSTALL_TARGETS.map((t) => [t.actionKind, t.key])) as Record<
21
+ InstallActionKind,
22
+ CliInstallArtifactKey
23
+ >;
24
+
25
+ /** Hosts with both MCP config and shell skill install targets. */
26
+ export const AGENT_PAIRS: [CliInstallArtifactKey, CliInstallArtifactKey][] =
27
+ INSTALL_TARGETS.flatMap((t) =>
28
+ t.category === "mcp" && t.pairedKey !== undefined
29
+ ? [[t.key, t.pairedKey] as [CliInstallArtifactKey, CliInstallArtifactKey]]
30
+ : [],
31
+ );
32
+
33
+ const targetByKey = new Map(INSTALL_TARGETS.map((t) => [t.key, t]));
34
+
35
+ /** Lookup a registered install target by artifact key. */
36
+ export function installTargetForKey(key: CliInstallArtifactKey): InstallTarget | undefined {
37
+ return targetByKey.get(key);
38
+ }
39
+
40
+ /** Lookup by plan action kind. */
41
+ export function installTargetForActionKind(kind: InstallActionKind): InstallTarget | undefined {
42
+ return INSTALL_TARGETS.find((t) => t.actionKind === kind);
43
+ }
44
+
45
+ /** Skill targets for uninstall summary → directory resolution. */
46
+ export function skillTargetForUninstallSummary(summary: string): SkillInstallTarget | undefined {
47
+ for (const t of INSTALL_TARGETS) {
48
+ if (
49
+ t.category === "skill" &&
50
+ (t as SkillInstallTarget).uninstallPrefix &&
51
+ summary.startsWith((t as SkillInstallTarget).uninstallPrefix)
52
+ ) {
53
+ return t as SkillInstallTarget;
54
+ }
55
+ }
56
+ return undefined;
57
+ }