argsbarg 7.0.6 → 7.0.8

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +4 -4
  3. package/docs/README.md +1 -1
  4. package/docs/ai-skills.md +14 -62
  5. package/docs/bundled-docs.md +9 -13
  6. package/docs/cli-program.md +10 -11
  7. package/docs/configure.md +9 -11
  8. package/docs/output-schema.md +1 -1
  9. package/examples/full-example/AGENTS.md +3 -3
  10. package/examples/full-example/docs/README.md +1 -1
  11. package/examples/full-example/justfile +0 -1
  12. package/examples/full-example/skills/full-example/SKILL.md +0 -2
  13. package/examples/full-example/src/program.ts +0 -1
  14. package/examples/full-example-json/AGENTS.md +3 -3
  15. package/examples/full-example-json/docs/README.md +1 -1
  16. package/examples/full-example-json/justfile +0 -1
  17. package/examples/full-example-json/skills/full-example-json/SKILL.md +0 -2
  18. package/examples/full-example-json/src/program.ts +0 -1
  19. package/index.d.ts +5 -3
  20. package/package.json +1 -1
  21. package/src/builtins/builtins.test.ts +1 -22
  22. package/src/builtins/configure-copy.ts +4 -11
  23. package/src/builtins/presentation.ts +2 -5
  24. package/src/configure/artifacts/status.test.ts +5 -5
  25. package/src/configure/artifacts/target-effective.ts +1 -2
  26. package/src/configure/artifacts/target-skill.ts +9 -15
  27. package/src/configure/artifacts/targets/skill.ts +8 -1
  28. package/src/configure/artifacts/targets.test.ts +5 -5
  29. package/src/configure/configure.test.ts +4 -4
  30. package/src/core/parse.test.ts +1 -112
  31. package/src/core/types.ts +5 -3
  32. package/src/core/validate.ts +4 -2
  33. package/src/docs/builtin.ts +5 -3
  34. package/src/docs/cli-guide.ts +3 -3
  35. package/src/docs/docs.test.ts +3 -47
  36. package/src/docs/resolve.ts +5 -5
  37. package/src/docs/save.ts +9 -20
  38. package/src/help.test.ts +250 -8
  39. package/src/help.ts +397 -46
  40. package/src/skill/generate.ts +30 -156
  41. package/src/skill/hint.ts +2 -15
  42. package/src/skill/install.ts +3 -35
package/src/help.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  /*
2
- This module renders CLI help with wrapping, boxes, tables, and TTY color.
3
- It formats both explicit help and error output, using the terminal width to keep the
4
- layout readable and aligned with the current display.
2
+ This module renders CLI help with wrapping, boxes, tables, and TTY color for interactive
3
+ terminal sessions, as well as clean unboxed plain text with in-band YAML input and output
4
+ schemas for non-TTY agent discovery and piping.
5
5
 
6
6
  It keeps help formatting shared across help and error paths so users see one consistent
7
7
  style no matter how help is reached.
8
8
  */
9
9
 
10
10
  import {
11
+ type CliLeaf,
11
12
  type CliNode,
12
13
  type CliOption,
13
14
  CliOptionKind,
@@ -73,9 +74,9 @@ function getHelpWidth(): number {
73
74
  return Math.max(40, process.stdout.columns || 80);
74
75
  }
75
76
 
76
- /** True when stdout is a TTY (used to decide on color). */
77
- function isStdoutTTY(): boolean {
78
- return !!process.stdout.isTTY;
77
+ /** True when stdout/stderr is a TTY (used to decide on boxes and color). */
78
+ function isOutputTTY(useStderr: boolean): boolean {
79
+ return useStderr ? !!process.stderr.isTTY : !!process.stdout.isTTY;
79
80
  }
80
81
 
81
82
  // ── Width Helpers ─────────────────────────────────────────────────────────────
@@ -321,6 +322,52 @@ function renderTableBox(title: string, rows: HelpRow[], hw: number, color: boole
321
322
  return out;
322
323
  }
323
324
 
325
+ /** Renders a plain-text section with a header and 2-space indented lines (non-TTY). */
326
+ function renderPlainSection(
327
+ /** Section header title (e.g. "Usage" or "Notes"). */
328
+ title: string,
329
+ /** Content lines to indent under the header. */
330
+ lines: string[],
331
+ ): string[] {
332
+ if (lines.length === 0) return [];
333
+ const out: string[] = [`${title}:`];
334
+ for (const line of lines) {
335
+ out.push(line.length > 0 ? ` ${line}` : "");
336
+ }
337
+ return out;
338
+ }
339
+
340
+ /** Renders a plain-text two-column table without box borders (non-TTY). */
341
+ function renderPlainTable(
342
+ /** Section header title (e.g. "Options" or "Subcommands"). */
343
+ title: string,
344
+ /** Rows with label and description to format. */
345
+ rows: HelpRow[],
346
+ /** Available terminal width. */
347
+ hw: number,
348
+ ): string[] {
349
+ if (rows.length === 0) return [];
350
+ let labelWidth = 0;
351
+ for (const row of rows) {
352
+ labelWidth = Math.max(labelWidth, visibleWidth(row.label));
353
+ }
354
+ const descWidth = Math.max(20, hw - labelWidth - 4);
355
+ const out: string[] = [`${title}:`];
356
+ for (const row of rows) {
357
+ const wrapped = wrapText(row.description, descWidth);
358
+ const paddedLabel = padVisible(row.label, labelWidth);
359
+ if (wrapped.length === 0 || wrapped[0].length === 0) {
360
+ out.push(` ${row.label}`);
361
+ } else {
362
+ out.push(` ${paddedLabel} ${wrapped[0]}`);
363
+ for (let idx = 1; idx < wrapped.length; idx++) {
364
+ out.push(` ${spaces(labelWidth)} ${wrapped[idx]}`);
365
+ }
366
+ }
367
+ }
368
+ return out;
369
+ }
370
+
324
371
  // ── Usage & Rows ──────────────────────────────────────────────────────────────
325
372
 
326
373
  /** Builds one or two usage line strings (OPTIONS / COMMAND / ARGS) for the help header. */
@@ -404,24 +451,287 @@ function rowsForSubcommands(cmds: CliNode[]): HelpRow[] {
404
451
  .map((c) => ({ label: c.key, description: c.description }));
405
452
  }
406
453
 
454
+ // ── Schema YAML Formatting ───────────────────────────────────────────────────
455
+
456
+ /**
457
+ * Resolves a JSON Schema $ref pointer from definitions or $defs.
458
+ */
459
+ function resolveRef(
460
+ /** Reference URI string (e.g. `#/definitions/Foo` or `#/$defs/Foo`). */
461
+ ref: string,
462
+ /** Definitions dictionary from the root schema. */
463
+ defs: Record<string, unknown>,
464
+ ): Record<string, unknown> | null {
465
+ const name = ref.replace(/^#\/(definitions|\$defs)\//, "");
466
+ const target = defs[name];
467
+ if (typeof target === "object" && target !== null) {
468
+ return target as Record<string, unknown>;
469
+ }
470
+ return null;
471
+ }
472
+
473
+ /**
474
+ * Formats a single-line type representation from a JSON Schema fragment.
475
+ * Returns null if the type is complex and requires multiline YAML formatting.
476
+ */
477
+ function formatType(
478
+ /** Schema fragment to inspect. */
479
+ schema: Record<string, unknown>,
480
+ /** Schema definitions for reference lookup. */
481
+ defs: Record<string, unknown>,
482
+ /** Ancestor reference names visited in the current descent. */
483
+ seen: Set<string>,
484
+ ): string | null {
485
+ if (typeof schema.$ref === "string") {
486
+ const name = schema.$ref.replace(/^#\/(definitions|\$defs)\//, "");
487
+ if (seen.has(name)) {
488
+ return name;
489
+ }
490
+ seen.add(name);
491
+ const resolved = resolveRef(schema.$ref, defs);
492
+ const res = resolved ? formatType(resolved, defs, seen) : name;
493
+ seen.delete(name);
494
+ return res;
495
+ }
496
+
497
+ if (Array.isArray(schema.enum) && schema.enum.length > 0) {
498
+ return schema.enum.map((v) => (typeof v === "string" ? JSON.stringify(v) : String(v))).join(" | ");
499
+ }
500
+
501
+ const union = (schema.anyOf ?? schema.oneOf) as unknown[];
502
+ if (Array.isArray(union) && union.length > 0) {
503
+ const parts: string[] = [];
504
+ let allSimple = true;
505
+ for (const variant of union) {
506
+ if (typeof variant === "object" && variant !== null) {
507
+ const formatted = formatType(variant as Record<string, unknown>, defs, seen);
508
+ if (formatted !== null) {
509
+ parts.push(formatted);
510
+ } else {
511
+ allSimple = false;
512
+ break;
513
+ }
514
+ }
515
+ }
516
+ if (allSimple && parts.length > 0) {
517
+ return parts.join(" | ");
518
+ }
519
+ }
520
+
521
+ if (Array.isArray(schema.type)) {
522
+ return schema.type.join(" | ");
523
+ }
524
+
525
+ if (schema.type === "string") {
526
+ if (typeof schema.format === "string") {
527
+ return `string (${schema.format})`;
528
+ }
529
+ return "string";
530
+ }
531
+
532
+ if (schema.type === "number") return "number";
533
+ if (schema.type === "integer") return "integer";
534
+ if (schema.type === "boolean") return "boolean";
535
+ if (schema.type === "null") return "null";
536
+
537
+ if (schema.type === "array" && schema.items && typeof schema.items === "object") {
538
+ const itemType = formatType(schema.items as Record<string, unknown>, defs, seen);
539
+ if (itemType !== null) {
540
+ if (itemType.includes(" | ")) {
541
+ return `(${itemType})[]`;
542
+ }
543
+ return `${itemType}[]`;
544
+ }
545
+ return null;
546
+ }
547
+
548
+ if (schema.type === "object" || schema.properties !== undefined) {
549
+ if (schema.properties && typeof schema.properties === "object" && Object.keys(schema.properties).length > 0) {
550
+ return null;
551
+ }
552
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
553
+ const valType = formatType(schema.additionalProperties as Record<string, unknown>, defs, seen) ?? "object";
554
+ return `{ [key: string]: ${valType} }`;
555
+ }
556
+ return "object";
557
+ }
558
+
559
+ return null;
560
+ }
561
+
562
+ /**
563
+ * Formats a JSON Schema node into multiline YAML lines with JSDoc comments.
564
+ */
565
+ function formatSchemaLines(
566
+ /** Schema object to format. */
567
+ schema: Record<string, unknown>,
568
+ /** Schema definitions dictionary. */
569
+ defs: Record<string, unknown>,
570
+ /** Indentation level in spaces. */
571
+ indent: number,
572
+ /** Ancestor reference names visited in the current descent. */
573
+ seen: Set<string>,
574
+ ): string[] {
575
+ if (typeof schema.$ref === "string") {
576
+ const name = schema.$ref.replace(/^#\/(definitions|\$defs)\//, "");
577
+ if (seen.has(name)) {
578
+ return [`${spaces(indent)}${name}`];
579
+ }
580
+ seen.add(name);
581
+ const resolved = resolveRef(schema.$ref, defs);
582
+ const res = resolved ? formatSchemaLines(resolved, defs, indent, seen) : [`${spaces(indent)}${name}`];
583
+ seen.delete(name);
584
+ return res;
585
+ }
586
+
587
+ if (schema.type === "object" || schema.properties !== undefined) {
588
+ const props = (schema.properties as Record<string, Record<string, unknown>>) ?? {};
589
+ const required = new Set(Array.isArray(schema.required) ? schema.required.map((k) => String(k)) : []);
590
+ const propEntries = Object.entries(props);
591
+ if (propEntries.length === 0) {
592
+ const simple = formatType(schema, defs, seen);
593
+ return simple ? [`${spaces(indent)}${simple}`] : [`${spaces(indent)}{}`];
594
+ }
595
+
596
+ const lines: string[] = [];
597
+ for (const [key, prop] of propEntries) {
598
+ if (typeof prop !== "object" || prop === null) continue;
599
+ const desc = typeof prop.description === "string" ? prop.description.trim() : "";
600
+ if (desc.length > 0) {
601
+ for (const dLine of desc.split("\n")) {
602
+ lines.push(`${spaces(indent)}# ${dLine.trim()}`);
603
+ }
604
+ }
605
+ const isReq = required.has(key);
606
+ const keyStr = isReq ? key : `${key}?`;
607
+ const simple = formatType(prop, defs, seen);
608
+ if (simple !== null) {
609
+ lines.push(`${spaces(indent)}${keyStr}: ${simple}`);
610
+ } else {
611
+ if (prop.type === "array" && prop.items && typeof prop.items === "object") {
612
+ lines.push(`${spaces(indent)}${keyStr}:`);
613
+ const itemSchema = prop.items as Record<string, unknown>;
614
+ const itemLines = formatSchemaLines(itemSchema, defs, 0, seen);
615
+ if (itemLines.length > 0) {
616
+ lines.push(`${spaces(indent + 2)}- ${itemLines[0]}`);
617
+ for (let i = 1; i < itemLines.length; i++) {
618
+ lines.push(`${spaces(indent + 4)}${itemLines[i]}`);
619
+ }
620
+ } else {
621
+ lines.push(`${spaces(indent + 2)}- {}`);
622
+ }
623
+ } else {
624
+ lines.push(`${spaces(indent)}${keyStr}:`);
625
+ const childLines = formatSchemaLines(prop, defs, indent + 2, seen);
626
+ lines.push(...childLines);
627
+ }
628
+ }
629
+ }
630
+ return lines;
631
+ }
632
+
633
+ if (schema.type === "array") {
634
+ if (schema.items && typeof schema.items === "object") {
635
+ const itemSchema = schema.items as Record<string, unknown>;
636
+ const simple = formatType(itemSchema, defs, seen);
637
+ if (simple !== null) {
638
+ return [`${spaces(indent)}- ${simple}`];
639
+ }
640
+ const itemLines = formatSchemaLines(itemSchema, defs, 0, seen);
641
+ if (itemLines.length > 0) {
642
+ const out: string[] = [`${spaces(indent)}- ${itemLines[0]}`];
643
+ for (let i = 1; i < itemLines.length; i++) {
644
+ out.push(`${spaces(indent + 2)}${itemLines[i]}`);
645
+ }
646
+ return out;
647
+ }
648
+ return [`${spaces(indent)}- {}`];
649
+ }
650
+ return [`${spaces(indent)}array`];
651
+ }
652
+
653
+ const fallback = formatType(schema, defs, seen);
654
+ return fallback ? [`${spaces(indent)}${fallback}`] : [`${spaces(indent)}object`];
655
+ }
656
+
657
+ /**
658
+ * Converts a JSON Schema object into human-readable, agent-friendly YAML lines.
659
+ */
660
+ export function schemaToYamlLines(
661
+ /** JSON Schema object to format. */
662
+ schema: Record<string, unknown>,
663
+ /** Starting indentation column in spaces (default 0). */
664
+ indent = 0,
665
+ ): string[] {
666
+ const defs = ((schema.definitions ?? schema.$defs) as Record<string, unknown>) ?? {};
667
+ const lines: string[] = [];
668
+ if (typeof schema.description === "string" && schema.description.trim().length > 0) {
669
+ for (const dLine of schema.description.trim().split("\n")) {
670
+ lines.push(`${spaces(indent)}# ${dLine.trim()}`);
671
+ }
672
+ }
673
+ lines.push(...formatSchemaLines(schema, defs, indent, new Set<string>()));
674
+ return lines;
675
+ }
676
+
407
677
  // ── Main Help Render ──────────────────────────────────────────────────────────
408
678
 
409
- function appendNotesBox(lines: string[], notes: string | undefined, appKey: string, hw: number, color: boolean): void {
679
+ /**
680
+ * Optional rendering options for CLI help output.
681
+ */
682
+ export interface CliHelpRenderOptions {
683
+ /** Override TTY detection for testing or headless environments. */
684
+ isTTY?: boolean;
685
+ /** Force schema display in TTY mode (always included by default in non-TTY). */
686
+ showSchema?: boolean;
687
+ }
688
+
689
+ /** Appends notes section to lines, using boxes in TTY mode and clean indentation in non-TTY mode. */
690
+ function appendNotesBox(
691
+ /** Accumulator lines for help output. */
692
+ lines: string[],
693
+ /** Raw notes text from schema. */
694
+ notes: string | undefined,
695
+ /** Program key for placeholder resolution. */
696
+ appKey: string,
697
+ /** Available terminal width. */
698
+ hw: number,
699
+ /** Whether ANSI color styling is enabled. */
700
+ color: boolean,
701
+ /** Whether output is targeting a TTY terminal. */
702
+ isTTY: boolean,
703
+ ): void {
410
704
  if ((notes ?? "").length === 0) {
411
705
  return;
412
706
  }
413
707
  const resolved = cliResolveNotes(notes ?? "", appKey);
414
708
  lines.push("");
415
- lines.push(renderTextBox("Notes", wrapText(resolved, hw - 4), hw, color).join("\n"));
709
+ if (isTTY) {
710
+ lines.push(renderTextBox("Notes", wrapText(resolved, hw - 4), hw, color).join("\n"));
711
+ } else {
712
+ lines.push(renderPlainSection("Notes", wrapText(resolved, hw - 4)).join("\n"));
713
+ }
416
714
  }
417
715
 
418
716
  /**
419
717
  * Renders full help for the app root or a nested command, following `helpPath` from the root key.
420
- * `useStderr` is reserved for call-site consistency; width and color use stdout TTY.
718
+ * In TTY mode, renders rounded UTF-8 boxes with ANSI color.
719
+ * In non-TTY mode, strips boxes and borders, and renders full untruncated YAML schemas by default.
421
720
  */
422
- export function cliHelpRender(schema: CliRouter, helpPath: string[], _useStderr: boolean): string {
721
+ export function cliHelpRender(
722
+ /** Root command presentation schema. */
723
+ schema: CliRouter,
724
+ /** Segment path to the target command node. */
725
+ helpPath: string[],
726
+ /** Whether output will be directed to stderr. */
727
+ useStderr: boolean,
728
+ /** Optional rendering overrides. */
729
+ opts?: CliHelpRenderOptions,
730
+ ): string {
423
731
  const hw = getHelpWidth();
424
- const color = isStdoutTTY();
732
+ const isTTY = opts?.isTTY ?? isOutputTTY(useStderr);
733
+ const color = isTTY;
734
+ const showSchema = opts?.showSchema ?? !isTTY;
425
735
 
426
736
  if (helpPath.length === 0) {
427
737
  const lines: string[] = [];
@@ -430,25 +740,43 @@ export function cliHelpRender(schema: CliRouter, helpPath: string[], _useStderr:
430
740
  lines.push(color ? style.white(schema.description) : schema.description);
431
741
  lines.push("");
432
742
  }
433
- lines.push(
434
- renderTextBox(
435
- "Usage",
436
- usageLines(schema.key, helpPath, (schema.commands ?? []).length > 0, false, false, color),
437
- hw,
438
- color,
439
- ).join("\n"),
440
- );
743
+ const usage = usageLines(schema.key, helpPath, (schema.commands ?? []).length > 0, false, false, color);
744
+ if (isTTY) {
745
+ lines.push(renderTextBox("Usage", usage, hw, color).join("\n"));
746
+ } else {
747
+ lines.push(renderPlainSection("Usage", usage).join("\n"));
748
+ }
441
749
 
442
- const optBox = renderTableBox("Options", rowsForOptions(visibleOptions(schema.options), color), hw, color);
750
+ const optRows = rowsForOptions(visibleOptions(schema.options), color);
751
+ const optBox = isTTY ? renderTableBox("Options", optRows, hw, color) : renderPlainTable("Options", optRows, hw);
443
752
  if (optBox.length > 0) {
444
753
  lines.push("");
445
754
  lines.push(optBox.join("\n"));
446
755
  }
447
756
  if ((schema.commands ?? []).length > 0) {
757
+ const subRows = rowsForSubcommands(schema.commands ?? []);
758
+ const subBox = isTTY ? renderTableBox("Commands", subRows, hw, color) : renderPlainTable("Commands", subRows, hw);
448
759
  lines.push("");
449
- lines.push(renderTableBox("Commands", rowsForSubcommands(schema.commands ?? []), hw, color).join("\n"));
760
+ lines.push(subBox.join("\n"));
450
761
  }
451
- appendNotesBox(lines, schema.notes, schema.key, hw, color);
762
+
763
+ if (isCliLeaf(schema as unknown as CliNode) && showSchema) {
764
+ const leaf = schema as unknown as CliLeaf;
765
+ if (leaf.outputSchema !== undefined) {
766
+ const title = isJsonLeaf(leaf) ? "Output Schema (JSON)" : "Output Schema (with --json)";
767
+ const yamlLines = schemaToYamlLines(leaf.outputSchema, 0);
768
+ if (yamlLines.length > 0) {
769
+ lines.push("");
770
+ if (isTTY) {
771
+ lines.push(renderTextBox(title, yamlLines, hw, color).join("\n"));
772
+ } else {
773
+ lines.push(renderPlainSection(title, yamlLines).join("\n"));
774
+ }
775
+ }
776
+ }
777
+ }
778
+
779
+ appendNotesBox(lines, schema.notes, schema.key, hw, color, isTTY);
452
780
  return `${lines.join("\n")}\n\n`;
453
781
  }
454
782
 
@@ -473,41 +801,48 @@ export function cliHelpRender(schema: CliRouter, helpPath: string[], _useStderr:
473
801
  lines.push("");
474
802
  }
475
803
  const nodeIsJsonLeaf = isCliLeaf(node) && isJsonLeaf(node);
476
- lines.push(
477
- renderTextBox(
478
- "Usage",
479
- usageLines(
480
- schema.key,
481
- helpPath,
482
- isCliRouter(node) && node.commands.length > 0,
483
- isCliLeaf(node) && (node.positionals ?? []).length > 0,
484
- nodeIsJsonLeaf,
485
- color,
486
- ),
487
- hw,
488
- color,
489
- ).join("\n"),
804
+ const usage = usageLines(
805
+ schema.key,
806
+ helpPath,
807
+ isCliRouter(node) && node.commands.length > 0,
808
+ isCliLeaf(node) && (node.positionals ?? []).length > 0,
809
+ nodeIsJsonLeaf,
810
+ color,
490
811
  );
812
+ if (isTTY) {
813
+ lines.push(renderTextBox("Usage", usage, hw, color).join("\n"));
814
+ } else {
815
+ lines.push(renderPlainSection("Usage", usage).join("\n"));
816
+ }
491
817
 
492
818
  if (nodeIsJsonLeaf && isCliLeaf(node)) {
493
- const inputBox = renderTableBox("Input", rowsForJsonInput(node.inputSchema), hw, color);
819
+ const inputRows = rowsForJsonInput(node.inputSchema);
820
+ const inputBox = isTTY ? renderTableBox("Input", inputRows, hw, color) : renderPlainTable("Input", inputRows, hw);
494
821
  if (inputBox.length > 0) {
495
822
  lines.push("");
496
823
  lines.push(inputBox.join("\n"));
497
824
  }
825
+ if (showSchema && node.inputSchema !== undefined) {
826
+ const yamlLines = schemaToYamlLines(node.inputSchema, 0);
827
+ if (yamlLines.length > 0) {
828
+ lines.push("");
829
+ if (isTTY) {
830
+ lines.push(renderTextBox("Input Schema", yamlLines, hw, color).join("\n"));
831
+ } else {
832
+ lines.push(renderPlainSection("Input Schema", yamlLines).join("\n"));
833
+ }
834
+ }
835
+ }
498
836
  } else {
499
- const optBox = renderTableBox("Options", rowsForOptions(visibleOptions(node.options), color), hw, color);
837
+ const optRows = rowsForOptions(visibleOptions(node.options), color);
838
+ const optBox = isTTY ? renderTableBox("Options", optRows, hw, color) : renderPlainTable("Options", optRows, hw);
500
839
  if (optBox.length > 0) {
501
840
  lines.push("");
502
841
  lines.push(optBox.join("\n"));
503
842
  }
504
843
 
505
- const posBox = renderTableBox(
506
- "Arguments",
507
- rowsForPositionals(isCliLeaf(node) ? (node.positionals ?? []) : [], color),
508
- hw,
509
- color,
510
- );
844
+ const posRows = rowsForPositionals(isCliLeaf(node) ? (node.positionals ?? []) : [], color);
845
+ const posBox = isTTY ? renderTableBox("Arguments", posRows, hw, color) : renderPlainTable("Arguments", posRows, hw);
511
846
  if (posBox.length > 0) {
512
847
  lines.push("");
513
848
  lines.push(posBox.join("\n"));
@@ -515,14 +850,30 @@ export function cliHelpRender(schema: CliRouter, helpPath: string[], _useStderr:
515
850
  }
516
851
 
517
852
  const subcmds = isCliRouter(node) ? node.commands : [];
518
- const subBox = renderTableBox("Subcommands", rowsForSubcommands(subcmds), hw, color);
853
+ const subRows = rowsForSubcommands(subcmds);
854
+ const subBox = isTTY
855
+ ? renderTableBox("Subcommands", subRows, hw, color)
856
+ : renderPlainTable("Subcommands", subRows, hw);
519
857
  if (subBox.length > 0) {
520
858
  lines.push("");
521
859
  lines.push(subBox.join("\n"));
522
860
  }
523
861
 
862
+ if (isCliLeaf(node) && node.outputSchema !== undefined && showSchema) {
863
+ const title = nodeIsJsonLeaf ? "Output Schema (JSON)" : "Output Schema (with --json)";
864
+ const yamlLines = schemaToYamlLines(node.outputSchema, 0);
865
+ if (yamlLines.length > 0) {
866
+ lines.push("");
867
+ if (isTTY) {
868
+ lines.push(renderTextBox(title, yamlLines, hw, color).join("\n"));
869
+ } else {
870
+ lines.push(renderPlainSection(title, yamlLines).join("\n"));
871
+ }
872
+ }
873
+ }
874
+
524
875
  if ((node.notes ?? "").length > 0) {
525
- appendNotesBox(lines, node.notes, schema.key, hw, color);
876
+ appendNotesBox(lines, node.notes, schema.key, hw, color, isTTY);
526
877
  }
527
878
 
528
879
  return `${lines.join("\n")}\n\n`;