argsbarg 1.3.0 → 1.4.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/src/parse.ts CHANGED
@@ -217,7 +217,7 @@ function consumeOptions(
217
217
  // ── Positional Collection ─────────────────────────────────────────────────────
218
218
 
219
219
  /** Merges option defs from the program root along the routed command path. */
220
- function collectOptionDefs(root: CliCommand, path: string[]): CliOption[] {
220
+ export function collectOptionDefs(root: CliCommand, path: string[]): CliOption[] {
221
221
  let defs = [...(root.options ?? [])];
222
222
  let cmds = root.commands ?? [];
223
223
 
@@ -458,7 +458,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
458
458
  // Walk the command tree
459
459
  while (true) {
460
460
  if (!forcePositionals) {
461
- const orep = consumeOptions(current.options ?? [], false, argv, i, opts);
461
+ const orep = consumeOptions(collectOptionDefs(root, path), false, argv, i, opts);
462
462
  if (orep.report.err) {
463
463
  return {
464
464
  kind: ParseKind.Error,
package/src/runtime.ts CHANGED
@@ -7,9 +7,10 @@ It keeps execution flow out of the public barrel so the exported API stays small
7
7
  the runtime responsibilities remain easy to reason about.
8
8
  */
9
9
 
10
- import { cliBuiltinCompletionGroup, completionBashScript, completionZshScript } from "./completion.ts";
10
+ import { cliBuiltinCompletionGroup, cliBuiltinMcpCommand, cliPresentationRoot, completionBashScript, completionZshScript } from "./completion.ts";
11
11
  import { CliContext } from "./context.ts";
12
12
  import { cliHelpRender } from "./help.ts";
13
+ import { cliMcpServeStdio } from "./mcp.ts";
13
14
  import { parse, postParseValidate, ParseKind } from "./parse.ts";
14
15
  import { cliSchemaJson } from "./schema.ts";
15
16
  import { CliCommand } from "./types.ts";
@@ -22,9 +23,7 @@ function cliRootMergedWithBuiltins(root: CliCommand): CliCommand {
22
23
  if (root.handler) {
23
24
  return root;
24
25
  }
25
- const merged = { ...root } as any;
26
- merged.commands = [...(root.commands ?? []), cliBuiltinCompletionGroup(root.key)];
27
- return merged as CliCommand;
26
+ return cliPresentationRoot(root);
28
27
  }
29
28
 
30
29
  /**
@@ -48,6 +47,11 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
48
47
  let parseRoot = root;
49
48
  let isLeafCompletionIntercept = false;
50
49
 
50
+ if (root.handler && argv.length >= 1 && argv[0] === "mcp" && !root.mcpServer) {
51
+ process.stderr.write("Unknown command: mcp\n");
52
+ process.exit(1);
53
+ }
54
+
51
55
  // Intercept completion for Leaf roots (since they can't natively have a completion subcommand)
52
56
  // but wrap them in a dummy router so that the parser handles `-h` and errors correctly.
53
57
  if (root.handler && argv.length >= 1 && argv[0] === "completion") {
@@ -57,6 +61,12 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
57
61
  description: root.description,
58
62
  commands: [cliBuiltinCompletionGroup(root.key)],
59
63
  } as any;
64
+ } else if (root.handler && argv.length >= 1 && argv[0] === "mcp" && root.mcpServer) {
65
+ parseRoot = {
66
+ key: root.key,
67
+ description: root.description,
68
+ commands: [cliBuiltinMcpCommand()],
69
+ } as CliCommand;
60
70
  } else {
61
71
  parseRoot = cliRootMergedWithBuiltins(root);
62
72
  }
@@ -65,7 +75,7 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
65
75
  pr = postParseValidate(parseRoot, pr);
66
76
 
67
77
  if (pr.kind === ParseKind.Help) {
68
- process.stdout.write(cliHelpRender(parseRoot, pr.helpPath, false));
78
+ process.stdout.write(cliHelpRender(cliPresentationRoot(root), pr.helpPath, false));
69
79
  process.exit(pr.helpExplicit ? 0 : 1);
70
80
  }
71
81
 
@@ -78,7 +88,7 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
78
88
  const color = process.stderr.isTTY;
79
89
  const msg = color ? `\u001B[31m${pr.errorMsg}\u001B[0m` : pr.errorMsg;
80
90
  process.stderr.write(msg + "\n");
81
- process.stderr.write(cliHelpRender(parseRoot, pr.errorHelpPath, true));
91
+ process.stderr.write(cliHelpRender(cliPresentationRoot(root), pr.errorHelpPath, true));
82
92
  process.exit(1);
83
93
  }
84
94
 
@@ -99,6 +109,18 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
99
109
  }
100
110
  }
101
111
 
112
+ if (pr.path[0] === "mcp") {
113
+ if (!root.mcpServer) {
114
+ process.stderr.write("Internal error: mcp not enabled.\n");
115
+ process.exit(1);
116
+ }
117
+ if (pr.path.length !== 1) {
118
+ process.stderr.write("Unknown subcommand: mcp " + pr.path.slice(1).join(" ") + "\n");
119
+ process.exit(1);
120
+ }
121
+ await cliMcpServeStdio(root);
122
+ }
123
+
102
124
  let current = parseRoot;
103
125
  for (const seg of pr.path) {
104
126
  const ch = (current.commands ?? []).find((candidate: CliCommand) => candidate.key === seg);
@@ -133,6 +155,6 @@ export function cliErrWithHelp(ctx: CliContext, msg: string): never {
133
155
  const color = process.stderr.isTTY;
134
156
  const line = color ? `\u001B[31m${msg}\u001B[0m` : msg;
135
157
  process.stderr.write(line + "\n");
136
- process.stderr.write(cliHelpRender(ctx.schema, ctx.commandPath, true));
158
+ process.stderr.write(cliHelpRender(cliPresentationRoot(ctx.schema), ctx.commandPath, true));
137
159
  process.exit(1);
138
160
  }
package/src/schema.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  CliOption,
14
14
  CliPositional,
15
15
  } from "./types.ts";
16
+ import { cliBuiltinCompletionGroup } from "./completion.ts";
16
17
 
17
18
  /** JSON-safe command node (no handlers). */
18
19
  export interface CliSchemaExport {
@@ -34,6 +35,20 @@ export interface CliSchemaExport {
34
35
  positionals?: CliPositional[];
35
36
  }
36
37
 
38
+ /** JSON-safe export of the reserved `completion` subtree (no handler recursion). */
39
+ function exportBuiltinCompletionGroup(appName: string): CliSchemaExport {
40
+ const group = cliBuiltinCompletionGroup(appName);
41
+ return {
42
+ key: group.key,
43
+ description: group.description,
44
+ commands: (group.commands ?? []).map((ch) => ({
45
+ key: ch.key,
46
+ description: ch.description,
47
+ ...((ch.notes ?? "").length > 0 ? { notes: ch.notes } : {}),
48
+ })),
49
+ };
50
+ }
51
+
37
52
  /** Converts one `CliCommand` node into a JSON-safe export (handlers omitted). */
38
53
  function exportCommand(cmd: CliCommand): CliSchemaExport {
39
54
  const out: CliSchemaExport = {
@@ -53,6 +68,7 @@ function exportCommand(cmd: CliCommand): CliSchemaExport {
53
68
  if ((cmd.positionals ?? []).length > 0) {
54
69
  out.positionals = cmd.positionals;
55
70
  }
71
+ out.commands = [exportBuiltinCompletionGroup(cmd.key)];
56
72
  return out;
57
73
  }
58
74
 
package/src/types.ts CHANGED
@@ -78,6 +78,26 @@ export interface CliPositional {
78
78
  argMax?: number;
79
79
  }
80
80
 
81
+ /**
82
+ * Root-only. Enables `myapp mcp` and MCP stdio server metadata.
83
+ */
84
+ export interface CliMcpServerConfig {
85
+ /** `initialize` serverInfo.name (default: root `key`). */
86
+ name?: string;
87
+ /** `initialize` serverInfo.version (default: see resolveMcpVersion). */
88
+ version?: string;
89
+ /** Resource URI for schema export (default: `"argsbarg://schema"`). */
90
+ schemaResourceUri?: string;
91
+ }
92
+
93
+ /**
94
+ * Leaf-only. Controls how this command appears as an MCP tool.
95
+ */
96
+ export interface CliMcpToolConfig {
97
+ /** When `false`, omit from `tools/list` (default: exposed). */
98
+ enabled?: boolean;
99
+ }
100
+
81
101
  /**
82
102
  * Base properties shared by all command nodes.
83
103
  */
@@ -90,6 +110,10 @@ export interface CliCommandBase {
90
110
  notes?: string;
91
111
  /** Global or command-level flags/options. */
92
112
  options?: CliOption[];
113
+ /** Root-only. When set, enables the `mcp` built-in subcommand. */
114
+ mcpServer?: CliMcpServerConfig;
115
+ /** Leaf-only. Per-tool MCP exposure and metadata. */
116
+ mcpTool?: CliMcpToolConfig;
93
117
  }
94
118
 
95
119
  /**
package/src/validate.ts CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  CliSchemaValidationError,
14
14
  } from "./types.ts";
15
15
 
16
- const reservedCommandNames = ["completion"];
16
+ const reservedCommandNames = ["completion", "mcp"];
17
17
 
18
18
  /**
19
19
  * Validates the static CliCommand tree against ArgBarg rules.
@@ -50,6 +50,21 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
50
50
  );
51
51
  }
52
52
 
53
+ if (!isRoot && cmd.mcpServer !== undefined) {
54
+ throw new CliSchemaValidationError(
55
+ "mcpServer is only supported on the program root (not on " + cmd.key + ")",
56
+ );
57
+ }
58
+
59
+ const isLeaf = "handler" in cmd && !!cmd.handler;
60
+ if (!isLeaf && cmd.mcpTool !== undefined) {
61
+ throw new CliSchemaValidationError(
62
+ "mcpTool is only supported on leaf commands (not on " + cmd.key + ")",
63
+ );
64
+ }
65
+ if (isRoot && cmd.mcpTool !== undefined) {
66
+ throw new CliSchemaValidationError("mcpTool is only supported on leaf commands");
67
+ }
53
68
 
54
69
  // Check for duplicate child names
55
70
  const seenNames = new Set<string>();