argsbarg 3.4.2 → 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 +14 -1
- package/biome.json +29 -6
- package/bun.lock +22 -0
- package/docs/install.md +1 -1
- package/docs/mcp.md +45 -1
- package/index.d.ts +50 -50
- 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 +1 -1
- 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 +18 -11
- package/src/docs/mcp-guide.ts +105 -24
- 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 +7 -6
- 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 +29 -4
- package/src/install/gh-release-update.ts +31 -23
- package/src/install/index.ts +69 -19
- package/src/install/install.test.ts +31 -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 +29 -3
- package/src/install/plan.ts +73 -6
- package/src/install/shell.ts +1 -4
- package/src/install/status.ts +12 -6
- package/src/install/uninstall.ts +38 -4
- 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 +36 -8
- 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/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 {
|
|
@@ -111,6 +113,38 @@ export function buildUninstallPlan(
|
|
|
111
113
|
},
|
|
112
114
|
});
|
|
113
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
|
+
}
|
|
114
148
|
}
|
|
115
149
|
|
|
116
150
|
return actions;
|
|
@@ -120,5 +154,5 @@ export function buildUninstallPlan(
|
|
|
120
154
|
export function uninstallSkillDir(dir: string, dry: boolean): string[] {
|
|
121
155
|
if (!existsSync(dir)) return [];
|
|
122
156
|
if (!dry) rmSync(dir, { recursive: true, force: true });
|
|
123
|
-
return [dir
|
|
157
|
+
return [`${dir}/`];
|
|
124
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
|
@@ -3,11 +3,19 @@ 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
|
}
|
|
@@ -208,7 +231,9 @@ export function packMcpBundle(program: CliProgram, opts: PackMcpBundleOpts = {})
|
|
|
208
231
|
const binaryName = basename(binaryPath);
|
|
209
232
|
|
|
210
233
|
if (!existsSync(binaryPath)) {
|
|
211
|
-
throw new Error(
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Binary not found: ${binaryPath}. Build with compile first (expected dist/${program.key}).`,
|
|
236
|
+
);
|
|
212
237
|
}
|
|
213
238
|
|
|
214
239
|
const staging = mkdtempSync(join(tmpdir(), "mcpb-"));
|
|
@@ -218,7 +243,10 @@ export function packMcpBundle(program: CliProgram, opts: PackMcpBundleOpts = {})
|
|
|
218
243
|
|
|
219
244
|
const manifest = generateMcpManifest(program, binaryName);
|
|
220
245
|
const files: { name: string; data: Buffer }[] = [
|
|
221
|
-
{
|
|
246
|
+
{
|
|
247
|
+
name: "manifest.json",
|
|
248
|
+
data: Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8"),
|
|
249
|
+
},
|
|
222
250
|
{ name: binaryName, data: readFileSync(stagedBinary) },
|
|
223
251
|
];
|
|
224
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
|
}
|
package/src/parse.ts
CHANGED
|
@@ -7,12 +7,11 @@ It keeps handler dispatch and help on one parser so the CLI behavior stays consi
|
|
|
7
7
|
across every entry path.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { CliContext } from "./context.ts";
|
|
11
10
|
import {
|
|
12
|
-
type CliLeaf,
|
|
13
|
-
CliNode,
|
|
14
11
|
CliFallbackMode,
|
|
15
|
-
|
|
12
|
+
type CliLeaf,
|
|
13
|
+
type CliNode,
|
|
14
|
+
type CliOption,
|
|
16
15
|
CliOptionKind,
|
|
17
16
|
isCliLeaf,
|
|
18
17
|
isCliRouter,
|
|
@@ -191,17 +190,30 @@ function consumeOptions(
|
|
|
191
190
|
|
|
192
191
|
if (tok === "--") {
|
|
193
192
|
idx += 1;
|
|
194
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
report: { err: null, stoppedOnUnknown: false, sawDoubleDash: true },
|
|
195
|
+
nextIndex: idx,
|
|
196
|
+
};
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
if (tok.startsWith("--")) {
|
|
198
200
|
const err = consumeLong(tok);
|
|
199
|
-
if (err === "")
|
|
200
|
-
|
|
201
|
+
if (err === "")
|
|
202
|
+
return {
|
|
203
|
+
report: { err: null, stoppedOnUnknown: true, sawDoubleDash: false },
|
|
204
|
+
nextIndex: idx,
|
|
205
|
+
};
|
|
206
|
+
if (err)
|
|
207
|
+
return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
|
|
201
208
|
} else {
|
|
202
209
|
const err = consumeShort(tok);
|
|
203
|
-
if (err === "")
|
|
204
|
-
|
|
210
|
+
if (err === "")
|
|
211
|
+
return {
|
|
212
|
+
report: { err: null, stoppedOnUnknown: true, sawDoubleDash: false },
|
|
213
|
+
nextIndex: idx,
|
|
214
|
+
};
|
|
215
|
+
if (err)
|
|
216
|
+
return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
|
|
205
217
|
}
|
|
206
218
|
}
|
|
207
219
|
|
|
@@ -212,7 +224,7 @@ function consumeOptions(
|
|
|
212
224
|
|
|
213
225
|
/** Merges option defs from the program root along the routed command path. */
|
|
214
226
|
export function collectOptionDefs(root: CliNode, path: string[]): CliOption[] {
|
|
215
|
-
|
|
227
|
+
const defs = [...(root.options ?? [])];
|
|
216
228
|
let node: CliNode = root;
|
|
217
229
|
|
|
218
230
|
for (const seg of path) {
|
|
@@ -343,7 +355,16 @@ function finishLeaf(
|
|
|
343
355
|
}
|
|
344
356
|
}
|
|
345
357
|
|
|
346
|
-
return {
|
|
358
|
+
return {
|
|
359
|
+
kind: ParseKind.Ok,
|
|
360
|
+
path,
|
|
361
|
+
opts,
|
|
362
|
+
args,
|
|
363
|
+
helpExplicit: false,
|
|
364
|
+
helpPath: [],
|
|
365
|
+
errorMsg: "",
|
|
366
|
+
errorHelpPath: [],
|
|
367
|
+
};
|
|
347
368
|
}
|
|
348
369
|
|
|
349
370
|
// ── Main Parser ───────────────────────────────────────────────────────────────
|
|
@@ -414,7 +435,16 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
414
435
|
cmdName = root.fallbackCommand;
|
|
415
436
|
node = findChild(root.commands, cmdName);
|
|
416
437
|
if (!node) {
|
|
417
|
-
return {
|
|
438
|
+
return {
|
|
439
|
+
kind: ParseKind.Error,
|
|
440
|
+
path: [],
|
|
441
|
+
opts: {},
|
|
442
|
+
args: [],
|
|
443
|
+
helpExplicit: false,
|
|
444
|
+
helpPath: [],
|
|
445
|
+
errorMsg: `Unknown command: ${cmdName}`,
|
|
446
|
+
errorHelpPath: path,
|
|
447
|
+
};
|
|
418
448
|
}
|
|
419
449
|
} else {
|
|
420
450
|
return helpResult([], false);
|
|
@@ -428,16 +458,26 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
428
458
|
i += 1;
|
|
429
459
|
node = childPick;
|
|
430
460
|
} else {
|
|
461
|
+
const fallbackCommand = root.fallbackCommand;
|
|
431
462
|
const canRouteUnknown =
|
|
432
|
-
|
|
463
|
+
fallbackCommand !== undefined &&
|
|
433
464
|
((root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOrUnknown ||
|
|
434
465
|
(root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.UnknownOnly);
|
|
435
466
|
|
|
436
467
|
if (canRouteUnknown) {
|
|
437
|
-
cmdName =
|
|
468
|
+
cmdName = fallbackCommand;
|
|
438
469
|
node = findChild(root.commands, cmdName);
|
|
439
470
|
if (!node) {
|
|
440
|
-
return {
|
|
471
|
+
return {
|
|
472
|
+
kind: ParseKind.Error,
|
|
473
|
+
path: [],
|
|
474
|
+
opts: {},
|
|
475
|
+
args: [],
|
|
476
|
+
helpExplicit: false,
|
|
477
|
+
helpPath: [],
|
|
478
|
+
errorMsg: `Unknown command: ${cmdName}`,
|
|
479
|
+
errorHelpPath: path,
|
|
480
|
+
};
|
|
441
481
|
}
|
|
442
482
|
} else {
|
|
443
483
|
cmdName = peek;
|
|
@@ -451,7 +491,9 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
451
491
|
args: [],
|
|
452
492
|
helpExplicit: false,
|
|
453
493
|
helpPath: [],
|
|
454
|
-
errorMsg: forcePositionals
|
|
494
|
+
errorMsg: forcePositionals
|
|
495
|
+
? `Expected subcommand but got positional: ${cmdName}`
|
|
496
|
+
: `Unknown command: ${cmdName}`,
|
|
455
497
|
errorHelpPath: path,
|
|
456
498
|
};
|
|
457
499
|
}
|
|
@@ -460,7 +502,19 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
460
502
|
}
|
|
461
503
|
|
|
462
504
|
path.push(cmdName);
|
|
463
|
-
|
|
505
|
+
if (!node) {
|
|
506
|
+
return {
|
|
507
|
+
kind: ParseKind.Error,
|
|
508
|
+
path,
|
|
509
|
+
opts: {},
|
|
510
|
+
args: [],
|
|
511
|
+
helpExplicit: false,
|
|
512
|
+
helpPath: [],
|
|
513
|
+
errorMsg: `Unknown command: ${cmdName}`,
|
|
514
|
+
errorHelpPath: path,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
let current = node;
|
|
464
518
|
|
|
465
519
|
// Walk the command tree
|
|
466
520
|
while (true) {
|
|
@@ -508,7 +562,15 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
508
562
|
if (!isCliLeaf(current)) {
|
|
509
563
|
return helpResult(path, false);
|
|
510
564
|
}
|
|
511
|
-
return finishLeaf(
|
|
565
|
+
return finishLeaf(
|
|
566
|
+
current,
|
|
567
|
+
i,
|
|
568
|
+
argv,
|
|
569
|
+
path,
|
|
570
|
+
opts,
|
|
571
|
+
collectOptionDefs(root, path),
|
|
572
|
+
forcePositionals,
|
|
573
|
+
);
|
|
512
574
|
}
|
|
513
575
|
|
|
514
576
|
const tok = argv[i];
|
|
@@ -542,10 +604,10 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
542
604
|
fb !== undefined &&
|
|
543
605
|
(fm === CliFallbackMode.MissingOrUnknown || fm === CliFallbackMode.UnknownOnly);
|
|
544
606
|
|
|
545
|
-
if (canRouteUnknown) {
|
|
546
|
-
const fbNode = findChild(current.commands, fb
|
|
607
|
+
if (canRouteUnknown && fb !== undefined) {
|
|
608
|
+
const fbNode = findChild(current.commands, fb);
|
|
547
609
|
if (fbNode) {
|
|
548
|
-
path.push(fb
|
|
610
|
+
path.push(fb);
|
|
549
611
|
current = fbNode;
|
|
550
612
|
continue;
|
|
551
613
|
}
|
|
@@ -558,7 +620,9 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
558
620
|
args: [],
|
|
559
621
|
helpExplicit: false,
|
|
560
622
|
helpPath: [],
|
|
561
|
-
errorMsg: forcePositionals
|
|
623
|
+
errorMsg: forcePositionals
|
|
624
|
+
? `Expected subcommand but got positional: ${tok}`
|
|
625
|
+
: `Unknown subcommand: ${tok}`,
|
|
562
626
|
errorHelpPath: path,
|
|
563
627
|
};
|
|
564
628
|
}
|
|
@@ -566,7 +630,15 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
566
630
|
if (!isCliLeaf(current)) {
|
|
567
631
|
return helpResult(path, false);
|
|
568
632
|
}
|
|
569
|
-
return finishLeaf(
|
|
633
|
+
return finishLeaf(
|
|
634
|
+
current,
|
|
635
|
+
i,
|
|
636
|
+
argv,
|
|
637
|
+
path,
|
|
638
|
+
opts,
|
|
639
|
+
collectOptionDefs(root, path),
|
|
640
|
+
forcePositionals,
|
|
641
|
+
);
|
|
570
642
|
}
|
|
571
643
|
}
|
|
572
644
|
|
|
@@ -578,7 +650,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
578
650
|
export function postParseValidate(root: CliNode, pr: ParseResult): ParseResult {
|
|
579
651
|
if (pr.kind !== ParseKind.Ok) return pr;
|
|
580
652
|
|
|
581
|
-
|
|
653
|
+
const defs = [...(root.options ?? [])];
|
|
582
654
|
let node: CliNode = root;
|
|
583
655
|
|
|
584
656
|
for (const seg of pr.path) {
|