argsbarg 1.5.0 → 2.0.1
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/CHANGELOG.md +31 -1
- package/README.md +12 -8
- package/docs/install.md +2 -2
- package/docs/mcp.md +3 -3
- 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 +38 -37
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +3 -3
- package/src/builtins/completion-bash.ts +3 -3
- package/src/builtins/completion-fish.ts +2 -2
- package/src/builtins/completion-group.ts +2 -2
- package/src/builtins/completion-zsh.ts +3 -3
- package/src/builtins/dispatch.ts +41 -26
- package/src/builtins/export.ts +15 -8
- package/src/builtins/install.ts +3 -3
- package/src/builtins/mcp.ts +2 -2
- package/src/builtins/presentation.ts +34 -23
- package/src/builtins/scopes.ts +9 -8
- package/src/capabilities.ts +32 -0
- package/src/context.ts +17 -7
- package/src/help.ts +21 -9
- package/src/index.test.ts +128 -121
- package/src/index.ts +1 -1
- package/src/install/binary.ts +3 -3
- package/src/install/completions.ts +2 -2
- package/src/install/detect-installed.ts +1 -1
- package/src/install/index.ts +4 -4
- package/src/install/install.test.ts +2 -2
- package/src/install/mcp-config.ts +2 -2
- package/src/install/paths.ts +3 -3
- package/src/install/plan.ts +4 -4
- package/src/install/status.ts +2 -2
- package/src/install/uninstall.ts +2 -2
- package/src/invoke.ts +14 -5
- package/src/mcp/server.ts +3 -3
- package/src/mcp/tools.ts +16 -16
- package/src/mcp.ts +2 -2
- package/src/parse.ts +55 -27
- package/src/runtime.ts +34 -25
- package/src/schema.ts +6 -6
- package/src/skill/generate.ts +6 -6
- package/src/skill/install.ts +2 -2
- package/src/types.test.ts +40 -0
- package/src/types.ts +54 -44
- package/src/validate.ts +87 -72
package/src/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { CliContext } from "./context.ts";
|
|
|
13
13
|
export { cliErrWithHelp, cliRun } from "./runtime";
|
|
14
14
|
export { CliFallbackMode, CliOptionKind, CliSchemaValidationError } from "./types.ts";
|
|
15
15
|
export type {
|
|
16
|
-
|
|
16
|
+
CliProgram,
|
|
17
17
|
CliHandler,
|
|
18
18
|
CliInvocation,
|
|
19
19
|
CliMcpResource,
|
package/src/install/binary.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { CliProgram } from "../types.ts";
|
|
4
4
|
import { InstallPaths } from "./paths.ts";
|
|
5
5
|
import {
|
|
6
6
|
buildPathRcBlock,
|
|
@@ -17,7 +17,7 @@ export interface BinaryInstallResult {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/** Copies the running binary to the install path and patches rc files when shells are detected. */
|
|
20
|
-
export function installBinary(root:
|
|
20
|
+
export function installBinary(root: CliProgram, paths: InstallPaths, dry: boolean): BinaryInstallResult {
|
|
21
21
|
const changed: string[] = [];
|
|
22
22
|
const shells = detectShells();
|
|
23
23
|
let patchedBashRc = false;
|
|
@@ -58,7 +58,7 @@ export function installBinary(root: CliCommand, paths: InstallPaths, dry: boolea
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/** Removes binary and rc marker blocks. */
|
|
61
|
-
export function uninstallBinary(root:
|
|
61
|
+
export function uninstallBinary(root: CliProgram, paths: InstallPaths, dry: boolean): string[] {
|
|
62
62
|
const changed: string[] = [];
|
|
63
63
|
if (existsSync(paths.binaryPath)) {
|
|
64
64
|
if (!dry) unlinkSync(paths.binaryPath);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { CliProgram } from "../types.ts";
|
|
4
4
|
import { completionBashScript, completionFishScript, completionZshScript } from "../builtins/index.ts";
|
|
5
5
|
import { cliPresentationRoot } from "../builtins/presentation.ts";
|
|
6
6
|
import { InstallPaths } from "./paths.ts";
|
|
7
7
|
import { detectShells } from "./shell.ts";
|
|
8
8
|
|
|
9
9
|
/** Writes shell completion scripts for detected shells. */
|
|
10
|
-
export function installCompletions(root:
|
|
10
|
+
export function installCompletions(root: CliProgram, paths: InstallPaths, dry: boolean): string[] {
|
|
11
11
|
const changed: string[] = [];
|
|
12
12
|
const shells = detectShells();
|
|
13
13
|
const schema = cliPresentationRoot(root);
|
package/src/install/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { CliProgram } from "../types.ts";
|
|
3
3
|
import { cliSkillInstall } from "../skill/install.ts";
|
|
4
4
|
import { checkMcpConflict, expectedMcpEntry } from "./mcp-config.ts";
|
|
5
5
|
import {
|
|
@@ -74,7 +74,7 @@ function promptConfirm(): boolean {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function runSkillAction(
|
|
77
|
-
root:
|
|
77
|
+
root: CliProgram,
|
|
78
78
|
kind: "cursor-skill" | "claude-skill",
|
|
79
79
|
opts: InstallOpts,
|
|
80
80
|
): string[] {
|
|
@@ -87,7 +87,7 @@ function runSkillAction(
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function executePlan(
|
|
90
|
-
root:
|
|
90
|
+
root: CliProgram,
|
|
91
91
|
actions: Array<InstallAction | UninstallAction>,
|
|
92
92
|
opts: InstallOpts,
|
|
93
93
|
): string[] {
|
|
@@ -114,7 +114,7 @@ function executePlan(
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/** Main install command orchestrator. */
|
|
117
|
-
export async function cliInstall(root:
|
|
117
|
+
export async function cliInstall(root: CliProgram, rawOpts: Record<string, string>): Promise<never> {
|
|
118
118
|
const opts = parseInstallOpts(rawOpts);
|
|
119
119
|
const err = validateOpts(opts);
|
|
120
120
|
if (err) {
|
|
@@ -2,14 +2,14 @@ import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
|
2
2
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import {
|
|
5
|
+
import { CliProgram } from "../types.ts";
|
|
6
6
|
import { detectInstalledArtifacts } from "./detect-installed.ts";
|
|
7
7
|
import { resolveInstallPaths } from "./paths.ts";
|
|
8
8
|
import { buildInstallPlan } from "./plan.ts";
|
|
9
9
|
import { printInstallStatus } from "./status.ts";
|
|
10
10
|
import { parseInstallOpts } from "./index.ts";
|
|
11
11
|
|
|
12
|
-
const fixture:
|
|
12
|
+
const fixture: CliProgram = {
|
|
13
13
|
key: "testapp",
|
|
14
14
|
description: "Test",
|
|
15
15
|
mcpServer: { name: "testapp" },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { CliProgram } from "../types.ts";
|
|
4
4
|
import { InstallPaths } from "./paths.ts";
|
|
5
5
|
|
|
6
6
|
export interface McpServerEntry {
|
|
@@ -8,7 +8,7 @@ export interface McpServerEntry {
|
|
|
8
8
|
args: string[];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function expectedMcpEntry(root:
|
|
11
|
+
export function expectedMcpEntry(root: CliProgram): McpServerEntry {
|
|
12
12
|
return { command: root.key, args: ["mcp"] };
|
|
13
13
|
}
|
|
14
14
|
|
package/src/install/paths.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { CliProgram } from "../types.ts";
|
|
4
4
|
import { sanitizeToolSegment } from "../mcp/tools.ts";
|
|
5
5
|
|
|
6
6
|
export interface InstallPaths {
|
|
@@ -35,7 +35,7 @@ function expandTilde(path: string): string {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/** Resolves the binary install directory from CLI flag, env, or config. */
|
|
38
|
-
export function resolveBindir(root:
|
|
38
|
+
export function resolveBindir(root: CliProgram, prefix?: string): string {
|
|
39
39
|
const raw = prefix ?? process.env.INSTALL_PREFIX ?? root.install?.prefix;
|
|
40
40
|
if (raw) {
|
|
41
41
|
return expandTilde(raw);
|
|
@@ -44,7 +44,7 @@ export function resolveBindir(root: CliCommand, prefix?: string): string {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Resolves all install artifact paths for a program root. */
|
|
47
|
-
export function resolveInstallPaths(root:
|
|
47
|
+
export function resolveInstallPaths(root: CliProgram, opts: { prefix?: string }): InstallPaths {
|
|
48
48
|
const home = userHome();
|
|
49
49
|
const bindir = resolveBindir(root, opts.prefix);
|
|
50
50
|
const key = root.key;
|
package/src/install/plan.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { CliProgram } from "../types.ts";
|
|
4
4
|
import { installBinary } from "./binary.ts";
|
|
5
5
|
import { installCompletions } from "./completions.ts";
|
|
6
6
|
import { detectInstalledArtifacts } from "./detect-installed.ts";
|
|
@@ -45,12 +45,12 @@ function wantsSkill(opts: InstallOpts): boolean {
|
|
|
45
45
|
return !!(opts.all || opts.skill);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function wantsMcp(opts: InstallOpts, root:
|
|
48
|
+
function wantsMcp(opts: InstallOpts, root: CliProgram): boolean {
|
|
49
49
|
return !!(opts.all || opts.mcp) && root.mcpServer !== undefined;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/** Builds install actions for normal mode (--all / scoped targets). */
|
|
53
|
-
export function buildInstallPlan(root:
|
|
53
|
+
export function buildInstallPlan(root: CliProgram, paths: InstallPaths, opts: InstallOpts): InstallAction[] {
|
|
54
54
|
const actions: InstallAction[] = [];
|
|
55
55
|
const dry = !!opts.dry;
|
|
56
56
|
|
|
@@ -148,7 +148,7 @@ export function buildInstallPlan(root: CliCommand, paths: InstallPaths, opts: In
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/** Builds update actions for artifacts already installed. */
|
|
151
|
-
export function buildUpdatePlan(root:
|
|
151
|
+
export function buildUpdatePlan(root: CliProgram, paths: InstallPaths, opts: InstallOpts): InstallAction[] {
|
|
152
152
|
const detected = detectInstalledArtifacts(paths);
|
|
153
153
|
const scoped: InstallOpts = {
|
|
154
154
|
bin: true,
|
package/src/install/status.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CliProgram } from "../types.ts";
|
|
2
2
|
import { buildInstallStatus, detectInstalledArtifacts } from "./detect-installed.ts";
|
|
3
3
|
import type { InstallOpts } from "./plan.ts";
|
|
4
4
|
import { resolveInstallPaths } from "./paths.ts";
|
|
@@ -20,7 +20,7 @@ export function installErr(msg: string): void {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/** Prints install status to stdout (human or JSON). */
|
|
23
|
-
export function printInstallStatus(root:
|
|
23
|
+
export function printInstallStatus(root: CliProgram, opts: InstallOpts): void {
|
|
24
24
|
const paths = resolveInstallPaths(root, opts);
|
|
25
25
|
const detected = detectInstalledArtifacts(paths);
|
|
26
26
|
const status = buildInstallStatus(paths, detected);
|
package/src/install/uninstall.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, rmSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { CliProgram } from "../types.ts";
|
|
4
4
|
import { uninstallBinary } from "./binary.ts";
|
|
5
5
|
import { uninstallCompletions } from "./completions.ts";
|
|
6
6
|
import { detectInstalledArtifacts } from "./detect-installed.ts";
|
|
@@ -20,7 +20,7 @@ function scopeAll(opts: InstallOpts): boolean {
|
|
|
20
20
|
|
|
21
21
|
/** Builds uninstall actions from detected artifacts. */
|
|
22
22
|
export function buildUninstallPlan(
|
|
23
|
-
root:
|
|
23
|
+
root: CliProgram,
|
|
24
24
|
paths: InstallPaths,
|
|
25
25
|
opts: InstallOpts,
|
|
26
26
|
): UninstallAction[] {
|
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,12 +144,12 @@ 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 (
|
|
151
|
+
function walk(cmd: CliNode, path: string[]): void {
|
|
152
|
+
if (isCliLeaf(cmd)) {
|
|
153
153
|
if (cmd.key === "completion" || cmd.key === "install" || cmd.key === "mcp") {
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
@@ -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);
|
package/src/parse.ts
CHANGED
|
@@ -9,10 +9,13 @@ across every entry path.
|
|
|
9
9
|
|
|
10
10
|
import { CliContext } from "./context.ts";
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
type CliLeaf,
|
|
13
|
+
CliNode,
|
|
13
14
|
CliFallbackMode,
|
|
14
15
|
CliOption,
|
|
15
16
|
CliOptionKind,
|
|
17
|
+
isCliLeaf,
|
|
18
|
+
isCliRouter,
|
|
16
19
|
} from "./types.ts";
|
|
17
20
|
import { fullStringIsDouble } from "./utils.ts";
|
|
18
21
|
|
|
@@ -69,7 +72,7 @@ function isSchemaTok(tok: string): boolean {
|
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
/** Looks up a subcommand or routing node by `key`. */
|
|
72
|
-
function findChild(cmds:
|
|
75
|
+
function findChild(cmds: CliNode[], name: string): CliNode | undefined {
|
|
73
76
|
return cmds.find((c) => c.key === name);
|
|
74
77
|
}
|
|
75
78
|
|
|
@@ -217,15 +220,16 @@ function consumeOptions(
|
|
|
217
220
|
// ── Positional Collection ─────────────────────────────────────────────────────
|
|
218
221
|
|
|
219
222
|
/** Merges option defs from the program root along the routed command path. */
|
|
220
|
-
export function collectOptionDefs(root:
|
|
223
|
+
export function collectOptionDefs(root: CliNode, path: string[]): CliOption[] {
|
|
221
224
|
let defs = [...(root.options ?? [])];
|
|
222
|
-
let
|
|
225
|
+
let node: CliNode = root;
|
|
223
226
|
|
|
224
227
|
for (const seg of path) {
|
|
225
|
-
|
|
228
|
+
if (!isCliRouter(node)) break;
|
|
229
|
+
const ch = findChild(node.commands, seg);
|
|
226
230
|
if (!ch) break;
|
|
227
231
|
defs.push(...(ch.options ?? []));
|
|
228
|
-
|
|
232
|
+
node = ch;
|
|
229
233
|
}
|
|
230
234
|
|
|
231
235
|
return defs;
|
|
@@ -233,7 +237,7 @@ export function collectOptionDefs(root: CliCommand, path: string[]): CliOption[]
|
|
|
233
237
|
|
|
234
238
|
/** Fills `args` for a leaf from `startIdx` according to `node.positionals`. */
|
|
235
239
|
function finishLeaf(
|
|
236
|
-
node:
|
|
240
|
+
node: CliLeaf,
|
|
237
241
|
startIdx: number,
|
|
238
242
|
argv: string[],
|
|
239
243
|
path: string[],
|
|
@@ -384,14 +388,16 @@ function schemaResult(): ParseResult {
|
|
|
384
388
|
/**
|
|
385
389
|
* Parses `argv` against the program root, routing into subcommands and filling `opts` / `args`.
|
|
386
390
|
*/
|
|
387
|
-
export function parse(root:
|
|
391
|
+
export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
388
392
|
let i = 0;
|
|
389
393
|
const path: string[] = [];
|
|
390
394
|
const opts: Record<string, string> = {};
|
|
391
395
|
|
|
392
396
|
const rootLenient =
|
|
397
|
+
isCliRouter(root) &&
|
|
393
398
|
root.fallbackCommand !== undefined &&
|
|
394
|
-
((root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOrUnknown ||
|
|
399
|
+
((root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOrUnknown ||
|
|
400
|
+
(root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.UnknownOnly);
|
|
395
401
|
|
|
396
402
|
// Consume root-level options first
|
|
397
403
|
const rootRep = consumeOptions(root.options ?? [], rootLenient, argv, i, opts);
|
|
@@ -420,16 +426,20 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
420
426
|
|
|
421
427
|
// Determine which subcommand to route to
|
|
422
428
|
let cmdName: string;
|
|
423
|
-
let node:
|
|
429
|
+
let node: CliNode | undefined;
|
|
424
430
|
|
|
425
|
-
if (root
|
|
426
|
-
return finishLeaf(root
|
|
431
|
+
if (isCliLeaf(root)) {
|
|
432
|
+
return finishLeaf(root, i, argv, path, opts, root.options ?? [], forcePositionals);
|
|
427
433
|
}
|
|
428
434
|
|
|
429
435
|
if (i >= argv.length) {
|
|
430
|
-
if (
|
|
436
|
+
if (
|
|
437
|
+
root.fallbackCommand !== undefined &&
|
|
438
|
+
((root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOnly ||
|
|
439
|
+
(root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOrUnknown)
|
|
440
|
+
) {
|
|
431
441
|
cmdName = root.fallbackCommand;
|
|
432
|
-
node = findChild(root.commands
|
|
442
|
+
node = findChild(root.commands, cmdName);
|
|
433
443
|
if (!node) {
|
|
434
444
|
return { kind: ParseKind.Error, path: [], opts: {}, args: [], helpExplicit: false, helpPath: [], errorMsg: `Unknown command: ${cmdName}`, errorHelpPath: path };
|
|
435
445
|
}
|
|
@@ -438,7 +448,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
438
448
|
}
|
|
439
449
|
} else {
|
|
440
450
|
const peek = argv[i];
|
|
441
|
-
const childPick = !forcePositionals ? findChild(root.commands
|
|
451
|
+
const childPick = !forcePositionals ? findChild(root.commands, peek) : undefined;
|
|
442
452
|
|
|
443
453
|
if (childPick !== undefined) {
|
|
444
454
|
cmdName = peek;
|
|
@@ -452,14 +462,14 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
452
462
|
|
|
453
463
|
if (canRouteUnknown) {
|
|
454
464
|
cmdName = root.fallbackCommand!;
|
|
455
|
-
node = findChild(root.commands
|
|
465
|
+
node = findChild(root.commands, cmdName);
|
|
456
466
|
if (!node) {
|
|
457
467
|
return { kind: ParseKind.Error, path: [], opts: {}, args: [], helpExplicit: false, helpPath: [], errorMsg: `Unknown command: ${cmdName}`, errorHelpPath: path };
|
|
458
468
|
}
|
|
459
469
|
} else {
|
|
460
470
|
cmdName = peek;
|
|
461
471
|
if (!forcePositionals) i += 1;
|
|
462
|
-
node = findChild(root.commands
|
|
472
|
+
node = findChild(root.commands, cmdName);
|
|
463
473
|
if (!node) {
|
|
464
474
|
return {
|
|
465
475
|
kind: ParseKind.Error,
|
|
@@ -506,14 +516,14 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
506
516
|
}
|
|
507
517
|
|
|
508
518
|
if (i >= argv.length) {
|
|
509
|
-
if ((current
|
|
519
|
+
if (isCliRouter(current) && current.commands.length > 0) {
|
|
510
520
|
const fb = current.fallbackCommand;
|
|
511
521
|
const fm = current.fallbackMode ?? CliFallbackMode.MissingOnly;
|
|
512
522
|
if (
|
|
513
523
|
fb !== undefined &&
|
|
514
524
|
(fm === CliFallbackMode.MissingOnly || fm === CliFallbackMode.MissingOrUnknown)
|
|
515
525
|
) {
|
|
516
|
-
const fbNode = findChild(current.commands
|
|
526
|
+
const fbNode = findChild(current.commands, fb);
|
|
517
527
|
if (fbNode) {
|
|
518
528
|
path.push(fb);
|
|
519
529
|
current = fbNode;
|
|
@@ -522,6 +532,9 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
522
532
|
}
|
|
523
533
|
return helpResult(path, false);
|
|
524
534
|
}
|
|
535
|
+
if (!isCliLeaf(current)) {
|
|
536
|
+
return helpResult(path, false);
|
|
537
|
+
}
|
|
525
538
|
return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
|
|
526
539
|
}
|
|
527
540
|
|
|
@@ -539,8 +552,8 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
539
552
|
};
|
|
540
553
|
}
|
|
541
554
|
|
|
542
|
-
if (!forcePositionals) {
|
|
543
|
-
const childOpt = findChild(current.commands
|
|
555
|
+
if (!forcePositionals && isCliRouter(current)) {
|
|
556
|
+
const childOpt = findChild(current.commands, tok);
|
|
544
557
|
if (childOpt !== undefined) {
|
|
545
558
|
i += 1;
|
|
546
559
|
path.push(tok);
|
|
@@ -549,7 +562,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
549
562
|
}
|
|
550
563
|
}
|
|
551
564
|
|
|
552
|
-
if ((current
|
|
565
|
+
if (isCliRouter(current) && current.commands.length > 0) {
|
|
553
566
|
const fb = current.fallbackCommand;
|
|
554
567
|
const fm = current.fallbackMode ?? CliFallbackMode.MissingOnly;
|
|
555
568
|
const canRouteUnknown =
|
|
@@ -557,7 +570,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
557
570
|
(fm === CliFallbackMode.MissingOrUnknown || fm === CliFallbackMode.UnknownOnly);
|
|
558
571
|
|
|
559
572
|
if (canRouteUnknown) {
|
|
560
|
-
const fbNode = findChild(current.commands
|
|
573
|
+
const fbNode = findChild(current.commands, fb!);
|
|
561
574
|
if (fbNode) {
|
|
562
575
|
path.push(fb!);
|
|
563
576
|
current = fbNode;
|
|
@@ -577,6 +590,9 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
577
590
|
};
|
|
578
591
|
}
|
|
579
592
|
|
|
593
|
+
if (!isCliLeaf(current)) {
|
|
594
|
+
return helpResult(path, false);
|
|
595
|
+
}
|
|
580
596
|
return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
|
|
581
597
|
}
|
|
582
598
|
}
|
|
@@ -586,14 +602,26 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
586
602
|
/**
|
|
587
603
|
* Validates option keys and numeric values for an Ok parse, merging in-scope options along `pr.path`.
|
|
588
604
|
*/
|
|
589
|
-
export function postParseValidate(root:
|
|
605
|
+
export function postParseValidate(root: CliNode, pr: ParseResult): ParseResult {
|
|
590
606
|
if (pr.kind !== ParseKind.Ok) return pr;
|
|
591
607
|
|
|
592
608
|
let defs = [...(root.options ?? [])];
|
|
593
|
-
let
|
|
609
|
+
let node: CliNode = root;
|
|
594
610
|
|
|
595
611
|
for (const seg of pr.path) {
|
|
596
|
-
|
|
612
|
+
if (!isCliRouter(node)) {
|
|
613
|
+
return {
|
|
614
|
+
kind: ParseKind.Error,
|
|
615
|
+
path: pr.path,
|
|
616
|
+
opts: {},
|
|
617
|
+
args: [],
|
|
618
|
+
helpExplicit: false,
|
|
619
|
+
helpPath: [],
|
|
620
|
+
errorMsg: "Internal path error",
|
|
621
|
+
errorHelpPath: pr.path,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
const ch = findChild(node.commands, seg);
|
|
597
625
|
if (!ch) {
|
|
598
626
|
return {
|
|
599
627
|
kind: ParseKind.Error,
|
|
@@ -607,7 +635,7 @@ export function postParseValidate(root: CliCommand, pr: ParseResult): ParseResul
|
|
|
607
635
|
};
|
|
608
636
|
}
|
|
609
637
|
defs.push(...(ch.options ?? []));
|
|
610
|
-
|
|
638
|
+
node = ch;
|
|
611
639
|
}
|
|
612
640
|
|
|
613
641
|
for (const d of defs) {
|