argsbarg 4.0.3 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -1
- package/README.md +6 -6
- package/docs/ai-skills.md +9 -4
- package/docs/bundled-docs.md +1 -0
- package/docs/cli-program.md +4 -4
- package/docs/config-schema.md +1 -4
- package/docs/developing.md +1 -1
- package/docs/install.md +160 -39
- package/docs/mcp.md +38 -11
- package/docs/templates/cursor/rules/cli-program.mdc +1 -1
- package/examples/config-app/program.ts +0 -3
- package/examples/consumer-app/README.md +1 -1
- package/examples/consumer-app/src/program.ts +5 -13
- package/examples/mcp-test.ts +14 -24
- package/index.d.ts +60 -5
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +20 -4
- package/src/builtins/config.test.ts +31 -25
- package/src/builtins/config.ts +4 -3
- package/src/builtins/install.ts +50 -57
- package/src/builtins/mcp.ts +1 -1
- package/src/capabilities.ts +4 -4
- package/src/cli.ts +2 -0
- package/src/config/bootstrap.ts +167 -66
- package/src/config/context.test.ts +22 -36
- package/src/config/context.ts +5 -4
- package/src/config/file.test.ts +66 -56
- package/src/config/file.ts +33 -25
- package/src/config/resolve.test.ts +25 -1
- package/src/config/resolve.ts +43 -8
- package/src/config.integration.test.ts +17 -10
- package/src/docs/api-guide.test.ts +1 -1
- package/src/docs/docs.test.ts +1 -1
- package/src/docs/mcp-guide.ts +15 -4
- package/src/docs/mcp-resources.test.ts +63 -0
- package/src/docs/mcp-resources.ts +68 -0
- package/src/hidden-mcpb.test.ts +41 -1
- package/src/index.ts +4 -0
- package/src/install/{binary.ts → app.ts} +13 -13
- package/src/install/bootstrap.ts +22 -0
- package/src/install/detect-installed.ts +2 -97
- package/src/install/index.ts +187 -110
- package/src/install/install-validate.test.ts +61 -0
- package/src/install/install.test.ts +138 -42
- package/src/install/mcp-openclaw.test.ts +40 -0
- package/src/install/mcp-openclaw.ts +106 -0
- package/src/install/normalize.ts +35 -0
- package/src/install/paths.ts +27 -13
- package/src/install/plan.ts +30 -259
- package/src/install/shell.ts +2 -2
- package/src/install/status.test.ts +85 -0
- package/src/install/status.ts +22 -9
- package/src/install/target-base.ts +93 -0
- package/src/install/target-detect.ts +20 -0
- package/src/install/target-effective.ts +131 -0
- package/src/install/target-mcp-cli.ts +149 -0
- package/src/install/target-mcp-json.ts +130 -0
- package/src/install/target-plan-build.ts +67 -0
- package/src/install/target-registry.ts +57 -0
- package/src/install/target-scope.ts +266 -0
- package/src/install/target-skill.ts +104 -0
- package/src/install/target-types.ts +145 -0
- package/src/install/targets/app.ts +69 -0
- package/src/install/targets/chatgpt-mcp.ts +12 -0
- package/src/install/targets/claude-code-mcp.ts +15 -0
- package/src/install/targets/claude-desktop-mcp.ts +12 -0
- package/src/install/targets/claude-skill.ts +16 -0
- package/src/install/targets/codex-mcp.ts +25 -0
- package/src/install/targets/codex-skill.ts +14 -0
- package/src/install/targets/completions.ts +133 -0
- package/src/install/targets/configure.ts +59 -0
- package/src/install/targets/cursor-mcp.ts +15 -0
- package/src/install/targets/cursor-skill.ts +16 -0
- package/src/install/targets/index.ts +53 -0
- package/src/install/targets/openclaw-mcp.ts +25 -0
- package/src/install/targets/openclaw-skill.ts +17 -0
- package/src/install/targets/opencode-mcp.ts +101 -0
- package/src/install/targets/opencode-skill.ts +15 -0
- package/src/install/targets.test.ts +136 -0
- package/src/install/uninstall.ts +16 -152
- package/src/install/update.test.ts +17 -2
- package/src/install/update.ts +2 -5
- package/src/invoke.test.ts +7 -1
- package/src/mcp/bundle.ts +16 -4
- package/src/mcp/claude.test.ts +23 -4
- package/src/mcp/claude.ts +18 -13
- package/src/mcp/tools.ts +4 -1
- package/src/mcp/zip.test.ts +17 -0
- package/src/mcp/zip.ts +62 -9
- package/src/mcp.integration.test.ts +18 -1
- package/src/parse.test.ts +57 -4
- package/src/paths/host.ts +11 -11
- package/src/paths/remove-empty-dir.ts +13 -0
- package/src/skill/generate.ts +89 -5
- package/src/skill/hint.ts +5 -0
- package/src/skill/install.ts +33 -6
- package/src/skill/naming.ts +28 -0
- package/src/types.ts +65 -4
- package/src/validate.ts +79 -4
package/src/install/plan.ts
CHANGED
|
@@ -1,282 +1,53 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
1
|
import { resolveCapabilities } from "../capabilities.ts";
|
|
4
2
|
import type { CliProgram } from "../types.ts";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { codexOnPath, mergeCodexMcpConfig } from "./mcp-codex.ts";
|
|
9
|
-
import { expectedMcpEntry, mergeMcpConfig } from "./mcp-config.ts";
|
|
10
|
-
import {
|
|
11
|
-
expectedOpenCodeMcpEntry,
|
|
12
|
-
mergeOpenCodeMcpConfig,
|
|
13
|
-
opencodePresent,
|
|
14
|
-
} from "./mcp-opencode.ts";
|
|
15
|
-
import {
|
|
16
|
-
chatGptDesktopPresent,
|
|
17
|
-
claudeDesktopPresent,
|
|
18
|
-
type InstallPaths,
|
|
19
|
-
userHome,
|
|
20
|
-
} from "./paths.ts";
|
|
21
|
-
import { detectShells } from "./shell.ts";
|
|
3
|
+
import type { InstallPaths } from "./paths.ts";
|
|
4
|
+
import { buildInstallPlanFromTargets } from "./target-plan-build.ts";
|
|
5
|
+
import type { InstallAction, InstallOpts } from "./target-types.ts";
|
|
22
6
|
|
|
23
|
-
export
|
|
24
|
-
all?: boolean;
|
|
25
|
-
bin?: boolean;
|
|
26
|
-
completions?: boolean;
|
|
27
|
-
skill?: boolean;
|
|
28
|
-
mcp?: boolean;
|
|
29
|
-
reinstall?: boolean;
|
|
30
|
-
update?: boolean;
|
|
31
|
-
from?: string;
|
|
32
|
-
status?: boolean;
|
|
33
|
-
uninstall?: boolean;
|
|
34
|
-
configure?: boolean;
|
|
35
|
-
config?: boolean;
|
|
36
|
-
yes?: boolean;
|
|
37
|
-
dry?: boolean;
|
|
38
|
-
json?: boolean;
|
|
39
|
-
quiet?: boolean;
|
|
40
|
-
prefix?: string;
|
|
41
|
-
}
|
|
7
|
+
export type { InstallAction, InstallActionKind, InstallOpts } from "./target-types.ts";
|
|
42
8
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
| "opencode-mcp"
|
|
52
|
-
| "codex-mcp"
|
|
53
|
-
| "chatgpt-desktop-mcp";
|
|
9
|
+
/** Builds install actions for normal mode (--all / scoped targets). */
|
|
10
|
+
export function buildInstallPlan(
|
|
11
|
+
root: CliProgram,
|
|
12
|
+
paths: InstallPaths,
|
|
13
|
+
opts: InstallOpts,
|
|
14
|
+
): InstallAction[] {
|
|
15
|
+
return buildInstallPlanFromTargets(root, paths, opts);
|
|
16
|
+
}
|
|
54
17
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
18
|
+
/** Builds update/reinstall actions for detected artifacts within effective targets. */
|
|
19
|
+
export function buildUpdatePlan(
|
|
20
|
+
root: CliProgram,
|
|
21
|
+
paths: InstallPaths,
|
|
22
|
+
opts: InstallOpts,
|
|
23
|
+
): InstallAction[] {
|
|
24
|
+
return buildInstallPlan(root, paths, { ...opts, reinstall: true, all: true });
|
|
60
25
|
}
|
|
61
26
|
|
|
62
|
-
|
|
63
|
-
|
|
27
|
+
/** @deprecated Use resolveInstallPlanMode + shouldIncludeArtifact. */
|
|
28
|
+
export function wantsInstallApp(opts: InstallOpts): boolean {
|
|
29
|
+
return !!(opts.all || opts.app || opts.reinstall);
|
|
64
30
|
}
|
|
65
31
|
|
|
32
|
+
/** @deprecated Use {@link wantsInstallApp}. */
|
|
33
|
+
export const wantsInstallBin = wantsInstallApp;
|
|
34
|
+
|
|
35
|
+
/** @deprecated Use resolveInstallPlanMode + shouldIncludeArtifact. */
|
|
66
36
|
export function wantsInstallCompletions(opts: InstallOpts): boolean {
|
|
67
37
|
return !!(opts.all || opts.completions);
|
|
68
38
|
}
|
|
69
39
|
|
|
40
|
+
/** @deprecated Use resolveInstallPlanMode + shouldIncludeArtifact. */
|
|
70
41
|
export function wantsInstallSkill(opts: InstallOpts): boolean {
|
|
71
42
|
return !!(opts.all || opts.skill);
|
|
72
43
|
}
|
|
73
44
|
|
|
45
|
+
/** @deprecated Use resolveInstallPlanMode + shouldIncludeArtifact. */
|
|
74
46
|
export function wantsInstallMcp(opts: InstallOpts, root: CliProgram): boolean {
|
|
75
47
|
return !!(opts.mcp || opts.all) && resolveCapabilities(root).mcp;
|
|
76
48
|
}
|
|
77
49
|
|
|
50
|
+
/** @deprecated Use configure target + shouldIncludeArtifact. */
|
|
78
51
|
export function wantsUninstallConfig(opts: InstallOpts, root: CliProgram): boolean {
|
|
79
|
-
return !!(opts.
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Builds install actions for normal mode (--all / scoped targets). */
|
|
83
|
-
export function buildInstallPlan(
|
|
84
|
-
root: CliProgram,
|
|
85
|
-
paths: InstallPaths,
|
|
86
|
-
opts: InstallOpts,
|
|
87
|
-
): InstallAction[] {
|
|
88
|
-
const actions: InstallAction[] = [];
|
|
89
|
-
const dry = !!opts.dry;
|
|
90
|
-
|
|
91
|
-
if (wantsInstallBin(opts)) {
|
|
92
|
-
const sourcePath = opts.from ?? process.execPath;
|
|
93
|
-
actions.push({
|
|
94
|
-
kind: "binary",
|
|
95
|
-
summary: `binary: ${paths.binaryPath}`,
|
|
96
|
-
message: `Installing binary to ${paths.binaryPath}`,
|
|
97
|
-
run: () => installBinary(root, paths, dry, sourcePath).changedFiles,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (wantsInstallCompletions(opts)) {
|
|
102
|
-
const shells = detectShells();
|
|
103
|
-
if (shells.bash) {
|
|
104
|
-
actions.push({
|
|
105
|
-
kind: "completions",
|
|
106
|
-
summary: `bash completion: ${paths.bashCompletion}`,
|
|
107
|
-
message: `Writing bash completion to ${paths.bashCompletion}`,
|
|
108
|
-
run: () => {
|
|
109
|
-
const all = installCompletions(root, paths, dry);
|
|
110
|
-
return all.filter((p) => p === paths.bashCompletion);
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
if (shells.zsh) {
|
|
115
|
-
actions.push({
|
|
116
|
-
kind: "completions",
|
|
117
|
-
summary: `zsh completion: ${paths.zshCompletion}`,
|
|
118
|
-
message: `Writing zsh completion to ${paths.zshCompletion}`,
|
|
119
|
-
run: () => {
|
|
120
|
-
const all = installCompletions(root, paths, dry);
|
|
121
|
-
return all.filter((p) => p === paths.zshCompletion);
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
if (shells.fish) {
|
|
126
|
-
actions.push({
|
|
127
|
-
kind: "completions",
|
|
128
|
-
summary: `fish completion: ${paths.fishCompletion}`,
|
|
129
|
-
message: `Writing fish completion to ${paths.fishCompletion}`,
|
|
130
|
-
run: () => {
|
|
131
|
-
const all = installCompletions(root, paths, dry);
|
|
132
|
-
return all.filter((p) => p === paths.fishCompletion);
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (wantsInstallSkill(opts)) {
|
|
139
|
-
const home = userHome();
|
|
140
|
-
if (existsSync(join(home, ".cursor"))) {
|
|
141
|
-
actions.push({
|
|
142
|
-
kind: "cursor-skill",
|
|
143
|
-
summary: `cursor skill: ${paths.cursorSkillDir}/`,
|
|
144
|
-
message: `Installing Cursor skill to ${paths.cursorSkillDir}/`,
|
|
145
|
-
run: () => [],
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
if (existsSync(join(home, ".claude"))) {
|
|
149
|
-
actions.push({
|
|
150
|
-
kind: "claude-skill",
|
|
151
|
-
summary: `claude skill: ${paths.claudeSkillDir}/`,
|
|
152
|
-
message: `Installing Claude Code skill to ${paths.claudeSkillDir}/`,
|
|
153
|
-
run: () => [],
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (wantsInstallMcp(opts, root)) {
|
|
159
|
-
const entry = expectedMcpEntry(root);
|
|
160
|
-
if (existsSync(join(userHome(), ".cursor"))) {
|
|
161
|
-
actions.push({
|
|
162
|
-
kind: "cursor-mcp",
|
|
163
|
-
summary: `cursor mcp: ${paths.cursorMcpPath} (server "${paths.mcpName}")`,
|
|
164
|
-
message: `Merging MCP server "${paths.mcpName}" into ${paths.cursorMcpPath}`,
|
|
165
|
-
run: () => {
|
|
166
|
-
mergeMcpConfig(paths.cursorMcpPath, paths.mcpName, entry, dry);
|
|
167
|
-
return [paths.cursorMcpPath];
|
|
168
|
-
},
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
actions.push({
|
|
172
|
-
kind: "claude-mcp",
|
|
173
|
-
summary: `claude code mcp: ${paths.claudeMcpPath} (server "${paths.mcpName}")`,
|
|
174
|
-
message: `Merging MCP server "${paths.mcpName}" into ${paths.claudeMcpPath}`,
|
|
175
|
-
run: () => {
|
|
176
|
-
mergeMcpConfig(paths.claudeMcpPath, paths.mcpName, entry, dry);
|
|
177
|
-
return [paths.claudeMcpPath];
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
if (claudeDesktopPresent(userHome(), paths.claudeDesktopMcpPath)) {
|
|
181
|
-
actions.push({
|
|
182
|
-
kind: "claude-desktop-mcp",
|
|
183
|
-
summary: `claude desktop mcp: ${paths.claudeDesktopMcpPath} (server "${paths.mcpName}")`,
|
|
184
|
-
message: `Merging MCP server "${paths.mcpName}" into ${paths.claudeDesktopMcpPath}`,
|
|
185
|
-
run: () => {
|
|
186
|
-
mergeMcpConfig(paths.claudeDesktopMcpPath, paths.mcpName, entry, dry);
|
|
187
|
-
return [paths.claudeDesktopMcpPath];
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
if (opencodePresent(userHome())) {
|
|
192
|
-
const openCodeEntry = expectedOpenCodeMcpEntry(root);
|
|
193
|
-
actions.push({
|
|
194
|
-
kind: "opencode-mcp",
|
|
195
|
-
summary: `opencode mcp: ${paths.opencodeMcpPath} (server "${paths.mcpName}")`,
|
|
196
|
-
message: `Merging MCP server "${paths.mcpName}" into ${paths.opencodeMcpPath}`,
|
|
197
|
-
run: () => {
|
|
198
|
-
mergeOpenCodeMcpConfig(paths.opencodeMcpPath, paths.mcpName, openCodeEntry, dry);
|
|
199
|
-
return [paths.opencodeMcpPath];
|
|
200
|
-
},
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
if (codexOnPath()) {
|
|
204
|
-
actions.push({
|
|
205
|
-
kind: "codex-mcp",
|
|
206
|
-
summary: `codex mcp: ${paths.codexConfigPath} (server "${paths.mcpName}")`,
|
|
207
|
-
message: `Registering MCP server "${paths.mcpName}" via codex mcp add`,
|
|
208
|
-
run: () => {
|
|
209
|
-
const configPath = mergeCodexMcpConfig(userHome(), paths.mcpName, entry, dry, !!opts.yes);
|
|
210
|
-
return [configPath];
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
if (chatGptDesktopPresent(userHome(), paths.chatGptMcpPath)) {
|
|
215
|
-
actions.push({
|
|
216
|
-
kind: "chatgpt-desktop-mcp",
|
|
217
|
-
summary: `chatgpt desktop mcp: ${paths.chatGptMcpPath} (server "${paths.mcpName}")`,
|
|
218
|
-
message: `Merging MCP server "${paths.mcpName}" into ${paths.chatGptMcpPath}`,
|
|
219
|
-
run: () => {
|
|
220
|
-
mergeMcpConfig(paths.chatGptMcpPath, paths.mcpName, entry, dry);
|
|
221
|
-
return [paths.chatGptMcpPath];
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return actions;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** Builds update actions for artifacts already installed. */
|
|
231
|
-
export function buildUpdatePlan(
|
|
232
|
-
root: CliProgram,
|
|
233
|
-
paths: InstallPaths,
|
|
234
|
-
opts: InstallOpts,
|
|
235
|
-
): InstallAction[] {
|
|
236
|
-
const detected = detectInstalledArtifacts(paths);
|
|
237
|
-
const scoped: InstallOpts = {
|
|
238
|
-
bin: true,
|
|
239
|
-
completions: detected.bashCompletion || detected.zshCompletion || detected.fishCompletion,
|
|
240
|
-
skill: detected.cursorSkill || detected.claudeSkill,
|
|
241
|
-
mcp:
|
|
242
|
-
(detected.cursorMcp ||
|
|
243
|
-
detected.claudeMcp ||
|
|
244
|
-
detected.claudeDesktopMcp ||
|
|
245
|
-
detected.opencodeMcp ||
|
|
246
|
-
detected.codexMcp ||
|
|
247
|
-
detected.chatGptMcp) &&
|
|
248
|
-
resolveCapabilities(root).mcp,
|
|
249
|
-
dry: opts.dry,
|
|
250
|
-
};
|
|
251
|
-
const plan = buildInstallPlan(root, paths, scoped);
|
|
252
|
-
|
|
253
|
-
return plan.filter((action) => {
|
|
254
|
-
switch (action.kind) {
|
|
255
|
-
case "binary":
|
|
256
|
-
return true;
|
|
257
|
-
case "completions":
|
|
258
|
-
if (action.summary.startsWith("bash")) return detected.bashCompletion;
|
|
259
|
-
if (action.summary.startsWith("zsh")) return detected.zshCompletion;
|
|
260
|
-
if (action.summary.startsWith("fish")) return detected.fishCompletion;
|
|
261
|
-
return false;
|
|
262
|
-
case "cursor-skill":
|
|
263
|
-
return detected.cursorSkill;
|
|
264
|
-
case "claude-skill":
|
|
265
|
-
return detected.claudeSkill;
|
|
266
|
-
case "cursor-mcp":
|
|
267
|
-
return detected.cursorMcp;
|
|
268
|
-
case "claude-mcp":
|
|
269
|
-
return detected.claudeMcp;
|
|
270
|
-
case "claude-desktop-mcp":
|
|
271
|
-
return detected.claudeDesktopMcp;
|
|
272
|
-
case "opencode-mcp":
|
|
273
|
-
return detected.opencodeMcp;
|
|
274
|
-
case "codex-mcp":
|
|
275
|
-
return detected.codexMcp;
|
|
276
|
-
case "chatgpt-desktop-mcp":
|
|
277
|
-
return detected.chatGptMcp;
|
|
278
|
-
default:
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
});
|
|
52
|
+
return !!(opts.configure || opts.all) && root.appConfig !== undefined;
|
|
282
53
|
}
|
package/src/install/shell.ts
CHANGED
|
@@ -22,10 +22,10 @@ export function rcMarkerEnd(appKey: string, tag: string): string {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/** Returns rc snippet block for PATH, or null if already present. */
|
|
25
|
-
export function buildPathRcBlock(appKey: string,
|
|
25
|
+
export function buildPathRcBlock(appKey: string, appDir: string): string {
|
|
26
26
|
const start = rcMarkerStart(appKey, "path");
|
|
27
27
|
const end = rcMarkerEnd(appKey, "path");
|
|
28
|
-
return [start, `export PATH="${
|
|
28
|
+
return [start, `export PATH="${appDir}:$PATH"`, end].join("\n");
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Returns rc snippet block for zsh fpath, or null if already present. */
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import type { CliProgram } from "../types.ts";
|
|
6
|
+
import { resolveInstallPaths } from "./paths.ts";
|
|
7
|
+
import { printInstallStatus } from "./status.ts";
|
|
8
|
+
import { resolveInstallTargetPreview } from "./target-scope.ts";
|
|
9
|
+
|
|
10
|
+
let home: string;
|
|
11
|
+
let prevHome: string | undefined;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
home = mkdtempSync(join(tmpdir(), "argsbarg-status-"));
|
|
15
|
+
prevHome = process.env.HOME;
|
|
16
|
+
process.env.HOME = home;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
if (prevHome === undefined) delete process.env.HOME;
|
|
21
|
+
else process.env.HOME = prevHome;
|
|
22
|
+
rmSync(home, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("resolveInstallTargetPreview", () => {
|
|
26
|
+
test("mcp app previews MCP keys for all and mcp scopes", () => {
|
|
27
|
+
const program: CliProgram = {
|
|
28
|
+
key: "mcpapp",
|
|
29
|
+
version: "1",
|
|
30
|
+
description: "x",
|
|
31
|
+
mcpServer: { enabled: true },
|
|
32
|
+
handler: () => {},
|
|
33
|
+
};
|
|
34
|
+
const paths = resolveInstallPaths(program);
|
|
35
|
+
const preview = resolveInstallTargetPreview(program, paths);
|
|
36
|
+
expect(preview.agentIntegration).toBe("mcp");
|
|
37
|
+
expect(preview.all).toContain("app");
|
|
38
|
+
expect(preview.all).toContain("completions");
|
|
39
|
+
expect(preview.skill).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("shell app previews skill keys", () => {
|
|
43
|
+
const program: CliProgram = {
|
|
44
|
+
key: "cliapp",
|
|
45
|
+
version: "1",
|
|
46
|
+
description: "x",
|
|
47
|
+
handler: () => {},
|
|
48
|
+
};
|
|
49
|
+
const paths = resolveInstallPaths(program);
|
|
50
|
+
const preview = resolveInstallTargetPreview(program, paths);
|
|
51
|
+
expect(preview.agentIntegration).toBe("skill");
|
|
52
|
+
expect(preview.all).toContain("app");
|
|
53
|
+
expect(preview.mcp).toEqual([]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("printInstallStatus json", () => {
|
|
58
|
+
test("includes agentIntegration and effective scopes", () => {
|
|
59
|
+
const program: CliProgram = {
|
|
60
|
+
key: "app",
|
|
61
|
+
version: "1",
|
|
62
|
+
description: "x",
|
|
63
|
+
mcpServer: { enabled: true },
|
|
64
|
+
handler: () => {},
|
|
65
|
+
};
|
|
66
|
+
const chunks: string[] = [];
|
|
67
|
+
const orig = process.stdout.write;
|
|
68
|
+
process.stdout.write = ((chunk: string | Uint8Array) => {
|
|
69
|
+
chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
|
|
70
|
+
return true;
|
|
71
|
+
}) as typeof process.stdout.write;
|
|
72
|
+
try {
|
|
73
|
+
printInstallStatus(program, { json: true });
|
|
74
|
+
const parsed = JSON.parse(chunks.join("")) as {
|
|
75
|
+
agentIntegration: string;
|
|
76
|
+
effective: { all: string[]; mcp: string[]; skill: string[] };
|
|
77
|
+
};
|
|
78
|
+
expect(parsed.agentIntegration).toBe("mcp");
|
|
79
|
+
expect(parsed.effective.all).toContain("app");
|
|
80
|
+
expect(Array.isArray(parsed.effective.skill)).toBe(true);
|
|
81
|
+
} finally {
|
|
82
|
+
process.stdout.write = orig;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
package/src/install/status.ts
CHANGED
|
@@ -2,15 +2,15 @@ import { appConfigStatus } from "../config/bootstrap.ts";
|
|
|
2
2
|
import type { CliProgram } from "../types.ts";
|
|
3
3
|
import { buildInstallStatus, detectInstalledArtifacts } from "./detect-installed.ts";
|
|
4
4
|
import { resolveInstallPaths } from "./paths.ts";
|
|
5
|
-
import
|
|
5
|
+
import { resolveInstallTargetPreview } from "./target-scope.ts";
|
|
6
|
+
import type { InstallOpts } from "./target-types.ts";
|
|
6
7
|
|
|
7
8
|
export function installOut(msg: string, opts: InstallOpts): void {
|
|
8
|
-
if (opts.
|
|
9
|
+
if (opts.json) return;
|
|
9
10
|
process.stdout.write(`${msg}\n`);
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export function installInfo(msg: string, opts: InstallOpts): void {
|
|
13
|
-
if (opts.quiet) return;
|
|
14
14
|
if (opts.json && !opts.dry) return;
|
|
15
15
|
const prefix = opts.dry ? "[dry run] " : "";
|
|
16
16
|
process.stderr.write(`${prefix + msg}\n`);
|
|
@@ -20,15 +20,28 @@ export function installErr(msg: string): void {
|
|
|
20
20
|
process.stderr.write(`${msg}\n`);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** Interactive install/uninstall banner (stderr; leading blank line). */
|
|
24
|
+
export function writeInteractiveInstallIntro(root: CliProgram): void {
|
|
25
|
+
process.stderr.write(`\n${root.key} Setup\n\n`);
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
/** Prints install status to stdout (human or JSON). */
|
|
24
29
|
export function printInstallStatus(root: CliProgram, opts: InstallOpts): void {
|
|
25
|
-
const paths = resolveInstallPaths(root
|
|
26
|
-
const detected = detectInstalledArtifacts(paths);
|
|
27
|
-
const status = buildInstallStatus(paths, detected);
|
|
30
|
+
const paths = resolveInstallPaths(root);
|
|
31
|
+
const detected = detectInstalledArtifacts(paths, root);
|
|
32
|
+
const status = buildInstallStatus(paths, detected, root);
|
|
28
33
|
|
|
29
34
|
if (opts.json) {
|
|
30
|
-
const
|
|
31
|
-
|
|
35
|
+
const preview = resolveInstallTargetPreview(root, paths);
|
|
36
|
+
const json: Record<string, unknown> = {
|
|
37
|
+
agentIntegration: preview.agentIntegration,
|
|
38
|
+
effective: {
|
|
39
|
+
all: preview.all,
|
|
40
|
+
mcp: preview.mcp,
|
|
41
|
+
skill: preview.skill,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
if (status.app) json.app = status.app;
|
|
32
45
|
if (status.bashCompletion) json.bashCompletion = status.bashCompletion;
|
|
33
46
|
if (status.zshCompletion) json.zshCompletion = status.zshCompletion;
|
|
34
47
|
if (status.fishCompletion) json.fishCompletion = status.fishCompletion;
|
|
@@ -46,7 +59,7 @@ export function printInstallStatus(root: CliProgram, opts: InstallOpts): void {
|
|
|
46
59
|
|
|
47
60
|
installOut(`Installed artifacts for ${root.key}:`, opts);
|
|
48
61
|
const lines: [string, string | undefined][] = [
|
|
49
|
-
["
|
|
62
|
+
["app", status.app],
|
|
50
63
|
["bash completion", status.bashCompletion],
|
|
51
64
|
["zsh completion", status.zshCompletion],
|
|
52
65
|
["fish completion", status.fishCompletion],
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { CliProgram, InstallAgentIntegration } from "../types.ts";
|
|
2
|
+
import type { InstallPaths } from "./paths.ts";
|
|
3
|
+
import type {
|
|
4
|
+
CliInstallArtifactKey,
|
|
5
|
+
DetectedSnapshot,
|
|
6
|
+
InstallAction,
|
|
7
|
+
InstallActionKind,
|
|
8
|
+
InstalledArtifacts,
|
|
9
|
+
InstallStatus,
|
|
10
|
+
InstallTargetCategory,
|
|
11
|
+
TargetPlanContext,
|
|
12
|
+
UninstallAction,
|
|
13
|
+
} from "./target-types.ts";
|
|
14
|
+
|
|
15
|
+
/** Shared lifecycle for one install artifact. */
|
|
16
|
+
export abstract class InstallTarget {
|
|
17
|
+
abstract readonly key: CliInstallArtifactKey;
|
|
18
|
+
abstract readonly actionKind: InstallActionKind;
|
|
19
|
+
abstract readonly category: InstallTargetCategory;
|
|
20
|
+
|
|
21
|
+
/** Paired MCP/skill key for agentIntegration dedupe. */
|
|
22
|
+
readonly pairedKey?: CliInstallArtifactKey;
|
|
23
|
+
|
|
24
|
+
defaultIncludedInAll(integration: InstallAgentIntegration): boolean {
|
|
25
|
+
if (this.category === "core") return true;
|
|
26
|
+
if (this.category === "mcp") {
|
|
27
|
+
return integration === "mcp" || integration === "both";
|
|
28
|
+
}
|
|
29
|
+
return integration === "skill" || integration === "both";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
abstract isAvailable(root: CliProgram, paths: InstallPaths): boolean;
|
|
33
|
+
|
|
34
|
+
/** Whether this artifact is installed (for scoped uninstall / refresh). */
|
|
35
|
+
abstract isDetected(paths: InstallPaths, root: CliProgram): boolean;
|
|
36
|
+
|
|
37
|
+
/** Writes detection flags into the shared snapshot. */
|
|
38
|
+
applyDetected(_paths: InstallPaths, _root: CliProgram, _out: InstalledArtifacts): void {
|
|
39
|
+
// default: subclasses override
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Maps artifact key to detected boolean for plan scoping. */
|
|
43
|
+
detectedForSnapshot(detected: DetectedSnapshot): boolean {
|
|
44
|
+
return this.isDetectedFromSnapshot(detected);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
protected abstract isDetectedFromSnapshot(detected: DetectedSnapshot): boolean;
|
|
48
|
+
|
|
49
|
+
statusLine(
|
|
50
|
+
paths: InstallPaths,
|
|
51
|
+
root: CliProgram,
|
|
52
|
+
detected: InstalledArtifacts,
|
|
53
|
+
): string | undefined {
|
|
54
|
+
if (!this.isDetectedFromInstalled(detected)) return undefined;
|
|
55
|
+
return this.formatStatusLine(paths, root);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected isDetectedFromInstalled(detected: InstalledArtifacts): boolean {
|
|
59
|
+
return this.isDetectedFromSnapshot(detected as DetectedSnapshot);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
protected abstract formatStatusLine(paths: InstallPaths, root: CliProgram): string;
|
|
63
|
+
|
|
64
|
+
planInstall(ctx: TargetPlanContext): InstallAction[] {
|
|
65
|
+
if (!ctx.include(this.key)) return [];
|
|
66
|
+
return this.buildInstallActions(ctx);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
planUninstall(ctx: TargetPlanContext): UninstallAction[] {
|
|
70
|
+
if (!ctx.include(this.key)) return [];
|
|
71
|
+
if (!this.isDetectedFromSnapshot(ctx.detected)) return [];
|
|
72
|
+
return this.buildUninstallActions(ctx);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
preflight?(ctx: TargetPlanContext): string | null;
|
|
76
|
+
|
|
77
|
+
protected abstract buildInstallActions(ctx: TargetPlanContext): InstallAction[];
|
|
78
|
+
|
|
79
|
+
protected abstract buildUninstallActions(ctx: TargetPlanContext): UninstallAction[];
|
|
80
|
+
|
|
81
|
+
contributeStatus(
|
|
82
|
+
paths: InstallPaths,
|
|
83
|
+
root: CliProgram,
|
|
84
|
+
detected: InstalledArtifacts,
|
|
85
|
+
status: InstallStatus,
|
|
86
|
+
): void {
|
|
87
|
+
const line = this.statusLine(paths, root, detected);
|
|
88
|
+
if (!line) return;
|
|
89
|
+
this.assignStatusLine(status, line);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected abstract assignStatusLine(status: InstallStatus, line: string): void;
|
|
93
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CliProgram } from "../types.ts";
|
|
2
|
+
import type { InstallPaths } from "./paths.ts";
|
|
3
|
+
import { INSTALL_TARGETS } from "./target-registry.ts";
|
|
4
|
+
import type { InstalledArtifacts, InstallStatus } from "./target-types.ts";
|
|
5
|
+
|
|
6
|
+
export { buildDetectedSnapshot, detectInstalledArtifacts } from "./target-scope.ts";
|
|
7
|
+
export type { InstalledArtifacts, InstallStatus } from "./target-types.ts";
|
|
8
|
+
|
|
9
|
+
/** Builds a status inventory from detected artifacts. */
|
|
10
|
+
export function buildInstallStatus(
|
|
11
|
+
paths: InstallPaths,
|
|
12
|
+
detected: InstalledArtifacts,
|
|
13
|
+
root: CliProgram,
|
|
14
|
+
): InstallStatus {
|
|
15
|
+
const status: InstallStatus = {};
|
|
16
|
+
for (const target of INSTALL_TARGETS) {
|
|
17
|
+
target.contributeStatus(paths, root, detected, status);
|
|
18
|
+
}
|
|
19
|
+
return status;
|
|
20
|
+
}
|