argsbarg 3.4.2 → 3.6.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 +31 -1
- package/README.md +24 -8
- package/biome.json +29 -6
- package/bun.lock +22 -0
- package/docs/cli-program.md +75 -1
- package/docs/install.md +1 -1
- package/docs/mcp.md +45 -1
- package/docs/templates/cursor/rules/cli-program.mdc +15 -21
- package/index.d.ts +95 -50
- package/justfile +24 -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/context.ts +91 -15
- package/src/docs/api-guide.test.ts +2 -2
- package/src/docs/api-guide.ts +19 -5
- package/src/docs/builtin.ts +27 -8
- package/src/docs/docs.test.ts +18 -11
- package/src/docs/mcp-guide.ts +108 -25
- package/src/docs/resolve.ts +10 -3
- package/src/docs/save.ts +11 -3
- package/src/formats.test.ts +35 -0
- package/src/formats.ts +135 -0
- 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 +120 -96
- package/src/index.ts +36 -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 +83 -18
- package/src/mcp.ts +3 -3
- package/src/parse.ts +129 -27
- 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/types.ts +24 -0
- package/src/validate.ts +75 -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,22 @@ 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
|
+
CliValueFormat,
|
|
18
|
+
isCliLeaf,
|
|
19
|
+
leafOutputSchema,
|
|
20
|
+
} from "../types.ts";
|
|
21
|
+
|
|
22
|
+
const DURATION_PATTERN = "^\\d+[hdms]?$";
|
|
11
23
|
|
|
12
24
|
/** Default URI pattern for the CLI schema MCP resource (`<mcpId>://schema`). */
|
|
13
25
|
export function defaultMcpSchemaUri(mcpId: string): string {
|
|
@@ -56,12 +68,37 @@ export function mcpToolName(root: CliProgram, path: string[]): string {
|
|
|
56
68
|
|
|
57
69
|
/** JSON Schema property for one option. */
|
|
58
70
|
function optionProperty(opt: CliOption): Record<string, unknown> {
|
|
59
|
-
const base = { description: opt.description };
|
|
71
|
+
const base: Record<string, unknown> = { description: opt.description };
|
|
72
|
+
if (opt.default !== undefined) {
|
|
73
|
+
base.default = opt.default;
|
|
74
|
+
}
|
|
60
75
|
switch (opt.kind) {
|
|
61
76
|
case CliOptionKind.Presence:
|
|
62
77
|
return { type: "boolean", ...base };
|
|
63
|
-
case CliOptionKind.String:
|
|
64
|
-
|
|
78
|
+
case CliOptionKind.String: {
|
|
79
|
+
if (opt.format === CliValueFormat.CommaList) {
|
|
80
|
+
return {
|
|
81
|
+
oneOf: [
|
|
82
|
+
{ type: "string", ...base },
|
|
83
|
+
{ type: "array", items: { type: "string" }, ...base },
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const stringBase = { type: "string", ...base };
|
|
88
|
+
if (opt.format === CliValueFormat.Duration) {
|
|
89
|
+
return { ...stringBase, pattern: DURATION_PATTERN };
|
|
90
|
+
}
|
|
91
|
+
if (opt.format === CliValueFormat.Date) {
|
|
92
|
+
return { ...stringBase, format: "date" };
|
|
93
|
+
}
|
|
94
|
+
if (opt.format === CliValueFormat.DateTime) {
|
|
95
|
+
return { ...stringBase, format: "date-time" };
|
|
96
|
+
}
|
|
97
|
+
if (opt.pattern !== undefined) {
|
|
98
|
+
return { ...stringBase, pattern: opt.pattern };
|
|
99
|
+
}
|
|
100
|
+
return stringBase;
|
|
101
|
+
}
|
|
65
102
|
case CliOptionKind.Number:
|
|
66
103
|
return { type: "number", ...base };
|
|
67
104
|
case CliOptionKind.Enum:
|
|
@@ -69,6 +106,23 @@ function optionProperty(opt: CliOption): Record<string, unknown> {
|
|
|
69
106
|
}
|
|
70
107
|
}
|
|
71
108
|
|
|
109
|
+
function formatMcpOptionValue(opt: CliOption, val: unknown): string | { error: string } {
|
|
110
|
+
if (opt.format === CliValueFormat.CommaList) {
|
|
111
|
+
if (Array.isArray(val)) {
|
|
112
|
+
const items = val.map(String).filter(Boolean);
|
|
113
|
+
if (items.length === 0) {
|
|
114
|
+
return { error: `Option --${opt.name} requires at least one value` };
|
|
115
|
+
}
|
|
116
|
+
return items.join(",");
|
|
117
|
+
}
|
|
118
|
+
if (typeof val === "string") {
|
|
119
|
+
return val;
|
|
120
|
+
}
|
|
121
|
+
return { error: `Option --${opt.name} must be a string or array of strings` };
|
|
122
|
+
}
|
|
123
|
+
return String(val);
|
|
124
|
+
}
|
|
125
|
+
|
|
72
126
|
/** JSON Schema property for one positional slot. */
|
|
73
127
|
function positionalProperty(p: CliPositional): Record<string, unknown> {
|
|
74
128
|
const base = { description: p.description };
|
|
@@ -80,7 +134,11 @@ function positionalProperty(p: CliPositional): Record<string, unknown> {
|
|
|
80
134
|
}
|
|
81
135
|
|
|
82
136
|
/** Builds inputSchema for a leaf command. */
|
|
83
|
-
function buildInputSchema(
|
|
137
|
+
function buildInputSchema(
|
|
138
|
+
root: CliProgram,
|
|
139
|
+
path: string[],
|
|
140
|
+
leaf: CliLeaf,
|
|
141
|
+
): Record<string, unknown> {
|
|
84
142
|
const properties: Record<string, unknown> = {};
|
|
85
143
|
const required: string[] = [];
|
|
86
144
|
|
|
@@ -238,7 +296,11 @@ export function mcpToolCallToArgv(
|
|
|
238
296
|
}
|
|
239
297
|
continue;
|
|
240
298
|
}
|
|
241
|
-
|
|
299
|
+
const formatted = formatMcpOptionValue(opt, val);
|
|
300
|
+
if (typeof formatted !== "string") {
|
|
301
|
+
return formatted;
|
|
302
|
+
}
|
|
303
|
+
argv.push(`--${opt.name}`, formatted);
|
|
242
304
|
}
|
|
243
305
|
|
|
244
306
|
for (const p of tool.leaf.positionals ?? []) {
|
|
@@ -247,17 +309,20 @@ export function mcpToolCallToArgv(
|
|
|
247
309
|
|
|
248
310
|
if (argMax === 0) {
|
|
249
311
|
const raw = args[p.name];
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
312
|
+
if (raw === undefined) {
|
|
313
|
+
if (argMin >= 1) {
|
|
314
|
+
return { error: `Missing argument: ${p.name} (use a JSON array)` };
|
|
315
|
+
}
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (!Array.isArray(raw)) {
|
|
319
|
+
return {
|
|
320
|
+
error: `Argument ${p.name} must be a JSON array of strings (not a comma-separated string)`,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
const items = raw.map(String).filter(Boolean);
|
|
324
|
+
if (items.length === 0 && argMin >= 1) {
|
|
325
|
+
return { error: `Missing argument: ${p.name}` };
|
|
261
326
|
}
|
|
262
327
|
argv.push(...items);
|
|
263
328
|
continue;
|
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
|
}
|