argsbarg 6.0.2 → 6.1.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.
@@ -1,10 +1,10 @@
1
1
  /*
2
- Discovers schema roots in schema.ts files via configType / inputType / outputType exports.
2
+ Discovers schema roots in types.ts files via configType / inputType / outputType exports.
3
3
  */
4
4
 
5
- import { readdirSync, readFileSync, statSync } from "node:fs";
6
- import { join, relative } from "node:path";
7
- import { SCHEMA_FILE } from "./names.ts";
5
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
6
+ import { dirname, join, relative } from "node:path";
7
+ import { TYPES_FILE } from "./names.ts";
8
8
 
9
9
  export type SchemaRootKind = "config" | "input" | "output";
10
10
 
@@ -13,11 +13,15 @@ export type SchemaRole = "configType" | "inputType" | "outputType";
13
13
  export interface SchemaRoot {
14
14
  kind: SchemaRootKind;
15
15
  typeName: string;
16
- /** Path relative to project root (e.g. src/config/schema.ts). */
16
+ /** Path to types.ts relative to project root (anchors __generated__/ output). */
17
17
  path: string;
18
+ /** Path to the file that defines `typeName` (defaults to `path`). */
19
+ sourcePath: string;
18
20
  }
19
21
 
20
22
  const ROLE_EXPORT_RE = /export\s+type\s+(configType|inputType|outputType)\s*=\s*(\w+)/g;
23
+ const HAS_ROLE_EXPORT_RE = /export\s+type\s+(configType|inputType|outputType)\s*=/;
24
+ const IMPORT_RE = /import\s+(?:type\s+)?\{([^}]+)\}\s+from\s+["']([^"']+)["']/g;
21
25
 
22
26
  const ROLE_TO_KIND: Record<SchemaRole, SchemaRootKind> = {
23
27
  configType: "config",
@@ -25,21 +29,25 @@ const ROLE_TO_KIND: Record<SchemaRole, SchemaRootKind> = {
25
29
  outputType: "output",
26
30
  };
27
31
 
28
- function listSchemaFiles(srcDir: string, baseDir: string, out: string[]): void {
32
+ function listTypesManifestFiles(srcDir: string, baseDir: string, out: string[]): void {
29
33
  for (const ent of readdirSync(srcDir)) {
30
34
  const full = join(srcDir, ent);
31
35
  const st = statSync(full);
32
36
  if (st.isDirectory()) {
33
- listSchemaFiles(full, baseDir, out);
37
+ listTypesManifestFiles(full, baseDir, out);
34
38
  continue;
35
39
  }
36
- if (ent === SCHEMA_FILE) {
40
+ if (ent !== TYPES_FILE) {
41
+ continue;
42
+ }
43
+ const text = readFileSync(full, "utf8");
44
+ if (HAS_ROLE_EXPORT_RE.test(text)) {
37
45
  out.push(relative(baseDir, full));
38
46
  }
39
47
  }
40
48
  }
41
49
 
42
- /** True when `typeName` is declared in this file (not a re-export alias to another module). */
50
+ /** True when `typeName` is declared in this file (not a schemagen role alias). */
43
51
  function isTypeDefinedInFile(text: string, typeName: string): boolean {
44
52
  if (new RegExp(`export\\s+interface\\s+${typeName}\\b`).test(text)) {
45
53
  return true;
@@ -50,7 +58,62 @@ function isTypeDefinedInFile(text: string, typeName: string): boolean {
50
58
  return false;
51
59
  }
52
60
 
53
- function discoverFromFile(path: string, text: string): SchemaRoot[] {
61
+ function parseLocalTypeImports(text: string): Map<string, string> {
62
+ const imports = new Map<string, string>();
63
+ for (const match of text.matchAll(IMPORT_RE)) {
64
+ const names = match[1];
65
+ const from = match[2];
66
+ if (!names || !from?.startsWith(".")) {
67
+ continue;
68
+ }
69
+ for (const part of names.split(",")) {
70
+ const trimmed = part.trim();
71
+ const nameMatch = trimmed.match(/^(?:type\s+)?(\w+)(?:\s+as\s+(\w+))?$/);
72
+ if (!nameMatch?.[1]) {
73
+ continue;
74
+ }
75
+ const localName = nameMatch[2] ?? nameMatch[1];
76
+ imports.set(localName, from);
77
+ }
78
+ }
79
+ return imports;
80
+ }
81
+
82
+ function resolveModuleFile(manifestFile: string, specifier: string): string | null {
83
+ const base = join(dirname(manifestFile), specifier);
84
+ const candidates = [base, `${base}.ts`, join(base, "index.ts")];
85
+ for (const candidate of candidates) {
86
+ if (existsSync(candidate)) {
87
+ return candidate;
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+
93
+ /** Resolve a role alias to the module that defines `typeName`, when imported from a relative path. */
94
+ function resolveAliasedTypeSource(
95
+ projectRoot: string,
96
+ manifestRelPath: string,
97
+ manifestText: string,
98
+ typeName: string,
99
+ ): string | null {
100
+ const manifestFile = join(projectRoot, manifestRelPath);
101
+ const specifier = parseLocalTypeImports(manifestText).get(typeName);
102
+ if (!specifier) {
103
+ return null;
104
+ }
105
+ const moduleFile = resolveModuleFile(manifestFile, specifier);
106
+ if (!moduleFile) {
107
+ return null;
108
+ }
109
+ const moduleText = readFileSync(moduleFile, "utf8");
110
+ if (!isTypeDefinedInFile(moduleText, typeName)) {
111
+ return null;
112
+ }
113
+ return relative(projectRoot, moduleFile);
114
+ }
115
+
116
+ function discoverFromFile(projectRoot: string, path: string, text: string): SchemaRoot[] {
54
117
  const rolesSeen = new Set<SchemaRole>();
55
118
  const roots: SchemaRoot[] = [];
56
119
 
@@ -64,27 +127,34 @@ function discoverFromFile(path: string, text: string): SchemaRoot[] {
64
127
  throw new Error(`${path}: duplicate export type ${role}`);
65
128
  }
66
129
  rolesSeen.add(role);
130
+
131
+ let sourcePath = path;
67
132
  if (!isTypeDefinedInFile(text, typeName)) {
68
- continue;
133
+ const resolved = resolveAliasedTypeSource(projectRoot, path, text, typeName);
134
+ if (!resolved) {
135
+ continue;
136
+ }
137
+ sourcePath = resolved;
69
138
  }
70
- roots.push({ kind: ROLE_TO_KIND[role], typeName, path });
139
+
140
+ roots.push({ kind: ROLE_TO_KIND[role], typeName, path, sourcePath });
71
141
  }
72
142
 
73
143
  return roots;
74
144
  }
75
145
 
76
- /** Find all schema roots under `srcDir` in files named `schema.ts`. */
146
+ /** Find all schema roots under `srcDir` in `types.ts` files with role exports. */
77
147
  export function discoverSchemaRoots(projectRoot: string, srcDir = "src"): SchemaRoot[] {
78
148
  const srcPath = join(projectRoot, srcDir);
79
149
  const files: string[] = [];
80
- listSchemaFiles(srcPath, projectRoot, files);
150
+ listTypesManifestFiles(srcPath, projectRoot, files);
81
151
 
82
152
  const roots: SchemaRoot[] = [];
83
153
  const typeOwners = new Map<string, string>();
84
154
 
85
155
  for (const relPath of files.sort()) {
86
156
  const text = readFileSync(join(projectRoot, relPath), "utf8");
87
- for (const root of discoverFromFile(relPath, text)) {
157
+ for (const root of discoverFromFile(projectRoot, relPath, text)) {
88
158
  const prev = typeOwners.get(root.typeName);
89
159
  if (prev) {
90
160
  throw new Error(`${relPath}: duplicate schema root type ${root.typeName} (already declared in ${prev})`);
@@ -1,3 +1,3 @@
1
1
  export { discoverSchemaRoots, type SchemaRoot, type SchemaRootKind } from "./discover-schema-roots.ts";
2
- export { GENERATED_DIR, SCHEMA_FILE, schemaExportName, schemaJsonBasename } from "./names.ts";
2
+ export { GENERATED_DIR, schemaExportName, schemaJsonBasename, TYPES_FILE } from "./names.ts";
3
3
  export { type RunSchemagenOptions, type RunSchemagenResult, runSchemagen } from "./run.ts";
@@ -1,10 +1,10 @@
1
1
  import type { SchemaRootKind } from "./discover-schema-roots.ts";
2
2
 
3
- /** Directory name for generated schema artifacts (next to `schema.ts`). */
3
+ /** Directory name for generated schema artifacts (next to `types.ts`). */
4
4
  export const GENERATED_DIR = "__generated__";
5
5
 
6
- /** TypeScript schema root filename under `src/`. */
7
- export const SCHEMA_FILE = "schema.ts";
6
+ /** TypeScript schemagen manifest filename under `src/`. */
7
+ export const TYPES_FILE = "types.ts";
8
8
 
9
9
  /** JSON basename for a schema root kind (`outputSchema.json`, etc.). */
10
10
  export function schemaJsonBasename(kind: SchemaRootKind): string {
@@ -36,8 +36,9 @@ function resolveTsconfig(projectRoot: string, tsconfig: string): string {
36
36
  }
37
37
 
38
38
  function generateJson(projectRoot: string, tsconfigPath: string, root: SchemaRoot): Record<string, unknown> {
39
+ const typeFile = join(projectRoot, root.sourcePath);
39
40
  const generator = createGenerator({
40
- path: join(projectRoot, root.path),
41
+ path: typeFile,
41
42
  type: root.typeName,
42
43
  tsconfig: tsconfigPath,
43
44
  topRef: false,
@@ -39,10 +39,10 @@ function makeTempProject(): string {
39
39
  return root;
40
40
  }
41
41
 
42
- function writeSchema(root: string, relDir: string, body: string): void {
42
+ function writeTypes(root: string, relDir: string, body: string): void {
43
43
  const dir = join(root, "src", relDir);
44
44
  mkdirSync(dir, { recursive: true });
45
- writeFileSync(join(dir, "schema.ts"), body);
45
+ writeFileSync(join(dir, "types.ts"), body);
46
46
  }
47
47
 
48
48
  describe("schemagen", () => {
@@ -62,14 +62,38 @@ describe("schemagen", () => {
62
62
  expect(counts).toEqual({ configRoots: 1, inputRoots: 0, outputRoots: 1 });
63
63
  });
64
64
 
65
+ test("discovers roots from types.ts with interface and role export", () => {
66
+ const root = makeTempProject();
67
+ const dir = join(root, "src/commands/demo");
68
+ writeTypes(
69
+ root,
70
+ "commands/demo",
71
+ `export interface DemoOutput { ok: boolean; }
72
+
73
+ /** Schemagen root for leaf outputSchema. */
74
+ export type outputType = DemoOutput;
75
+ `,
76
+ );
77
+
78
+ const roots = discoverSchemaRoots(root);
79
+ expect(roots).toEqual([
80
+ {
81
+ kind: "output",
82
+ typeName: "DemoOutput",
83
+ path: "src/commands/demo/types.ts",
84
+ sourcePath: "src/commands/demo/types.ts",
85
+ },
86
+ ]);
87
+ runSchemagen({ projectRoot: root });
88
+ expect(existsSync(join(dir, "__generated__/outputSchema.json"))).toBe(true);
89
+ });
90
+
65
91
  test("removes stale JSON files when a schema kind is dropped", () => {
66
92
  const root = makeTempProject();
67
- writeSchema(
93
+ writeTypes(
68
94
  root,
69
95
  "commands/demo",
70
- `/** Tool input. */
71
- export interface DemoInput { id: string; }
72
- /** Tool output. */
96
+ `export interface DemoInput { id: string; }
73
97
  export interface DemoOutput { ok: boolean; }
74
98
  export type inputType = DemoInput;
75
99
  export type outputType = DemoOutput;
@@ -81,11 +105,10 @@ export type outputType = DemoOutput;
81
105
  expect(existsSync(join(generatedDir, "inputSchema.json"))).toBe(true);
82
106
  expect(existsSync(join(generatedDir, "outputSchema.json"))).toBe(true);
83
107
 
84
- writeSchema(
108
+ writeTypes(
85
109
  root,
86
110
  "commands/demo",
87
- `/** Tool output. */
88
- export interface DemoOutput { ok: boolean; }
111
+ `export interface DemoOutput { ok: boolean; }
89
112
  export type outputType = DemoOutput;
90
113
  `,
91
114
  );
@@ -96,7 +119,7 @@ export type outputType = DemoOutput;
96
119
  expect(existsSync(join(generatedDir, "index.ts"))).toBe(true);
97
120
  });
98
121
 
99
- test("removes orphan __generated__ when schema.ts is gone", () => {
122
+ test("removes orphan __generated__ when types.ts manifest is gone", () => {
100
123
  const root = makeTempProject();
101
124
  const generatedDir = join(root, "src/orphan/__generated__");
102
125
  mkdirSync(generatedDir, { recursive: true });
@@ -107,14 +130,9 @@ export type outputType = DemoOutput;
107
130
  expect(existsSync(generatedDir)).toBe(false);
108
131
  });
109
132
 
110
- test("removes __generated__ when schema.ts has no discoverable roots", () => {
133
+ test("removes __generated__ when types.ts has no discoverable roots", () => {
111
134
  const root = makeTempProject();
112
- writeSchema(
113
- root,
114
- "commands/empty",
115
- `export type outputType = never;
116
- `,
117
- );
135
+ writeTypes(root, "commands/empty", `export type outputType = never;\n`);
118
136
  const generatedDir = join(root, "src/commands/empty/__generated__");
119
137
  mkdirSync(generatedDir, { recursive: true });
120
138
  writeFileSync(join(generatedDir, "outputSchema.json"), "{}\n");
package/src/cli.ts CHANGED
@@ -18,6 +18,7 @@ import { readAppConfigFileRaw, resolveAppConfigPath } from "./config/file.ts";
18
18
  import { effectiveJsonSchema } from "./config/schema.ts";
19
19
  import { CliContext } from "./context.ts";
20
20
  import { cliHelpRender } from "./help.ts";
21
+ import { LeafInputError, preloadPipableJson } from "./leaf-inputs.ts";
21
22
  import { bootstrapMcpEnv } from "./mcp/env.ts";
22
23
  import { mcpServeStdioLoop } from "./mcp/server.ts";
23
24
  import { ParseKind, type ParseResult, parse, postParseValidate } from "./parse.ts";
@@ -124,14 +125,41 @@ export class Cli {
124
125
  exitOnMissing: !skipRequiredConfig,
125
126
  });
126
127
 
127
- const ctx = new CliContext(this.program.key, pr.path, pr.args, pr.opts, this.program, "cli", snapshot);
128
+ let preloadedJson: Record<string, unknown> = {};
128
129
  try {
130
+ preloadedJson = await preloadPipableJson(this.program, pr.path, pr.opts, "cli");
131
+ } catch (err) {
132
+ if (err instanceof LeafInputError) {
133
+ this.exitLeafInputError(err, pr.path);
134
+ }
135
+ const msg = err instanceof Error ? err.message : String(err);
136
+ const color = process.stderr.isTTY;
137
+ process.stderr.write(color ? `\u001B[31m${msg}\u001B[0m\n` : `${msg}\n`);
138
+ process.exit(1);
139
+ }
140
+
141
+ const ctx = new CliContext(
142
+ this.program.key,
143
+ pr.path,
144
+ pr.args,
145
+ pr.opts,
146
+ this.program,
147
+ "cli",
148
+ snapshot,
149
+ undefined,
150
+ preloadedJson,
151
+ );
152
+ try {
153
+ this.ensureValidatedLeafInputs(ctx, leaf);
129
154
  const handlerResult = await Promise.resolve(leaf.handler(ctx));
130
155
  if (handlerResult !== undefined && ctx.getResponse() === undefined) {
131
156
  ctx.respond({ body: handlerResult as CliRespondOptions["body"] });
132
157
  }
133
158
  process.exit(0);
134
159
  } catch (err) {
160
+ if (err instanceof LeafInputError) {
161
+ this.exitLeafInputError(err, pr.path);
162
+ }
135
163
  if (err instanceof Error) {
136
164
  process.stderr.write(`${err.message}\n`);
137
165
  }
@@ -232,6 +260,7 @@ export class Cli {
232
260
  });
233
261
  }
234
262
 
263
+ this.ensureValidatedLeafInputs(ctx, leaf);
235
264
  const handlerResult = await Promise.resolve(leaf.handler(ctx));
236
265
  if (handlerResult !== undefined && ctx.getResponse() === undefined) {
237
266
  ctx.respond({ body: handlerResult as CliRespondOptions["body"] });
@@ -260,6 +289,15 @@ export class Cli {
260
289
  const msg = stderr.trim() || `Exit code ${err.code}`;
261
290
  return { kind: "error", exitCode: err.code, stdout, stderr, errorMsg: msg };
262
291
  }
292
+ if (err instanceof LeafInputError) {
293
+ return {
294
+ kind: "error",
295
+ exitCode: 1,
296
+ stdout,
297
+ stderr: `${err.message}\n`,
298
+ errorMsg: err.message,
299
+ };
300
+ }
263
301
  if (err instanceof Error) {
264
302
  return {
265
303
  kind: "error",
@@ -320,6 +358,21 @@ export class Cli {
320
358
  }
321
359
  }
322
360
 
361
+ private ensureValidatedLeafInputs(ctx: CliContext, leaf: CliLeaf): void {
362
+ if (leaf.inputSchema === undefined) {
363
+ return;
364
+ }
365
+ ctx.inputs;
366
+ }
367
+
368
+ private exitLeafInputError(err: LeafInputError, helpPath: string[]): never {
369
+ const color = process.stderr.isTTY;
370
+ const msg = color ? `\u001B[31m${err.message}\u001B[0m` : err.message;
371
+ process.stderr.write(`${msg}\n`);
372
+ process.stderr.write(cliHelpRender(this.presentationRoot, helpPath, true));
373
+ process.exit(1);
374
+ }
375
+
323
376
  private prepareDispatch(
324
377
  argv: string[],
325
378
  opts?: { presentationFallback?: boolean },
package/src/context.ts CHANGED
@@ -10,14 +10,14 @@ parsed values.
10
10
  import type { AnyAppConfigSnapshot } from "./config/context.ts";
11
11
  import { EmptyAppConfigSnapshot } from "./config/context.ts";
12
12
  import { parseCommaList, parseDate, parseDateTime, parseDurationMs } from "./formats.ts";
13
- import { collectOptionDefs } from "./parse.ts";
13
+ import { loadLeafInputs, readJsonOptionValue } from "./leaf-inputs.ts";
14
14
  import { normalizeRespondOptions, writeRespondBodyToStdout } from "./respond.ts";
15
- import type { CliInvocation, CliLeaf, CliNode, CliOption, CliProgram, CliRespondOptions } from "./types.ts";
16
- import { CliOptionKind, CliValueFormat, isCliLeaf, isCliRouter } from "./types.ts";
15
+ import type { CliInvocation, CliLeaf, CliNode, CliProgram, CliRespondOptions } from "./types.ts";
16
+ import { isCliLeaf, isCliRouter } from "./types.ts";
17
17
  import { strictParseDouble } from "./utils.ts";
18
18
 
19
19
  /** Coerced leaf inputs keyed by option and positional names. */
20
- export type CliLeafInputs = Record<string, boolean | number | string | string[] | undefined>;
20
+ export type CliLeafInputs = Record<string, boolean | number | string | string[] | unknown | undefined>;
21
21
 
22
22
  /**
23
23
  * Values passed to a leaf command handler after parsing: app name, routed path, args, and merged options.
@@ -32,8 +32,11 @@ export class CliContext {
32
32
  readonly appConfig: AnyAppConfigSnapshot;
33
33
  /** Original flat tool arguments for API/MCP invocations (when provided). */
34
34
  readonly toolArgs?: Record<string, unknown>;
35
+ /** Pipable Json option values read from stdin before the handler (CLI only). */
36
+ readonly preloadedJson: Record<string, unknown>;
35
37
 
36
38
  private response?: CliRespondOptions;
39
+ private leafInputsCache?: CliLeafInputs;
37
40
 
38
41
  /** Captures the program root, routed path, positional words, and option map for a leaf handler. */
39
42
  constructor(
@@ -45,6 +48,7 @@ export class CliContext {
45
48
  invocation: CliInvocation = "cli",
46
49
  appConfig: AnyAppConfigSnapshot = new EmptyAppConfigSnapshot(program),
47
50
  toolArgs?: Record<string, unknown>,
51
+ preloadedJson: Record<string, unknown> = {},
48
52
  ) {
49
53
  this.appName = appName;
50
54
  this.commandPath = commandPath;
@@ -54,6 +58,7 @@ export class CliContext {
54
58
  this.invocation = invocation;
55
59
  this.appConfig = appConfig;
56
60
  this.toolArgs = toolArgs;
61
+ this.preloadedJson = preloadedJson;
57
62
  }
58
63
 
59
64
  /**
@@ -136,54 +141,50 @@ export class CliContext {
136
141
  return parseDateTime(s);
137
142
  }
138
143
 
144
+ /**
145
+ * Parsed Json option: `--name '<json>'`, preloaded piped stdin (when `pipable`), or MCP/API toolArgs.
146
+ * Flag wins over stdin and toolArgs.
147
+ */
148
+ jsonOpt(name: string): unknown | undefined {
149
+ return readJsonOptionValue(this, name);
150
+ }
151
+
139
152
  /** Returns the value(s) for a named positional slot. Varargs slots return string[]; single slots return string | undefined. */
140
153
  positional(name: string): string | string[] | undefined {
141
154
  return this._positionalMap()[name];
142
155
  }
143
156
 
144
- /** Reads coerced option and positional values for the current leaf from schema metadata. */
145
- readLeafInputs(): CliLeafInputs {
146
- const leaf = this._leafNode();
147
- if (!leaf) return {};
148
-
149
- const out: CliLeafInputs = {};
150
- for (const opt of collectOptionDefs(this.program, this.commandPath)) {
151
- out[opt.name] = this._readOptionValue(opt);
152
- }
153
- for (const p of leaf.positionals ?? []) {
154
- const val = this.positional(p.name);
155
- if (val === undefined) {
156
- out[p.name] = undefined;
157
- } else if (Array.isArray(val)) {
158
- out[p.name] = val;
159
- } else {
160
- out[p.name] = val;
161
- }
157
+ /**
158
+ * Coerced option and positional values for the current leaf.
159
+ * When `leaf.inputSchema` is set, argsbarg validates before the handler runs; this returns the cached result.
160
+ */
161
+ get inputs(): CliLeafInputs {
162
+ if (this.leafInputsCache !== undefined) {
163
+ return this.leafInputsCache;
162
164
  }
163
- return out;
165
+ this.leafInputsCache = loadLeafInputs(this);
166
+ return this.leafInputsCache;
164
167
  }
165
168
 
166
- private _readOptionValue(opt: CliOption): boolean | number | string | string[] | undefined {
167
- if (opt.kind === CliOptionKind.Presence) {
168
- return this.hasFlag(opt.name);
169
- }
170
- if (opt.kind === CliOptionKind.Number) {
171
- const n = this.numberOpt(opt.name);
172
- return n === null ? undefined : n;
173
- }
174
- if (opt.format === CliValueFormat.Duration) {
175
- return this.durationOpt(opt.name);
176
- }
177
- if (opt.format === CliValueFormat.CommaList) {
178
- return this.commaListOpt(opt.name);
179
- }
180
- if (opt.format === CliValueFormat.Date) {
181
- return this.dateOpt(opt.name);
182
- }
183
- if (opt.format === CliValueFormat.DateTime) {
184
- return this.dateTimeOpt(opt.name);
185
- }
186
- return this.stringOpt(opt.name);
169
+ /**
170
+ * {@link inputs} cast to a schemagen or app-defined input type (consumer-asserted; not inferred from `inputSchema`).
171
+ */
172
+ inputsAs<T = CliLeafInputs>(): T {
173
+ return this.inputs as T;
174
+ }
175
+
176
+ /**
177
+ * @deprecated Use {@link inputs} or {@link inputsAs}.
178
+ */
179
+ readLeafInputs(): CliLeafInputs {
180
+ return this.inputs;
181
+ }
182
+
183
+ /**
184
+ * @deprecated Use sync {@link readLeafInputs} — stdin is preloaded before the handler runs.
185
+ */
186
+ readLeafInputsAsync(): Promise<CliLeafInputs> {
187
+ return Promise.resolve(this.readLeafInputs());
187
188
  }
188
189
 
189
190
  private _leafNode(): CliLeaf | undefined {
package/src/help.ts CHANGED
@@ -178,6 +178,8 @@ function optKindLabel(k: CliOptionKind, o?: CliOption): string {
178
178
  }
179
179
  return ` <${choices.slice(0, 3).join("|")}|…>`;
180
180
  }
181
+ case CliOptionKind.Json:
182
+ return " <json>";
181
183
  }
182
184
  }
183
185
 
package/src/index.ts CHANGED
@@ -28,6 +28,14 @@ export {
28
28
  shouldRunHeadlessWithYes,
29
29
  wantsExplicitJson,
30
30
  } from "./headless.ts";
31
+ export {
32
+ LeafInputError,
33
+ loadLeafInputs,
34
+ preloadPipableJson,
35
+ readJsonOptionValue,
36
+ readLeafInputs,
37
+ readLeafInputsAsync,
38
+ } from "./leaf-inputs.ts";
31
39
  export type { McpBundlePaths, PackMcpBundleOpts } from "./mcp/bundle.ts";
32
40
  export { defaultMcpBundlePaths, generateMcpManifest, packMcpBundle } from "./mcp/bundle.ts";
33
41
  export type {