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.
@@ -0,0 +1,279 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { Cli, CliContext, CliOptionKind } from "./index.ts";
3
+ import { LeafInputError, readLeafInputs } from "./leaf-inputs.ts";
4
+ import type { CliProgram } from "./types.ts";
5
+
6
+ const invoiceSchema = {
7
+ type: "object",
8
+ properties: {
9
+ format: { type: "string", enum: ["pdf", "html"] },
10
+ invoice: {
11
+ type: "object",
12
+ properties: {
13
+ id: { type: "string" },
14
+ },
15
+ required: ["id"],
16
+ additionalProperties: false,
17
+ },
18
+ },
19
+ required: ["format", "invoice"],
20
+ additionalProperties: false,
21
+ } as const;
22
+
23
+ function renderProgram(): CliProgram {
24
+ return {
25
+ key: "json-pipe-test",
26
+ version: "1.0.0",
27
+ description: "Json pipable option tests",
28
+ commands: [
29
+ {
30
+ key: "render",
31
+ description: "Render with pipable invoice JSON",
32
+ inputSchema: invoiceSchema,
33
+ options: [
34
+ {
35
+ name: "format",
36
+ description: "Output format",
37
+ kind: CliOptionKind.Enum,
38
+ choices: ["pdf", "html"],
39
+ required: true,
40
+ },
41
+ {
42
+ name: "invoice",
43
+ description: "Invoice JSON (flag or stdin)",
44
+ kind: CliOptionKind.Json,
45
+ pipable: true,
46
+ required: true,
47
+ },
48
+ ],
49
+ handler: (ctx) => ctx.readLeafInputs(),
50
+ },
51
+ ],
52
+ } satisfies CliProgram;
53
+ }
54
+
55
+ describe("readLeafInputs / jsonOpt", () => {
56
+ test("reads Json option from MCP toolArgs", async () => {
57
+ const cli = new Cli(renderProgram());
58
+ const result = await cli.invoke(["render", "--format", "pdf"], {
59
+ invocation: "mcp",
60
+ toolArgs: { format: "pdf", invoice: { id: "INV-1" } },
61
+ });
62
+ expect(result.kind).toBe("ok");
63
+ expect(result.exitCode).toBe(0);
64
+ expect(result.response?.body).toEqual({
65
+ format: "pdf",
66
+ invoice: { id: "INV-1" },
67
+ });
68
+ });
69
+
70
+ test("flag wins over toolArgs for Json option", async () => {
71
+ const cli = new Cli(renderProgram());
72
+ const result = await cli.invoke(["render", "--format", "pdf", "--invoice", '{"id":"from-flag"}'], {
73
+ invocation: "mcp",
74
+ toolArgs: { format: "pdf", invoice: { id: "from-tool-args" } },
75
+ });
76
+ expect(result.kind).toBe("ok");
77
+ expect(result.response?.body).toEqual({
78
+ format: "pdf",
79
+ invoice: { id: "from-flag" },
80
+ });
81
+ });
82
+
83
+ test("jsonOpt reads from preloadedJson", () => {
84
+ const program = renderProgram();
85
+ const ctx = new CliContext(
86
+ "json-pipe-test",
87
+ ["render"],
88
+ [],
89
+ { format: "pdf" },
90
+ program,
91
+ "cli",
92
+ undefined,
93
+ undefined,
94
+ { invoice: { id: "piped" } },
95
+ );
96
+ expect(ctx.jsonOpt("invoice")).toEqual({ id: "piped" });
97
+ expect(ctx.inputs).toEqual({ format: "pdf", invoice: { id: "piped" } });
98
+ });
99
+
100
+ test("inputsAs returns schemagen-shaped inputs", () => {
101
+ type RenderInput = { format: "pdf" | "html"; invoice: { id: string } };
102
+ const program = renderProgram();
103
+ const ctx = new CliContext("json-pipe-test", ["render"], [], { format: "pdf" }, program, "mcp", undefined, {
104
+ format: "pdf",
105
+ invoice: { id: "INV-1" },
106
+ });
107
+ expect(ctx.inputsAs<RenderInput>()).toEqual({ format: "pdf", invoice: { id: "INV-1" } });
108
+ });
109
+
110
+ test("rejects invalid Json flag at parse time", async () => {
111
+ const cli = new Cli(renderProgram());
112
+ const result = await cli.invoke(["render", "--format", "pdf", "--invoice", "not-json"], {
113
+ invocation: "mcp",
114
+ toolArgs: {},
115
+ });
116
+ expect(result.kind).toBe("error");
117
+ expect(result.errorMsg).toContain("Invalid JSON");
118
+ });
119
+
120
+ test("validates merged inputs against inputSchema", async () => {
121
+ const cli = new Cli(renderProgram());
122
+ const result = await cli.invoke(["render", "--format", "pdf"], {
123
+ invocation: "mcp",
124
+ toolArgs: { format: "pdf", invoice: { id: 123 } },
125
+ });
126
+ expect(result.kind).toBe("error");
127
+ expect(result.stderr).toContain("invoice.id");
128
+ });
129
+
130
+ test("validates inputSchema before handler runs", async () => {
131
+ let handlerCalled = false;
132
+ const program = {
133
+ key: "json-pipe-test",
134
+ version: "1.0.0",
135
+ description: "pre-handler validation",
136
+ commands: [
137
+ {
138
+ key: "render",
139
+ description: "Render",
140
+ inputSchema: invoiceSchema,
141
+ options: [
142
+ {
143
+ name: "format",
144
+ description: "Output format",
145
+ kind: CliOptionKind.Enum,
146
+ choices: ["pdf", "html"],
147
+ required: true,
148
+ },
149
+ {
150
+ name: "invoice",
151
+ description: "Invoice JSON",
152
+ kind: CliOptionKind.Json,
153
+ pipable: true,
154
+ required: true,
155
+ },
156
+ ],
157
+ handler: () => {
158
+ handlerCalled = true;
159
+ return { ok: true };
160
+ },
161
+ },
162
+ ],
163
+ } satisfies CliProgram;
164
+ const cli = new Cli(program);
165
+ const result = await cli.invoke(["render", "--format", "pdf"], {
166
+ invocation: "mcp",
167
+ toolArgs: { format: "pdf", invoice: { id: 123 } },
168
+ });
169
+ expect(result.kind).toBe("error");
170
+ expect(handlerCalled).toBe(false);
171
+ });
172
+
173
+ test("inputs returns cached inputs after pre-handler validation", async () => {
174
+ const inputs: unknown[] = [];
175
+ const program = {
176
+ key: "json-pipe-test",
177
+ version: "1.0.0",
178
+ description: "cached inputs",
179
+ commands: [
180
+ {
181
+ key: "render",
182
+ description: "Render",
183
+ inputSchema: invoiceSchema,
184
+ options: [
185
+ {
186
+ name: "format",
187
+ description: "Output format",
188
+ kind: CliOptionKind.Enum,
189
+ choices: ["pdf", "html"],
190
+ required: true,
191
+ },
192
+ {
193
+ name: "invoice",
194
+ description: "Invoice JSON",
195
+ kind: CliOptionKind.Json,
196
+ required: true,
197
+ },
198
+ ],
199
+ handler: (ctx) => {
200
+ inputs.push(ctx.inputs);
201
+ inputs.push(ctx.inputs);
202
+ },
203
+ },
204
+ ],
205
+ } satisfies CliProgram;
206
+ const cli = new Cli(program);
207
+ await cli.invoke(["render", "--format", "pdf"], {
208
+ invocation: "mcp",
209
+ toolArgs: { format: "pdf", invoice: { id: "INV-1" } },
210
+ });
211
+ expect(inputs).toHaveLength(2);
212
+ expect(inputs[0]).toEqual(inputs[1]);
213
+ });
214
+
215
+ test("readLeafInputs throws LeafInputError when required Json is missing", () => {
216
+ const program = renderProgram();
217
+ const ctx = new CliContext("json-pipe-test", ["render"], [], { format: "pdf" }, program, "mcp", undefined, {});
218
+ expect(() => readLeafInputs(ctx)).toThrow(LeafInputError);
219
+ });
220
+
221
+ test("omits undefined optional properties before inputSchema validation", async () => {
222
+ const schemaWithOptional = {
223
+ type: "object",
224
+ properties: {
225
+ format: { type: "string", enum: ["pdf", "html"] },
226
+ template: { type: "string" },
227
+ invoice: {
228
+ type: "object",
229
+ properties: { id: { type: "string" } },
230
+ required: ["id"],
231
+ additionalProperties: false,
232
+ },
233
+ },
234
+ required: ["format", "invoice"],
235
+ additionalProperties: false,
236
+ };
237
+ const program = {
238
+ key: "json-pipe-test",
239
+ version: "1.0.0",
240
+ description: "optional template",
241
+ commands: [
242
+ {
243
+ key: "render",
244
+ description: "Render",
245
+ inputSchema: schemaWithOptional,
246
+ options: [
247
+ {
248
+ name: "format",
249
+ description: "Output format",
250
+ kind: CliOptionKind.Enum,
251
+ choices: ["pdf", "html"],
252
+ required: true,
253
+ },
254
+ {
255
+ name: "template",
256
+ description: "Template name",
257
+ kind: CliOptionKind.String,
258
+ },
259
+ {
260
+ name: "invoice",
261
+ description: "Invoice JSON",
262
+ kind: CliOptionKind.Json,
263
+ pipable: true,
264
+ required: true,
265
+ },
266
+ ],
267
+ handler: (ctx) => ctx.readLeafInputs(),
268
+ },
269
+ ],
270
+ } satisfies CliProgram;
271
+ const cli = new Cli(program);
272
+ const result = await cli.invoke(["render", "--format", "pdf"], {
273
+ invocation: "mcp",
274
+ toolArgs: { format: "pdf", invoice: { id: "INV-1" } },
275
+ });
276
+ expect(result.kind).toBe("ok");
277
+ expect(result.response?.body).toEqual({ format: "pdf", invoice: { id: "INV-1" } });
278
+ });
279
+ });
@@ -0,0 +1,195 @@
1
+ /*
2
+ Leaf input reads: Json options (flag, preloaded stdin, or toolArgs), optional inputSchema validation.
3
+ */
4
+
5
+ import { validateConfigDocument } from "./config/validate.ts";
6
+ import type { CliContext, CliLeafInputs } from "./context.ts";
7
+ import { collectOptionDefs } from "./parse.ts";
8
+ import type { CliInvocation, CliLeaf, CliNode, CliOption, CliProgram } from "./types.ts";
9
+ import { CliOptionKind, CliValueFormat, isCliLeaf, isCliRouter } from "./types.ts";
10
+ import { isInteractiveTty } from "./utils.ts";
11
+
12
+ /** Thrown when leaf input resolution or validation fails. */
13
+ export class LeafInputError extends Error {
14
+ constructor(message: string) {
15
+ super(message);
16
+ this.name = "LeafInputError";
17
+ }
18
+ }
19
+
20
+ function leafNode(ctx: CliContext): CliLeaf | undefined {
21
+ let node: CliNode = ctx.program;
22
+ for (const seg of ctx.commandPath) {
23
+ if (!isCliRouter(node)) return undefined;
24
+ const child = node.commands.find((c) => c.key === seg);
25
+ if (!child) return undefined;
26
+ node = child;
27
+ }
28
+ return isCliLeaf(node) ? node : undefined;
29
+ }
30
+
31
+ /** Parses a JSON string from a `--name` flag value. */
32
+ export function parseJsonText(raw: string, label: string): unknown {
33
+ const trimmed = raw.trim();
34
+ if (trimmed.length === 0) {
35
+ throw new LeafInputError(`${label}: JSON value is empty`);
36
+ }
37
+ try {
38
+ return JSON.parse(trimmed);
39
+ } catch {
40
+ throw new LeafInputError(`${label}: invalid JSON`);
41
+ }
42
+ }
43
+
44
+ async function readPipedJsonStdin(): Promise<unknown> {
45
+ const raw = await new Response(Bun.stdin).text();
46
+ const trimmed = raw.trim();
47
+ if (trimmed.length === 0) {
48
+ throw new LeafInputError("stdin is empty; pass JSON via the option flag or pipe a JSON document to stdin");
49
+ }
50
+ try {
51
+ return JSON.parse(trimmed);
52
+ } catch {
53
+ throw new LeafInputError("stdin is not valid JSON");
54
+ }
55
+ }
56
+
57
+ function pipableJsonHelp(opt: CliOption): string {
58
+ return `Missing required option --${opt.name}: pass JSON via --${opt.name} '<json>' or pipe a JSON document to stdin`;
59
+ }
60
+
61
+ function omitUndefinedInputs(out: CliLeafInputs): Record<string, unknown> {
62
+ const stripped: Record<string, unknown> = {};
63
+ for (const [key, value] of Object.entries(out)) {
64
+ if (value !== undefined) {
65
+ stripped[key] = value;
66
+ }
67
+ }
68
+ return stripped;
69
+ }
70
+
71
+ function validateAgainstInputSchema(out: CliLeafInputs, inputSchema: Record<string, unknown>): void {
72
+ const result = validateConfigDocument(omitUndefinedInputs(out), inputSchema);
73
+ if (!result.valid) {
74
+ throw new LeafInputError(result.errors.join("; "));
75
+ }
76
+ }
77
+
78
+ /** Resolves a Json option from argv, preloaded stdin, or toolArgs (flag wins). */
79
+ export function readJsonOptionValue(ctx: CliContext, name: string): unknown | undefined {
80
+ const flagValue = ctx.stringOpt(name);
81
+ if (flagValue !== undefined) {
82
+ return parseJsonText(flagValue, `--${name}`);
83
+ }
84
+ if (name in ctx.preloadedJson) {
85
+ return ctx.preloadedJson[name];
86
+ }
87
+ if (ctx.toolArgs !== undefined && name in ctx.toolArgs) {
88
+ return ctx.toolArgs[name];
89
+ }
90
+ return undefined;
91
+ }
92
+
93
+ /**
94
+ * Reads piped stdin for a pipable Json option when the flag is omitted (CLI only).
95
+ * Call from {@link Cli.run} before constructing the handler context.
96
+ */
97
+ export async function preloadPipableJson(
98
+ program: CliProgram,
99
+ commandPath: string[],
100
+ opts: Record<string, string>,
101
+ invocation: CliInvocation,
102
+ ): Promise<Record<string, unknown>> {
103
+ if (invocation !== "cli" || isInteractiveTty) {
104
+ return {};
105
+ }
106
+ for (const opt of collectOptionDefs(program, commandPath)) {
107
+ if (opt.kind === CliOptionKind.Json && opt.pipable && !(opt.name in opts)) {
108
+ return { [opt.name]: await readPipedJsonStdin() };
109
+ }
110
+ }
111
+ return {};
112
+ }
113
+
114
+ function readSyncOptionValue(
115
+ ctx: CliContext,
116
+ opt: CliOption,
117
+ ): boolean | number | string | string[] | unknown | undefined {
118
+ if (opt.kind === CliOptionKind.Presence) {
119
+ return ctx.hasFlag(opt.name);
120
+ }
121
+ if (opt.kind === CliOptionKind.Number) {
122
+ const n = ctx.numberOpt(opt.name);
123
+ return n === null ? undefined : n;
124
+ }
125
+ if (opt.kind === CliOptionKind.Json) {
126
+ return readJsonOptionValue(ctx, opt.name);
127
+ }
128
+ if (opt.format !== undefined) {
129
+ if (opt.format === CliValueFormat.Duration) {
130
+ return ctx.durationOpt(opt.name);
131
+ }
132
+ if (opt.format === CliValueFormat.CommaList) {
133
+ return ctx.commaListOpt(opt.name);
134
+ }
135
+ if (opt.format === CliValueFormat.Date) {
136
+ return ctx.dateOpt(opt.name);
137
+ }
138
+ if (opt.format === CliValueFormat.DateTime) {
139
+ return ctx.dateTimeOpt(opt.name);
140
+ }
141
+ }
142
+ return ctx.stringOpt(opt.name);
143
+ }
144
+
145
+ /**
146
+ * Loads coerced leaf inputs and validates against `leaf.inputSchema` when set.
147
+ * Used by {@link CliContext.inputs}; prefer `ctx.inputs` or `ctx.inputsAs()` in handlers.
148
+ */
149
+ export function loadLeafInputs(ctx: CliContext): CliLeafInputs {
150
+ const leaf = leafNode(ctx);
151
+ if (!leaf) return {};
152
+
153
+ const out: CliLeafInputs = {};
154
+ const options = collectOptionDefs(ctx.program, ctx.commandPath);
155
+
156
+ for (const opt of options) {
157
+ out[opt.name] = readSyncOptionValue(ctx, opt);
158
+ }
159
+
160
+ for (const p of leaf.positionals ?? []) {
161
+ const val = ctx.positional(p.name);
162
+ if (val === undefined) {
163
+ out[p.name] = undefined;
164
+ } else if (Array.isArray(val)) {
165
+ out[p.name] = val;
166
+ } else {
167
+ out[p.name] = val;
168
+ }
169
+ }
170
+
171
+ for (const opt of options) {
172
+ if (opt.required && out[opt.name] === undefined) {
173
+ if (opt.kind === CliOptionKind.Json && opt.pipable && ctx.invocation === "cli" && isInteractiveTty) {
174
+ throw new LeafInputError(pipableJsonHelp(opt));
175
+ }
176
+ throw new LeafInputError(`Missing required option: --${opt.name}`);
177
+ }
178
+ }
179
+
180
+ if (leaf.inputSchema !== undefined) {
181
+ validateAgainstInputSchema(out, leaf.inputSchema);
182
+ }
183
+
184
+ return omitUndefinedInputs(out);
185
+ }
186
+
187
+ /** @deprecated Use {@link CliContext.inputs} or {@link loadLeafInputs} via `ctx.inputs`. */
188
+ export function readLeafInputs(ctx: CliContext): CliLeafInputs {
189
+ return ctx.inputs;
190
+ }
191
+
192
+ /** @deprecated Use sync {@link readLeafInputs} — stdin is preloaded before the handler runs. */
193
+ export function readLeafInputsAsync(ctx: CliContext): Promise<CliLeafInputs> {
194
+ return Promise.resolve(readLeafInputs(ctx));
195
+ }
package/src/mcp/tools.ts CHANGED
@@ -116,6 +116,8 @@ function optionProperty(opt: CliOption): Record<string, unknown> {
116
116
  return { type: "number", ...base };
117
117
  case CliOptionKind.Enum:
118
118
  return { type: "string", enum: opt.choices, ...base };
119
+ case CliOptionKind.Json:
120
+ return { type: "object", ...base };
119
121
  }
120
122
  }
121
123
 
@@ -287,6 +289,9 @@ export function mcpToolCallToArgv(
287
289
  const argv = [...tool.path];
288
290
 
289
291
  for (const opt of collectOptionDefs(root, tool.path)) {
292
+ if (opt.kind === CliOptionKind.Json) {
293
+ continue;
294
+ }
290
295
  const val = args[opt.name];
291
296
  if (val === undefined) {
292
297
  continue;
package/src/parse.ts CHANGED
@@ -583,6 +583,9 @@ export function postParseValidate(root: CliNode, pr: ParseResult): ParseResult {
583
583
 
584
584
  for (const d of defs) {
585
585
  if (d.required && !(d.name in opts)) {
586
+ if (d.kind === CliOptionKind.Json) {
587
+ continue;
588
+ }
586
589
  return errorResult(`Missing required option: --${d.name}`, pr.path);
587
590
  }
588
591
  }
@@ -592,6 +595,14 @@ export function postParseValidate(root: CliNode, pr: ParseResult): ParseResult {
592
595
  if (!d) {
593
596
  return errorResult(`Unknown option key: ${k}`, pr.path);
594
597
  }
598
+ if (d.kind === CliOptionKind.Json) {
599
+ try {
600
+ JSON.parse(v);
601
+ } catch {
602
+ return errorResult(`Invalid JSON for option --${k}`, pr.path);
603
+ }
604
+ continue;
605
+ }
595
606
  if (d.kind === CliOptionKind.Number) {
596
607
  if (!fullStringIsDouble(v)) {
597
608
  return errorResult(`Invalid number for option --${k}: ${v}`, pr.path);
package/src/types.ts CHANGED
@@ -12,7 +12,7 @@ import type { CliContext } from "./context.ts";
12
12
  export type CliInvocation = "cli" | "mcp" | "api";
13
13
 
14
14
  /**
15
- * Option kinds: presence (boolean flag), string (free-form text), number (strict double), or enum (fixed choices).
15
+ * Option kinds: presence (boolean flag), string (free-form text), number (strict double), enum (fixed choices), or json (parsed JSON object/array).
16
16
  */
17
17
  export enum CliOptionKind {
18
18
  /** Boolean flag: no value token (may be implicit `"1"` when set). */
@@ -23,6 +23,8 @@ export enum CliOptionKind {
23
23
  Number = "number",
24
24
  /** Fixed set of allowed string values. Requires non-empty `choices` on the option. */
25
25
  Enum = "enum",
26
+ /** JSON object or array (parsed from `--name '<json>'`, piped stdin when `pipable`, or MCP/API tool body). */
27
+ Json = "json",
26
28
  }
27
29
 
28
30
  /**
@@ -89,6 +91,11 @@ export interface CliOption {
89
91
  default?: string;
90
92
  /** Regex pattern for string options. Mutually exclusive with `format`. */
91
93
  pattern?: string;
94
+ /**
95
+ * When `true` on a `Json` option, CLI may omit `--name` and supply JSON via stdin instead.
96
+ * If `--name` is set, the flag value wins and stdin is not read.
97
+ */
98
+ pipable?: boolean;
92
99
  }
93
100
 
94
101
  /**
package/src/validate.ts CHANGED
@@ -247,6 +247,31 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
247
247
  if (resolved !== undefined && (typeof resolved !== "object" || resolved === null || Array.isArray(resolved))) {
248
248
  throw new CliSchemaValidationError("outputSchema must be a JSON Schema object (not null or an array)");
249
249
  }
250
+ const inputSchema = node.inputSchema;
251
+ if (
252
+ inputSchema !== undefined &&
253
+ (typeof inputSchema !== "object" || inputSchema === null || Array.isArray(inputSchema))
254
+ ) {
255
+ throw new CliSchemaValidationError("inputSchema must be a JSON Schema object (not null or an array)");
256
+ }
257
+ if (inputSchema !== undefined) {
258
+ const properties = inputSchema.properties;
259
+ if (
260
+ properties !== undefined &&
261
+ (typeof properties !== "object" || properties === null || Array.isArray(properties))
262
+ ) {
263
+ throw new CliSchemaValidationError(`inputSchema.properties must be an object on ${node.key}`);
264
+ }
265
+ if (properties) {
266
+ for (const opt of node.options ?? []) {
267
+ if (opt.kind === CliOptionKind.Json && !(opt.name in properties)) {
268
+ throw new CliSchemaValidationError(
269
+ `Json option '${opt.name}' is missing from inputSchema.properties on ${node.key}`,
270
+ );
271
+ }
272
+ }
273
+ }
274
+ }
250
275
  } else {
251
276
  const rogue = node as unknown as CliLeaf;
252
277
  if (rogue.mcpTool !== undefined) {
@@ -301,7 +326,22 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
301
326
 
302
327
  function validateOptions(scopeKey: string, options: import("./types.ts").CliOption[]): void {
303
328
  const seenShorts = new Set<string>();
329
+ let pipableCount = 0;
304
330
  for (const opt of options) {
331
+ if (opt.pipable) {
332
+ pipableCount++;
333
+ if (opt.kind !== CliOptionKind.Json) {
334
+ throw new CliSchemaValidationError(`pipable is only valid on Json kind: ${scopeKey}/${opt.name}`);
335
+ }
336
+ }
337
+ if (opt.kind === CliOptionKind.Json) {
338
+ if (opt.format !== undefined || opt.pattern !== undefined || opt.default !== undefined) {
339
+ throw new CliSchemaValidationError(
340
+ `Json option cannot use format, pattern, or default: ${scopeKey}/${opt.name}`,
341
+ );
342
+ }
343
+ }
344
+
305
345
  if (opt.required && opt.kind === CliOptionKind.Presence) {
306
346
  throw new CliSchemaValidationError(`Presence option cannot be required: ${scopeKey}/${opt.name}`);
307
347
  }
@@ -340,6 +380,9 @@ function validateOptions(scopeKey: string, options: import("./types.ts").CliOpti
340
380
  validateOptionValueMetadata(scopeKey, opt);
341
381
  }
342
382
  }
383
+ if (pipableCount > 1) {
384
+ throw new CliSchemaValidationError(`At most one pipable Json option per command: ${scopeKey}`);
385
+ }
343
386
  }
344
387
 
345
388
  function validateOptionValueMetadata(scopeKey: string, opt: import("./types.ts").CliOption): void {
@@ -1,14 +0,0 @@
1
- /** JSON stdout for `full-example status --json`. */
2
- export interface StatusJsonOutput {
3
- /** Resolved AWS region. */
4
- defaultRegion?: string;
5
- /** Resolved retry count. */
6
- maxRetries?: number;
7
- /** Whether apiToken is set (value never included). */
8
- apiTokenSet: boolean;
9
- /** App version from program root. */
10
- version: string;
11
- }
12
-
13
- /** Schemagen root for leaf outputSchema. */
14
- export type outputType = StatusJsonOutput;