argsbarg 3.4.1 → 3.5.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 +26 -1
- package/biome.json +29 -6
- package/bun.lock +22 -0
- package/docs/install.md +1 -1
- package/docs/mcp.md +63 -3
- package/index.d.ts +51 -51
- package/justfile +27 -6
- package/package.json +4 -2
- package/scripts/release.ts +26 -9
- package/src/builtins/builtins.test.ts +9 -4
- package/src/builtins/completion-bash.ts +74 -50
- package/src/builtins/completion-fish.ts +3 -8
- package/src/builtins/completion-group.ts +1 -1
- package/src/builtins/completion-zsh.ts +80 -42
- package/src/builtins/dispatch.ts +20 -16
- package/src/builtins/export.ts +19 -10
- package/src/builtins/index.ts +9 -4
- package/src/builtins/install.ts +10 -10
- package/src/builtins/mcp.ts +3 -3
- package/src/builtins/presentation.ts +8 -8
- package/src/builtins/scopes.ts +1 -1
- package/src/builtins/version.ts +1 -1
- package/src/completion.ts +4 -4
- package/src/docs/api-guide.test.ts +2 -2
- package/src/docs/api-guide.ts +2 -2
- package/src/docs/builtin.ts +27 -8
- package/src/docs/docs.test.ts +23 -12
- package/src/docs/mcp-guide.ts +112 -11
- package/src/docs/resolve.ts +10 -3
- package/src/docs/save.ts +11 -3
- package/src/headless.test.ts +8 -16
- package/src/help.ts +73 -43
- package/src/hidden-mcpb.test.ts +8 -10
- package/src/hidden.ts +2 -2
- package/src/index.test.ts +113 -89
- package/src/index.ts +24 -24
- package/src/install/binary.ts +12 -5
- package/src/install/completions.ts +7 -3
- package/src/install/detect-installed.ts +35 -4
- package/src/install/gh-release-update.ts +31 -23
- package/src/install/index.ts +69 -19
- package/src/install/install.test.ts +57 -8
- package/src/install/mcp-codex.test.ts +57 -0
- package/src/install/mcp-codex.ts +125 -0
- package/src/install/mcp-config.ts +12 -5
- package/src/install/mcp-opencode.test.ts +98 -0
- package/src/install/mcp-opencode.ts +149 -0
- package/src/install/paths.ts +51 -3
- package/src/install/plan.ts +96 -7
- package/src/install/shell.ts +1 -4
- package/src/install/status.ts +15 -7
- package/src/install/uninstall.ts +49 -5
- package/src/install/update.test.ts +2 -2
- package/src/install/update.ts +3 -1
- package/src/invoke.ts +12 -9
- package/src/mcp/bundle.ts +38 -14
- package/src/mcp/env.ts +7 -13
- package/src/mcp/server.ts +12 -6
- package/src/mcp/tools.ts +20 -4
- package/src/mcp.ts +3 -3
- package/src/parse.ts +96 -24
- package/src/runtime.ts +22 -12
- package/src/schema.ts +11 -5
- package/src/skill/generate.ts +4 -4
- package/src/skill/install.ts +6 -2
- package/src/validate.ts +21 -16
package/src/install/status.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { CliProgram } from "../types.ts";
|
|
1
|
+
import type { CliProgram } from "../types.ts";
|
|
2
2
|
import { buildInstallStatus, detectInstalledArtifacts } from "./detect-installed.ts";
|
|
3
|
-
import type { InstallOpts } from "./plan.ts";
|
|
4
3
|
import { resolveInstallPaths } from "./paths.ts";
|
|
4
|
+
import type { InstallOpts } from "./plan.ts";
|
|
5
5
|
|
|
6
6
|
export function installOut(msg: string, opts: InstallOpts): void {
|
|
7
7
|
if (opts.quiet || opts.json) return;
|
|
8
|
-
process.stdout.write(msg
|
|
8
|
+
process.stdout.write(`${msg}\n`);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function installInfo(msg: string, opts: InstallOpts): void {
|
|
12
12
|
if (opts.quiet) return;
|
|
13
13
|
if (opts.json && !opts.dry) return;
|
|
14
14
|
const prefix = opts.dry ? "[dry run] " : "";
|
|
15
|
-
process.stderr.write(prefix + msg
|
|
15
|
+
process.stderr.write(`${prefix + msg}\n`);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function installErr(msg: string): void {
|
|
19
|
-
process.stderr.write(msg
|
|
19
|
+
process.stderr.write(`${msg}\n`);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/** Prints install status to stdout (human or JSON). */
|
|
@@ -35,7 +35,11 @@ export function printInstallStatus(root: CliProgram, opts: InstallOpts): void {
|
|
|
35
35
|
if (status.claudeSkill) json.claudeSkill = status.claudeSkill;
|
|
36
36
|
if (status.cursorMcp) json.cursorMcp = status.cursorMcp;
|
|
37
37
|
if (status.claudeMcp) json.claudeMcp = status.claudeMcp;
|
|
38
|
-
|
|
38
|
+
if (status.claudeDesktopMcp) json.claudeDesktopMcp = status.claudeDesktopMcp;
|
|
39
|
+
if (status.opencodeMcp) json.opencodeMcp = status.opencodeMcp;
|
|
40
|
+
if (status.codexMcp) json.codexMcp = status.codexMcp;
|
|
41
|
+
if (status.chatGptMcp) json.chatGptMcp = status.chatGptMcp;
|
|
42
|
+
process.stdout.write(`${JSON.stringify(json, null, 2)}\n`);
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
41
45
|
|
|
@@ -48,7 +52,11 @@ export function printInstallStatus(root: CliProgram, opts: InstallOpts): void {
|
|
|
48
52
|
["cursor skill", status.cursorSkill],
|
|
49
53
|
["claude skill", status.claudeSkill],
|
|
50
54
|
["cursor mcp", status.cursorMcp],
|
|
51
|
-
["claude mcp", status.claudeMcp],
|
|
55
|
+
["claude code mcp", status.claudeMcp],
|
|
56
|
+
["claude desktop mcp", status.claudeDesktopMcp],
|
|
57
|
+
["opencode mcp", status.opencodeMcp],
|
|
58
|
+
["codex mcp", status.codexMcp],
|
|
59
|
+
["chatgpt desktop mcp", status.chatGptMcp],
|
|
52
60
|
];
|
|
53
61
|
let any = false;
|
|
54
62
|
for (const [label, value] of lines) {
|
package/src/install/uninstall.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { existsSync, rmSync } from "node:fs";
|
|
2
|
-
import { CliProgram } from "../types.ts";
|
|
2
|
+
import type { CliProgram } from "../types.ts";
|
|
3
3
|
import { uninstallBinary } from "./binary.ts";
|
|
4
4
|
import { uninstallCompletions } from "./completions.ts";
|
|
5
5
|
import { detectInstalledArtifacts } from "./detect-installed.ts";
|
|
6
|
+
import { removeCodexMcpConfig } from "./mcp-codex.ts";
|
|
6
7
|
import { removeMcpConfig } from "./mcp-config.ts";
|
|
7
|
-
import {
|
|
8
|
+
import { detectOpenCodeMcpConfigPath, removeOpenCodeMcpConfig } from "./mcp-opencode.ts";
|
|
9
|
+
import { type InstallPaths, userHome } from "./paths.ts";
|
|
8
10
|
import {
|
|
11
|
+
type InstallOpts,
|
|
9
12
|
wantsInstallBin,
|
|
10
13
|
wantsInstallCompletions,
|
|
11
14
|
wantsInstallMcp,
|
|
12
15
|
wantsInstallSkill,
|
|
13
|
-
type InstallOpts,
|
|
14
16
|
} from "./plan.ts";
|
|
15
17
|
|
|
16
18
|
export interface UninstallAction {
|
|
@@ -93,7 +95,7 @@ export function buildUninstallPlan(
|
|
|
93
95
|
}
|
|
94
96
|
if (detected.claudeMcp) {
|
|
95
97
|
actions.push({
|
|
96
|
-
summary: `claude mcp: ${paths.claudeMcpPath}`,
|
|
98
|
+
summary: `claude code mcp: ${paths.claudeMcpPath}`,
|
|
97
99
|
message: `Removing MCP server "${paths.mcpName}" from ${paths.claudeMcpPath}`,
|
|
98
100
|
run: () => {
|
|
99
101
|
removeMcpConfig(paths.claudeMcpPath, paths.mcpName, dry);
|
|
@@ -101,6 +103,48 @@ export function buildUninstallPlan(
|
|
|
101
103
|
},
|
|
102
104
|
});
|
|
103
105
|
}
|
|
106
|
+
if (detected.claudeDesktopMcp) {
|
|
107
|
+
actions.push({
|
|
108
|
+
summary: `claude desktop mcp: ${paths.claudeDesktopMcpPath}`,
|
|
109
|
+
message: `Removing MCP server "${paths.mcpName}" from ${paths.claudeDesktopMcpPath}`,
|
|
110
|
+
run: () => {
|
|
111
|
+
removeMcpConfig(paths.claudeDesktopMcpPath, paths.mcpName, dry);
|
|
112
|
+
return [paths.claudeDesktopMcpPath];
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (detected.opencodeMcp) {
|
|
117
|
+
const openCodePath =
|
|
118
|
+
detectOpenCodeMcpConfigPath(userHome(), paths.mcpName) ?? paths.opencodeMcpPath;
|
|
119
|
+
actions.push({
|
|
120
|
+
summary: `opencode mcp: ${openCodePath}`,
|
|
121
|
+
message: `Removing MCP server "${paths.mcpName}" from ${openCodePath}`,
|
|
122
|
+
run: () => {
|
|
123
|
+
removeOpenCodeMcpConfig(openCodePath, paths.mcpName, dry);
|
|
124
|
+
return [openCodePath];
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (detected.codexMcp) {
|
|
129
|
+
actions.push({
|
|
130
|
+
summary: `codex mcp: ${paths.codexConfigPath}`,
|
|
131
|
+
message: `Removing MCP server "${paths.mcpName}" from Codex via codex mcp remove`,
|
|
132
|
+
run: () => {
|
|
133
|
+
removeCodexMcpConfig(userHome(), paths.mcpName, dry);
|
|
134
|
+
return [paths.codexConfigPath];
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (detected.chatGptMcp) {
|
|
139
|
+
actions.push({
|
|
140
|
+
summary: `chatgpt desktop mcp: ${paths.chatGptMcpPath}`,
|
|
141
|
+
message: `Removing MCP server "${paths.mcpName}" from ${paths.chatGptMcpPath}`,
|
|
142
|
+
run: () => {
|
|
143
|
+
removeMcpConfig(paths.chatGptMcpPath, paths.mcpName, dry);
|
|
144
|
+
return [paths.chatGptMcpPath];
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
104
148
|
}
|
|
105
149
|
|
|
106
150
|
return actions;
|
|
@@ -110,5 +154,5 @@ export function buildUninstallPlan(
|
|
|
110
154
|
export function uninstallSkillDir(dir: string, dry: boolean): string[] {
|
|
111
155
|
if (!existsSync(dir)) return [];
|
|
112
156
|
if (!dry) rmSync(dir, { recursive: true, force: true });
|
|
113
|
-
return [dir
|
|
157
|
+
return [`${dir}/`];
|
|
114
158
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { afterEach, beforeEach, expect, test } from "bun:test";
|
|
2
2
|
import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
3
|
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
5
|
import { cliPresentationRoot } from "../builtins/presentation.ts";
|
|
6
6
|
import { cliInvoke } from "../index.ts";
|
|
7
7
|
import type { CliProgram, CliUpdateArtifact } from "../types.ts";
|
package/src/install/update.ts
CHANGED
|
@@ -7,7 +7,9 @@ import { installErr } from "./status.ts";
|
|
|
7
7
|
export async function cliUpdate(root: CliProgram): Promise<never> {
|
|
8
8
|
const hook = root.install?.updateGetLatest;
|
|
9
9
|
if (!hook) {
|
|
10
|
-
installErr(
|
|
10
|
+
installErr(
|
|
11
|
+
"install --update is not configured. Set install.updateGetLatest on the program root.",
|
|
12
|
+
);
|
|
11
13
|
process.exit(1);
|
|
12
14
|
}
|
|
13
15
|
|
package/src/invoke.ts
CHANGED
|
@@ -4,12 +4,12 @@ It parses argv against the user schema, captures stdout/stderr, and patches
|
|
|
4
4
|
process.exit so MCP tool calls can run handlers repeatedly.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { format } from "node:util";
|
|
8
8
|
import { builtinInterceptRoot, dispatchBuiltin } from "./builtins/dispatch.ts";
|
|
9
9
|
import { cliParseRoot, cliPresentationRoot } from "./builtins/presentation.ts";
|
|
10
|
-
import {
|
|
10
|
+
import { CliContext } from "./context.ts";
|
|
11
|
+
import { ParseKind, parse, postParseValidate } from "./parse.ts";
|
|
11
12
|
import { type CliNode, type CliProgram, type CliRouter, isCliLeaf, isCliRouter } from "./types.ts";
|
|
12
|
-
import { format } from "node:util";
|
|
13
13
|
|
|
14
14
|
/** Outcome of a non-exiting CLI invocation. */
|
|
15
15
|
export type CliInvokeKind = "ok" | "help" | "error";
|
|
@@ -159,21 +159,24 @@ export async function cliInvoke(root: CliProgram, argv: string[]): Promise<CliIn
|
|
|
159
159
|
}) as typeof process.stderr.write;
|
|
160
160
|
|
|
161
161
|
console.log = (...args: unknown[]) => {
|
|
162
|
-
stdout += format(...args)
|
|
162
|
+
stdout += `${format(...args)}\n`;
|
|
163
163
|
};
|
|
164
164
|
console.info = (...args: unknown[]) => {
|
|
165
|
-
stdout += format(...args)
|
|
165
|
+
stdout += `${format(...args)}\n`;
|
|
166
166
|
};
|
|
167
167
|
console.warn = (...args: unknown[]) => {
|
|
168
|
-
stderr += format(...args)
|
|
168
|
+
stderr += `${format(...args)}\n`;
|
|
169
169
|
};
|
|
170
170
|
console.error = (...args: unknown[]) => {
|
|
171
|
-
stderr += format(...args)
|
|
171
|
+
stderr += `${format(...args)}\n`;
|
|
172
172
|
};
|
|
173
173
|
|
|
174
174
|
try {
|
|
175
175
|
if (pr.kind === ParseKind.Ok) {
|
|
176
|
-
await dispatchBuiltin(root, pr, {
|
|
176
|
+
await dispatchBuiltin(root, pr, {
|
|
177
|
+
isLeafCompletionIntercept,
|
|
178
|
+
parseRoot: completionParseRoot,
|
|
179
|
+
});
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
await Promise.resolve(handler(ctx));
|
|
@@ -191,7 +194,7 @@ export async function cliInvoke(root: CliProgram, argv: string[]): Promise<CliIn
|
|
|
191
194
|
kind: "error",
|
|
192
195
|
exitCode: 1,
|
|
193
196
|
stdout,
|
|
194
|
-
stderr: err.message
|
|
197
|
+
stderr: `${err.message}\n`,
|
|
195
198
|
errorMsg: err.message,
|
|
196
199
|
};
|
|
197
200
|
}
|
package/src/mcp/bundle.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
/*
|
|
2
2
|
Packs a CLI program into an MCP Bundle (`.mcpb`) for Claude Desktop.
|
|
3
|
-
|
|
3
|
+
Expects `dist/<program.key>` as the compiled binary input.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
cpSync,
|
|
8
|
+
existsSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
mkdtempSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
rmSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
} from "node:fs";
|
|
7
15
|
import { tmpdir } from "node:os";
|
|
8
16
|
import { basename, join, resolve } from "node:path";
|
|
9
|
-
import { collectMcpTools, mcpServerId } from "./tools.ts";
|
|
10
17
|
import type { CliMcpBundleConfig, CliProgram } from "../types.ts";
|
|
18
|
+
import { collectMcpTools, mcpServerId } from "./tools.ts";
|
|
11
19
|
|
|
12
20
|
const MANIFEST_VERSION = "0.3";
|
|
13
21
|
const DIST_DIR = "dist";
|
|
@@ -51,7 +59,11 @@ function buildUserConfig(program: CliProgram): Record<string, unknown> | undefin
|
|
|
51
59
|
}
|
|
52
60
|
const out: Record<string, unknown> = {};
|
|
53
61
|
for (const name of envVars) {
|
|
54
|
-
const key =
|
|
62
|
+
const key =
|
|
63
|
+
name
|
|
64
|
+
.toLowerCase()
|
|
65
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
66
|
+
.replace(/^_|_$/g, "") || "env_var";
|
|
55
67
|
out[key] = {
|
|
56
68
|
type: "string",
|
|
57
69
|
title: name,
|
|
@@ -64,12 +76,19 @@ function buildUserConfig(program: CliProgram): Record<string, unknown> | undefin
|
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
/** Default author when `mcpServer.bundle.author` is unset. */
|
|
67
|
-
function defaultAuthor(bundle?: CliMcpBundleConfig): {
|
|
79
|
+
function defaultAuthor(bundle?: CliMcpBundleConfig): {
|
|
80
|
+
name: string;
|
|
81
|
+
email?: string;
|
|
82
|
+
url?: string;
|
|
83
|
+
} {
|
|
68
84
|
return bundle?.author ?? { name: "Unknown" };
|
|
69
85
|
}
|
|
70
86
|
|
|
71
87
|
/** Generates MCPB `manifest.json` object from program schema and MCP tools. */
|
|
72
|
-
export function generateMcpManifest(
|
|
88
|
+
export function generateMcpManifest(
|
|
89
|
+
program: CliProgram,
|
|
90
|
+
binaryName: string,
|
|
91
|
+
): Record<string, unknown> {
|
|
73
92
|
const bundle = program.mcpServer?.bundle;
|
|
74
93
|
const tools = collectMcpTools(program).map((t) => ({
|
|
75
94
|
name: t.name,
|
|
@@ -119,7 +138,11 @@ export function generateMcpManifest(program: CliProgram, binaryName: string): Re
|
|
|
119
138
|
function crc32(data: Buffer): number {
|
|
120
139
|
let crc = 0xffffffff;
|
|
121
140
|
for (let i = 0; i < data.length; i++) {
|
|
122
|
-
|
|
141
|
+
const byte = data[i];
|
|
142
|
+
if (byte === undefined) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
crc ^= byte;
|
|
123
146
|
for (let j = 0; j < 8; j++) {
|
|
124
147
|
crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
|
|
125
148
|
}
|
|
@@ -198,13 +221,9 @@ export interface PackMcpBundleOpts {
|
|
|
198
221
|
|
|
199
222
|
/**
|
|
200
223
|
* Stages manifest + binary (+ optional icon) and writes a `.mcpb` ZIP.
|
|
201
|
-
*
|
|
224
|
+
* Requires the compiled binary to exist.
|
|
202
225
|
*/
|
|
203
226
|
export function packMcpBundle(program: CliProgram, opts: PackMcpBundleOpts = {}): string {
|
|
204
|
-
if (process.platform !== "darwin") {
|
|
205
|
-
throw new Error("mcp bundle is macOS-only in this release. Build the bundle on darwin.");
|
|
206
|
-
}
|
|
207
|
-
|
|
208
227
|
const cwd = opts.cwd ?? process.cwd();
|
|
209
228
|
const defaults = defaultMcpBundlePaths(program, cwd);
|
|
210
229
|
const binaryPath = resolve(cwd, opts.binaryPath ?? defaults.binaryPath);
|
|
@@ -212,7 +231,9 @@ export function packMcpBundle(program: CliProgram, opts: PackMcpBundleOpts = {})
|
|
|
212
231
|
const binaryName = basename(binaryPath);
|
|
213
232
|
|
|
214
233
|
if (!existsSync(binaryPath)) {
|
|
215
|
-
throw new Error(
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Binary not found: ${binaryPath}. Build with compile first (expected dist/${program.key}).`,
|
|
236
|
+
);
|
|
216
237
|
}
|
|
217
238
|
|
|
218
239
|
const staging = mkdtempSync(join(tmpdir(), "mcpb-"));
|
|
@@ -222,7 +243,10 @@ export function packMcpBundle(program: CliProgram, opts: PackMcpBundleOpts = {})
|
|
|
222
243
|
|
|
223
244
|
const manifest = generateMcpManifest(program, binaryName);
|
|
224
245
|
const files: { name: string; data: Buffer }[] = [
|
|
225
|
-
{
|
|
246
|
+
{
|
|
247
|
+
name: "manifest.json",
|
|
248
|
+
data: Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8"),
|
|
249
|
+
},
|
|
226
250
|
{ name: binaryName, data: readFileSync(stagedBinary) },
|
|
227
251
|
];
|
|
228
252
|
|
package/src/mcp/env.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
This module bootstraps process.env for MCP servers from login shell and .env files.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
6
5
|
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
7
|
|
|
8
8
|
/** Parses `env` stdout from a login shell into a key/value map. */
|
|
9
9
|
export function captureShellEnv(shell: string): Record<string, string> {
|
|
@@ -42,9 +42,7 @@ export function applyShellEnv(env: Record<string, string>): void {
|
|
|
42
42
|
|
|
43
43
|
/** Loads a .env file into process.env (always overwrites). Warns on stderr if missing. */
|
|
44
44
|
export function loadEnvFile(envFile: string): void {
|
|
45
|
-
const resolved = envFile.startsWith("~")
|
|
46
|
-
? envFile.replace("~", process.env.HOME ?? "")
|
|
47
|
-
: envFile;
|
|
45
|
+
const resolved = envFile.startsWith("~") ? envFile.replace("~", process.env.HOME ?? "") : envFile;
|
|
48
46
|
let text: string;
|
|
49
47
|
try {
|
|
50
48
|
text = readFileSync(resolved, "utf8");
|
|
@@ -63,10 +61,7 @@ export function loadEnvFile(envFile: string): void {
|
|
|
63
61
|
}
|
|
64
62
|
const key = trimmed.slice(0, eq).trim();
|
|
65
63
|
let val = trimmed.slice(eq + 1).trim();
|
|
66
|
-
if (
|
|
67
|
-
(val.startsWith('"') && val.endsWith('"')) ||
|
|
68
|
-
(val.startsWith("'") && val.endsWith("'"))
|
|
69
|
-
) {
|
|
64
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
70
65
|
val = val.slice(1, -1);
|
|
71
66
|
}
|
|
72
67
|
if (key) {
|
|
@@ -76,10 +71,7 @@ export function loadEnvFile(envFile: string): void {
|
|
|
76
71
|
}
|
|
77
72
|
|
|
78
73
|
/** Applies mcpServer shellEnv and envFile bootstrap in order. */
|
|
79
|
-
export function bootstrapMcpEnv(config: {
|
|
80
|
-
shellEnv?: boolean | string;
|
|
81
|
-
envFile?: string;
|
|
82
|
-
}): void {
|
|
74
|
+
export function bootstrapMcpEnv(config: { shellEnv?: boolean | string; envFile?: string }): void {
|
|
83
75
|
const shellEnvCfg = config.shellEnv;
|
|
84
76
|
if (shellEnvCfg) {
|
|
85
77
|
const shell =
|
|
@@ -88,7 +80,9 @@ export function bootstrapMcpEnv(config: {
|
|
|
88
80
|
: (process.env.SHELL ?? (process.platform === "darwin" ? "/bin/zsh" : "/bin/bash"));
|
|
89
81
|
const captured = captureShellEnv(shell);
|
|
90
82
|
if (Object.keys(captured).length === 0) {
|
|
91
|
-
process.stderr.write(
|
|
83
|
+
process.stderr.write(
|
|
84
|
+
`[argsbarg] shellEnv: failed to capture shell environment from ${shell}\n`,
|
|
85
|
+
);
|
|
92
86
|
} else {
|
|
93
87
|
applyShellEnv(captured);
|
|
94
88
|
}
|
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 { CliProgram } from "../types.ts";
|
|
7
|
+
import type { CliProgram } from "../types.ts";
|
|
8
8
|
import { buildToolCallSuccess } from "./result.ts";
|
|
9
9
|
import {
|
|
10
10
|
allMcpResources,
|
|
@@ -25,7 +25,7 @@ interface JsonRpcRequest {
|
|
|
25
25
|
|
|
26
26
|
/** Writes a JSON-RPC response line to stdout. */
|
|
27
27
|
function writeResponse(msg: Record<string, unknown>): void {
|
|
28
|
-
process.stdout.write(JSON.stringify(msg)
|
|
28
|
+
process.stdout.write(`${JSON.stringify(msg)}\n`);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Writes a JSON-RPC error response. */
|
|
@@ -108,7 +108,10 @@ async function handleRequestLine(root: CliProgram, line: string): Promise<void>
|
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
110
|
const rawArgs = params.arguments;
|
|
111
|
-
if (
|
|
111
|
+
if (
|
|
112
|
+
rawArgs !== undefined &&
|
|
113
|
+
(typeof rawArgs !== "object" || rawArgs === null || Array.isArray(rawArgs))
|
|
114
|
+
) {
|
|
112
115
|
writeError(id, -32602, "Invalid params: arguments must be an object");
|
|
113
116
|
return;
|
|
114
117
|
}
|
|
@@ -151,7 +154,8 @@ async function handleRequestLine(root: CliProgram, line: string): Promise<void>
|
|
|
151
154
|
});
|
|
152
155
|
return;
|
|
153
156
|
}
|
|
154
|
-
const errText =
|
|
157
|
+
const errText =
|
|
158
|
+
invokeResult.stderr.trim() || invokeResult.errorMsg || `Exit code ${invokeResult.exitCode}`;
|
|
155
159
|
writeResponse({
|
|
156
160
|
jsonrpc: "2.0",
|
|
157
161
|
id,
|
|
@@ -222,14 +226,16 @@ export async function mcpServeStdioLoop(root: CliProgram): Promise<void> {
|
|
|
222
226
|
let buffer = "";
|
|
223
227
|
for await (const chunk of Bun.stdin.stream()) {
|
|
224
228
|
buffer += new TextDecoder().decode(chunk);
|
|
225
|
-
let nl
|
|
226
|
-
while (
|
|
229
|
+
let nl = buffer.indexOf("\n");
|
|
230
|
+
while (nl !== -1) {
|
|
227
231
|
const line = buffer.slice(0, nl).trim();
|
|
228
232
|
buffer = buffer.slice(nl + 1);
|
|
229
233
|
if (line.length === 0) {
|
|
234
|
+
nl = buffer.indexOf("\n");
|
|
230
235
|
continue;
|
|
231
236
|
}
|
|
232
237
|
await handleRequestLine(root, line);
|
|
238
|
+
nl = buffer.indexOf("\n");
|
|
233
239
|
}
|
|
234
240
|
}
|
|
235
241
|
const trailing = buffer.trim();
|
package/src/mcp/tools.ts
CHANGED
|
@@ -4,10 +4,19 @@ flat JSON tool arguments into argv for cliInvoke.
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { cliResolveNotes } from "../help.ts";
|
|
7
|
-
import { collectOptionDefs } from "../parse.ts";
|
|
8
7
|
import { visibleOptions } from "../hidden.ts";
|
|
8
|
+
import { collectOptionDefs } from "../parse.ts";
|
|
9
9
|
import { cliSchemaJson } from "../schema.ts";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
type CliLeaf,
|
|
12
|
+
type CliNode,
|
|
13
|
+
type CliOption,
|
|
14
|
+
CliOptionKind,
|
|
15
|
+
type CliPositional,
|
|
16
|
+
type CliProgram,
|
|
17
|
+
isCliLeaf,
|
|
18
|
+
leafOutputSchema,
|
|
19
|
+
} from "../types.ts";
|
|
11
20
|
|
|
12
21
|
/** Default URI pattern for the CLI schema MCP resource (`<mcpId>://schema`). */
|
|
13
22
|
export function defaultMcpSchemaUri(mcpId: string): string {
|
|
@@ -80,7 +89,11 @@ function positionalProperty(p: CliPositional): Record<string, unknown> {
|
|
|
80
89
|
}
|
|
81
90
|
|
|
82
91
|
/** Builds inputSchema for a leaf command. */
|
|
83
|
-
function buildInputSchema(
|
|
92
|
+
function buildInputSchema(
|
|
93
|
+
root: CliProgram,
|
|
94
|
+
path: string[],
|
|
95
|
+
leaf: CliLeaf,
|
|
96
|
+
): Record<string, unknown> {
|
|
84
97
|
const properties: Record<string, unknown> = {};
|
|
85
98
|
const required: string[] = [];
|
|
86
99
|
|
|
@@ -252,7 +265,10 @@ export function mcpToolCallToArgv(
|
|
|
252
265
|
items = raw.map(String);
|
|
253
266
|
} else if (typeof raw === "string") {
|
|
254
267
|
items = raw.includes(",")
|
|
255
|
-
? raw
|
|
268
|
+
? raw
|
|
269
|
+
.split(",")
|
|
270
|
+
.map((s) => s.trim())
|
|
271
|
+
.filter(Boolean)
|
|
256
272
|
: raw.trim()
|
|
257
273
|
? [raw.trim()]
|
|
258
274
|
: [];
|
package/src/mcp.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
This module starts the ArgsBarg MCP stdio server for opt-in program roots.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { mcpServeStdioLoop } from "./mcp/server.ts";
|
|
6
5
|
import { bootstrapMcpEnv } from "./mcp/env.ts";
|
|
7
|
-
import {
|
|
6
|
+
import { mcpServeStdioLoop } from "./mcp/server.ts";
|
|
7
|
+
import type { CliProgram } from "./types.ts";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Runs the MCP JSON-RPC server on stdin/stdout until stdin closes, then exits.
|
|
@@ -19,7 +19,7 @@ export async function cliMcpServeStdio(root: CliProgram): Promise<never> {
|
|
|
19
19
|
process.exit(0);
|
|
20
20
|
} catch (err) {
|
|
21
21
|
if (err instanceof Error) {
|
|
22
|
-
process.stderr.write(err.message
|
|
22
|
+
process.stderr.write(`${err.message}\n`);
|
|
23
23
|
} else {
|
|
24
24
|
process.stderr.write("MCP server error.\n");
|
|
25
25
|
}
|