argsbarg 1.4.3 → 2.0.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/.cursor/plans/cliprogram_capabilities_refactor_081e1737.plan.md +224 -0
- package/.private/scratch.md +1 -1
- package/CHANGELOG.md +39 -1
- package/README.md +29 -21
- package/docs/ai-skills.md +24 -52
- package/docs/install.md +84 -0
- package/docs/mcp.md +8 -8
- package/examples/mcp-test.ts +3 -3
- package/examples/minimal.ts +3 -3
- package/examples/nested.ts +3 -3
- package/examples/option-required.ts +3 -3
- package/index.d.ts +44 -50
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +101 -0
- package/src/builtins/completion-bash.ts +240 -0
- package/src/builtins/completion-fish.ts +73 -0
- package/src/builtins/completion-group.ts +50 -0
- package/src/builtins/completion-zsh.ts +244 -0
- package/src/builtins/dispatch.ts +138 -0
- package/src/builtins/export.ts +53 -0
- package/src/builtins/index.ts +10 -0
- package/src/builtins/install.ts +99 -0
- package/src/builtins/mcp.ts +13 -0
- package/src/builtins/presentation.ts +50 -0
- package/src/builtins/scopes.ts +46 -0
- package/src/builtins/shell-helpers.ts +24 -0
- package/src/capabilities.ts +32 -0
- package/src/completion.ts +10 -693
- package/src/context.ts +21 -6
- package/src/help.ts +21 -9
- package/src/index.test.ts +114 -118
- package/src/index.ts +2 -1
- package/src/install/binary.ts +82 -0
- package/src/install/compiled.ts +15 -0
- package/src/install/completions.ts +52 -0
- package/src/install/detect-installed.ts +67 -0
- package/src/install/index.ts +196 -0
- package/src/install/install.test.ts +124 -0
- package/src/install/mcp-config.ts +70 -0
- package/src/install/paths.ts +69 -0
- package/src/install/plan.ts +183 -0
- package/src/install/shell.ts +56 -0
- package/src/install/status.ts +63 -0
- package/src/install/uninstall.ts +111 -0
- package/src/invoke.ts +14 -5
- package/src/mcp/server.ts +3 -3
- package/src/mcp/tools.ts +17 -17
- package/src/mcp.ts +2 -2
- package/src/parse.ts +55 -27
- package/src/runtime.ts +47 -100
- package/src/schema.ts +10 -52
- package/src/skill/generate.ts +10 -10
- package/src/skill/install.ts +21 -19
- package/src/types.test.ts +40 -0
- package/src/types.ts +59 -49
- package/src/validate.ts +89 -83
- package/src/ai.ts +0 -7
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { CliProgram } from "../types.ts";
|
|
4
|
+
import { installBinary } from "./binary.ts";
|
|
5
|
+
import { installCompletions } from "./completions.ts";
|
|
6
|
+
import { detectInstalledArtifacts } from "./detect-installed.ts";
|
|
7
|
+
import { expectedMcpEntry, mergeMcpConfig } from "./mcp-config.ts";
|
|
8
|
+
import { InstallPaths, userHome } from "./paths.ts";
|
|
9
|
+
import { detectShells } from "./shell.ts";
|
|
10
|
+
|
|
11
|
+
export interface InstallOpts {
|
|
12
|
+
all?: boolean;
|
|
13
|
+
bin?: boolean;
|
|
14
|
+
completions?: boolean;
|
|
15
|
+
skill?: boolean;
|
|
16
|
+
mcp?: boolean;
|
|
17
|
+
update?: boolean;
|
|
18
|
+
status?: boolean;
|
|
19
|
+
uninstall?: boolean;
|
|
20
|
+
yes?: boolean;
|
|
21
|
+
dry?: boolean;
|
|
22
|
+
json?: boolean;
|
|
23
|
+
quiet?: boolean;
|
|
24
|
+
prefix?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type InstallActionKind = "binary" | "completions" | "cursor-skill" | "claude-skill" | "cursor-mcp" | "claude-mcp";
|
|
28
|
+
|
|
29
|
+
export interface InstallAction {
|
|
30
|
+
kind: InstallActionKind;
|
|
31
|
+
summary: string;
|
|
32
|
+
message: string;
|
|
33
|
+
run: () => string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function wantsBin(opts: InstallOpts): boolean {
|
|
37
|
+
return !!(opts.all || opts.bin || opts.update);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function wantsCompletions(opts: InstallOpts): boolean {
|
|
41
|
+
return !!(opts.all || opts.completions);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function wantsSkill(opts: InstallOpts): boolean {
|
|
45
|
+
return !!(opts.all || opts.skill);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function wantsMcp(opts: InstallOpts, root: CliProgram): boolean {
|
|
49
|
+
return !!(opts.all || opts.mcp) && root.mcpServer !== undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Builds install actions for normal mode (--all / scoped targets). */
|
|
53
|
+
export function buildInstallPlan(root: CliProgram, paths: InstallPaths, opts: InstallOpts): InstallAction[] {
|
|
54
|
+
const actions: InstallAction[] = [];
|
|
55
|
+
const dry = !!opts.dry;
|
|
56
|
+
|
|
57
|
+
if (wantsBin(opts)) {
|
|
58
|
+
actions.push({
|
|
59
|
+
kind: "binary",
|
|
60
|
+
summary: `binary: ${paths.binaryPath}`,
|
|
61
|
+
message: `Installing binary to ${paths.binaryPath}`,
|
|
62
|
+
run: () => installBinary(root, paths, dry).changedFiles,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (wantsCompletions(opts)) {
|
|
67
|
+
const shells = detectShells();
|
|
68
|
+
if (shells.bash) {
|
|
69
|
+
actions.push({
|
|
70
|
+
kind: "completions",
|
|
71
|
+
summary: `bash completion: ${paths.bashCompletion}`,
|
|
72
|
+
message: `Writing bash completion to ${paths.bashCompletion}`,
|
|
73
|
+
run: () => {
|
|
74
|
+
const all = installCompletions(root, paths, dry);
|
|
75
|
+
return all.filter((p) => p === paths.bashCompletion);
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (shells.zsh) {
|
|
80
|
+
actions.push({
|
|
81
|
+
kind: "completions",
|
|
82
|
+
summary: `zsh completion: ${paths.zshCompletion}`,
|
|
83
|
+
message: `Writing zsh completion to ${paths.zshCompletion}`,
|
|
84
|
+
run: () => {
|
|
85
|
+
const all = installCompletions(root, paths, dry);
|
|
86
|
+
return all.filter((p) => p === paths.zshCompletion);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (shells.fish) {
|
|
91
|
+
actions.push({
|
|
92
|
+
kind: "completions",
|
|
93
|
+
summary: `fish completion: ${paths.fishCompletion}`,
|
|
94
|
+
message: `Writing fish completion to ${paths.fishCompletion}`,
|
|
95
|
+
run: () => {
|
|
96
|
+
const all = installCompletions(root, paths, dry);
|
|
97
|
+
return all.filter((p) => p === paths.fishCompletion);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (wantsSkill(opts)) {
|
|
104
|
+
const home = userHome();
|
|
105
|
+
if (existsSync(join(home, ".cursor"))) {
|
|
106
|
+
actions.push({
|
|
107
|
+
kind: "cursor-skill",
|
|
108
|
+
summary: `cursor skill: ${paths.cursorSkillDir}/`,
|
|
109
|
+
message: `Installing Cursor skill to ${paths.cursorSkillDir}/`,
|
|
110
|
+
run: () => [],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (existsSync(join(home, ".claude"))) {
|
|
114
|
+
actions.push({
|
|
115
|
+
kind: "claude-skill",
|
|
116
|
+
summary: `claude skill: ${paths.claudeSkillDir}/`,
|
|
117
|
+
message: `Installing Claude Code skill to ${paths.claudeSkillDir}/`,
|
|
118
|
+
run: () => [],
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (wantsMcp(opts, root)) {
|
|
124
|
+
const entry = expectedMcpEntry(root);
|
|
125
|
+
if (existsSync(join(userHome(), ".cursor"))) {
|
|
126
|
+
actions.push({
|
|
127
|
+
kind: "cursor-mcp",
|
|
128
|
+
summary: `cursor mcp: ${paths.cursorMcpPath} (server "${paths.mcpName}")`,
|
|
129
|
+
message: `Merging MCP server "${paths.mcpName}" into ${paths.cursorMcpPath}`,
|
|
130
|
+
run: () => {
|
|
131
|
+
mergeMcpConfig(paths.cursorMcpPath, paths.mcpName, entry, dry);
|
|
132
|
+
return [paths.cursorMcpPath];
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
actions.push({
|
|
137
|
+
kind: "claude-mcp",
|
|
138
|
+
summary: `claude mcp: ${paths.claudeMcpPath} (server "${paths.mcpName}")`,
|
|
139
|
+
message: `Merging MCP server "${paths.mcpName}" into ${paths.claudeMcpPath}`,
|
|
140
|
+
run: () => {
|
|
141
|
+
mergeMcpConfig(paths.claudeMcpPath, paths.mcpName, entry, dry);
|
|
142
|
+
return [paths.claudeMcpPath];
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return actions;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Builds update actions for artifacts already installed. */
|
|
151
|
+
export function buildUpdatePlan(root: CliProgram, paths: InstallPaths, opts: InstallOpts): InstallAction[] {
|
|
152
|
+
const detected = detectInstalledArtifacts(paths);
|
|
153
|
+
const scoped: InstallOpts = {
|
|
154
|
+
bin: true,
|
|
155
|
+
completions: detected.bashCompletion || detected.zshCompletion || detected.fishCompletion,
|
|
156
|
+
skill: detected.cursorSkill || detected.claudeSkill,
|
|
157
|
+
mcp: (detected.cursorMcp || detected.claudeMcp) && root.mcpServer !== undefined,
|
|
158
|
+
dry: opts.dry,
|
|
159
|
+
};
|
|
160
|
+
const plan = buildInstallPlan(root, paths, scoped);
|
|
161
|
+
|
|
162
|
+
return plan.filter((action) => {
|
|
163
|
+
switch (action.kind) {
|
|
164
|
+
case "binary":
|
|
165
|
+
return true;
|
|
166
|
+
case "completions":
|
|
167
|
+
if (action.summary.startsWith("bash")) return detected.bashCompletion;
|
|
168
|
+
if (action.summary.startsWith("zsh")) return detected.zshCompletion;
|
|
169
|
+
if (action.summary.startsWith("fish")) return detected.fishCompletion;
|
|
170
|
+
return false;
|
|
171
|
+
case "cursor-skill":
|
|
172
|
+
return detected.cursorSkill;
|
|
173
|
+
case "claude-skill":
|
|
174
|
+
return detected.claudeSkill;
|
|
175
|
+
case "cursor-mcp":
|
|
176
|
+
return detected.cursorMcp;
|
|
177
|
+
case "claude-mcp":
|
|
178
|
+
return detected.claudeMcp;
|
|
179
|
+
default:
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export interface ShellDetection {
|
|
2
|
+
bash: boolean;
|
|
3
|
+
zsh: boolean;
|
|
4
|
+
fish: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Detects which shells are available on PATH. */
|
|
8
|
+
export function detectShells(): ShellDetection {
|
|
9
|
+
return {
|
|
10
|
+
bash: Bun.which("bash") !== null,
|
|
11
|
+
zsh: Bun.which("zsh") !== null,
|
|
12
|
+
fish: Bun.which("fish") !== null,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function rcMarkerStart(appKey: string, tag: string): string {
|
|
17
|
+
return `# ${appKey}:${tag}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function rcMarkerEnd(appKey: string, tag: string): string {
|
|
21
|
+
return `# end ${appKey}:${tag}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Returns rc snippet block for PATH, or null if already present. */
|
|
25
|
+
export function buildPathRcBlock(appKey: string, bindir: string): string {
|
|
26
|
+
const start = rcMarkerStart(appKey, "path");
|
|
27
|
+
const end = rcMarkerEnd(appKey, "path");
|
|
28
|
+
return [start, `export PATH="${bindir}:$PATH"`, end].join("\n");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Returns rc snippet block for zsh fpath, or null if already present. */
|
|
32
|
+
export function buildZshFpathRcBlock(appKey: string, completionsDir: string): string {
|
|
33
|
+
const start = rcMarkerStart(appKey, "fpath");
|
|
34
|
+
const end = rcMarkerEnd(appKey, "fpath");
|
|
35
|
+
return [start, `fpath=(${completionsDir} $fpath)`, end].join("\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Removes a marker-delimited block from rc file content. */
|
|
39
|
+
export function removeRcBlock(content: string, appKey: string, tag: string): string {
|
|
40
|
+
const start = rcMarkerStart(appKey, tag);
|
|
41
|
+
const end = rcMarkerEnd(appKey, tag);
|
|
42
|
+
const re = new RegExp(
|
|
43
|
+
`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}\\n?`,
|
|
44
|
+
"g",
|
|
45
|
+
);
|
|
46
|
+
return content.replace(re, "");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Returns true when the marker block already exists in content. */
|
|
50
|
+
export function hasRcBlock(content: string, appKey: string, tag: string): boolean {
|
|
51
|
+
return content.includes(rcMarkerStart(appKey, tag));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function escapeRegExp(s: string): string {
|
|
55
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
56
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CliProgram } from "../types.ts";
|
|
2
|
+
import { buildInstallStatus, detectInstalledArtifacts } from "./detect-installed.ts";
|
|
3
|
+
import type { InstallOpts } from "./plan.ts";
|
|
4
|
+
import { resolveInstallPaths } from "./paths.ts";
|
|
5
|
+
|
|
6
|
+
export function installOut(msg: string, opts: InstallOpts): void {
|
|
7
|
+
if (opts.quiet || opts.json) return;
|
|
8
|
+
process.stdout.write(msg + "\n");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function installInfo(msg: string, opts: InstallOpts): void {
|
|
12
|
+
if (opts.quiet) return;
|
|
13
|
+
if (opts.json && !opts.dry) return;
|
|
14
|
+
const prefix = opts.dry ? "[dry run] " : "";
|
|
15
|
+
process.stderr.write(prefix + msg + "\n");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function installErr(msg: string): void {
|
|
19
|
+
process.stderr.write(msg + "\n");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Prints install status to stdout (human or JSON). */
|
|
23
|
+
export function printInstallStatus(root: CliProgram, opts: InstallOpts): void {
|
|
24
|
+
const paths = resolveInstallPaths(root, opts);
|
|
25
|
+
const detected = detectInstalledArtifacts(paths);
|
|
26
|
+
const status = buildInstallStatus(paths, detected);
|
|
27
|
+
|
|
28
|
+
if (opts.json) {
|
|
29
|
+
const json: Record<string, string> = {};
|
|
30
|
+
if (status.binary) json.binary = status.binary;
|
|
31
|
+
if (status.bashCompletion) json.bashCompletion = status.bashCompletion;
|
|
32
|
+
if (status.zshCompletion) json.zshCompletion = status.zshCompletion;
|
|
33
|
+
if (status.fishCompletion) json.fishCompletion = status.fishCompletion;
|
|
34
|
+
if (status.cursorSkill) json.cursorSkill = status.cursorSkill;
|
|
35
|
+
if (status.claudeSkill) json.claudeSkill = status.claudeSkill;
|
|
36
|
+
if (status.cursorMcp) json.cursorMcp = status.cursorMcp;
|
|
37
|
+
if (status.claudeMcp) json.claudeMcp = status.claudeMcp;
|
|
38
|
+
process.stdout.write(JSON.stringify(json, null, 2) + "\n");
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
installOut(`Installed artifacts for ${root.key}:`, opts);
|
|
43
|
+
const lines: [string, string | undefined][] = [
|
|
44
|
+
["binary", status.binary],
|
|
45
|
+
["bash completion", status.bashCompletion],
|
|
46
|
+
["zsh completion", status.zshCompletion],
|
|
47
|
+
["fish completion", status.fishCompletion],
|
|
48
|
+
["cursor skill", status.cursorSkill],
|
|
49
|
+
["claude skill", status.claudeSkill],
|
|
50
|
+
["cursor mcp", status.cursorMcp],
|
|
51
|
+
["claude mcp", status.claudeMcp],
|
|
52
|
+
];
|
|
53
|
+
let any = false;
|
|
54
|
+
for (const [label, value] of lines) {
|
|
55
|
+
if (value) {
|
|
56
|
+
installOut(` ${label}: ${value}`, opts);
|
|
57
|
+
any = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!any) {
|
|
61
|
+
installOut(" (none detected)", opts);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { existsSync, rmSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { CliProgram } from "../types.ts";
|
|
4
|
+
import { uninstallBinary } from "./binary.ts";
|
|
5
|
+
import { uninstallCompletions } from "./completions.ts";
|
|
6
|
+
import { detectInstalledArtifacts } from "./detect-installed.ts";
|
|
7
|
+
import { removeMcpConfig } from "./mcp-config.ts";
|
|
8
|
+
import { InstallPaths, userHome } from "./paths.ts";
|
|
9
|
+
import type { InstallOpts } from "./plan.ts";
|
|
10
|
+
|
|
11
|
+
export interface UninstallAction {
|
|
12
|
+
summary: string;
|
|
13
|
+
message: string;
|
|
14
|
+
run: () => string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function scopeAll(opts: InstallOpts): boolean {
|
|
18
|
+
return !opts.bin && !opts.completions && !opts.skill && !opts.mcp;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Builds uninstall actions from detected artifacts. */
|
|
22
|
+
export function buildUninstallPlan(
|
|
23
|
+
root: CliProgram,
|
|
24
|
+
paths: InstallPaths,
|
|
25
|
+
opts: InstallOpts,
|
|
26
|
+
): UninstallAction[] {
|
|
27
|
+
const detected = detectInstalledArtifacts(paths);
|
|
28
|
+
const all = scopeAll(opts);
|
|
29
|
+
const dry = !!opts.dry;
|
|
30
|
+
const actions: UninstallAction[] = [];
|
|
31
|
+
|
|
32
|
+
if ((all || opts.bin) && detected.binary) {
|
|
33
|
+
actions.push({
|
|
34
|
+
summary: `binary: ${paths.binaryPath}`,
|
|
35
|
+
message: `Removing binary ${paths.binaryPath}`,
|
|
36
|
+
run: () => uninstallBinary(root, paths, dry),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if ((all || opts.completions) && (detected.bashCompletion || detected.zshCompletion || detected.fishCompletion)) {
|
|
41
|
+
if (detected.bashCompletion) {
|
|
42
|
+
actions.push({
|
|
43
|
+
summary: `bash completion: ${paths.bashCompletion}`,
|
|
44
|
+
message: `Removing bash completion ${paths.bashCompletion}`,
|
|
45
|
+
run: () => uninstallCompletions(paths, dry).filter((p) => p === paths.bashCompletion),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (detected.zshCompletion) {
|
|
49
|
+
actions.push({
|
|
50
|
+
summary: `zsh completion: ${paths.zshCompletion}`,
|
|
51
|
+
message: `Removing zsh completion ${paths.zshCompletion}`,
|
|
52
|
+
run: () => uninstallCompletions(paths, dry).filter((p) => p === paths.zshCompletion),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (detected.fishCompletion) {
|
|
56
|
+
actions.push({
|
|
57
|
+
summary: `fish completion: ${paths.fishCompletion}`,
|
|
58
|
+
message: `Removing fish completion ${paths.fishCompletion}`,
|
|
59
|
+
run: () => uninstallCompletions(paths, dry).filter((p) => p === paths.fishCompletion),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if ((all || opts.skill) && detected.cursorSkill) {
|
|
65
|
+
actions.push({
|
|
66
|
+
summary: `cursor skill: ${paths.cursorSkillDir}/`,
|
|
67
|
+
message: `Removing Cursor skill ${paths.cursorSkillDir}/`,
|
|
68
|
+
run: () => [],
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if ((all || opts.skill) && detected.claudeSkill) {
|
|
73
|
+
actions.push({
|
|
74
|
+
summary: `claude skill: ${paths.claudeSkillDir}/`,
|
|
75
|
+
message: `Removing Claude Code skill ${paths.claudeSkillDir}/`,
|
|
76
|
+
run: () => [],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if ((all || opts.mcp) && root.mcpServer !== undefined) {
|
|
81
|
+
if (detected.cursorMcp) {
|
|
82
|
+
actions.push({
|
|
83
|
+
summary: `cursor mcp: ${paths.cursorMcpPath}`,
|
|
84
|
+
message: `Removing MCP server "${paths.mcpName}" from ${paths.cursorMcpPath}`,
|
|
85
|
+
run: () => {
|
|
86
|
+
removeMcpConfig(paths.cursorMcpPath, paths.mcpName, dry);
|
|
87
|
+
return [paths.cursorMcpPath];
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (detected.claudeMcp) {
|
|
92
|
+
actions.push({
|
|
93
|
+
summary: `claude mcp: ${paths.claudeMcpPath}`,
|
|
94
|
+
message: `Removing MCP server "${paths.mcpName}" from ${paths.claudeMcpPath}`,
|
|
95
|
+
run: () => {
|
|
96
|
+
removeMcpConfig(paths.claudeMcpPath, paths.mcpName, dry);
|
|
97
|
+
return [paths.claudeMcpPath];
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return actions;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Rimraf skill directories during uninstall. */
|
|
107
|
+
export function uninstallSkillDir(dir: string, dry: boolean): string[] {
|
|
108
|
+
if (!existsSync(dir)) return [];
|
|
109
|
+
if (!dry) rmSync(dir, { recursive: true, force: true });
|
|
110
|
+
return [dir + "/"];
|
|
111
|
+
}
|
package/src/invoke.ts
CHANGED
|
@@ -6,7 +6,7 @@ process.exit so MCP tool calls can run handlers repeatedly.
|
|
|
6
6
|
|
|
7
7
|
import { CliContext } from "./context.ts";
|
|
8
8
|
import { parse, postParseValidate, ParseKind } from "./parse.ts";
|
|
9
|
-
import {
|
|
9
|
+
import { type CliNode, type CliProgram, isCliRouter } from "./types.ts";
|
|
10
10
|
import { format } from "node:util";
|
|
11
11
|
|
|
12
12
|
/** Outcome of a non-exiting CLI invocation. */
|
|
@@ -40,7 +40,7 @@ class CliInvokeExit extends Error {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/** Looks up a subcommand or routing node by `key`. */
|
|
43
|
-
function findChild(cmds:
|
|
43
|
+
function findChild(cmds: CliNode[], name: string): CliNode | undefined {
|
|
44
44
|
return cmds.find((c) => c.key === name);
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -48,7 +48,7 @@ function findChild(cmds: CliCommand[], name: string): CliCommand | undefined {
|
|
|
48
48
|
* Parses argv against the user root, runs the leaf handler, and returns captured output.
|
|
49
49
|
* Never calls process.exit.
|
|
50
50
|
*/
|
|
51
|
-
export async function cliInvoke(root:
|
|
51
|
+
export async function cliInvoke(root: CliProgram, argv: string[]): Promise<CliInvokeResult> {
|
|
52
52
|
let pr = parse(root, argv);
|
|
53
53
|
pr = postParseValidate(root, pr);
|
|
54
54
|
|
|
@@ -82,9 +82,18 @@ export async function cliInvoke(root: CliCommand, argv: string[]): Promise<CliIn
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
let current:
|
|
85
|
+
let current: CliProgram = root;
|
|
86
86
|
for (const seg of pr.path) {
|
|
87
|
-
|
|
87
|
+
if (!isCliRouter(current)) {
|
|
88
|
+
return {
|
|
89
|
+
kind: "error",
|
|
90
|
+
exitCode: 1,
|
|
91
|
+
stdout: "",
|
|
92
|
+
stderr: "Internal error: missing handler for path.",
|
|
93
|
+
errorMsg: "Internal error: missing handler for path.",
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const ch = findChild(current.commands, seg);
|
|
88
97
|
if (!ch) {
|
|
89
98
|
return {
|
|
90
99
|
kind: "error",
|
package/src/mcp/server.ts
CHANGED
|
@@ -4,7 +4,7 @@ resources, and ping. Responses are newline-delimited JSON on stdout only.
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { cliInvoke } from "../invoke.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { CliProgram } from "../types.ts";
|
|
8
8
|
import { buildToolCallSuccess } from "./result.ts";
|
|
9
9
|
import {
|
|
10
10
|
allMcpResources,
|
|
@@ -41,7 +41,7 @@ function writeError(id: string | number | null | undefined, code: number, messag
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/** Handles one NDJSON request line. */
|
|
44
|
-
async function handleRequestLine(root:
|
|
44
|
+
async function handleRequestLine(root: CliProgram, line: string): Promise<void> {
|
|
45
45
|
let req: JsonRpcRequest;
|
|
46
46
|
try {
|
|
47
47
|
req = JSON.parse(line) as JsonRpcRequest;
|
|
@@ -217,7 +217,7 @@ async function handleRequestLine(root: CliCommand, line: string): Promise<void>
|
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
/** Runs the MCP NDJSON read loop on stdin until EOF. */
|
|
220
|
-
export async function mcpServeStdioLoop(root:
|
|
220
|
+
export async function mcpServeStdioLoop(root: CliProgram): Promise<void> {
|
|
221
221
|
let buffer = "";
|
|
222
222
|
for await (const chunk of Bun.stdin.stream()) {
|
|
223
223
|
buffer += new TextDecoder().decode(chunk);
|
package/src/mcp/tools.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
This module maps
|
|
2
|
+
This module maps CliProgram leaf nodes to MCP tool definitions and converts
|
|
3
3
|
flat JSON tool arguments into argv for cliInvoke.
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ import { readFileSync } from "node:fs";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { collectOptionDefs } from "../parse.ts";
|
|
9
9
|
import { cliSchemaJson } from "../schema.ts";
|
|
10
|
-
import {
|
|
10
|
+
import { CliProgram, CliLeaf, CliNode, CliOption, CliOptionKind, CliPositional, isCliLeaf, isCliRouter } from "../types.ts";
|
|
11
11
|
|
|
12
12
|
/** Default URI for the CLI schema MCP resource. */
|
|
13
13
|
export const MCP_SCHEMA_URI_DEFAULT = "argsbarg://schema";
|
|
@@ -21,7 +21,7 @@ export interface McpToolDef {
|
|
|
21
21
|
/** Command path segments from the program root. */
|
|
22
22
|
path: string[];
|
|
23
23
|
/** Leaf command node. */
|
|
24
|
-
leaf:
|
|
24
|
+
leaf: CliLeaf;
|
|
25
25
|
/** JSON Schema for tools/call arguments. */
|
|
26
26
|
inputSchema: Record<string, unknown>;
|
|
27
27
|
}
|
|
@@ -38,7 +38,7 @@ export function sanitizeToolSegment(key: string): string {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/** Builds the MCP tool name for a leaf at the given path. */
|
|
41
|
-
export function mcpToolName(root:
|
|
41
|
+
export function mcpToolName(root: CliProgram, path: string[]): string {
|
|
42
42
|
if (path.length === 0) {
|
|
43
43
|
return sanitizeToolSegment(root.key);
|
|
44
44
|
}
|
|
@@ -71,7 +71,7 @@ function positionalProperty(p: CliPositional): Record<string, unknown> {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/** Builds inputSchema for a leaf command. */
|
|
74
|
-
function buildInputSchema(root:
|
|
74
|
+
function buildInputSchema(root: CliProgram, path: string[], leaf: CliLeaf): Record<string, unknown> {
|
|
75
75
|
const properties: Record<string, unknown> = {};
|
|
76
76
|
const required: string[] = [];
|
|
77
77
|
|
|
@@ -102,7 +102,7 @@ function buildInputSchema(root: CliCommand, path: string[], leaf: CliCommand): R
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/** Resolves MCP tool description with optional override and requiresEnv suffix. */
|
|
105
|
-
function resolveToolDescription(root:
|
|
105
|
+
function resolveToolDescription(root: CliProgram, path: string[], leaf: CliLeaf): string {
|
|
106
106
|
if (leaf.mcpTool?.description) {
|
|
107
107
|
return leaf.mcpTool.description;
|
|
108
108
|
}
|
|
@@ -124,7 +124,7 @@ export interface McpResourceEntry {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/** Returns built-in schema resource plus user mcpServer.resources. */
|
|
127
|
-
export function allMcpResources(root:
|
|
127
|
+
export function allMcpResources(root: CliProgram): McpResourceEntry[] {
|
|
128
128
|
const schemaUri = resolveMcpSchemaUri(root);
|
|
129
129
|
const builtIn: McpResourceEntry = {
|
|
130
130
|
uri: schemaUri,
|
|
@@ -144,13 +144,13 @@ export function allMcpResources(root: CliCommand): McpResourceEntry[] {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
/** Recursively collects MCP tool definitions from user leaf commands. */
|
|
147
|
-
export function collectMcpTools(root:
|
|
147
|
+
export function collectMcpTools(root: CliProgram): McpToolDef[] {
|
|
148
148
|
const out: McpToolDef[] = [];
|
|
149
149
|
|
|
150
150
|
/** Walks the command tree and appends leaf tools. */
|
|
151
|
-
function walk(cmd:
|
|
152
|
-
if (
|
|
153
|
-
if (cmd.key === "completion" || cmd.key === "
|
|
151
|
+
function walk(cmd: CliNode, path: string[]): void {
|
|
152
|
+
if (isCliLeaf(cmd)) {
|
|
153
|
+
if (cmd.key === "completion" || cmd.key === "install" || cmd.key === "mcp") {
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
156
|
if (cmd.mcpTool?.enabled === false) {
|
|
@@ -165,15 +165,15 @@ export function collectMcpTools(root: CliCommand): McpToolDef[] {
|
|
|
165
165
|
});
|
|
166
166
|
return;
|
|
167
167
|
}
|
|
168
|
-
for (const ch of cmd.commands
|
|
168
|
+
for (const ch of cmd.commands) {
|
|
169
169
|
walk(ch, [...path, ch.key]);
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
if (
|
|
173
|
+
if (isCliLeaf(root)) {
|
|
174
174
|
walk(root, []);
|
|
175
175
|
} else {
|
|
176
|
-
for (const ch of root.commands
|
|
176
|
+
for (const ch of root.commands) {
|
|
177
177
|
walk(ch, [ch.key]);
|
|
178
178
|
}
|
|
179
179
|
}
|
|
@@ -193,7 +193,7 @@ function resolveMcpVersionFromPackageJson(): string | undefined {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
/** Resolves MCP server name and version for initialize. */
|
|
196
|
-
export function resolveMcpServerInfo(root:
|
|
196
|
+
export function resolveMcpServerInfo(root: CliProgram): { name: string; version: string } {
|
|
197
197
|
return {
|
|
198
198
|
name: root.mcpServer?.name ?? root.key,
|
|
199
199
|
version: root.mcpServer?.version ?? resolveMcpVersionFromPackageJson() ?? "0.0.0",
|
|
@@ -201,13 +201,13 @@ export function resolveMcpServerInfo(root: CliCommand): { name: string; version:
|
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
/** Resolves the schema resource URI for this app. */
|
|
204
|
-
export function resolveMcpSchemaUri(root:
|
|
204
|
+
export function resolveMcpSchemaUri(root: CliProgram): string {
|
|
205
205
|
return root.mcpServer?.schemaResourceUri ?? MCP_SCHEMA_URI_DEFAULT;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
/** Converts flat MCP tool arguments to argv for cliInvoke. */
|
|
209
209
|
export function mcpToolCallToArgv(
|
|
210
|
-
root:
|
|
210
|
+
root: CliProgram,
|
|
211
211
|
tool: McpToolDef,
|
|
212
212
|
args: Record<string, unknown>,
|
|
213
213
|
): string[] | { error: string } {
|
package/src/mcp.ts
CHANGED
|
@@ -4,13 +4,13 @@ This module starts the ArgsBarg MCP stdio server for opt-in program roots.
|
|
|
4
4
|
|
|
5
5
|
import { mcpServeStdioLoop } from "./mcp/server.ts";
|
|
6
6
|
import { bootstrapMcpEnv } from "./mcp/env.ts";
|
|
7
|
-
import {
|
|
7
|
+
import { CliProgram } from "./types.ts";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Runs the MCP JSON-RPC server on stdin/stdout until stdin closes, then exits.
|
|
11
11
|
* Caller must ensure `root.mcpServer` is set.
|
|
12
12
|
*/
|
|
13
|
-
export async function cliMcpServeStdio(root:
|
|
13
|
+
export async function cliMcpServeStdio(root: CliProgram): Promise<never> {
|
|
14
14
|
try {
|
|
15
15
|
if (root.mcpServer) {
|
|
16
16
|
bootstrapMcpEnv(root.mcpServer);
|