stitchkit 0.90.2 → 0.90.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -15,6 +15,61 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [0.90.3] — 2026-09-15
19
+
20
+ ### Added
21
+
22
+ - **A trailing positional may be a list.** When the last field of a command's
23
+ declared `positionals` is an array, it takes every remaining argv token,
24
+ coerced by the element type: `myapp handoff proj a.md b.md` instead of
25
+ `--files '["a.md","b.md"]'`. One token is a one-element list, so the parsed
26
+ shape never depends on how many a caller happened to pass, and command help
27
+ says which field is the list (`<to> <files...>`). The flag form still works;
28
+ passing both forms in one call is an argument error rather than a silent
29
+ merge. Only the trailing position is variadic — an array declared earlier
30
+ keeps taking exactly one JSON token, unchanged.
31
+ - **`globalOptions` — the application's own invocation context.** A CLI's
32
+ globals were only ever the framework's (`--json`, `--wait`, …), so an app that
33
+ needed `--caller <key>` or `--root <dir>` had to read an environment variable
34
+ or parse argv itself, next to the parser it already had. Declare them as a Zod
35
+ object and `createCli` lifts them out of argv wherever they stand — before or
36
+ after the command name — validates them, keeps them out of every operation's
37
+ arguments, and hands them to `resolveAuth(globals)`, `context(auth, globals)`
38
+ and a native command's `globals`. A name that collides with a framework option
39
+ or with a field of any command is refused at startup instead of shadowing it
40
+ silently, and both help levels list them under `Application options:`.
41
+
42
+ ### Fixed
43
+
44
+ - **Output past the pipe buffer is no longer lost to a slow reader.** The
45
+ default writer called `writeSync` and ignored the byte count it returns. Once
46
+ anything in the process has touched `process.stdout` the descriptor is
47
+ non-blocking, so a write into a pipe whose reader has not started yet stores
48
+ what the 64 KB buffer takes, returns that count and throws nothing: the tail
49
+ vanished with exit code `0`, at exactly 65536 bytes — the same number as the
50
+ truncation the synchronous writer had been introduced to fix, which is why it
51
+ read as already fixed for so long. A consuming CLI saw `… | jq` fail with
52
+ "Unfinished string at column 65536" while the same command redirected to a
53
+ file wrote the whole result. The writer now loops on the returned count and
54
+ treats a full buffer (`EAGAIN`) as a wait rather than a failure, which is what
55
+ a blocking write into an undrained pipe is supposed to do; where an exotic
56
+ descriptor still forces the async fallback, the default `exit` waits for that
57
+ write instead of cutting it off, and output that could not be delivered at all
58
+ now exits non-zero.
59
+ - **Help survives a managed surface that cannot resolve.** A CLI whose commands
60
+ come from a running server declares them with a factory, and the factory needs
61
+ an identity. When `resolveAuth` failed — server down, key file missing — the
62
+ rejection escaped `createCli` and `--help` printed *nothing*: not the native
63
+ commands, which never depended on identity, and not the reason. Calling such a
64
+ name was worse than silence, because the surface never resolved and the answer
65
+ would have been `Unknown command` — a claim that the name does not exist when
66
+ the truth is that it could not be looked up. Top-level help now lists the
67
+ native commands and one line naming the refusal (`Managed commands are
68
+ unavailable: UNREACHABLE: socket closed`), and an unresolvable name answers
69
+ with that refusal and the exit code its error class declares through
70
+ `exitCodes`. Identity is still resolved at most once per invocation, failure
71
+ included.
72
+
18
73
  ## [0.90.2] — 2026-09-15
19
74
 
20
75
  ### Fixed
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  emitResult,
6
6
  parseCliArgs,
7
7
  pollUntilDone
8
- } from "./index-8dph3pw6.js";
8
+ } from "./index-1923shw9.js";
9
9
  import"./index-sbdmyz75.js";
10
10
  import"./index-wb15909q.js";
11
11
  import"./index-2hrfpw2c.js";
@@ -9,6 +9,7 @@ import {
9
9
  formatToolError
10
10
  } from "./index-2hrfpw2c.js";
11
11
  import {
12
+ toolErrorFromResult,
12
13
  toolResultFromError
13
14
  } from "./index-44ht2790.js";
14
15
  import {
@@ -434,10 +435,19 @@ function parseCliArgs(argv, schema, config = {}) {
434
435
  }
435
436
  const toolArgs = {};
436
437
  const fillable = config.positionals === undefined ? [...fields.entries()].filter(([, info]) => info.kind !== "boolean").map(([name]) => name) : [...config.positionals];
438
+ const variadicTail = config.positionals !== undefined && fields.get(fillable[fillable.length - 1] ?? "")?.kind === "array" ? fillable[fillable.length - 1] : undefined;
437
439
  let pi = 0;
438
440
  for (const key of fillable) {
439
441
  if (pi >= positionals.length)
440
442
  break;
443
+ if (key === variadicTail) {
444
+ if (flags.has(key)) {
445
+ throw new CliArgumentError(`--${key} conflicts with the positional values for "${key}" — pass one form, not both`);
446
+ }
447
+ toolArgs[key] = coerceField(fields.get(key), positionals.slice(pi));
448
+ pi = positionals.length;
449
+ break;
450
+ }
441
451
  if (flags.has(key))
442
452
  continue;
443
453
  const value = positionals[pi++];
@@ -477,6 +487,55 @@ function parseCliArgs(argv, schema, config = {}) {
477
487
  }
478
488
  return { toolArgs, options };
479
489
  }
490
+ function extractCliGlobalOptions(argv, schema) {
491
+ if (schema === undefined)
492
+ return { argv: [...argv], globals: {} };
493
+ const fields = describeSchemaFields(schema);
494
+ const rest = [];
495
+ const raw = new Map;
496
+ let ended = false;
497
+ for (let i = 0;i < argv.length; i++) {
498
+ const token = argv[i];
499
+ if (token === undefined)
500
+ continue;
501
+ if (!ended && token === "--")
502
+ ended = true;
503
+ const option = ended ? undefined : classifyLongOptionToken(token);
504
+ const info = option && option.globalKind === undefined ? fields.get(option.name) : undefined;
505
+ if (!option || !info) {
506
+ rest.push(token);
507
+ continue;
508
+ }
509
+ let { value } = option;
510
+ if (value === undefined && info.kind === "boolean") {
511
+ value = "true";
512
+ } else if (value === undefined) {
513
+ const next = argv[i + 1];
514
+ if (next !== undefined && (!next.startsWith("-") || NUMERIC_VALUE.test(next))) {
515
+ value = next;
516
+ i++;
517
+ } else {
518
+ throw new CliArgumentError(`--${option.name} requires a value`);
519
+ }
520
+ }
521
+ const existing = raw.get(option.name);
522
+ if (existing)
523
+ existing.push(value);
524
+ else
525
+ raw.set(option.name, [value]);
526
+ }
527
+ const args = {};
528
+ for (const [name, values] of raw)
529
+ args[name] = coerceField(fields.get(name), values);
530
+ const parsed = schema.safeParse(coerceJsonArgs(args, schema));
531
+ if (!parsed.success) {
532
+ const issue = parsed.error.issues[0];
533
+ const field = issue?.path[0];
534
+ const where = typeof field === "string" ? `--${field}` : "application option";
535
+ throw new CliArgumentError(`${where}: ${issue?.message ?? "invalid value"}`);
536
+ }
537
+ return { argv: rest, globals: parsed.data };
538
+ }
480
539
 
481
540
  // src/tools/cli-command.ts
482
541
  function defineCliCommand(definition) {
@@ -509,7 +568,7 @@ function cliCommandPresentationSchema(definition) {
509
568
  unrepresentable: "any"
510
569
  });
511
570
  }
512
- async function executeCliCommand(definition, rawArgs, options, writers, coerceJson) {
571
+ async function executeCliCommand(definition, rawArgs, options, writers, coerceJson, globals = {}) {
513
572
  let parsed;
514
573
  try {
515
574
  parsed = definition.input.safeParse(coerceJson ? coerceJsonArgs(rawArgs, definition.input) : rawArgs);
@@ -527,6 +586,7 @@ async function executeCliCommand(definition, rawArgs, options, writers, coerceJs
527
586
  const data = await definition.handler({
528
587
  input: parsed.data,
529
588
  options,
589
+ globals,
530
590
  ...writers
531
591
  });
532
592
  const checked = validateDeclaredOutput(definition.output, data);
@@ -1190,7 +1250,19 @@ function collectPassthrough(toolArgs, field, knownKeys) {
1190
1250
  const base = passthroughBase(toolArgs[field]);
1191
1251
  toolArgs[field] = base ? { ...base, ...bag } : bag;
1192
1252
  }
1193
- function renderTopHelp(name, version, commands, defaultCommand) {
1253
+ function applicationOptionLines(fields) {
1254
+ if (fields.length === 0)
1255
+ return [];
1256
+ const labels = new Map(fields.map((field) => [field.name, `--${field.name} <${typeLabel(field.schema)}>`]));
1257
+ const width = Math.max(...fields.map((field) => labels.get(field.name)?.length ?? 0));
1258
+ return [
1259
+ "",
1260
+ "Application options:",
1261
+ ...fields.map((field) => ` ${padRight(labels.get(field.name) ?? `--${field.name}`, width)} ${field.description ?? ""}`.trimEnd())
1262
+ ];
1263
+ }
1264
+ function renderTopHelp(input) {
1265
+ const { name, version, commands, defaultCommand } = input;
1194
1266
  const lines = [
1195
1267
  `${name} ${version}`,
1196
1268
  "",
@@ -1202,29 +1274,35 @@ function renderTopHelp(name, version, commands, defaultCommand) {
1202
1274
  for (const [command, descriptor] of commands) {
1203
1275
  lines.push(` ${padRight(command, width)} ${summarize(descriptor.description)}${command === defaultCommand ? " (default)" : ""}`);
1204
1276
  }
1277
+ if (input.unavailable !== undefined) {
1278
+ lines.push("", `Managed commands are unavailable: ${input.unavailable}`);
1279
+ }
1205
1280
  lines.push("", "Global options:");
1206
1281
  const optWidth = Math.max(...GLOBAL_OPTIONS.map(([flag]) => flag.length));
1207
1282
  for (const [flag, desc] of GLOBAL_OPTIONS)
1208
1283
  lines.push(` ${padRight(flag, optWidth)} ${desc}`);
1284
+ lines.push(...applicationOptionLines(input.applicationOptions));
1209
1285
  lines.push("", `Run "${name} <command> --help" for command-specific flags.`);
1210
1286
  return `${lines.join(`
1211
1287
  `)}
1212
1288
  `;
1213
1289
  }
1214
- function renderCommandHelp(name, command, descriptor) {
1290
+ function renderCommandHelp(name, command, descriptor, applicationOptions = []) {
1215
1291
  const fields = jsonSchemaFields(descriptor.presentationSchema);
1216
1292
  const fieldsByName = new Map(fields.map((field) => [field.name, field]));
1293
+ const kinds = describeSchemaFields(descriptor.argumentSchema);
1217
1294
  const positionals = [];
1218
- const positionalNames = descriptor.positionals ?? [...describeSchemaFields(descriptor.argumentSchema)].filter(([, info]) => info.kind !== "boolean").map(([fieldName]) => fieldName);
1295
+ const positionalNames = descriptor.positionals ?? [...kinds].filter(([, info]) => info.kind !== "boolean").map(([fieldName]) => fieldName);
1219
1296
  for (const fieldName of positionalNames) {
1220
1297
  const field = fieldsByName.get(fieldName);
1221
1298
  if (field)
1222
1299
  positionals.push(field);
1223
1300
  }
1224
- const positionalSyntax = new Map(positionals.map((field) => [
1225
- field.name,
1226
- field.required ? `<${field.name}>` : `[${field.name}]`
1227
- ]));
1301
+ const variadicTail = descriptor.positionals !== undefined && kinds.get(descriptor.positionals[descriptor.positionals.length - 1] ?? "")?.kind === "array" ? descriptor.positionals[descriptor.positionals.length - 1] : undefined;
1302
+ const positionalSyntax = new Map(positionals.map((field) => {
1303
+ const label = field.name === variadicTail ? `${field.name}...` : field.name;
1304
+ return [field.name, field.required ? `<${label}>` : `[${label}]`];
1305
+ }));
1228
1306
  const usage = [
1229
1307
  `Usage: ${name} ${command}`,
1230
1308
  ...positionals.map((field) => positionalSyntax.get(field.name) ?? field.name),
@@ -1248,6 +1326,9 @@ function renderCommandHelp(name, command, descriptor) {
1248
1326
  }
1249
1327
  lines.push("");
1250
1328
  }
1329
+ const applicationLines = applicationOptionLines(applicationOptions);
1330
+ if (applicationLines.length > 0)
1331
+ lines.push(...applicationLines.slice(1), "");
1251
1332
  return `${lines.join(`
1252
1333
  `)}
1253
1334
  `;
@@ -1266,15 +1347,20 @@ function nativeDescriptor(definition) {
1266
1347
  presentationSchema: cliCommandPresentationSchema(definition)
1267
1348
  };
1268
1349
  }
1269
- function assertCommandShape(name, descriptor, exists, passthroughField) {
1350
+ function assertCommandShape(name, descriptor, exists, passthroughField, applicationGlobals = new Set) {
1270
1351
  assertUniqueToolName(name, exists, "CLI command");
1271
1352
  if (name === "help" || name === "version") {
1272
1353
  throw new Error(`[stitchkit] CLI command "${name}" is reserved`);
1273
1354
  }
1274
- const conflicting = jsonSchemaFields(descriptor.presentationSchema).map((field) => field.name).filter((field) => RESERVED_CLI_OPTIONS.has(field));
1355
+ const fieldNames = jsonSchemaFields(descriptor.presentationSchema).map((field) => field.name);
1356
+ const conflicting = fieldNames.filter((field) => RESERVED_CLI_OPTIONS.has(field));
1275
1357
  if (conflicting.length > 0) {
1276
1358
  throw new Error(`[stitchkit] CLI command "${name}" declares reserved option field(s): ${conflicting.join(", ")}`);
1277
1359
  }
1360
+ const shadowed = fieldNames.filter((field) => applicationGlobals.has(field));
1361
+ if (shadowed.length > 0) {
1362
+ throw new Error(`[stitchkit] CLI command "${name}" declares field(s) shadowed by application global options: ${shadowed.join(", ")}`);
1363
+ }
1278
1364
  if (passthroughField !== undefined && descriptor.aliases.has(passthroughField)) {
1279
1365
  throw new Error(`[stitchkit] CLI command "${name}" cannot alias passthrough field "${passthroughField}"`);
1280
1366
  }
@@ -1338,30 +1424,78 @@ async function downloadResults(files, dir, stderr, quiet, allowPrivate, maxBytes
1338
1424
  return succeeded;
1339
1425
  }
1340
1426
  async function createCli(config) {
1427
+ let pendingWrite;
1428
+ let writeFailed = false;
1341
1429
  const writeFd = (fd, text) => {
1342
- try {
1343
- writeSync(fd, text);
1344
- } catch {
1345
- (fd === 1 ? process.stdout : process.stderr).write(text);
1430
+ const bytes = Buffer.from(text, "utf8");
1431
+ let offset = 0;
1432
+ while (offset < bytes.length) {
1433
+ try {
1434
+ offset += writeSync(fd, bytes, offset, bytes.length - offset);
1435
+ } catch (error) {
1436
+ if (isRecord(error) && error.code === "EAGAIN") {
1437
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1);
1438
+ continue;
1439
+ }
1440
+ const stream = fd === 1 ? process.stdout : process.stderr;
1441
+ const rest = bytes.subarray(offset);
1442
+ pendingWrite = new Promise((resolve2) => {
1443
+ stream.write(rest, (failure) => {
1444
+ if (failure)
1445
+ writeFailed = true;
1446
+ resolve2();
1447
+ });
1448
+ });
1449
+ return;
1450
+ }
1346
1451
  }
1347
1452
  };
1348
1453
  const stdout = config.stdout ?? ((text) => writeFd(1, text));
1349
1454
  const stderr = config.stderr ?? ((text) => writeFd(2, text));
1350
- const exit = config.exit ?? ((code) => void process.exit(code));
1455
+ const exit = config.exit ?? ((code) => {
1456
+ if (pendingWrite === undefined)
1457
+ process.exit(code);
1458
+ else
1459
+ pendingWrite.then(() => process.exit(writeFailed ? 1 : code));
1460
+ });
1351
1461
  const argv = config.argv ?? process.argv.slice(2);
1352
1462
  const readStdin = config.stdin ?? readPipedStdin;
1353
1463
  if (config.auth !== undefined && config.resolveAuth !== undefined) {
1354
1464
  throw new Error("[stitchkit] createCli: use either auth or resolveAuth, not both");
1355
1465
  }
1466
+ const applicationOptions = config.globalOptions ? jsonSchemaFields(buildToolPresentationSchema({
1467
+ inputSchema: config.globalOptions,
1468
+ unrepresentable: "any"
1469
+ })) : [];
1470
+ const applicationGlobalNames = new Set(applicationOptions.map((field) => field.name));
1471
+ for (const name of applicationGlobalNames) {
1472
+ if (RESERVED_CLI_OPTIONS.has(name)) {
1473
+ throw new Error(`[stitchkit] CLI global option "--${name}" is reserved by the framework`);
1474
+ }
1475
+ }
1356
1476
  const nativeCommands = new Map;
1357
1477
  const nativeHelp = new Map;
1358
1478
  for (const definition of config.commands ?? []) {
1359
1479
  const descriptor2 = applyCliPresentationPolicy(definition.name, nativeDescriptor(definition), config);
1360
- assertCommandShape(definition.name, descriptor2, nativeCommands.has(definition.name), config.passthrough?.[definition.name]);
1480
+ assertCommandShape(definition.name, descriptor2, nativeCommands.has(definition.name), config.passthrough?.[definition.name], applicationGlobalNames);
1361
1481
  nativeCommands.set(definition.name, definition);
1362
1482
  nativeHelp.set(definition.name, descriptor2);
1363
1483
  }
1364
- const route = routeCliArgv(argv, config.defaultCommand);
1484
+ let globals;
1485
+ let routableArgv;
1486
+ try {
1487
+ const lifted = extractCliGlobalOptions(argv, config.globalOptions);
1488
+ globals = lifted.globals;
1489
+ routableArgv = lifted.argv;
1490
+ } catch (error) {
1491
+ if (!(error instanceof CliArgumentError))
1492
+ throw error;
1493
+ stderr(`${error.message}
1494
+ `);
1495
+ return exit(2);
1496
+ }
1497
+ const typedGlobals = globals;
1498
+ const route = routeCliArgv(routableArgv, config.defaultCommand);
1365
1499
  if (route.error) {
1366
1500
  stderr(`${route.error}
1367
1501
  `);
@@ -1381,7 +1515,7 @@ async function createCli(config) {
1381
1515
  if (!descriptor2)
1382
1516
  throw new Error("[stitchkit] native CLI descriptor invariant failed");
1383
1517
  if (helpRequested) {
1384
- stdout(renderCommandHelp(config.name, command, descriptor2));
1518
+ stdout(renderCommandHelp(config.name, command, descriptor2, applicationOptions));
1385
1519
  return exit(0);
1386
1520
  }
1387
1521
  const prepared2 = await prepareInvocation(command, commandArgv, descriptor2, config, readStdin);
@@ -1392,7 +1526,7 @@ async function createCli(config) {
1392
1526
  }
1393
1527
  const { toolArgs: toolArgs2, options: options2 } = prepared2;
1394
1528
  if (options2.help) {
1395
- stdout(renderCommandHelp(config.name, command, descriptor2));
1529
+ stdout(renderCommandHelp(config.name, command, descriptor2, applicationOptions));
1396
1530
  return exit(0);
1397
1531
  }
1398
1532
  if (options2.wait) {
@@ -1415,7 +1549,7 @@ async function createCli(config) {
1415
1549
  `);
1416
1550
  return exit(0);
1417
1551
  }
1418
- const result2 = await executeCliCommand(native, toolArgs2, options2, { stdout, stderr }, config.coerceJsonArgs ?? true);
1552
+ const result2 = await executeCliCommand(native, toolArgs2, options2, { stdout, stderr }, config.coerceJsonArgs ?? true, globals);
1419
1553
  const emission = prepareCliCommandEmission(native, result2, options2);
1420
1554
  let emittedExitCode;
1421
1555
  if (emission.result.ok && emission.presentation !== undefined) {
@@ -1433,12 +1567,26 @@ async function createCli(config) {
1433
1567
  }
1434
1568
  let authPromise;
1435
1569
  const resolveIdentity = () => {
1436
- authPromise ??= Promise.resolve(config.resolveAuth ? config.resolveAuth() : config.auth);
1570
+ authPromise ??= (async () => config.resolveAuth ? await config.resolveAuth(typedGlobals) : await config.auth)();
1437
1571
  return authPromise;
1438
1572
  };
1439
1573
  const dynamicSurface = typeof config.services === "function" || typeof config.runtimeTools === "function";
1440
1574
  const buildManagedSurface = async (forExecution) => {
1441
- const auth = dynamicSurface || forExecution ? await resolveIdentity() : undefined;
1575
+ let auth;
1576
+ if (dynamicSurface || forExecution) {
1577
+ try {
1578
+ auth = await resolveIdentity();
1579
+ } catch (error) {
1580
+ const failure = toolResultFromError(error);
1581
+ const normalized = toolErrorFromResult(failure);
1582
+ return {
1583
+ resolved: false,
1584
+ failure,
1585
+ reason: `${normalized.code}: ${normalized.message}`,
1586
+ help: new Map(nativeHelp)
1587
+ };
1588
+ }
1589
+ }
1442
1590
  const services = typeof config.services === "function" ? config.services(auth) : config.services ?? [];
1443
1591
  const runtimeTools = typeof config.runtimeTools === "function" ? config.runtimeTools(auth) : config.runtimeTools ?? [];
1444
1592
  const tools = new Map;
@@ -1448,19 +1596,34 @@ async function createCli(config) {
1448
1596
  transport: "CLI"
1449
1597
  })) {
1450
1598
  const descriptor2 = applyCliPresentationPolicy(mountable.name, managedDescriptor(mountable), config);
1451
- assertCommandShape(mountable.name, descriptor2, help.has(mountable.name), config.passthrough?.[mountable.name]);
1599
+ assertCommandShape(mountable.name, descriptor2, help.has(mountable.name), config.passthrough?.[mountable.name], applicationGlobalNames);
1452
1600
  tools.set(mountable.name, mountable);
1453
1601
  help.set(mountable.name, descriptor2);
1454
1602
  }
1455
1603
  assertCliPoliciesResolved(help, config);
1456
- return { auth, help, tools };
1604
+ return { resolved: true, auth, help, tools };
1457
1605
  };
1458
1606
  const topLevelHelp = route.topLevelHelp || command === undefined || command === "help" || command === "--help" || command === "-h";
1459
1607
  const managed = await buildManagedSurface(!topLevelHelp && !helpRequested);
1460
1608
  if (topLevelHelp) {
1461
- stdout(renderTopHelp(config.name, config.version, managed.help, config.defaultCommand));
1609
+ stdout(renderTopHelp({
1610
+ name: config.name,
1611
+ version: config.version,
1612
+ commands: managed.help,
1613
+ defaultCommand: config.defaultCommand,
1614
+ applicationOptions,
1615
+ ...managed.resolved ? {} : { unavailable: managed.reason }
1616
+ }));
1462
1617
  return exit(0);
1463
1618
  }
1619
+ if (!managed.resolved) {
1620
+ return exit(emitResult(managed.failure, { stdout, stderr }, {
1621
+ json: beforeSeparator.includes("--json"),
1622
+ toolName: command ?? config.name,
1623
+ errorHint: config.errorHint,
1624
+ exitCodes: { ...DEFAULT_EXIT_CODES, ...config.exitCodes }
1625
+ }));
1626
+ }
1464
1627
  const tool = managed.tools.get(command);
1465
1628
  if (!tool) {
1466
1629
  stderr(`Unknown command "${command}". Run "${config.name} --help" for the command list.
@@ -1471,7 +1634,7 @@ async function createCli(config) {
1471
1634
  if (!descriptor)
1472
1635
  throw new Error("[stitchkit] managed CLI descriptor invariant failed");
1473
1636
  if (helpRequested) {
1474
- stdout(renderCommandHelp(config.name, command, descriptor));
1637
+ stdout(renderCommandHelp(config.name, command, descriptor, applicationOptions));
1475
1638
  return exit(0);
1476
1639
  }
1477
1640
  const prepared = await prepareInvocation(command, commandArgv, descriptor, config, readStdin);
@@ -1497,7 +1660,7 @@ async function createCli(config) {
1497
1660
  return exit(2);
1498
1661
  }
1499
1662
  if (options.help) {
1500
- stdout(renderCommandHelp(config.name, command, descriptor));
1663
+ stdout(renderCommandHelp(config.name, command, descriptor, applicationOptions));
1501
1664
  return exit(0);
1502
1665
  }
1503
1666
  if (options.dryRun) {
@@ -1507,7 +1670,7 @@ async function createCli(config) {
1507
1670
  }
1508
1671
  const runTool = createToolRunner({
1509
1672
  source: "cli",
1510
- context: { ...config.context?.(managed.auth), signal: config.signal },
1673
+ context: { ...config.context?.(managed.auth, typedGlobals), signal: config.signal },
1511
1674
  hooks: config.hooks,
1512
1675
  lifecycle: config.lifecycle,
1513
1676
  errorHint: config.errorHint,
@@ -88,5 +88,32 @@ export declare function parseCliArgs(argv: string[], schema: z.ZodType | undefin
88
88
  optionAliases?: ReadonlyMap<string, string>;
89
89
  positionals?: readonly string[];
90
90
  }): ParsedCliArgs;
91
+ /** The application's own global options, lifted out of one invocation's argv. */
92
+ export interface CliGlobalOptionsParse {
93
+ /** argv with the application-global tokens removed, ready for routing. */
94
+ argv: string[];
95
+ /** The validated values, as the application's own schema types them. */
96
+ globals: Record<string, unknown>;
97
+ }
98
+ /**
99
+ * Lift the APPLICATION's global options out of argv, wherever they stand.
100
+ *
101
+ * These are not arguments of any operation: which identity key to use, which
102
+ * checkout a call speaks for, which profile. They belong to the invocation, so
103
+ * they may precede the command name as easily as follow it — and a command's
104
+ * own parser must never see them, or an app-global would read as an unknown
105
+ * flag on every operation that does not declare it.
106
+ *
107
+ * Stripping them BEFORE routing is what keeps this one grammar rather than two:
108
+ * `routeCliArgv` then sees a command where a command is, `parseCliArgs` sees
109
+ * only operation arguments, and `passthrough` cannot swallow an app-global into
110
+ * a freeform bag. The token shape is the same `classifyLongOptionToken` the
111
+ * framework's own globals use, so `--root /x`, `--root=/x` and a bare boolean
112
+ * `--verbose` all behave as they do everywhere else.
113
+ *
114
+ * `--` ends the sweep: past it every token is a literal value, so a positional
115
+ * that happens to read as `--root` survives intact.
116
+ */
117
+ export declare function extractCliGlobalOptions(argv: readonly string[], schema: z.ZodObject | undefined): CliGlobalOptionsParse;
91
118
  export {};
92
119
  //# sourceMappingURL=cli-args.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../../src/tools/cli-args.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,IAAI,EAAE,OAAO,CAAC;IACd,iEAAiE;IACjE,IAAI,EAAE,OAAO,CAAC;IACd,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,+BAA+B;IAC/B,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,SAAS,GACV,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,MAAM,GACN,OAAO,GACP,QAAQ,GACR,OAAO,CAAC;AAEZ,UAAU,SAAS;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB,uEAAuE;IACvE,WAAW,CAAC,EAAE,SAAS,CAAC;CACzB;AAOD,eAAO,MAAM,oBAAoB,aAA+C,CAAC;AA0BjF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,YAAY,CAsFlF;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IAChC,IAAI,SAAsB;CACpC;AAuED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAI1F;AA8FD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,SAAS,EAC7B,MAAM,GAAE;IACN,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,GACL,aAAa,CAwMf"}
1
+ {"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../../src/tools/cli-args.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,IAAI,EAAE,OAAO,CAAC;IACd,iEAAiE;IACjE,IAAI,EAAE,OAAO,CAAC;IACd,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,+BAA+B;IAC/B,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,KAAK,SAAS,GACV,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,MAAM,GACN,OAAO,GACP,QAAQ,GACR,OAAO,CAAC;AAEZ,UAAU,SAAS;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB,uEAAuE;IACvE,WAAW,CAAC,EAAE,SAAS,CAAC;CACzB;AAOD,eAAO,MAAM,oBAAoB,aAA+C,CAAC;AA0BjF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,YAAY,CAsFlF;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IAChC,IAAI,SAAsB;CACpC;AAuED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAI1F;AA8FD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,SAAS,EAC7B,MAAM,GAAE;IACN,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B,GACL,aAAa,CA+Nf;AAED,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACpC,0EAA0E;IAC1E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,MAAM,EAAE,CAAC,CAAC,SAAS,GAAG,SAAS,GAC9B,qBAAqB,CA8CvB"}
@@ -5,6 +5,13 @@ import { type ToolResult } from './execute.js';
5
5
  export interface CliCommandContext<TInput extends ZodObject> extends CliWriters {
6
6
  input: z.output<TInput>;
7
7
  options: Readonly<CliRunOptions>;
8
+ /**
9
+ * The application's own global options for this invocation, already validated
10
+ * against `CliConfig.globalOptions`. Empty when the CLI declares none. Typed
11
+ * loosely because a command is defined independently of the CLI it is mounted
12
+ * on — read it through the same schema the application declared.
13
+ */
14
+ globals: Readonly<Record<string, unknown>>;
8
15
  }
9
16
  export interface CliCommandDefinitionBase<TInput extends ZodObject> {
10
17
  name: string;
@@ -41,5 +48,5 @@ export interface PreparedCliCommandEmission {
41
48
  export declare function prepareCliCommandEmission(definition: CliCommandDefinition, result: ToolResult, options: Readonly<CliRunOptions>): PreparedCliCommandEmission;
42
49
  export declare function cliCommandPresentationSchema(definition: CliCommandDefinition): Record<string, unknown>;
43
50
  /** Execute a CLI-only definition without inventing a tool operation identity. */
44
- export declare function executeCliCommand(definition: CliCommandDefinition, rawArgs: Record<string, unknown>, options: CliRunOptions, writers: CliWriters, coerceJson: boolean): Promise<ToolResult>;
51
+ export declare function executeCliCommand(definition: CliCommandDefinition, rawArgs: Record<string, unknown>, options: CliRunOptions, writers: CliWriters, coerceJson: boolean, globals?: Readonly<Record<string, unknown>>): Promise<ToolResult>;
45
52
  //# sourceMappingURL=cli-command.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli-command.d.ts","sourceRoot":"","sources":["../../src/tools/cli-command.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli-command.d.ts","sourceRoot":"","sources":["../../src/tools/cli-command.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,12 @@ export interface CliCommandPresentation {
13
13
  presentationSchema: Record<string, unknown>;
14
14
  /** Canonical field name → one short alias. */
15
15
  aliases: ReadonlyMap<string, string>;
16
- /** `undefined` retains automatic non-boolean schema order. */
16
+ /**
17
+ * `undefined` retains automatic non-boolean schema order. A trailing ARRAY
18
+ * field is variadic — it takes every remaining token. An array declared
19
+ * anywhere else keeps taking exactly one (a JSON-array token), and command
20
+ * help says which is which: only the variadic tail renders as `<name...>`.
21
+ */
17
22
  positionals?: readonly string[];
18
23
  }
19
24
  export declare function applyCliPresentationPolicy(command: string, descriptor: Omit<CliCommandPresentation, 'aliases' | 'positionals'>, config: CliPresentationPolicyConfig): CliCommandPresentation;
@@ -1 +1 @@
1
- {"version":3,"file":"cli-policy.d.ts","sourceRoot":"","sources":["../../src/tools/cli-policy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGlD,MAAM,WAAW,2BAA2B;IAC1C,+DAA+D;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3E,6FAA6F;IAC7F,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;CAC3D;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,8CAA8C;IAC9C,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,8DAA8D;IAC9D,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAkFD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,aAAa,CAAC,EACnE,MAAM,EAAE,2BAA2B,GAClC,sBAAsB,CAexB;AAED,wFAAwF;AACxF,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,sBAAsB,CAAC,EACrD,MAAM,EAAE,2BAA2B,GAClC,IAAI,CAWN"}
1
+ {"version":3,"file":"cli-policy.d.ts","sourceRoot":"","sources":["../../src/tools/cli-policy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAGlD,MAAM,WAAW,2BAA2B;IAC1C,+DAA+D;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3E,6FAA6F;IAC7F,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;CAC3D;AAED,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5C,8CAA8C;IAC9C,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAkFD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,aAAa,CAAC,EACnE,MAAM,EAAE,2BAA2B,GAClC,sBAAsB,CAexB;AAED,wFAAwF;AACxF,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,sBAAsB,CAAC,EACrD,MAAM,EAAE,2BAA2B,GAClC,IAAI,CAWN"}
@@ -1,3 +1,4 @@
1
+ import type { ZodObject, z } from 'zod';
1
2
  import type { ServiceDef, StitchLogger } from '../server/types.js';
2
3
  import { type CliCommandDefinition } from './cli-command.js';
3
4
  import { type ExitCodeMap } from './cli-format.js';
@@ -6,7 +7,7 @@ import { type CliWaitConfig } from './cli-wait.js';
6
7
  import { type ErrorHintFn, type ToolCallHooks, type ToolLifecycle } from './execute.js';
7
8
  import type { RuntimeToolDefinition } from './runtime-tool.js';
8
9
  export type CliSurfaceSource<TAuth, TValue> = readonly TValue[] | ((auth: Awaited<TAuth> | undefined) => readonly TValue[]);
9
- export interface CliConfig<TAuth = unknown, TContext extends Record<string, unknown> = Record<string, unknown>> extends CliPresentationPolicyConfig {
10
+ export interface CliConfig<TAuth = unknown, TContext extends Record<string, unknown> = Record<string, unknown>, TGlobals extends ZodObject = ZodObject> extends CliPresentationPolicyConfig {
10
11
  /** Program name — shown in help and unknown-command messages. */
11
12
  name: string;
12
13
  /** Program version — printed by `--version`. */
@@ -23,13 +24,27 @@ export interface CliConfig<TAuth = unknown, TContext extends Record<string, unkn
23
24
  * promise of one.
24
25
  */
25
26
  auth?: TAuth | Promise<TAuth>;
26
- /** Lazily resolve identity only when a managed command/surface actually needs it. */
27
- resolveAuth?: () => TAuth | Promise<TAuth>;
27
+ /**
28
+ * Lazily resolve identity only when a managed command/surface actually needs
29
+ * it. Receives the application's global options, so `--caller <key>` can
30
+ * select WHICH identity this invocation speaks as.
31
+ */
32
+ resolveAuth?: (globals: z.output<TGlobals>) => TAuth | Promise<TAuth>;
33
+ /**
34
+ * The application's OWN global options — invocation context that belongs to
35
+ * no single operation: which identity key, which checkout, which profile.
36
+ * Declared as a Zod object of optional fields; `createCli` lifts these flags
37
+ * out of argv wherever they stand (before or after the command name),
38
+ * validates them against this schema and keeps them out of every operation's
39
+ * arguments. A name that collides with a framework option or with a field of
40
+ * any command is a startup error, never silent shadowing.
41
+ */
42
+ globalOptions?: TGlobals;
28
43
  /**
29
44
  * Context merged into every handler. Typed against the app's context shape
30
45
  * when the CLI is built via `createToolkit<AppContext>()`.
31
46
  */
32
- context?: (auth: Awaited<TAuth> | undefined) => TContext;
47
+ context?: (auth: Awaited<TAuth> | undefined, globals: z.output<TGlobals>) => TContext;
33
48
  /** Explicit cancellation for this invocation; applications may bind SIGINT to it. */
34
49
  signal?: AbortSignal;
35
50
  /** Tool-call observability hooks — `afterToolCall` fires for every result,
@@ -87,5 +102,5 @@ export interface CliConfig<TAuth = unknown, TContext extends Record<string, unkn
87
102
  stdin?: () => Promise<string | null>;
88
103
  }
89
104
  /** Build and run one mixed contract/runtime/native CLI surface, then exit. */
90
- export declare function createCli<TAuth = unknown, TContext extends Record<string, unknown> = Record<string, unknown>>(config: CliConfig<TAuth, TContext>): Promise<void>;
105
+ export declare function createCli<TAuth = unknown, TContext extends Record<string, unknown> = Record<string, unknown>, TGlobals extends ZodObject = ZodObject>(config: CliConfig<TAuth, TContext, TGlobals>): Promise<void>;
91
106
  //# sourceMappingURL=cli.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/tools/cli.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAQhE,OAAO,EACL,KAAK,oBAAoB,EAI1B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAsB,KAAK,WAAW,EAAc,MAAM,cAAc,CAAC;AAChF,OAAO,EAIL,KAAK,2BAA2B,EACjC,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,KAAK,aAAa,EAAiB,MAAM,YAAY,CAAC;AAC/D,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EAGnB,MAAM,WAAW,CAAC;AAInB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAI5D,MAAM,MAAM,gBAAgB,CAAC,KAAK,EAAE,MAAM,IACtC,SAAS,MAAM,EAAE,GACjB,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,KAAK,SAAS,MAAM,EAAE,CAAC,CAAC;AAE9D,MAAM,WAAW,SAAS,CACxB,KAAK,GAAG,OAAO,EACf,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAClE,SAAQ,2BAA2B;IACnC,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,gBAAgB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;IAC/C,uFAAuF;IACvF,YAAY,CAAC,EAAE,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAC;IAC9D,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC3C;;;;OAIG;IACH,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9B,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC3C;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,SAAS,KAAK,QAAQ,CAAC;IACzD,qFAAqF;IACrF,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;+DAC2D;IAC3D,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,wEAAwE;IACxE,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,0EAA0E;IAC1E,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kEAAkE;IAClE,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACrC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE;;;OAGG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,oEAAoE;IACpE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,oEAAoE;IACpE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,gEAAgE;IAChE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACtC;AAmUD,8EAA8E;AAC9E,wBAAsB,SAAS,CAC7B,KAAK,GAAG,OAAO,EACf,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClE,MAAM,EAAE,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA+RnD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/tools/cli.ts"],"names":[],"mappings":"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"}
package/dist/tools.js CHANGED
@@ -46,7 +46,7 @@ import {
46
46
  fetchPinnedDocument,
47
47
  readCapped,
48
48
  runWaitOperation
49
- } from "./index-8dph3pw6.js";
49
+ } from "./index-1923shw9.js";
50
50
  import"./index-sbdmyz75.js";
51
51
  import {
52
52
  collectToolSurface
package/llms-full.txt CHANGED
@@ -7443,6 +7443,30 @@ command schema. `-f` / `-f=false` are boolean forms; values accept `-n 100` and
7443
7443
  bundles such as `-fn`, attached values such as `-n100`, `--no-f` and unknown
7444
7444
  short flags are rejected. Canonical `--no-follow` remains available.
7445
7445
 
7446
+ ### A trailing list
7447
+
7448
+ When the LAST declared positional is an array field, it takes every remaining
7449
+ token, each coerced by the array's element type:
7450
+
7451
+ ```ts
7452
+ positionals: { handoff: ['to', 'files'] } // files: z.array(z.string())
7453
+ ```
7454
+
7455
+ ```
7456
+ myapp handoff proj a.md b.md → { to: 'proj', files: ['a.md', 'b.md'] }
7457
+ myapp handoff proj a.md → { to: 'proj', files: ['a.md'] }
7458
+ ```
7459
+
7460
+ One token is a one-element list, not a scalar, so the parsed shape never depends
7461
+ on how many a caller happened to pass. Command help marks it:
7462
+ `Usage: myapp handoff <to> <files...>`. The flag form still works
7463
+ (`--files '["a.md","b.md"]'`, or a repeated `--files`) — but passing both forms
7464
+ in one call is an argument error rather than a silent merge.
7465
+
7466
+ Only the trailing position is variadic. An array declared anywhere else in the
7467
+ list keeps taking exactly one token (a JSON array), and its help stays `<tags>`,
7468
+ so the usage line always says which field is the list.
7469
+
7446
7470
  `positionals` replaces automatic schema-order selection only for the named
7447
7471
  command. An empty array disables argv positionals. Fields remain available as
7448
7472
  long/short options and stdin still fills the first required unset field with the
@@ -7474,6 +7498,68 @@ diagnostics remain ordinary stderr text. This keeps stdout pipeable and
7474
7498
  `VALIDATION_ERROR → 1`, `UNAUTHORIZED → 2`, `FORBIDDEN → 3`, `NOT_FOUND → 4`,
7475
7499
  …) — override per app with `exitCodes`.
7476
7500
 
7501
+ ## Application global options
7502
+
7503
+ `--json` and friends above are the framework's. An application usually has
7504
+ globals of its own — which identity key to use, which checkout a call speaks
7505
+ for, which profile — and they belong to no single operation:
7506
+
7507
+ ```ts
7508
+ await createCli({
7509
+ name: 'myapp',
7510
+ version: '1.0.0',
7511
+ globalOptions: z.object({
7512
+ caller: z.string().optional().describe('Identity key file'),
7513
+ root: z.string().optional().describe('Checkout the call speaks for'),
7514
+ }),
7515
+ resolveAuth: (globals) => loadIdentity(globals.caller),
7516
+ context: (auth, globals) => ({ auth, root: globals.root }),
7517
+ runtimeTools: (auth) => catalogFor(auth),
7518
+ })
7519
+ ```
7520
+
7521
+ ```
7522
+ myapp --root /srv/app handoff_read --handoffId u
7523
+ myapp handoff_read --root /srv/app --handoffId u # the same call
7524
+ ```
7525
+
7526
+ These flags are lifted out of argv wherever they stand — before or after the
7527
+ command name — validated against the declared schema, and kept out of every
7528
+ operation's arguments: `handoff_read` above receives `{ handoffId: 'u' }` and
7529
+ nothing else. The values reach `resolveAuth(globals)`, `context(auth, globals)`
7530
+ and a native command's `globals`. An invalid value is an argument error naming
7531
+ the flag; a name that collides with a framework option, or with a field of any
7532
+ command, is refused at startup rather than shadowing it silently. Past a bare
7533
+ `--` every token is a literal, so a positional value that reads like a global
7534
+ survives intact.
7535
+
7536
+ Both help levels list them under `Application options:`.
7537
+
7538
+ ## When the managed surface cannot resolve
7539
+
7540
+ A CLI whose commands come from a running server declares them with a factory,
7541
+ and that factory needs an identity. When `resolveAuth` fails — the server is
7542
+ down, the key file is missing — the commands it would have named are unknown to
7543
+ the CLI, but the native ones are not:
7544
+
7545
+ ```
7546
+ $ myapp --help
7547
+ myapp 1.0.0
7548
+ ...
7549
+ Commands:
7550
+ serve Run the server
7551
+
7552
+ Managed commands are unavailable: UNREACHABLE: socket closed
7553
+ ```
7554
+
7555
+ Help still lists what does not depend on identity and says, in one line, why the
7556
+ rest is missing. Calling a name the CLI cannot resolve answers with that same
7557
+ refusal and the exit code its error class declares through `exitCodes` — never
7558
+ `Unknown command`, which would claim the name does not exist when the truth is
7559
+ that it could not be looked up. A native command and its help still run: they
7560
+ never needed an identity. Identity is still resolved at most once per
7561
+ invocation, failure included.
7562
+
7477
7563
  Per-command help derives the positional form from the same resolved policy as
7478
7564
  the argv parser. For example, a required `action` and optional `profile` render as
7479
7565
  `Usage: myapp skill <action> [profile] [--flags]`; the argument table also shows
@@ -16443,14 +16529,14 @@ payload.
16443
16529
  | `AgentToolRegistry` / `AgentToolRegistryBuilder` / `AgentToolRegistryInput` | _type_ | the composed runtime surface and its builder: declared defaults, `replace`/`disable` by name, and the exact `{ tools, names }` a mount receives |
16444
16530
  | `defineToolRegistry` | function | compose runtime tools over one declared default set; an unknown `replace`/`disable` name is refused instead of silently leaving the default in place |
16445
16531
  | `AgentContext` | _type_ | the context merged into agent tool handlers |
16446
- | `CliConfig` | _type_ | config for `createCli`, including program-level `defaultCommand` selection and command-scoped `optionAliases` / `positionals` policy |
16532
+ | `CliConfig` | _type_ | config for `createCli`, including program-level `defaultCommand` selection, application-wide `globalOptions` and command-scoped `optionAliases` / `positionals` policy |
16447
16533
  | `CliPresentationPolicyConfig` | _type_ | reusable default-command, short-alias and explicit-positional policy inherited by `CliConfig` |
16448
16534
  | `CliSurfaceSource` | _type_ | static managed surface or identity-dependent surface factory for `createCli` |
16449
16535
  | `CliCommandDefinition` | _type_ | Zod-first CLI-only command union |
16450
16536
  | `CliCommandDefinitionBase` | _type_ | native command name, description and input schema |
16451
16537
  | `CliCommandDefinitionWithOutput` | _type_ | native command with declared output schema, validated handler result and typed optional `present` / `exitCode` callbacks |
16452
16538
  | `CliCommandDefinitionWithoutOutput` | _type_ | void native command with no output schema |
16453
- | `CliCommandContext` | _type_ | parsed native command input, global options and injected writers |
16539
+ | `CliCommandContext` | _type_ | parsed native command input, framework run options, the application's `globals` and injected writers |
16454
16540
  | `CliWaitConfig` | _type_ | `--wait` polling config |
16455
16541
  | `ExitCodeMap` | _type_ | `ToolResult.code` → process exit code |
16456
16542
  | `Toolkit` | _type_ | the context-pinned tool surface from `createToolkit` |
@@ -16949,14 +17035,14 @@ SDK nor the `ai` peer.
16949
17035
  | `pollUntilDone` | function | the generic `--wait` poller (advanced) |
16950
17036
  | `emitResult` | function | write a pretty or compact `ToolResult` record to stdout/stderr + exit code (advanced) |
16951
17037
  | `DEFAULT_EXIT_CODES` | const | the default `ToolResult.code` → exit-code map |
16952
- | `CliConfig` | _type_ | config for `createCli`; `defaultCommand`, `optionAliases` and `positionals` define the shared command presentation policy |
17038
+ | `CliConfig` | _type_ | config for `createCli`; `defaultCommand`, `globalOptions`, `optionAliases` and `positionals` define the shared command presentation policy |
16953
17039
  | `CliPresentationPolicyConfig` | _type_ | shared command presentation-policy subset of `CliConfig` |
16954
17040
  | `CliSurfaceSource` | _type_ | static service/runtime array or identity-dependent factory |
16955
17041
  | `CliCommandDefinition` | _type_ | native command definition union |
16956
17042
  | `CliCommandDefinitionBase` | _type_ | native command name, description and input schema |
16957
17043
  | `CliCommandDefinitionWithOutput` | _type_ | native command with validated declared output and typed optional `present` / successful `exitCode` callbacks |
16958
17044
  | `CliCommandDefinitionWithoutOutput` | _type_ | native void command without an output contract |
16959
- | `CliCommandContext` | _type_ | parsed input, global options and stdout/stderr writers |
17045
+ | `CliCommandContext` | _type_ | parsed input, framework run options, the application's `globals` and stdout/stderr writers |
16960
17046
  | `CliRunOptions` | _type_ | parsed global flags (`--json` compacts success/error records, `--wait`, …) |
16961
17047
  | `ParsedCliArgs` | _type_ | result of `parseCliArgs` |
16962
17048
  | `CliWaitConfig` | _type_ | per-command `--wait` polling config; optional `failed(result)` maps a terminal domain failure to `WAIT_FAILED` and a non-zero exit |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.90.2",
3
+ "version": "0.90.3",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",