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,253 @@
1
+ import { resolveCapabilities } from "../capabilities.ts";
2
+ import { appConfigInstalled } from "../config/file.ts";
3
+ import type { CliInstallTargets, CliProgram } from "../types.ts";
4
+ import type { InstallPaths } from "./paths.ts";
5
+ import {
6
+ type InstallPlanMode,
7
+ type InstallScope,
8
+ type InstallTargetPreview,
9
+ resolveAgentIntegration,
10
+ resolveEffectiveInstallTargets,
11
+ resolveInstallPlanMode,
12
+ } from "./target-effective.ts";
13
+ import {
14
+ INSTALL_ARTIFACT_KEYS,
15
+ INSTALL_TARGETS,
16
+ installTargetForKey,
17
+ MCP_KEYS,
18
+ SKILL_KEYS,
19
+ } from "./target-registry.ts";
20
+ import type {
21
+ CliInstallArtifactKey,
22
+ DetectedSnapshot,
23
+ InstalledArtifacts,
24
+ InstallOpts,
25
+ TargetPlanContext,
26
+ } from "./target-types.ts";
27
+
28
+ export { mcpCategoryEnabled } from "./target-effective.ts";
29
+
30
+ function emptyInstalledArtifacts(): InstalledArtifacts {
31
+ return {
32
+ app: false,
33
+ cursorSkill: false,
34
+ claudeSkill: false,
35
+ codexSkill: false,
36
+ opencodeSkill: false,
37
+ openclawSkill: false,
38
+ cursorMcp: false,
39
+ claudeMcp: false,
40
+ claudeDesktopMcp: false,
41
+ opencodeMcp: false,
42
+ codexMcp: false,
43
+ openclawMcp: false,
44
+ chatGptMcp: false,
45
+ };
46
+ }
47
+
48
+ /** Detects which install artifacts are currently present. */
49
+ export function detectInstalledArtifacts(
50
+ paths: InstallPaths,
51
+ root: CliProgram,
52
+ ): InstalledArtifacts {
53
+ const out = emptyInstalledArtifacts();
54
+ for (const target of INSTALL_TARGETS) {
55
+ target.applyDetected(paths, root, out);
56
+ }
57
+ return out;
58
+ }
59
+
60
+ /** Scope flags for install/uninstall. */
61
+ export function resolveInstallScope(opts: InstallOpts): InstallScope {
62
+ return {
63
+ all: opts.all,
64
+ skill: opts.skill,
65
+ mcp: opts.mcp,
66
+ configure: opts.configure,
67
+ uninstall: opts.uninstall,
68
+ };
69
+ }
70
+
71
+ /** Builds detected snapshot including app config for plan scoping. */
72
+ export function buildDetectedSnapshot(root: CliProgram, paths: InstallPaths): DetectedSnapshot {
73
+ const base = detectInstalledArtifacts(paths, root);
74
+ return {
75
+ ...base,
76
+ appConfig: root.appConfig ? appConfigInstalled(root) : false,
77
+ };
78
+ }
79
+
80
+ function targetExplicitlyConfigured(
81
+ user: CliInstallTargets | undefined,
82
+ key: CliInstallArtifactKey,
83
+ ): boolean {
84
+ return user?.[key] !== undefined;
85
+ }
86
+
87
+ function agentCategoryInScope(
88
+ key: CliInstallArtifactKey,
89
+ categoryKeys: readonly CliInstallArtifactKey[],
90
+ category: "skill" | "mcp",
91
+ effective: Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>,
92
+ targets: CliInstallTargets | undefined,
93
+ root: CliProgram,
94
+ ): boolean {
95
+ if (!categoryKeys.includes(key)) return false;
96
+ if (!effective[key].enabled) return false;
97
+ if (category === "mcp" && !resolveCapabilities(root).mcp) return false;
98
+ return effective[key].includedInAll || targetExplicitlyConfigured(targets, key);
99
+ }
100
+
101
+ /** Whether an artifact is in scope for the current CLI flags and install mode. */
102
+ export function isArtifactInScope(
103
+ key: CliInstallArtifactKey,
104
+ scope: InstallScope,
105
+ effective: Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>,
106
+ mode: InstallPlanMode,
107
+ root: CliProgram,
108
+ targets?: CliInstallTargets,
109
+ ): boolean {
110
+ if (mode === "uninstall-all") {
111
+ return true;
112
+ }
113
+
114
+ if (mode === "refresh") {
115
+ return effective[key].enabled;
116
+ }
117
+
118
+ if (mode === "install-all") {
119
+ const t = effective[key];
120
+ return t.enabled && t.includedInAll;
121
+ }
122
+
123
+ if (mode === "uninstall-scoped" || mode === "install-scoped") {
124
+ if (!effective[key].enabled) return false;
125
+
126
+ let inScope = false;
127
+ if (scope.skill) {
128
+ inScope = inScope || agentCategoryInScope(key, SKILL_KEYS, "skill", effective, targets, root);
129
+ }
130
+ if (scope.mcp) {
131
+ inScope = inScope || agentCategoryInScope(key, MCP_KEYS, "mcp", effective, targets, root);
132
+ }
133
+ if (scope.configure && key === "configure") {
134
+ inScope = true;
135
+ }
136
+ return inScope;
137
+ }
138
+
139
+ return false;
140
+ }
141
+
142
+ /** Whether to include an artifact in the current install/uninstall/refresh plan. */
143
+ export function shouldIncludeArtifact(
144
+ key: CliInstallArtifactKey,
145
+ root: CliProgram,
146
+ paths: InstallPaths,
147
+ scope: InstallScope,
148
+ mode: InstallPlanMode,
149
+ effective: Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>,
150
+ detected?: Partial<Record<CliInstallArtifactKey, boolean>>,
151
+ ): boolean {
152
+ const target = installTargetForKey(key);
153
+ const targets = root.install?.targets;
154
+
155
+ if (key !== "configure" && target && !target.isAvailable(root, paths)) {
156
+ return false;
157
+ }
158
+ if (key === "configure" && !root.appConfig) {
159
+ return false;
160
+ }
161
+ if (mode === "refresh" && detected) {
162
+ if (key === "app") {
163
+ // app is status-only; refresh skills/MCP only
164
+ } else if (!detected[key]) {
165
+ return false;
166
+ }
167
+ }
168
+ if ((mode === "uninstall-all" || mode === "uninstall-scoped") && detected) {
169
+ const det = detected[key];
170
+ if (key === "configure") {
171
+ if (!det) return false;
172
+ } else if (mode === "uninstall-scoped") {
173
+ if (!isArtifactInScope(key, scope, effective, mode, root, targets)) return false;
174
+ if (!det) return false;
175
+ } else if (!det) {
176
+ return false;
177
+ }
178
+ }
179
+ if (mode === "uninstall-all") {
180
+ return true;
181
+ }
182
+ if (mode === "uninstall-scoped") {
183
+ return isArtifactInScope(key, scope, effective, mode, root, targets);
184
+ }
185
+ if (mode === "refresh") {
186
+ if (key === "app") return false;
187
+ return effective[key].enabled;
188
+ }
189
+ return isArtifactInScope(key, scope, effective, mode, root, targets);
190
+ }
191
+
192
+ /** Builds plan context shared by install and uninstall planners. */
193
+ export function buildTargetPlanContext(
194
+ root: CliProgram,
195
+ paths: InstallPaths,
196
+ opts: InstallOpts,
197
+ detected: DetectedSnapshot,
198
+ ): TargetPlanContext {
199
+ const effective = resolveEffectiveInstallTargets(root.install, root);
200
+ const mode = resolveInstallPlanMode(opts);
201
+ const scope = resolveInstallScope(opts);
202
+ const detPartial = Object.fromEntries(
203
+ INSTALL_TARGETS.map((t) => [t.key, t.detectedForSnapshot(detected)]),
204
+ ) as Partial<Record<CliInstallArtifactKey, boolean>>;
205
+
206
+ const include = (key: CliInstallArtifactKey) =>
207
+ shouldIncludeArtifact(key, root, paths, scope, mode, effective, detPartial);
208
+
209
+ return {
210
+ root,
211
+ paths,
212
+ opts,
213
+ dry: !!opts.dry,
214
+ detected,
215
+ effective,
216
+ scope,
217
+ mode,
218
+ include,
219
+ };
220
+ }
221
+
222
+ /** Maps detected snapshot to artifact key (for tests and legacy callers). */
223
+ export function detectedForArtifact(
224
+ key: CliInstallArtifactKey,
225
+ detected: DetectedSnapshot,
226
+ ): boolean {
227
+ const target = installTargetForKey(key);
228
+ if (key === "configure") {
229
+ return detected.appConfig ?? false;
230
+ }
231
+ return target?.detectedForSnapshot(detected) ?? false;
232
+ }
233
+
234
+ /** Resolved artifact keys for `--all`, `--mcp`, and `--skill` (availability-gated). */
235
+ export function resolveInstallTargetPreview(
236
+ program: CliProgram,
237
+ paths: InstallPaths,
238
+ ): InstallTargetPreview {
239
+ const effective = resolveEffectiveInstallTargets(program.install, program);
240
+ const integration = resolveAgentIntegration(program.install, program.mcpServer?.enabled === true);
241
+
242
+ const keysForScope = (scope: InstallScope, mode: InstallPlanMode): CliInstallArtifactKey[] =>
243
+ INSTALL_ARTIFACT_KEYS.filter((key) =>
244
+ shouldIncludeArtifact(key, program, paths, scope, mode, effective),
245
+ );
246
+
247
+ return {
248
+ agentIntegration: integration,
249
+ all: keysForScope({ all: true }, "install-all"),
250
+ mcp: keysForScope({ mcp: true }, "install-scoped"),
251
+ skill: keysForScope({ skill: true }, "install-scoped"),
252
+ };
253
+ }
@@ -0,0 +1,104 @@
1
+ import { existsSync } from "node:fs";
2
+ import type { CliProgram } from "../types.ts";
3
+ import { displayInstallPath, type InstallPaths } from "./paths.ts";
4
+ import { InstallTarget } from "./target-base.ts";
5
+ import type {
6
+ CliInstallArtifactKey,
7
+ DetectedSnapshot,
8
+ InstallAction,
9
+ InstallActionKind,
10
+ InstalledArtifacts,
11
+ InstallStatus,
12
+ TargetPlanContext,
13
+ UninstallAction,
14
+ } from "./target-types.ts";
15
+
16
+ export interface SkillHostSpec {
17
+ key: CliInstallArtifactKey;
18
+ actionKind: InstallActionKind;
19
+ label: string;
20
+ uninstallPrefix: string;
21
+ pairedMcpKey: CliInstallArtifactKey;
22
+ skillDir: (paths: InstallPaths) => string;
23
+ detectedKey: keyof Pick<
24
+ InstalledArtifacts,
25
+ "cursorSkill" | "claudeSkill" | "codexSkill" | "opencodeSkill" | "openclawSkill"
26
+ >;
27
+ statusField: keyof Pick<
28
+ InstallStatus,
29
+ "cursorSkill" | "claudeSkill" | "codexSkill" | "opencodeSkill" | "openclawSkill"
30
+ >;
31
+ isAvailable: (root: CliProgram, paths: InstallPaths) => boolean;
32
+ }
33
+
34
+ /** Agent shell skill directory install. */
35
+ export class SkillInstallTarget extends InstallTarget {
36
+ readonly key: CliInstallArtifactKey;
37
+ readonly actionKind: InstallActionKind;
38
+ readonly category = "skill" as const;
39
+ readonly pairedKey: CliInstallArtifactKey;
40
+ readonly uninstallPrefix: string;
41
+
42
+ private readonly spec: SkillHostSpec;
43
+
44
+ constructor(spec: SkillHostSpec) {
45
+ super();
46
+ this.spec = spec;
47
+ this.key = spec.key;
48
+ this.actionKind = spec.actionKind;
49
+ this.pairedKey = spec.pairedMcpKey;
50
+ this.uninstallPrefix = spec.uninstallPrefix;
51
+ }
52
+
53
+ isAvailable(root: CliProgram, paths: InstallPaths): boolean {
54
+ return this.spec.isAvailable(root, paths);
55
+ }
56
+
57
+ isDetected(paths: InstallPaths, _root: CliProgram): boolean {
58
+ return existsSync(this.spec.skillDir(paths));
59
+ }
60
+
61
+ applyDetected(paths: InstallPaths, root: CliProgram, out: InstalledArtifacts): void {
62
+ out[this.spec.detectedKey] = this.isDetected(paths, root);
63
+ }
64
+
65
+ protected isDetectedFromSnapshot(detected: DetectedSnapshot): boolean {
66
+ return detected[this.spec.detectedKey];
67
+ }
68
+
69
+ protected formatStatusLine(paths: InstallPaths, _root: CliProgram): string {
70
+ return `${displayInstallPath(this.spec.skillDir(paths))}/`;
71
+ }
72
+
73
+ protected assignStatusLine(status: InstallStatus, line: string): void {
74
+ status[this.spec.statusField] = line;
75
+ }
76
+
77
+ skillDir(paths: InstallPaths): string {
78
+ return this.spec.skillDir(paths);
79
+ }
80
+
81
+ protected buildInstallActions(ctx: TargetPlanContext): InstallAction[] {
82
+ const dir = this.spec.skillDir(ctx.paths);
83
+ return [
84
+ {
85
+ kind: this.actionKind,
86
+ summary: `${this.spec.label.toLowerCase()} skill: ${displayInstallPath(dir)}/`,
87
+ message: `Installing ${this.spec.label} skill to ${displayInstallPath(dir)}/`,
88
+ run: () => [],
89
+ },
90
+ ];
91
+ }
92
+
93
+ protected buildUninstallActions(ctx: TargetPlanContext): UninstallAction[] {
94
+ const dir = this.spec.skillDir(ctx.paths);
95
+ return [
96
+ {
97
+ kind: this.actionKind,
98
+ summary: `${this.uninstallPrefix}: ${displayInstallPath(dir)}/`,
99
+ message: `Removing ${this.spec.label} skill ${displayInstallPath(dir)}/`,
100
+ run: () => [],
101
+ },
102
+ ];
103
+ }
104
+ }
@@ -0,0 +1,129 @@
1
+ import type { CliProgram } from "../types.ts";
2
+ import type { InstallPaths } from "./paths.ts";
3
+
4
+ export type { InstallTargetSpec, ResolvedInstallTarget } from "../types.ts";
5
+
6
+ export type InstallPlanMode =
7
+ | "install-all"
8
+ | "install-scoped"
9
+ | "uninstall-all"
10
+ | "uninstall-scoped"
11
+ | "refresh";
12
+
13
+ export interface InstallScope {
14
+ all?: boolean;
15
+ skill?: boolean;
16
+ mcp?: boolean;
17
+ configure?: boolean;
18
+ uninstall?: boolean;
19
+ }
20
+
21
+ /** Artifact keys for install.targets. */
22
+ export type CliInstallArtifactKey =
23
+ | "app"
24
+ | "chatgptMcp"
25
+ | "claudeCodeMcp"
26
+ | "claudeDesktopMcp"
27
+ | "claudeSkill"
28
+ | "codexMcp"
29
+ | "codexSkill"
30
+ | "configure"
31
+ | "cursorMcp"
32
+ | "cursorSkill"
33
+ | "openclawMcp"
34
+ | "openclawSkill"
35
+ | "opencodeMcp"
36
+ | "opencodeSkill";
37
+
38
+ export type InstallActionKind =
39
+ | "app"
40
+ | "cursor-skill"
41
+ | "claude-skill"
42
+ | "codex-skill"
43
+ | "opencode-skill"
44
+ | "openclaw-skill"
45
+ | "cursor-mcp"
46
+ | "claude-mcp"
47
+ | "claude-desktop-mcp"
48
+ | "opencode-mcp"
49
+ | "codex-mcp"
50
+ | "openclaw-mcp"
51
+ | "chatgpt-desktop-mcp"
52
+ | "configure";
53
+
54
+ export type InstallTargetCategory = "core" | "skill" | "mcp";
55
+
56
+ export interface InstallAction {
57
+ kind: InstallActionKind;
58
+ summary: string;
59
+ message: string;
60
+ run: () => string[];
61
+ }
62
+
63
+ export interface UninstallAction {
64
+ kind?: InstallActionKind | "configure";
65
+ summary: string;
66
+ message: string;
67
+ run: () => string[];
68
+ }
69
+
70
+ export interface InstalledArtifacts {
71
+ app: boolean;
72
+ cursorSkill: boolean;
73
+ claudeSkill: boolean;
74
+ codexSkill: boolean;
75
+ opencodeSkill: boolean;
76
+ openclawSkill: boolean;
77
+ cursorMcp: boolean;
78
+ claudeMcp: boolean;
79
+ claudeDesktopMcp: boolean;
80
+ opencodeMcp: boolean;
81
+ codexMcp: boolean;
82
+ openclawMcp: boolean;
83
+ chatGptMcp: boolean;
84
+ }
85
+
86
+ export interface InstallStatus {
87
+ app?: string;
88
+ cursorSkill?: string;
89
+ claudeSkill?: string;
90
+ codexSkill?: string;
91
+ opencodeSkill?: string;
92
+ openclawSkill?: string;
93
+ cursorMcp?: string;
94
+ claudeMcp?: string;
95
+ claudeDesktopMcp?: string;
96
+ opencodeMcp?: string;
97
+ codexMcp?: string;
98
+ openclawMcp?: string;
99
+ chatGptMcp?: string;
100
+ }
101
+
102
+ export interface DetectedSnapshot extends InstalledArtifacts {
103
+ appConfig?: boolean;
104
+ }
105
+
106
+ export interface InstallOpts {
107
+ all?: boolean;
108
+ skill?: boolean;
109
+ mcp?: boolean;
110
+ reinstall?: boolean;
111
+ status?: boolean;
112
+ uninstall?: boolean;
113
+ configure?: boolean;
114
+ yes?: boolean;
115
+ dry?: boolean;
116
+ json?: boolean;
117
+ }
118
+
119
+ export interface TargetPlanContext {
120
+ root: CliProgram;
121
+ paths: InstallPaths;
122
+ opts: InstallOpts;
123
+ dry: boolean;
124
+ detected: DetectedSnapshot;
125
+ effective: Record<CliInstallArtifactKey, { enabled: boolean; includedInAll: boolean }>;
126
+ scope: InstallScope;
127
+ mode: InstallPlanMode;
128
+ include: (key: CliInstallArtifactKey) => boolean;
129
+ }
@@ -0,0 +1,60 @@
1
+ import type { CliProgram } from "../../types.ts";
2
+ import { isExternallyManagedBinary } from "../binary-placement.ts";
3
+ import { displayInstallPath, type InstallPaths } from "../paths.ts";
4
+ import { InstallTarget } from "../target-base.ts";
5
+ import type {
6
+ DetectedSnapshot,
7
+ InstallAction,
8
+ InstalledArtifacts,
9
+ InstallStatus,
10
+ TargetPlanContext,
11
+ UninstallAction,
12
+ } from "../target-types.ts";
13
+
14
+ /** Reports app install location (Homebrew PATH or legacy ~/.local/bin). No self-install actions. */
15
+ class AppInstallTarget extends InstallTarget {
16
+ readonly key = "app" as const;
17
+ readonly actionKind = "app" as const;
18
+ readonly category = "core" as const;
19
+
20
+ defaultIncludedInAll(): boolean {
21
+ return false;
22
+ }
23
+
24
+ isAvailable(_root: CliProgram, _paths: InstallPaths): boolean {
25
+ return true;
26
+ }
27
+
28
+ isDetected(paths: InstallPaths, root: CliProgram): boolean {
29
+ return isExternallyManagedBinary(root.key) || false;
30
+ }
31
+
32
+ applyDetected(_paths: InstallPaths, root: CliProgram, out: InstalledArtifacts): void {
33
+ out.app = isExternallyManagedBinary(root.key);
34
+ }
35
+
36
+ protected isDetectedFromSnapshot(detected: DetectedSnapshot): boolean {
37
+ return detected.app;
38
+ }
39
+
40
+ protected formatStatusLine(_paths: InstallPaths, root: CliProgram): string {
41
+ if (isExternallyManagedBinary(root.key)) {
42
+ return "system (PATH)";
43
+ }
44
+ return "not installed (use Homebrew)";
45
+ }
46
+
47
+ protected assignStatusLine(status: InstallStatus, line: string): void {
48
+ status.app = line;
49
+ }
50
+
51
+ protected buildInstallActions(_ctx: TargetPlanContext): InstallAction[] {
52
+ return [];
53
+ }
54
+
55
+ protected buildUninstallActions(_ctx: TargetPlanContext): UninstallAction[] {
56
+ return [];
57
+ }
58
+ }
59
+
60
+ export const appTarget = new AppInstallTarget();
@@ -0,0 +1,12 @@
1
+ import { chatGptDesktopPresent, userHome } from "../paths.ts";
2
+ import { McpJsonInstallTarget } from "../target-mcp-json.ts";
3
+
4
+ export const chatgptMcpTarget = new McpJsonInstallTarget({
5
+ key: "chatgptMcp",
6
+ actionKind: "chatgpt-desktop-mcp",
7
+ label: "chatgpt desktop mcp",
8
+ configPath: (p) => p.chatGptMcpPath,
9
+ detectedKey: "chatGptMcp",
10
+ statusField: "chatGptMcp",
11
+ isAvailable: (_root, paths) => chatGptDesktopPresent(userHome(), paths.chatGptMcpPath),
12
+ });
@@ -0,0 +1,15 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { userHome } from "../paths.ts";
4
+ import { McpJsonInstallTarget } from "../target-mcp-json.ts";
5
+
6
+ export const claudeCodeMcpTarget = new McpJsonInstallTarget({
7
+ key: "claudeCodeMcp",
8
+ actionKind: "claude-mcp",
9
+ label: "claude code mcp",
10
+ pairedSkillKey: "claudeSkill",
11
+ configPath: (p) => p.claudeMcpPath,
12
+ detectedKey: "claudeMcp",
13
+ statusField: "claudeMcp",
14
+ isAvailable: () => existsSync(join(userHome(), ".claude")),
15
+ });
@@ -0,0 +1,12 @@
1
+ import { claudeDesktopPresent, userHome } from "../paths.ts";
2
+ import { McpJsonInstallTarget } from "../target-mcp-json.ts";
3
+
4
+ export const claudeDesktopMcpTarget = new McpJsonInstallTarget({
5
+ key: "claudeDesktopMcp",
6
+ actionKind: "claude-desktop-mcp",
7
+ label: "claude desktop mcp",
8
+ configPath: (p) => p.claudeDesktopMcpPath,
9
+ detectedKey: "claudeDesktopMcp",
10
+ statusField: "claudeDesktopMcp",
11
+ isAvailable: (_root, paths) => claudeDesktopPresent(userHome(), paths.claudeDesktopMcpPath),
12
+ });
@@ -0,0 +1,16 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { userHome } from "../paths.ts";
4
+ import { SkillInstallTarget } from "../target-skill.ts";
5
+
6
+ export const claudeSkillTarget = new SkillInstallTarget({
7
+ key: "claudeSkill",
8
+ actionKind: "claude-skill",
9
+ label: "Claude Code",
10
+ uninstallPrefix: "claude skill",
11
+ pairedMcpKey: "claudeCodeMcp",
12
+ skillDir: (p) => p.claudeSkillDir,
13
+ detectedKey: "claudeSkill",
14
+ statusField: "claudeSkill",
15
+ isAvailable: () => existsSync(join(userHome(), ".claude")),
16
+ });
@@ -0,0 +1,25 @@
1
+ import { codexOnPath } from "../mcp-codex.ts";
2
+ import {
3
+ checkCodexMcpConflict,
4
+ codexMcpHasServer,
5
+ McpCliInstallTarget,
6
+ mergeCodexMcpConfig,
7
+ removeCodexMcpConfig,
8
+ } from "../target-mcp-cli.ts";
9
+
10
+ export const codexMcpTarget = new McpCliInstallTarget({
11
+ key: "codexMcp",
12
+ actionKind: "codex-mcp",
13
+ label: "codex mcp",
14
+ pairedSkillKey: "codexSkill",
15
+ configPath: (p) => p.codexConfigPath,
16
+ detectedKey: "codexMcp",
17
+ statusField: "codexMcp",
18
+ isAvailable: () => codexOnPath(),
19
+ hasServer: codexMcpHasServer,
20
+ installMessage: 'Registering MCP server "{name}" via codex mcp add',
21
+ uninstallMessage: 'Removing MCP server "{name}" from Codex via codex mcp remove',
22
+ merge: mergeCodexMcpConfig,
23
+ remove: removeCodexMcpConfig,
24
+ preflight: checkCodexMcpConflict,
25
+ });
@@ -0,0 +1,14 @@
1
+ import { codexOnPath } from "../mcp-codex.ts";
2
+ import { SkillInstallTarget } from "../target-skill.ts";
3
+
4
+ export const codexSkillTarget = new SkillInstallTarget({
5
+ key: "codexSkill",
6
+ actionKind: "codex-skill",
7
+ label: "Codex",
8
+ uninstallPrefix: "codex skill",
9
+ pairedMcpKey: "codexMcp",
10
+ skillDir: (p) => p.codexSkillDir,
11
+ detectedKey: "codexSkill",
12
+ statusField: "codexSkill",
13
+ isAvailable: () => codexOnPath(),
14
+ });