stitchkit 0.90.2 → 0.90.4

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 (57) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/dist/agent-runtime-coding-tools.js +3 -3
  3. package/dist/agent-runtime-harness.js +4 -4
  4. package/dist/agent-runtime-sandbox.js +3 -3
  5. package/dist/agent-runtime.js +7 -7
  6. package/dist/cli.d.ts +14 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +359 -11
  9. package/dist/{index-gbqjt8jz.js → index-19ryv24q.js} +3 -3
  10. package/dist/{index-2hrfpw2c.js → index-32vjke6q.js} +2 -2
  11. package/dist/{index-f475yj00.js → index-6atvfjc1.js} +1 -1
  12. package/dist/{index-79hb1wyh.js → index-7t0wq6j5.js} +3 -3
  13. package/dist/{index-j7q0xj6d.js → index-8crm1srv.js} +1 -1
  14. package/dist/{index-kc6h6hg0.js → index-8z9we758.js} +1 -1
  15. package/dist/{index-r159gjwy.js → index-fenaekmk.js} +94 -8
  16. package/dist/{index-wb15909q.js → index-kzxpsf8y.js} +3 -3
  17. package/dist/{index-44ht2790.js → index-vcnfwrtr.js} +1 -1
  18. package/dist/{index-8dph3pw6.js → index-y47z614h.js} +405 -35
  19. package/dist/testing.js +1 -1
  20. package/dist/tool-invoker.js +4 -4
  21. package/dist/tools/cli-args.d.ts +49 -0
  22. package/dist/tools/cli-args.d.ts.map +1 -1
  23. package/dist/tools/cli-command.d.ts +8 -1
  24. package/dist/tools/cli-command.d.ts.map +1 -1
  25. package/dist/tools/cli-installer.d.ts +29 -0
  26. package/dist/tools/cli-installer.d.ts.map +1 -0
  27. package/dist/tools/cli-manifest.d.ts +83 -0
  28. package/dist/tools/cli-manifest.d.ts.map +1 -0
  29. package/dist/tools/cli-policy.d.ts +6 -1
  30. package/dist/tools/cli-policy.d.ts.map +1 -1
  31. package/dist/tools/cli-profile.d.ts +40 -0
  32. package/dist/tools/cli-profile.d.ts.map +1 -0
  33. package/dist/tools/cli-update.d.ts +64 -0
  34. package/dist/tools/cli-update.d.ts.map +1 -0
  35. package/dist/tools/cli-view.d.ts +12 -0
  36. package/dist/tools/cli-view.d.ts.map +1 -0
  37. package/dist/tools/cli.d.ts +20 -5
  38. package/dist/tools/cli.d.ts.map +1 -1
  39. package/dist/tools/connections/index.d.ts +2 -1
  40. package/dist/tools/connections/index.d.ts.map +1 -1
  41. package/dist/tools/connections/index.js +37 -10
  42. package/dist/tools/connections/mcp.d.ts +2 -1
  43. package/dist/tools/connections/mcp.d.ts.map +1 -1
  44. package/dist/tools/connections/mount.d.ts.map +1 -1
  45. package/dist/tools/connections/openapi.d.ts +2 -1
  46. package/dist/tools/connections/openapi.d.ts.map +1 -1
  47. package/dist/tools/connections/runtime.d.ts +17 -0
  48. package/dist/tools/connections/runtime.d.ts.map +1 -1
  49. package/dist/tools/connections/types.d.ts +18 -1
  50. package/dist/tools/connections/types.d.ts.map +1 -1
  51. package/dist/tools/json-schema-dialect.d.ts +17 -0
  52. package/dist/tools/json-schema-dialect.d.ts.map +1 -0
  53. package/dist/tools/presentation.d.ts +8 -1
  54. package/dist/tools/presentation.d.ts.map +1 -1
  55. package/dist/tools.js +8 -8
  56. package/llms-full.txt +293 -7
  57. package/package.json +1 -1
@@ -3,14 +3,15 @@ import {
3
3
  } from "./index-sbdmyz75.js";
4
4
  import {
5
5
  collectToolSurface
6
- } from "./index-wb15909q.js";
6
+ } from "./index-kzxpsf8y.js";
7
7
  import {
8
8
  createToolRunner,
9
9
  formatToolError
10
- } from "./index-2hrfpw2c.js";
10
+ } from "./index-32vjke6q.js";
11
11
  import {
12
+ toolErrorFromResult,
12
13
  toolResultFromError
13
- } from "./index-44ht2790.js";
14
+ } from "./index-vcnfwrtr.js";
14
15
  import {
15
16
  formatZodError,
16
17
  validateDeclaredOutput
@@ -26,7 +27,7 @@ import {
26
27
  assertUniqueToolName,
27
28
  buildToolPresentationSchema,
28
29
  objectShapeKeys
29
- } from "./index-r159gjwy.js";
30
+ } from "./index-fenaekmk.js";
30
31
  import {
31
32
  jsonSchemaFields
32
33
  } from "./index-cby4ar3v.js";
@@ -38,7 +39,8 @@ import {
38
39
  import { z } from "zod";
39
40
  var NUMERIC_VALUE = /^-(\d+\.?\d*|\.\d+)$/;
40
41
  var BOOL_OPTIONS = new Set(["json", "wait", "quiet", "dry-run", "help"]);
41
- var VALUE_OPTIONS = new Set(["wait-timeout", "output-dir"]);
42
+ var VIEW_OPTIONS = new Set(["count-by", "sum", "by", "top", "table"]);
43
+ var VALUE_OPTIONS = new Set(["wait-timeout", "output-dir", ...VIEW_OPTIONS]);
42
44
  var RESERVED_CLI_OPTIONS = new Set([...BOOL_OPTIONS, ...VALUE_OPTIONS]);
43
45
  function classifyLongOptionToken(token) {
44
46
  if (!token.startsWith("--") || token === "--")
@@ -314,6 +316,7 @@ function parseCliArgs(argv, schema, config = {}) {
314
316
  }
315
317
  const flags = new Map;
316
318
  const boolFlags = new Map;
319
+ const viewFlags = new Map;
317
320
  const positionals = [];
318
321
  const pushFlag = (name, value) => {
319
322
  const existing = flags.get(name);
@@ -399,6 +402,10 @@ function parseCliArgs(argv, schema, config = {}) {
399
402
  throw new CliArgumentError("--wait-timeout must be a positive number");
400
403
  }
401
404
  options.waitTimeout = timeout;
405
+ } else if (VIEW_OPTIONS.has(name)) {
406
+ if (viewFlags.has(name))
407
+ throw new CliArgumentError(`--${name} was given twice`);
408
+ viewFlags.set(name, value);
402
409
  } else {
403
410
  options.outputDir = value;
404
411
  }
@@ -432,12 +439,24 @@ function parseCliArgs(argv, schema, config = {}) {
432
439
  }
433
440
  pushFlag(name, value);
434
441
  }
442
+ const view = resolveCliView(viewFlags);
443
+ if (view)
444
+ options.view = view;
435
445
  const toolArgs = {};
436
446
  const fillable = config.positionals === undefined ? [...fields.entries()].filter(([, info]) => info.kind !== "boolean").map(([name]) => name) : [...config.positionals];
447
+ const variadicTail = config.positionals !== undefined && fields.get(fillable[fillable.length - 1] ?? "")?.kind === "array" ? fillable[fillable.length - 1] : undefined;
437
448
  let pi = 0;
438
449
  for (const key of fillable) {
439
450
  if (pi >= positionals.length)
440
451
  break;
452
+ if (key === variadicTail) {
453
+ if (flags.has(key)) {
454
+ throw new CliArgumentError(`--${key} conflicts with the positional values for "${key}" — pass one form, not both`);
455
+ }
456
+ toolArgs[key] = coerceField(fields.get(key), positionals.slice(pi));
457
+ pi = positionals.length;
458
+ break;
459
+ }
441
460
  if (flags.has(key))
442
461
  continue;
443
462
  const value = positionals[pi++];
@@ -477,6 +496,112 @@ function parseCliArgs(argv, schema, config = {}) {
477
496
  }
478
497
  return { toolArgs, options };
479
498
  }
499
+ function extractCliGlobalOptions(argv, schema) {
500
+ if (schema === undefined)
501
+ return { argv: [...argv], globals: {} };
502
+ const fields = describeSchemaFields(schema);
503
+ const rest = [];
504
+ const raw = new Map;
505
+ let ended = false;
506
+ for (let i = 0;i < argv.length; i++) {
507
+ const token = argv[i];
508
+ if (token === undefined)
509
+ continue;
510
+ if (!ended && token === "--")
511
+ ended = true;
512
+ const option = ended ? undefined : classifyLongOptionToken(token);
513
+ const info = option && option.globalKind === undefined ? fields.get(option.name) : undefined;
514
+ if (!option || !info) {
515
+ rest.push(token);
516
+ continue;
517
+ }
518
+ let { value } = option;
519
+ if (value === undefined && info.kind === "boolean") {
520
+ value = "true";
521
+ } else if (value === undefined) {
522
+ const next = argv[i + 1];
523
+ if (next !== undefined && (!next.startsWith("-") || NUMERIC_VALUE.test(next))) {
524
+ value = next;
525
+ i++;
526
+ } else {
527
+ throw new CliArgumentError(`--${option.name} requires a value`);
528
+ }
529
+ }
530
+ const existing = raw.get(option.name);
531
+ if (existing)
532
+ existing.push(value);
533
+ else
534
+ raw.set(option.name, [value]);
535
+ }
536
+ const args = {};
537
+ for (const [name, values] of raw)
538
+ args[name] = coerceField(fields.get(name), values);
539
+ const parsed = schema.safeParse(coerceJsonArgs(args, schema));
540
+ if (!parsed.success) {
541
+ const issue = parsed.error.issues[0];
542
+ const field = issue?.path[0];
543
+ const where = typeof field === "string" ? `--${field}` : "application option";
544
+ throw new CliArgumentError(`${where}: ${issue?.message ?? "invalid value"}`);
545
+ }
546
+ return { argv: rest, globals: parsed.data };
547
+ }
548
+ function resolveCliView(flags) {
549
+ if (flags.size === 0)
550
+ return;
551
+ const countBy = flags.get("count-by");
552
+ const sum = flags.get("sum");
553
+ const table = flags.get("table");
554
+ const by = flags.get("by");
555
+ const rawTop = flags.get("top");
556
+ const named = [
557
+ ["--count-by", countBy],
558
+ ["--sum", sum],
559
+ ["--table", table]
560
+ ].filter(([, value]) => value !== undefined);
561
+ if (named.length > 1) {
562
+ throw new CliArgumentError(`${named.map(([flag]) => flag).join(" and ")} ask for different shapes — pass one`);
563
+ }
564
+ let top;
565
+ if (rawTop !== undefined) {
566
+ top = Number(rawTop);
567
+ if (!Number.isInteger(top) || top <= 0) {
568
+ throw new CliArgumentError("--top must be a positive whole number");
569
+ }
570
+ }
571
+ if (table !== undefined) {
572
+ if (by !== undefined || top !== undefined) {
573
+ throw new CliArgumentError("--table lists records; it takes neither --by nor --top");
574
+ }
575
+ const fields = table.split(",").map((field) => field.trim()).filter(Boolean);
576
+ if (fields.length === 0)
577
+ throw new CliArgumentError("--table needs at least one field");
578
+ return { kind: "table", fields };
579
+ }
580
+ if (sum !== undefined) {
581
+ if (top !== undefined && by === undefined) {
582
+ throw new CliArgumentError("--top needs --by: a single sum has nothing to rank");
583
+ }
584
+ return {
585
+ kind: "sum",
586
+ field: sum,
587
+ ...by !== undefined && { by },
588
+ ...top !== undefined && { top }
589
+ };
590
+ }
591
+ if (countBy !== undefined) {
592
+ if (by !== undefined) {
593
+ throw new CliArgumentError("--count-by already names the grouping field; drop --by");
594
+ }
595
+ return { kind: "count", field: countBy, ...top !== undefined && { top } };
596
+ }
597
+ if (by !== undefined) {
598
+ if (top === undefined) {
599
+ throw new CliArgumentError("--by groups a view; pass --count-by, --sum or --top with it");
600
+ }
601
+ return { kind: "count", field: by, top };
602
+ }
603
+ throw new CliArgumentError("--top needs --by, --count-by or --sum to rank");
604
+ }
480
605
 
481
606
  // src/tools/cli-command.ts
482
607
  function defineCliCommand(definition) {
@@ -509,7 +634,7 @@ function cliCommandPresentationSchema(definition) {
509
634
  unrepresentable: "any"
510
635
  });
511
636
  }
512
- async function executeCliCommand(definition, rawArgs, options, writers, coerceJson) {
637
+ async function executeCliCommand(definition, rawArgs, options, writers, coerceJson, globals = {}) {
513
638
  let parsed;
514
639
  try {
515
640
  parsed = definition.input.safeParse(coerceJson ? coerceJsonArgs(rawArgs, definition.input) : rawArgs);
@@ -527,6 +652,7 @@ async function executeCliCommand(definition, rawArgs, options, writers, coerceJs
527
652
  const data = await definition.handler({
528
653
  input: parsed.data,
529
654
  options,
655
+ globals,
530
656
  ...writers
531
657
  });
532
658
  const checked = validateDeclaredOutput(definition.output, data);
@@ -573,6 +699,126 @@ function emitResult(result, writers, opts) {
573
699
  return codes[result.code] ?? 1;
574
700
  }
575
701
 
702
+ // src/tools/cli-view.ts
703
+ var ABSENT = "(absent)";
704
+ function selectRecords(data) {
705
+ if (Array.isArray(data))
706
+ return data;
707
+ if (isRecord(data)) {
708
+ const arrays = Object.entries(data).filter(([, value]) => Array.isArray(value));
709
+ const only = arrays[0];
710
+ if (arrays.length === 1 && only && Array.isArray(only[1]))
711
+ return only[1];
712
+ if (arrays.length > 1) {
713
+ throw new CliArgumentError(`an aggregate needs one collection, but the result carries several: ${arrays.map(([key]) => key).join(", ")} — name the shape you want with jq instead`);
714
+ }
715
+ }
716
+ throw new CliArgumentError("an aggregate needs a collection; this result is not one");
717
+ }
718
+ function readField(record, path) {
719
+ let current = record;
720
+ for (const segment of path) {
721
+ if (!isRecord(current))
722
+ return;
723
+ current = current[segment];
724
+ }
725
+ return current;
726
+ }
727
+ function fieldPath(field) {
728
+ const path = field.split(".");
729
+ if (path.some((segment) => segment.length === 0 || isUnsafeKey(segment))) {
730
+ throw new CliArgumentError(`"${field}" is not a readable field path`);
731
+ }
732
+ return path;
733
+ }
734
+ function availableFields(records) {
735
+ const keys = new Set;
736
+ for (const record of records) {
737
+ if (isRecord(record))
738
+ for (const key of Object.keys(record))
739
+ keys.add(key);
740
+ }
741
+ return [...keys].sort();
742
+ }
743
+ function assertFieldPresent(records, field) {
744
+ const path = fieldPath(field);
745
+ const present = records.some((record) => readField(record, path) !== undefined);
746
+ if (!present) {
747
+ const available = availableFields(records);
748
+ throw new CliArgumentError(`no record carries the field "${field}"${available.length > 0 ? ` — available: ${available.join(", ")}` : ""}`);
749
+ }
750
+ return path;
751
+ }
752
+ function groupKey(value) {
753
+ if (value === undefined)
754
+ return ABSENT;
755
+ if (value === null)
756
+ return "null";
757
+ if (typeof value === "object")
758
+ return JSON.stringify(value);
759
+ return String(value);
760
+ }
761
+ function ordered(totals, top) {
762
+ const entries = [...totals].sort(([leftKey, left], [rightKey, right]) => right === left ? leftKey.localeCompare(rightKey) : right - left);
763
+ const sliced = top === undefined ? entries : entries.slice(0, top);
764
+ return Object.fromEntries(sliced);
765
+ }
766
+ function numericValue(value, field) {
767
+ if (value === undefined || value === null)
768
+ return;
769
+ if (typeof value === "number" && Number.isFinite(value))
770
+ return value;
771
+ if (typeof value === "bigint")
772
+ return Number(value);
773
+ throw new CliArgumentError(`--sum ${field}: "${groupKey(value)}" is not a number`);
774
+ }
775
+ function renderTable(records, fields) {
776
+ const paths = fields.map((field) => assertFieldPresent(records, field));
777
+ const cell = (value) => {
778
+ if (value === undefined)
779
+ return "";
780
+ if (value === null)
781
+ return "null";
782
+ if (typeof value === "object")
783
+ return JSON.stringify(value);
784
+ return String(value);
785
+ };
786
+ const rows = records.map((record) => paths.map((path) => cell(readField(record, path))));
787
+ const widths = fields.map((field, column) => Math.max(field.length, ...rows.map((row) => row[column]?.length ?? 0)));
788
+ const line = (cells) => cells.map((value, column) => value.padEnd(widths[column] ?? value.length)).join(" ").trimEnd();
789
+ return `${[line(fields), line(widths.map((width) => "-".repeat(width))), ...rows.map(line)].join(`
790
+ `)}
791
+ `;
792
+ }
793
+ function renderCliView(data, view) {
794
+ const records = selectRecords(data);
795
+ if (view.kind === "table")
796
+ return { kind: "text", text: renderTable(records, view.fields) };
797
+ if (view.kind === "count") {
798
+ const path2 = assertFieldPresent(records, view.field);
799
+ const totals2 = new Map;
800
+ for (const record of records) {
801
+ const key = groupKey(readField(record, path2));
802
+ totals2.set(key, (totals2.get(key) ?? 0) + 1);
803
+ }
804
+ return { kind: "json", data: ordered(totals2, view.top) };
805
+ }
806
+ const path = assertFieldPresent(records, view.field);
807
+ if (view.by === undefined) {
808
+ let total = 0;
809
+ for (const record of records)
810
+ total += numericValue(readField(record, path), view.field) ?? 0;
811
+ return { kind: "json", data: total };
812
+ }
813
+ const byPath = assertFieldPresent(records, view.by);
814
+ const totals = new Map;
815
+ for (const record of records) {
816
+ const key = groupKey(readField(record, byPath));
817
+ totals.set(key, (totals.get(key) ?? 0) + (numericValue(readField(record, path), view.field) ?? 0));
818
+ }
819
+ return { kind: "json", data: ordered(totals, view.top) };
820
+ }
821
+
576
822
  // src/tools/wait-core.ts
577
823
  var DEFAULT_BACKOFF = [2, 3, 5, 5, 8, 10];
578
824
  var DEFAULT_TIMEOUT = 600;
@@ -1121,7 +1367,11 @@ var GLOBAL_OPTIONS = [
1121
1367
  ["--output-dir <dir>", "Download result media into a directory"],
1122
1368
  ["--quiet", "Suppress non-essential stderr output"],
1123
1369
  ["--dry-run", "Print the resolved call without executing it"],
1124
- ["--help, -h", "Show help for a command"]
1370
+ ["--help, -h", "Show help for a command"],
1371
+ ["--count-by <field>", "Count records per distinct value of a field"],
1372
+ ["--sum <field>", "Total a numeric field, optionally grouped by --by"],
1373
+ ["--top <n> --by <f>", "Keep only the n largest groups"],
1374
+ ["--table <a,b>", "Render the named fields as an aligned table"]
1125
1375
  ];
1126
1376
  async function readPipedStdin() {
1127
1377
  if (process.stdin.isTTY)
@@ -1190,7 +1440,19 @@ function collectPassthrough(toolArgs, field, knownKeys) {
1190
1440
  const base = passthroughBase(toolArgs[field]);
1191
1441
  toolArgs[field] = base ? { ...base, ...bag } : bag;
1192
1442
  }
1193
- function renderTopHelp(name, version, commands, defaultCommand) {
1443
+ function applicationOptionLines(fields) {
1444
+ if (fields.length === 0)
1445
+ return [];
1446
+ const labels = new Map(fields.map((field) => [field.name, `--${field.name} <${typeLabel(field.schema)}>`]));
1447
+ const width = Math.max(...fields.map((field) => labels.get(field.name)?.length ?? 0));
1448
+ return [
1449
+ "",
1450
+ "Application options:",
1451
+ ...fields.map((field) => ` ${padRight(labels.get(field.name) ?? `--${field.name}`, width)} ${field.description ?? ""}`.trimEnd())
1452
+ ];
1453
+ }
1454
+ function renderTopHelp(input) {
1455
+ const { name, version, commands, defaultCommand } = input;
1194
1456
  const lines = [
1195
1457
  `${name} ${version}`,
1196
1458
  "",
@@ -1202,29 +1464,35 @@ function renderTopHelp(name, version, commands, defaultCommand) {
1202
1464
  for (const [command, descriptor] of commands) {
1203
1465
  lines.push(` ${padRight(command, width)} ${summarize(descriptor.description)}${command === defaultCommand ? " (default)" : ""}`);
1204
1466
  }
1467
+ if (input.unavailable !== undefined) {
1468
+ lines.push("", `Managed commands are unavailable: ${input.unavailable}`);
1469
+ }
1205
1470
  lines.push("", "Global options:");
1206
1471
  const optWidth = Math.max(...GLOBAL_OPTIONS.map(([flag]) => flag.length));
1207
1472
  for (const [flag, desc] of GLOBAL_OPTIONS)
1208
1473
  lines.push(` ${padRight(flag, optWidth)} ${desc}`);
1474
+ lines.push(...applicationOptionLines(input.applicationOptions));
1209
1475
  lines.push("", `Run "${name} <command> --help" for command-specific flags.`);
1210
1476
  return `${lines.join(`
1211
1477
  `)}
1212
1478
  `;
1213
1479
  }
1214
- function renderCommandHelp(name, command, descriptor) {
1480
+ function renderCommandHelp(name, command, descriptor, applicationOptions = []) {
1215
1481
  const fields = jsonSchemaFields(descriptor.presentationSchema);
1216
1482
  const fieldsByName = new Map(fields.map((field) => [field.name, field]));
1483
+ const kinds = describeSchemaFields(descriptor.argumentSchema);
1217
1484
  const positionals = [];
1218
- const positionalNames = descriptor.positionals ?? [...describeSchemaFields(descriptor.argumentSchema)].filter(([, info]) => info.kind !== "boolean").map(([fieldName]) => fieldName);
1485
+ const positionalNames = descriptor.positionals ?? [...kinds].filter(([, info]) => info.kind !== "boolean").map(([fieldName]) => fieldName);
1219
1486
  for (const fieldName of positionalNames) {
1220
1487
  const field = fieldsByName.get(fieldName);
1221
1488
  if (field)
1222
1489
  positionals.push(field);
1223
1490
  }
1224
- const positionalSyntax = new Map(positionals.map((field) => [
1225
- field.name,
1226
- field.required ? `<${field.name}>` : `[${field.name}]`
1227
- ]));
1491
+ const variadicTail = descriptor.positionals !== undefined && kinds.get(descriptor.positionals[descriptor.positionals.length - 1] ?? "")?.kind === "array" ? descriptor.positionals[descriptor.positionals.length - 1] : undefined;
1492
+ const positionalSyntax = new Map(positionals.map((field) => {
1493
+ const label = field.name === variadicTail ? `${field.name}...` : field.name;
1494
+ return [field.name, field.required ? `<${label}>` : `[${label}]`];
1495
+ }));
1228
1496
  const usage = [
1229
1497
  `Usage: ${name} ${command}`,
1230
1498
  ...positionals.map((field) => positionalSyntax.get(field.name) ?? field.name),
@@ -1248,6 +1516,9 @@ function renderCommandHelp(name, command, descriptor) {
1248
1516
  }
1249
1517
  lines.push("");
1250
1518
  }
1519
+ const applicationLines = applicationOptionLines(applicationOptions);
1520
+ if (applicationLines.length > 0)
1521
+ lines.push(...applicationLines.slice(1), "");
1251
1522
  return `${lines.join(`
1252
1523
  `)}
1253
1524
  `;
@@ -1266,15 +1537,20 @@ function nativeDescriptor(definition) {
1266
1537
  presentationSchema: cliCommandPresentationSchema(definition)
1267
1538
  };
1268
1539
  }
1269
- function assertCommandShape(name, descriptor, exists, passthroughField) {
1540
+ function assertCommandShape(name, descriptor, exists, passthroughField, applicationGlobals = new Set) {
1270
1541
  assertUniqueToolName(name, exists, "CLI command");
1271
1542
  if (name === "help" || name === "version") {
1272
1543
  throw new Error(`[stitchkit] CLI command "${name}" is reserved`);
1273
1544
  }
1274
- const conflicting = jsonSchemaFields(descriptor.presentationSchema).map((field) => field.name).filter((field) => RESERVED_CLI_OPTIONS.has(field));
1545
+ const fieldNames = jsonSchemaFields(descriptor.presentationSchema).map((field) => field.name);
1546
+ const conflicting = fieldNames.filter((field) => RESERVED_CLI_OPTIONS.has(field));
1275
1547
  if (conflicting.length > 0) {
1276
1548
  throw new Error(`[stitchkit] CLI command "${name}" declares reserved option field(s): ${conflicting.join(", ")}`);
1277
1549
  }
1550
+ const shadowed = fieldNames.filter((field) => applicationGlobals.has(field));
1551
+ if (shadowed.length > 0) {
1552
+ throw new Error(`[stitchkit] CLI command "${name}" declares field(s) shadowed by application global options: ${shadowed.join(", ")}`);
1553
+ }
1278
1554
  if (passthroughField !== undefined && descriptor.aliases.has(passthroughField)) {
1279
1555
  throw new Error(`[stitchkit] CLI command "${name}" cannot alias passthrough field "${passthroughField}"`);
1280
1556
  }
@@ -1338,30 +1614,78 @@ async function downloadResults(files, dir, stderr, quiet, allowPrivate, maxBytes
1338
1614
  return succeeded;
1339
1615
  }
1340
1616
  async function createCli(config) {
1617
+ let pendingWrite;
1618
+ let writeFailed = false;
1341
1619
  const writeFd = (fd, text) => {
1342
- try {
1343
- writeSync(fd, text);
1344
- } catch {
1345
- (fd === 1 ? process.stdout : process.stderr).write(text);
1620
+ const bytes = Buffer.from(text, "utf8");
1621
+ let offset = 0;
1622
+ while (offset < bytes.length) {
1623
+ try {
1624
+ offset += writeSync(fd, bytes, offset, bytes.length - offset);
1625
+ } catch (error) {
1626
+ if (isRecord(error) && error.code === "EAGAIN") {
1627
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1);
1628
+ continue;
1629
+ }
1630
+ const stream = fd === 1 ? process.stdout : process.stderr;
1631
+ const rest = bytes.subarray(offset);
1632
+ pendingWrite = new Promise((resolve2) => {
1633
+ stream.write(rest, (failure) => {
1634
+ if (failure)
1635
+ writeFailed = true;
1636
+ resolve2();
1637
+ });
1638
+ });
1639
+ return;
1640
+ }
1346
1641
  }
1347
1642
  };
1348
1643
  const stdout = config.stdout ?? ((text) => writeFd(1, text));
1349
1644
  const stderr = config.stderr ?? ((text) => writeFd(2, text));
1350
- const exit = config.exit ?? ((code) => void process.exit(code));
1645
+ const exit = config.exit ?? ((code) => {
1646
+ if (pendingWrite === undefined)
1647
+ process.exit(code);
1648
+ else
1649
+ pendingWrite.then(() => process.exit(writeFailed ? 1 : code));
1650
+ });
1351
1651
  const argv = config.argv ?? process.argv.slice(2);
1352
1652
  const readStdin = config.stdin ?? readPipedStdin;
1353
1653
  if (config.auth !== undefined && config.resolveAuth !== undefined) {
1354
1654
  throw new Error("[stitchkit] createCli: use either auth or resolveAuth, not both");
1355
1655
  }
1656
+ const applicationOptions = config.globalOptions ? jsonSchemaFields(buildToolPresentationSchema({
1657
+ inputSchema: config.globalOptions,
1658
+ unrepresentable: "any"
1659
+ })) : [];
1660
+ const applicationGlobalNames = new Set(applicationOptions.map((field) => field.name));
1661
+ for (const name of applicationGlobalNames) {
1662
+ if (RESERVED_CLI_OPTIONS.has(name)) {
1663
+ throw new Error(`[stitchkit] CLI global option "--${name}" is reserved by the framework`);
1664
+ }
1665
+ }
1356
1666
  const nativeCommands = new Map;
1357
1667
  const nativeHelp = new Map;
1358
1668
  for (const definition of config.commands ?? []) {
1359
1669
  const descriptor2 = applyCliPresentationPolicy(definition.name, nativeDescriptor(definition), config);
1360
- assertCommandShape(definition.name, descriptor2, nativeCommands.has(definition.name), config.passthrough?.[definition.name]);
1670
+ assertCommandShape(definition.name, descriptor2, nativeCommands.has(definition.name), config.passthrough?.[definition.name], applicationGlobalNames);
1361
1671
  nativeCommands.set(definition.name, definition);
1362
1672
  nativeHelp.set(definition.name, descriptor2);
1363
1673
  }
1364
- const route = routeCliArgv(argv, config.defaultCommand);
1674
+ let globals;
1675
+ let routableArgv;
1676
+ try {
1677
+ const lifted = extractCliGlobalOptions(argv, config.globalOptions);
1678
+ globals = lifted.globals;
1679
+ routableArgv = lifted.argv;
1680
+ } catch (error) {
1681
+ if (!(error instanceof CliArgumentError))
1682
+ throw error;
1683
+ stderr(`${error.message}
1684
+ `);
1685
+ return exit(2);
1686
+ }
1687
+ const typedGlobals = globals;
1688
+ const route = routeCliArgv(routableArgv, config.defaultCommand);
1365
1689
  if (route.error) {
1366
1690
  stderr(`${route.error}
1367
1691
  `);
@@ -1381,7 +1705,7 @@ async function createCli(config) {
1381
1705
  if (!descriptor2)
1382
1706
  throw new Error("[stitchkit] native CLI descriptor invariant failed");
1383
1707
  if (helpRequested) {
1384
- stdout(renderCommandHelp(config.name, command, descriptor2));
1708
+ stdout(renderCommandHelp(config.name, command, descriptor2, applicationOptions));
1385
1709
  return exit(0);
1386
1710
  }
1387
1711
  const prepared2 = await prepareInvocation(command, commandArgv, descriptor2, config, readStdin);
@@ -1392,7 +1716,7 @@ async function createCli(config) {
1392
1716
  }
1393
1717
  const { toolArgs: toolArgs2, options: options2 } = prepared2;
1394
1718
  if (options2.help) {
1395
- stdout(renderCommandHelp(config.name, command, descriptor2));
1719
+ stdout(renderCommandHelp(config.name, command, descriptor2, applicationOptions));
1396
1720
  return exit(0);
1397
1721
  }
1398
1722
  if (options2.wait) {
@@ -1415,7 +1739,7 @@ async function createCli(config) {
1415
1739
  `);
1416
1740
  return exit(0);
1417
1741
  }
1418
- const result2 = await executeCliCommand(native, toolArgs2, options2, { stdout, stderr }, config.coerceJsonArgs ?? true);
1742
+ const result2 = await executeCliCommand(native, toolArgs2, options2, { stdout, stderr }, config.coerceJsonArgs ?? true, globals);
1419
1743
  const emission = prepareCliCommandEmission(native, result2, options2);
1420
1744
  let emittedExitCode;
1421
1745
  if (emission.result.ok && emission.presentation !== undefined) {
@@ -1433,12 +1757,26 @@ async function createCli(config) {
1433
1757
  }
1434
1758
  let authPromise;
1435
1759
  const resolveIdentity = () => {
1436
- authPromise ??= Promise.resolve(config.resolveAuth ? config.resolveAuth() : config.auth);
1760
+ authPromise ??= (async () => config.resolveAuth ? await config.resolveAuth(typedGlobals) : await config.auth)();
1437
1761
  return authPromise;
1438
1762
  };
1439
1763
  const dynamicSurface = typeof config.services === "function" || typeof config.runtimeTools === "function";
1440
1764
  const buildManagedSurface = async (forExecution) => {
1441
- const auth = dynamicSurface || forExecution ? await resolveIdentity() : undefined;
1765
+ let auth;
1766
+ if (dynamicSurface || forExecution) {
1767
+ try {
1768
+ auth = await resolveIdentity();
1769
+ } catch (error) {
1770
+ const failure = toolResultFromError(error);
1771
+ const normalized = toolErrorFromResult(failure);
1772
+ return {
1773
+ resolved: false,
1774
+ failure,
1775
+ reason: `${normalized.code}: ${normalized.message}`,
1776
+ help: new Map(nativeHelp)
1777
+ };
1778
+ }
1779
+ }
1442
1780
  const services = typeof config.services === "function" ? config.services(auth) : config.services ?? [];
1443
1781
  const runtimeTools = typeof config.runtimeTools === "function" ? config.runtimeTools(auth) : config.runtimeTools ?? [];
1444
1782
  const tools = new Map;
@@ -1448,19 +1786,34 @@ async function createCli(config) {
1448
1786
  transport: "CLI"
1449
1787
  })) {
1450
1788
  const descriptor2 = applyCliPresentationPolicy(mountable.name, managedDescriptor(mountable), config);
1451
- assertCommandShape(mountable.name, descriptor2, help.has(mountable.name), config.passthrough?.[mountable.name]);
1789
+ assertCommandShape(mountable.name, descriptor2, help.has(mountable.name), config.passthrough?.[mountable.name], applicationGlobalNames);
1452
1790
  tools.set(mountable.name, mountable);
1453
1791
  help.set(mountable.name, descriptor2);
1454
1792
  }
1455
1793
  assertCliPoliciesResolved(help, config);
1456
- return { auth, help, tools };
1794
+ return { resolved: true, auth, help, tools };
1457
1795
  };
1458
1796
  const topLevelHelp = route.topLevelHelp || command === undefined || command === "help" || command === "--help" || command === "-h";
1459
1797
  const managed = await buildManagedSurface(!topLevelHelp && !helpRequested);
1460
1798
  if (topLevelHelp) {
1461
- stdout(renderTopHelp(config.name, config.version, managed.help, config.defaultCommand));
1799
+ stdout(renderTopHelp({
1800
+ name: config.name,
1801
+ version: config.version,
1802
+ commands: managed.help,
1803
+ defaultCommand: config.defaultCommand,
1804
+ applicationOptions,
1805
+ ...managed.resolved ? {} : { unavailable: managed.reason }
1806
+ }));
1462
1807
  return exit(0);
1463
1808
  }
1809
+ if (!managed.resolved) {
1810
+ return exit(emitResult(managed.failure, { stdout, stderr }, {
1811
+ json: beforeSeparator.includes("--json"),
1812
+ toolName: command ?? config.name,
1813
+ errorHint: config.errorHint,
1814
+ exitCodes: { ...DEFAULT_EXIT_CODES, ...config.exitCodes }
1815
+ }));
1816
+ }
1464
1817
  const tool = managed.tools.get(command);
1465
1818
  if (!tool) {
1466
1819
  stderr(`Unknown command "${command}". Run "${config.name} --help" for the command list.
@@ -1471,7 +1824,7 @@ async function createCli(config) {
1471
1824
  if (!descriptor)
1472
1825
  throw new Error("[stitchkit] managed CLI descriptor invariant failed");
1473
1826
  if (helpRequested) {
1474
- stdout(renderCommandHelp(config.name, command, descriptor));
1827
+ stdout(renderCommandHelp(config.name, command, descriptor, applicationOptions));
1475
1828
  return exit(0);
1476
1829
  }
1477
1830
  const prepared = await prepareInvocation(command, commandArgv, descriptor, config, readStdin);
@@ -1497,7 +1850,7 @@ async function createCli(config) {
1497
1850
  return exit(2);
1498
1851
  }
1499
1852
  if (options.help) {
1500
- stdout(renderCommandHelp(config.name, command, descriptor));
1853
+ stdout(renderCommandHelp(config.name, command, descriptor, applicationOptions));
1501
1854
  return exit(0);
1502
1855
  }
1503
1856
  if (options.dryRun) {
@@ -1507,7 +1860,7 @@ async function createCli(config) {
1507
1860
  }
1508
1861
  const runTool = createToolRunner({
1509
1862
  source: "cli",
1510
- context: { ...config.context?.(managed.auth), signal: config.signal },
1863
+ context: { ...config.context?.(managed.auth, typedGlobals), signal: config.signal },
1511
1864
  hooks: config.hooks,
1512
1865
  lifecycle: config.lifecycle,
1513
1866
  errorHint: config.errorHint,
@@ -1545,6 +1898,23 @@ async function createCli(config) {
1545
1898
  if (options.outputDir && result.ok && config.download) {
1546
1899
  downloadsOk = await downloadResults(config.download(result.data), options.outputDir, stderr, options.quiet, config.allowPrivateDownloadHosts ?? false, config.maxDownloadBytes ?? DEFAULT_DOWNLOAD_MAX_BYTES, config.downloadTimeoutMs);
1547
1900
  }
1901
+ if (options.view && result.ok) {
1902
+ let viewed;
1903
+ try {
1904
+ viewed = renderCliView(result.data, options.view);
1905
+ } catch (error) {
1906
+ if (!(error instanceof CliArgumentError))
1907
+ throw error;
1908
+ stderr(`${error.message}
1909
+ `);
1910
+ return exit(2);
1911
+ }
1912
+ if (viewed.kind === "text") {
1913
+ stdout(viewed.text);
1914
+ return exit(downloadsOk ? 0 : 1);
1915
+ }
1916
+ result = { ...result, data: viewed.data };
1917
+ }
1548
1918
  const exitCode = emitResult(result, { stdout, stderr }, {
1549
1919
  json: options.json,
1550
1920
  toolName: command,
@@ -1554,4 +1924,4 @@ async function createCli(config) {
1554
1924
  return exit(downloadsOk ? exitCode : 1);
1555
1925
  }
1556
1926
 
1557
- export { WaitTimeoutError, runWaitOperation, fetchGuarded, fetchPinnedDocument, readCapped, parseCliArgs, defineCliCommand, DEFAULT_EXIT_CODES, emitResult, pollUntilDone, createCli };
1927
+ export { WaitTimeoutError, runWaitOperation, fetchGuarded, fetchPinnedDocument, readCapped, routeCliArgv, CliArgumentError, parseCliArgs, extractCliGlobalOptions, defineCliCommand, DEFAULT_EXIT_CODES, emitResult, renderCliView, pollUntilDone, createCli };
package/dist/testing.js CHANGED
@@ -48,7 +48,7 @@ import {
48
48
  mcpProjectionCandidate,
49
49
  prepareProjectedMcpTools,
50
50
  projectToolSurface
51
- } from "./index-r159gjwy.js";
51
+ } from "./index-fenaekmk.js";
52
52
  import {
53
53
  toJsonSchema
54
54
  } from "./index-cby4ar3v.js";