stitchkit 0.59.0 → 0.59.2

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 (61) hide show
  1. package/README.md +44 -4
  2. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  3. package/dist/agent-runtime/terminal-commit.d.ts +102 -0
  4. package/dist/agent-runtime/terminal-commit.d.ts.map +1 -0
  5. package/dist/agent-runtime.js +143 -46
  6. package/dist/application/activity.d.ts +70 -0
  7. package/dist/application/activity.d.ts.map +1 -0
  8. package/dist/application/events.d.ts +65 -0
  9. package/dist/application/events.d.ts.map +1 -0
  10. package/dist/application/grammy.d.ts +35 -0
  11. package/dist/application/grammy.d.ts.map +1 -0
  12. package/dist/application/graph.d.ts +11 -0
  13. package/dist/application/graph.d.ts.map +1 -0
  14. package/dist/application/health.d.ts +13 -0
  15. package/dist/application/health.d.ts.map +1 -0
  16. package/dist/application/kernel.d.ts +31 -0
  17. package/dist/application/kernel.d.ts.map +1 -0
  18. package/dist/application/latest-sink.d.ts +39 -0
  19. package/dist/application/latest-sink.d.ts.map +1 -0
  20. package/dist/application/resource.d.ts +30 -0
  21. package/dist/application/resource.d.ts.map +1 -0
  22. package/dist/application/schedule.d.ts +112 -0
  23. package/dist/application/schedule.d.ts.map +1 -0
  24. package/dist/application/schemas.d.ts +160 -0
  25. package/dist/application/schemas.d.ts.map +1 -0
  26. package/dist/application/server-resource.d.ts +12 -0
  27. package/dist/application/server-resource.d.ts.map +1 -0
  28. package/dist/application-grammy.d.ts +2 -0
  29. package/dist/application-grammy.d.ts.map +1 -0
  30. package/dist/application-grammy.js +165 -0
  31. package/dist/application.d.ts +10 -0
  32. package/dist/application.d.ts.map +1 -0
  33. package/dist/application.js +1370 -0
  34. package/dist/cli.d.ts +1 -0
  35. package/dist/cli.d.ts.map +1 -1
  36. package/dist/cli.js +1 -1
  37. package/dist/{index-zk5zn2nh.js → index-3nwng7hs.js} +287 -38
  38. package/dist/index-dk6e56g0.js +211 -0
  39. package/dist/{index-9zn9fb4e.js → index-he4psyve.js} +6 -213
  40. package/dist/index-yr276yz0.js +6 -0
  41. package/dist/internal/fetch-port.d.ts +2 -0
  42. package/dist/internal/fetch-port.d.ts.map +1 -0
  43. package/dist/node.js +115 -13
  44. package/dist/server/index.js +8 -6
  45. package/dist/server/node.d.ts.map +1 -1
  46. package/dist/server/process-signals.d.ts +10 -8
  47. package/dist/server/process-signals.d.ts.map +1 -1
  48. package/dist/tools/cli-args.d.ts +16 -0
  49. package/dist/tools/cli-args.d.ts.map +1 -1
  50. package/dist/tools/cli-command.d.ts +16 -0
  51. package/dist/tools/cli-command.d.ts.map +1 -1
  52. package/dist/tools/cli-policy.d.ts +22 -0
  53. package/dist/tools/cli-policy.d.ts.map +1 -0
  54. package/dist/tools/cli.d.ts +2 -1
  55. package/dist/tools/cli.d.ts.map +1 -1
  56. package/dist/tools.d.ts +1 -0
  57. package/dist/tools.d.ts.map +1 -1
  58. package/dist/tools.js +1 -1
  59. package/llms-full.txt +471 -14
  60. package/llms.txt +1 -0
  61. package/package.json +15 -2
package/dist/cli.d.ts CHANGED
@@ -20,5 +20,6 @@ export { type CliConfig, type CliSurfaceSource, createCli } from './tools/cli';
20
20
  export { type CliRunOptions, type ParsedCliArgs, parseCliArgs } from './tools/cli-args';
21
21
  export { type CliCommandContext, type CliCommandDefinition, type CliCommandDefinitionBase, type CliCommandDefinitionWithOutput, type CliCommandDefinitionWithoutOutput, defineCliCommand, } from './tools/cli-command';
22
22
  export { type CliWriters, DEFAULT_EXIT_CODES, type EmitOptions, type ExitCodeMap, emitResult, } from './tools/cli-format';
23
+ export type { CliPresentationPolicyConfig } from './tools/cli-policy';
23
24
  export { type CliWaitConfig, type PollParams, pollUntilDone } from './tools/cli-wait';
24
25
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACxF,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EACnC,KAAK,iCAAiC,EACtC,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,KAAK,UAAU,EACf,kBAAkB,EAClB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,UAAU,GACX,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACxF,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EACnC,KAAK,iCAAiC,EACtC,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,KAAK,UAAU,EACf,kBAAkB,EAClB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,UAAU,GACX,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  emitResult,
6
6
  parseCliArgs,
7
7
  pollUntilDone
8
- } from "./index-zk5zn2nh.js";
8
+ } from "./index-3nwng7hs.js";
9
9
  import"./index-sa2mbwa7.js";
10
10
  import"./index-sbdmyz75.js";
11
11
  import"./index-1tkyng3g.js";
@@ -182,6 +182,105 @@ var NUMERIC_VALUE = /^-(\d+\.?\d*|\.\d+)$/;
182
182
  var BOOL_OPTIONS = new Set(["json", "wait", "quiet", "dry-run", "help"]);
183
183
  var VALUE_OPTIONS = new Set(["wait-timeout", "output-dir"]);
184
184
  var RESERVED_CLI_OPTIONS = new Set([...BOOL_OPTIONS, ...VALUE_OPTIONS]);
185
+ function classifyLongOptionToken(token) {
186
+ if (!token.startsWith("--") || token === "--")
187
+ return;
188
+ const equals = token.indexOf("=");
189
+ const name = equals >= 0 ? token.slice(2, equals) : token.slice(2);
190
+ return {
191
+ name,
192
+ value: equals >= 0 ? token.slice(equals + 1) : undefined,
193
+ inline: equals >= 0,
194
+ globalKind: BOOL_OPTIONS.has(name) ? "boolean" : VALUE_OPTIONS.has(name) ? "value" : undefined
195
+ };
196
+ }
197
+ function routeCliArgv(argv, defaultCommand) {
198
+ if (defaultCommand === undefined) {
199
+ const [command, ...commandArgv] = argv;
200
+ return { command, commandArgv, topLevelHelp: false, version: false };
201
+ }
202
+ const globals = [];
203
+ let index = 0;
204
+ while (index < argv.length) {
205
+ const token = argv[index];
206
+ if (token === undefined)
207
+ break;
208
+ if (token === "--") {
209
+ return {
210
+ commandArgv: [],
211
+ topLevelHelp: false,
212
+ version: false,
213
+ error: 'A command is required before "--"'
214
+ };
215
+ }
216
+ if (token === "--help" || token === "-h") {
217
+ return { commandArgv: [], topLevelHelp: true, version: false };
218
+ }
219
+ if (token === "--version" || token === "version") {
220
+ return { commandArgv: [], topLevelHelp: false, version: true };
221
+ }
222
+ if (!token.startsWith("-") || token === "-") {
223
+ return {
224
+ command: token,
225
+ commandArgv: [...globals, ...argv.slice(index + 1)],
226
+ topLevelHelp: false,
227
+ version: false
228
+ };
229
+ }
230
+ if (!token.startsWith("--")) {
231
+ return {
232
+ command: defaultCommand,
233
+ commandArgv: [...globals, ...argv.slice(index)],
234
+ topLevelHelp: false,
235
+ version: false
236
+ };
237
+ }
238
+ const option = classifyLongOptionToken(token);
239
+ if (!option) {
240
+ return {
241
+ command: defaultCommand,
242
+ commandArgv: [...globals, ...argv.slice(index)],
243
+ topLevelHelp: false,
244
+ version: false
245
+ };
246
+ }
247
+ if (option.globalKind === "boolean") {
248
+ globals.push(token);
249
+ index += 1;
250
+ continue;
251
+ }
252
+ if (option.globalKind === "value") {
253
+ globals.push(token);
254
+ if (!option.inline) {
255
+ const value = argv[index + 1];
256
+ if (value === "--help" || value === "-h") {
257
+ return { commandArgv: [], topLevelHelp: true, version: false };
258
+ }
259
+ if (value === "--version" || value === "version") {
260
+ return { commandArgv: [], topLevelHelp: false, version: true };
261
+ }
262
+ if (value !== undefined)
263
+ globals.push(value);
264
+ index += value === undefined ? 1 : 2;
265
+ } else {
266
+ index += 1;
267
+ }
268
+ continue;
269
+ }
270
+ return {
271
+ command: defaultCommand,
272
+ commandArgv: [...globals, ...argv.slice(index)],
273
+ topLevelHelp: false,
274
+ version: false
275
+ };
276
+ }
277
+ return {
278
+ command: defaultCommand,
279
+ commandArgv: globals,
280
+ topLevelHelp: false,
281
+ version: false
282
+ };
283
+ }
185
284
 
186
285
  class CliArgumentError extends Error {
187
286
  name = "CliArgumentError";
@@ -383,14 +482,38 @@ function parseCliArgs(argv, schema, config = {}) {
383
482
  continue;
384
483
  }
385
484
  if (!tok.startsWith("--")) {
386
- throw new CliArgumentError(`Unknown option "${tok}"`);
485
+ const match = /^-([A-Za-z])(?:=(.*))?$/.exec(tok);
486
+ const alias = match?.[1];
487
+ const field = alias === undefined ? undefined : config.optionAliases?.get(alias);
488
+ if (!alias || !field)
489
+ throw new CliArgumentError(`Unknown option "${tok}"`);
490
+ const inline = match?.[2];
491
+ const info2 = fields.get(field);
492
+ if (info2?.kind === "boolean") {
493
+ boolFlags.set(field, inline === undefined ? true : parseReservedBool(field, inline));
494
+ continue;
495
+ }
496
+ let value2 = inline;
497
+ if (value2 === undefined) {
498
+ const next = argv[i + 1];
499
+ if (next !== undefined && (!next.startsWith("-") || NUMERIC_VALUE.test(next))) {
500
+ value2 = next;
501
+ i++;
502
+ } else {
503
+ throw new CliArgumentError(`--${field} requires a value`);
504
+ }
505
+ }
506
+ pushFlag(field, value2);
507
+ continue;
387
508
  }
388
- const eq = tok.indexOf("=");
389
- const name = eq >= 0 ? tok.slice(2, eq) : tok.slice(2);
390
- let value = eq >= 0 ? tok.slice(eq + 1) : undefined;
509
+ const option = classifyLongOptionToken(tok);
510
+ if (!option)
511
+ throw new CliArgumentError(`Unknown option "${tok}"`);
512
+ const { name } = option;
513
+ let { value } = option;
391
514
  if (name.length === 0)
392
515
  throw new CliArgumentError("Invalid empty option name");
393
- if (BOOL_OPTIONS.has(name)) {
516
+ if (option.globalKind === "boolean") {
394
517
  const enabled = value === undefined ? true : parseReservedBool(name, value);
395
518
  if (name === "dry-run")
396
519
  options.dryRun = enabled;
@@ -404,13 +527,13 @@ function parseCliArgs(argv, schema, config = {}) {
404
527
  options.help = enabled;
405
528
  continue;
406
529
  }
407
- if (VALUE_OPTIONS.has(name)) {
530
+ if (option.globalKind === "value") {
408
531
  const next = argv[i + 1];
409
532
  value = value ?? next;
410
533
  if (value === undefined || value.startsWith("-") && !NUMERIC_VALUE.test(value)) {
411
534
  throw new CliArgumentError(`--${name} requires a value`);
412
535
  }
413
- if (eq < 0)
536
+ if (!option.inline)
414
537
  i++;
415
538
  if (name === "wait-timeout") {
416
539
  const timeout = Number(value);
@@ -452,7 +575,7 @@ function parseCliArgs(argv, schema, config = {}) {
452
575
  pushFlag(name, value);
453
576
  }
454
577
  const toolArgs = {};
455
- const fillable = [...fields.entries()].filter(([, info]) => info.kind !== "boolean").map(([name]) => name);
578
+ const fillable = config.positionals === undefined ? [...fields.entries()].filter(([, info]) => info.kind !== "boolean").map(([name]) => name) : [...config.positionals];
456
579
  let pi = 0;
457
580
  for (const key of fillable) {
458
581
  if (pi >= positionals.length)
@@ -501,6 +624,27 @@ function parseCliArgs(argv, schema, config = {}) {
501
624
  function defineCliCommand(definition) {
502
625
  return definition;
503
626
  }
627
+ function prepareCliCommandEmission(definition, result, options) {
628
+ if (!result.ok || definition.output === undefined) {
629
+ return { result, successExitCode: 0 };
630
+ }
631
+ try {
632
+ const successExitCode = definition.exitCode?.(result.data) ?? 0;
633
+ if (!Number.isSafeInteger(successExitCode) || successExitCode < 0 || successExitCode > 255) {
634
+ throw new Error("CLI success exit code must be an integer from 0 to 255");
635
+ }
636
+ const presentation = definition.present?.({ result: result.data, options });
637
+ if (presentation !== undefined && typeof presentation !== "string") {
638
+ throw new Error("CLI result presenter must return a string");
639
+ }
640
+ return { result, presentation, successExitCode };
641
+ } catch (error) {
642
+ return {
643
+ result: toolResultFromError(new Error("CLI result policy failed", { cause: error })),
644
+ successExitCode: 0
645
+ };
646
+ }
647
+ }
504
648
  function cliCommandPresentationSchema(definition) {
505
649
  return buildToolPresentationSchema({
506
650
  inputSchema: definition.input,
@@ -1032,6 +1176,86 @@ async function writeDownload(root, target, data) {
1032
1176
  await writeFile(target, data);
1033
1177
  }
1034
1178
 
1179
+ // src/tools/cli-policy.ts
1180
+ var SHORT_ALIAS = /^[A-Za-z]$/;
1181
+ var RESERVED_SHORT_OPTIONS = new Set(["h"]);
1182
+ function fieldMap(descriptor) {
1183
+ return new Map(jsonSchemaFields(descriptor.presentationSchema).map((field) => [field.name, field]));
1184
+ }
1185
+ function resolveAliases(command, descriptor, configured) {
1186
+ if (!configured)
1187
+ return new Map;
1188
+ const fields = fieldMap(descriptor);
1189
+ const aliases = new Map;
1190
+ for (const [alias, target] of Object.entries(configured)) {
1191
+ if (!SHORT_ALIAS.test(alias)) {
1192
+ throw new Error(`[stitchkit] CLI command "${command}" alias "${alias}" must be one ASCII letter`);
1193
+ }
1194
+ if (RESERVED_SHORT_OPTIONS.has(alias)) {
1195
+ throw new Error(`[stitchkit] CLI command "${command}" alias "-${alias}" is reserved`);
1196
+ }
1197
+ if (isUnsafeKey(target) || !fields.has(target)) {
1198
+ throw new Error(`[stitchkit] CLI command "${command}" alias "-${alias}" targets unknown field "${target}"`);
1199
+ }
1200
+ const existing = aliases.get(target);
1201
+ if (existing !== undefined) {
1202
+ throw new Error(`[stitchkit] CLI command "${command}" field "${target}" has multiple aliases: -${existing}, -${alias}`);
1203
+ }
1204
+ aliases.set(target, alias);
1205
+ }
1206
+ return aliases;
1207
+ }
1208
+ function resolvePositionals(command, descriptor, configured) {
1209
+ if (configured === undefined)
1210
+ return;
1211
+ const fields = fieldMap(descriptor);
1212
+ const kinds = describeSchemaFields(descriptor.argumentSchema);
1213
+ const seen = new Set;
1214
+ let optionalSeen = false;
1215
+ for (const name of configured) {
1216
+ const field = fields.get(name);
1217
+ if (isUnsafeKey(name) || !field || !kinds.has(name)) {
1218
+ throw new Error(`[stitchkit] CLI command "${command}" positional targets unknown field "${name}"`);
1219
+ }
1220
+ if (seen.has(name)) {
1221
+ throw new Error(`[stitchkit] CLI command "${command}" repeats positional field "${name}"`);
1222
+ }
1223
+ if (kinds.get(name)?.kind === "boolean") {
1224
+ throw new Error(`[stitchkit] CLI command "${command}" boolean field "${name}" cannot be positional`);
1225
+ }
1226
+ if (optionalSeen && field.required) {
1227
+ throw new Error(`[stitchkit] CLI command "${command}" required positional "${name}" cannot follow an optional positional`);
1228
+ }
1229
+ if (!field.required)
1230
+ optionalSeen = true;
1231
+ seen.add(name);
1232
+ }
1233
+ return [...configured];
1234
+ }
1235
+ function applyCliPresentationPolicy(command, descriptor, config) {
1236
+ const base = { ...descriptor, aliases: new Map };
1237
+ const configuredAliases = config.optionAliases && Object.hasOwn(config.optionAliases, command) ? config.optionAliases[command] : undefined;
1238
+ const configuredPositionals = config.positionals && Object.hasOwn(config.positionals, command) ? config.positionals[command] : undefined;
1239
+ return {
1240
+ ...base,
1241
+ aliases: resolveAliases(command, base, configuredAliases),
1242
+ positionals: resolvePositionals(command, base, configuredPositionals)
1243
+ };
1244
+ }
1245
+ function assertCliPoliciesResolved(commands, config) {
1246
+ const configuredNames = new Set([
1247
+ ...Object.keys(config.optionAliases ?? {}),
1248
+ ...Object.keys(config.positionals ?? {})
1249
+ ]);
1250
+ if (config.defaultCommand !== undefined)
1251
+ configuredNames.add(config.defaultCommand);
1252
+ for (const name of configuredNames) {
1253
+ if (!commands.has(name)) {
1254
+ throw new Error(`[stitchkit] CLI policy targets unavailable command "${name}"`);
1255
+ }
1256
+ }
1257
+ }
1258
+
1035
1259
  // src/tools/surface.ts
1036
1260
  function collectToolSurface({
1037
1261
  surface,
@@ -1149,17 +1373,17 @@ function collectPassthrough(toolArgs, field, knownKeys) {
1149
1373
  const base = passthroughBase(toolArgs[field]);
1150
1374
  toolArgs[field] = base ? { ...base, ...bag } : bag;
1151
1375
  }
1152
- function renderTopHelp(name, version, commands) {
1376
+ function renderTopHelp(name, version, commands, defaultCommand) {
1153
1377
  const lines = [
1154
1378
  `${name} ${version}`,
1155
1379
  "",
1156
- `Usage: ${name} <command> [args] [--flags]`,
1380
+ `Usage: ${name} ${defaultCommand ? "[command]" : "<command>"} [args] [--flags]`,
1157
1381
  "",
1158
1382
  "Commands:"
1159
1383
  ];
1160
1384
  const width = Math.max(0, ...[...commands.keys()].map((key) => key.length));
1161
1385
  for (const [command, descriptor] of commands) {
1162
- lines.push(` ${padRight(command, width)} ${summarize(descriptor.description)}`);
1386
+ lines.push(` ${padRight(command, width)} ${summarize(descriptor.description)}${command === defaultCommand ? " (default)" : ""}`);
1163
1387
  }
1164
1388
  lines.push("", "Global options:");
1165
1389
  const optWidth = Math.max(...GLOBAL_OPTIONS.map(([flag]) => flag.length));
@@ -1174,9 +1398,8 @@ function renderCommandHelp(name, command, descriptor) {
1174
1398
  const fields = jsonSchemaFields(descriptor.presentationSchema);
1175
1399
  const fieldsByName = new Map(fields.map((field) => [field.name, field]));
1176
1400
  const positionals = [];
1177
- for (const [fieldName, info] of describeSchemaFields(descriptor.argumentSchema)) {
1178
- if (info.kind === "boolean")
1179
- continue;
1401
+ const positionalNames = descriptor.positionals ?? [...describeSchemaFields(descriptor.argumentSchema)].filter(([, info]) => info.kind !== "boolean").map(([fieldName]) => fieldName);
1402
+ for (const fieldName of positionalNames) {
1180
1403
  const field = fieldsByName.get(fieldName);
1181
1404
  if (field)
1182
1405
  positionals.push(field);
@@ -1195,10 +1418,9 @@ function renderCommandHelp(name, command, descriptor) {
1195
1418
  lines.push("Arguments:");
1196
1419
  const labels = new Map(fields.map((field) => {
1197
1420
  const positional = positionalSyntax.get(field.name);
1198
- return [
1199
- field.name,
1200
- positional ? `${positional} | --${field.name}` : `--${field.name}`
1201
- ];
1421
+ const alias = descriptor.aliases.get(field.name);
1422
+ const option = alias ? `-${alias}, --${field.name}` : `--${field.name}`;
1423
+ return [field.name, positional ? `${positional} | ${option}` : option];
1202
1424
  }));
1203
1425
  const width = Math.max(...fields.map((field) => labels.get(field.name)?.length ?? 0));
1204
1426
  for (const f of fields) {
@@ -1227,7 +1449,7 @@ function nativeDescriptor(definition) {
1227
1449
  presentationSchema: cliCommandPresentationSchema(definition)
1228
1450
  };
1229
1451
  }
1230
- function assertCommandShape(name, descriptor, exists) {
1452
+ function assertCommandShape(name, descriptor, exists, passthroughField) {
1231
1453
  assertUniqueToolName(name, exists, "CLI command");
1232
1454
  if (name === "help" || name === "version") {
1233
1455
  throw new Error(`[stitchkit] CLI command "${name}" is reserved`);
@@ -1236,13 +1458,18 @@ function assertCommandShape(name, descriptor, exists) {
1236
1458
  if (conflicting.length > 0) {
1237
1459
  throw new Error(`[stitchkit] CLI command "${name}" declares reserved option field(s): ${conflicting.join(", ")}`);
1238
1460
  }
1461
+ if (passthroughField !== undefined && descriptor.aliases.has(passthroughField)) {
1462
+ throw new Error(`[stitchkit] CLI command "${name}" cannot alias passthrough field "${passthroughField}"`);
1463
+ }
1239
1464
  }
1240
1465
  async function prepareInvocation(command, commandArgv, descriptor, config, readStdin) {
1241
1466
  let parsed;
1242
1467
  try {
1243
1468
  parsed = parseCliArgs(commandArgv, descriptor.argumentSchema, {
1244
1469
  allowUnknown: config.passthrough?.[command] !== undefined,
1245
- knownFields: jsonSchemaFields(descriptor.presentationSchema).map((field) => field.name)
1470
+ knownFields: jsonSchemaFields(descriptor.presentationSchema).map((field) => field.name),
1471
+ optionAliases: new Map([...descriptor.aliases].map(([field, alias]) => [alias, field])),
1472
+ positionals: descriptor.positionals
1246
1473
  });
1247
1474
  } catch (error) {
1248
1475
  if (!(error instanceof CliArgumentError))
@@ -1253,8 +1480,14 @@ async function prepareInvocation(command, commandArgv, descriptor, config, readS
1253
1480
  const firstUnset = jsonSchemaFields(descriptor.presentationSchema).find((field) => field.required && !(field.name in toolArgs) && field.schema.type !== "boolean");
1254
1481
  if (firstUnset) {
1255
1482
  const piped = await readStdin();
1256
- if (piped !== null)
1257
- toolArgs[firstUnset.name] = piped;
1483
+ if (piped !== null) {
1484
+ if (descriptor.positionals === undefined) {
1485
+ toolArgs[firstUnset.name] = piped;
1486
+ } else {
1487
+ const stdinArgs = parseCliArgs([`--${firstUnset.name}`, piped], descriptor.argumentSchema);
1488
+ toolArgs[firstUnset.name] = stdinArgs.toolArgs[firstUnset.name];
1489
+ }
1490
+ }
1258
1491
  }
1259
1492
  const passthroughField = config.passthrough?.[command];
1260
1493
  if (passthroughField) {
@@ -1306,13 +1539,19 @@ async function createCli(config) {
1306
1539
  const nativeCommands = new Map;
1307
1540
  const nativeHelp = new Map;
1308
1541
  for (const definition of config.commands ?? []) {
1309
- const descriptor2 = nativeDescriptor(definition);
1310
- assertCommandShape(definition.name, descriptor2, nativeCommands.has(definition.name));
1542
+ const descriptor2 = applyCliPresentationPolicy(definition.name, nativeDescriptor(definition), config);
1543
+ assertCommandShape(definition.name, descriptor2, nativeCommands.has(definition.name), config.passthrough?.[definition.name]);
1311
1544
  nativeCommands.set(definition.name, definition);
1312
1545
  nativeHelp.set(definition.name, descriptor2);
1313
1546
  }
1314
- const [command, ...commandArgv] = argv;
1315
- if (command === "--version" || command === "version") {
1547
+ const route = routeCliArgv(argv, config.defaultCommand);
1548
+ if (route.error) {
1549
+ stderr(`${route.error}
1550
+ `);
1551
+ return exit(2);
1552
+ }
1553
+ const { command, commandArgv } = route;
1554
+ if (route.version || command === "--version" || command === "version") {
1316
1555
  stdout(`${config.name} ${config.version}
1317
1556
  `);
1318
1557
  return exit(0);
@@ -1360,13 +1599,20 @@ async function createCli(config) {
1360
1599
  return exit(0);
1361
1600
  }
1362
1601
  const result2 = await executeCliCommand(native, toolArgs2, options2, { stdout, stderr }, config.coerceJsonArgs ?? true);
1363
- const exitCode2 = emitResult(result2, { stdout, stderr }, {
1364
- json: options2.json,
1365
- toolName: command,
1366
- errorHint: config.errorHint,
1367
- exitCodes: { ...DEFAULT_EXIT_CODES, ...config.exitCodes }
1368
- });
1369
- return exit(exitCode2);
1602
+ const emission = prepareCliCommandEmission(native, result2, options2);
1603
+ let emittedExitCode;
1604
+ if (emission.result.ok && emission.presentation !== undefined) {
1605
+ stdout(emission.presentation);
1606
+ emittedExitCode = 0;
1607
+ } else {
1608
+ emittedExitCode = emitResult(emission.result, { stdout, stderr }, {
1609
+ json: options2.json,
1610
+ toolName: command,
1611
+ errorHint: config.errorHint,
1612
+ exitCodes: { ...DEFAULT_EXIT_CODES, ...config.exitCodes }
1613
+ });
1614
+ }
1615
+ return exit(emission.result.ok ? emission.successExitCode : emittedExitCode);
1370
1616
  }
1371
1617
  let authPromise;
1372
1618
  const resolveIdentity = () => {
@@ -1384,17 +1630,18 @@ async function createCli(config) {
1384
1630
  surface: { services, runtimeTools },
1385
1631
  transport: "CLI"
1386
1632
  })) {
1387
- const descriptor2 = managedDescriptor(mountable);
1388
- assertCommandShape(mountable.name, descriptor2, help.has(mountable.name));
1633
+ const descriptor2 = applyCliPresentationPolicy(mountable.name, managedDescriptor(mountable), config);
1634
+ assertCommandShape(mountable.name, descriptor2, help.has(mountable.name), config.passthrough?.[mountable.name]);
1389
1635
  tools.set(mountable.name, mountable);
1390
1636
  help.set(mountable.name, descriptor2);
1391
1637
  }
1638
+ assertCliPoliciesResolved(help, config);
1392
1639
  return { auth, help, tools };
1393
1640
  };
1394
- const topLevelHelp = command === undefined || command === "help" || command === "--help" || command === "-h";
1641
+ const topLevelHelp = route.topLevelHelp || command === undefined || command === "help" || command === "--help" || command === "-h";
1395
1642
  const managed = await buildManagedSurface(!topLevelHelp && !helpRequested);
1396
1643
  if (topLevelHelp) {
1397
- stdout(renderTopHelp(config.name, config.version, managed.help));
1644
+ stdout(renderTopHelp(config.name, config.version, managed.help, config.defaultCommand));
1398
1645
  return exit(0);
1399
1646
  }
1400
1647
  const tool = managed.tools.get(command);
@@ -1403,7 +1650,9 @@ async function createCli(config) {
1403
1650
  `);
1404
1651
  return exit(1);
1405
1652
  }
1406
- const descriptor = managedDescriptor(tool);
1653
+ const descriptor = managed.help.get(command);
1654
+ if (!descriptor)
1655
+ throw new Error("[stitchkit] managed CLI descriptor invariant failed");
1407
1656
  if (helpRequested) {
1408
1657
  stdout(renderCommandHelp(config.name, command, descriptor));
1409
1658
  return exit(0);