argsbarg 1.4.0 → 1.4.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/src/parse.ts CHANGED
@@ -601,6 +601,21 @@ export function postParseValidate(root: CliCommand, pr: ParseResult): ParseResul
601
601
  };
602
602
  }
603
603
  }
604
+ if (d.kind === CliOptionKind.Enum) {
605
+ const choices = d.choices ?? [];
606
+ if (!choices.includes(v)) {
607
+ return {
608
+ kind: ParseKind.Error,
609
+ path: pr.path,
610
+ opts: {},
611
+ args: [],
612
+ helpExplicit: false,
613
+ helpPath: [],
614
+ errorMsg: `Option --${k}: '${v}' is not one of: ${choices.join(", ")}`,
615
+ errorHelpPath: pr.path,
616
+ };
617
+ }
618
+ }
604
619
  }
605
620
 
606
621
  return pr;
package/src/runtime.ts CHANGED
@@ -136,7 +136,7 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
136
136
  process.exit(1);
137
137
  }
138
138
 
139
- const ctx = new CliContext(parseRoot.key, pr.path, pr.args, pr.opts, parseRoot);
139
+ const ctx = new CliContext(parseRoot.key, pr.path, pr.args, pr.opts, parseRoot, "cli");
140
140
  try {
141
141
  await Promise.resolve(current.handler(ctx));
142
142
  process.exit(0);
package/src/types.ts CHANGED
@@ -9,7 +9,12 @@ It gives the package one shared model for both library users and internal module
9
9
  import type { CliContext } from "./context.ts";
10
10
 
11
11
  /**
12
- * Option kinds: presence (boolean flag), string (free-form text), or number (strict double).
12
+ * How a leaf handler was dispatched.
13
+ */
14
+ export type CliInvocation = "cli" | "mcp";
15
+
16
+ /**
17
+ * Option kinds: presence (boolean flag), string (free-form text), number (strict double), or enum (fixed choices).
13
18
  */
14
19
  export enum CliOptionKind {
15
20
  /** Boolean flag: no value token (may be implicit `"1"` when set). */
@@ -18,6 +23,8 @@ export enum CliOptionKind {
18
23
  String = "string",
19
24
  /** Strict floating-point value (parsed at validation time). */
20
25
  Number = "number",
26
+ /** Fixed set of allowed string values. Requires non-empty `choices` on the option. */
27
+ Enum = "enum",
21
28
  }
22
29
 
23
30
  /**
@@ -54,6 +61,11 @@ export interface CliOption {
54
61
  shortName?: string;
55
62
  /** Whether this option must be provided. Cannot be used with Presence kind. */
56
63
  required?: boolean;
64
+ /**
65
+ * Allowed values. Required when kind === Enum; ignored otherwise.
66
+ * Must be a non-empty array of distinct non-empty strings.
67
+ */
68
+ choices?: string[];
57
69
  }
58
70
 
59
71
  /**
@@ -88,6 +100,39 @@ export interface CliMcpServerConfig {
88
100
  version?: string;
89
101
  /** Resource URI for schema export (default: `"argsbarg://schema"`). */
90
102
  schemaResourceUri?: string;
103
+ /**
104
+ * Capture the user's login shell environment at MCP server start and merge it
105
+ * into process.env. Solves missing PATH, nvm/rbenv shims, Homebrew binaries,
106
+ * and shell exports that MCP hosts (e.g. Cursor) don't inherit.
107
+ */
108
+ shellEnv?: boolean | string;
109
+ /**
110
+ * Path to a .env file loaded into process.env at MCP server start, after shellEnv.
111
+ * Supports `~` expansion. Warns on stderr if the file does not exist.
112
+ * Always overwrites — envFile is authoritative for its keys.
113
+ */
114
+ envFile?: string;
115
+ /**
116
+ * Custom MCP resources exposed alongside the built-in argsbarg://schema resource.
117
+ * URIs must be unique and must not equal schemaResourceUri.
118
+ */
119
+ resources?: CliMcpResource[];
120
+ }
121
+
122
+ /**
123
+ * A custom MCP resource exposed under resources/list and resources/read.
124
+ */
125
+ export interface CliMcpResource {
126
+ /** Resource URI (must be unique; must not equal schemaResourceUri). */
127
+ uri: string;
128
+ /** Short display name for resources/list. */
129
+ name: string;
130
+ /** Optional human description for resources/list. */
131
+ description?: string;
132
+ /** MIME type (default: "text/plain"). */
133
+ mimeType?: string;
134
+ /** Called at resources/read time; must return the resource body. */
135
+ load: () => string;
91
136
  }
92
137
 
93
138
  /**
@@ -96,6 +141,17 @@ export interface CliMcpServerConfig {
96
141
  export interface CliMcpToolConfig {
97
142
  /** When `false`, omit from `tools/list` (default: exposed). */
98
143
  enabled?: boolean;
144
+ /**
145
+ * Override the generated MCP tool description.
146
+ * Default: auto-generated from command path and description.
147
+ */
148
+ description?: string;
149
+ /**
150
+ * Environment variable names required at runtime.
151
+ * Appended to auto-generated MCP tool descriptions; enforced at tools/call time.
152
+ * Empty string counts as absent.
153
+ */
154
+ requiresEnv?: string[];
99
155
  }
100
156
 
101
157
  /**
package/src/validate.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  CliOptionKind,
13
13
  CliSchemaValidationError,
14
14
  } from "./types.ts";
15
+ import { MCP_SCHEMA_URI_DEFAULT } from "./mcp/tools.ts";
15
16
 
16
17
  const reservedCommandNames = ["completion", "mcp"];
17
18
 
@@ -66,6 +67,19 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
66
67
  throw new CliSchemaValidationError("mcpTool is only supported on leaf commands");
67
68
  }
68
69
 
70
+ if (isRoot && cmd.mcpServer?.resources) {
71
+ const schemaUri = cmd.mcpServer.schemaResourceUri ?? MCP_SCHEMA_URI_DEFAULT;
72
+ const uris = cmd.mcpServer.resources.map((r) => r.uri);
73
+ if (uris.includes(schemaUri)) {
74
+ throw new CliSchemaValidationError(
75
+ `mcpServer.resources URI '${schemaUri}' conflicts with the built-in schema resource`,
76
+ );
77
+ }
78
+ if (new Set(uris).size !== uris.length) {
79
+ throw new CliSchemaValidationError("mcpServer.resources URIs must be unique");
80
+ }
81
+ }
82
+
69
83
  // Check for duplicate child names
70
84
  const seenNames = new Set<string>();
71
85
  for (const child of cmd.commands ?? []) {
@@ -103,6 +117,30 @@ function walkCommand(cmd: CliCommand, isRoot: boolean = false): void {
103
117
  }
104
118
  seenShorts.add(opt.shortName);
105
119
  }
120
+
121
+ if (opt.kind === CliOptionKind.Enum) {
122
+ if (!opt.choices || opt.choices.length === 0) {
123
+ throw new CliSchemaValidationError(
124
+ `Option '${opt.name}' on '${cmd.key}': Enum kind requires non-empty choices`,
125
+ );
126
+ }
127
+ if (new Set(opt.choices).size !== opt.choices.length) {
128
+ throw new CliSchemaValidationError(
129
+ `Option '${opt.name}' on '${cmd.key}': Enum choices must be distinct`,
130
+ );
131
+ }
132
+ for (const choice of opt.choices) {
133
+ if (choice.length === 0) {
134
+ throw new CliSchemaValidationError(
135
+ `Option '${opt.name}' on '${cmd.key}': Enum choices must be non-empty strings`,
136
+ );
137
+ }
138
+ }
139
+ } else if (opt.choices !== undefined) {
140
+ throw new CliSchemaValidationError(
141
+ `Option '${opt.name}' on '${cmd.key}': choices is only valid for Enum kind`,
142
+ );
143
+ }
106
144
  }
107
145
 
108
146
  // Validate positionals