stitchkit 0.53.2 → 0.55.0

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 (125) hide show
  1. package/README.md +4 -1
  2. package/dist/browser/socket-io.d.ts +11 -1
  3. package/dist/browser/socket-io.d.ts.map +1 -1
  4. package/dist/cli.d.ts +2 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +15 -8
  7. package/dist/contract/define.d.ts +27 -0
  8. package/dist/contract/define.d.ts.map +1 -1
  9. package/dist/contract/file-ref.d.ts +13 -0
  10. package/dist/contract/file-ref.d.ts.map +1 -0
  11. package/dist/contract/index.d.ts +2 -1
  12. package/dist/contract/index.d.ts.map +1 -1
  13. package/dist/contract/index.js +21 -17
  14. package/dist/files/boundary.d.ts +52 -0
  15. package/dist/files/boundary.d.ts.map +1 -0
  16. package/dist/files.d.ts +3 -0
  17. package/dist/files.d.ts.map +1 -0
  18. package/dist/files.js +16 -0
  19. package/dist/{index-c78w2y09.js → index-0gv7k0ra.js} +515 -156
  20. package/dist/index-1bx83sw4.js +4 -0
  21. package/dist/index-1k16zv57.js +269 -0
  22. package/dist/index-6k1937bx.js +20 -0
  23. package/dist/{index-bmnhqtya.js → index-7eq9va6e.js} +1 -4
  24. package/dist/{index-yxpe3phd.js → index-cby4ar3v.js} +1 -18
  25. package/dist/index-h60df7rj.js +110 -0
  26. package/dist/{index-yez419px.js → index-js7yexng.js} +19 -6
  27. package/dist/index-k4dftwf7.js +66 -0
  28. package/dist/{index-pdv1mjjr.js → index-npye5kt7.js} +30 -59
  29. package/dist/{index-yydqk4fh.js → index-pzc32v75.js} +90 -3
  30. package/dist/{index-e1es808r.js → index-qh4xevf5.js} +1 -1
  31. package/dist/{index-gvha32jh.js → index-qx84bqzk.js} +2 -2
  32. package/dist/{index-pfqjb5xy.js → index-rgd18hx3.js} +23 -5
  33. package/dist/index-sbdmyz75.js +18 -0
  34. package/dist/{index-173tvqcp.js → index-v4bbq3p4.js} +9 -114
  35. package/dist/index.js +162 -35
  36. package/dist/node.js +30 -27
  37. package/dist/observability/event.d.ts +2 -16
  38. package/dist/observability/event.d.ts.map +1 -1
  39. package/dist/observability/index.js +28 -25
  40. package/dist/react.js +2 -2
  41. package/dist/realtime/contract.d.ts +9 -0
  42. package/dist/realtime/contract.d.ts.map +1 -1
  43. package/dist/realtime/index.d.ts +2 -1
  44. package/dist/realtime/index.d.ts.map +1 -1
  45. package/dist/realtime/request.d.ts +21 -0
  46. package/dist/realtime/request.d.ts.map +1 -0
  47. package/dist/realtime/socket.d.ts +3 -0
  48. package/dist/realtime/socket.d.ts.map +1 -1
  49. package/dist/remote.d.ts +9 -0
  50. package/dist/remote.d.ts.map +1 -0
  51. package/dist/remote.js +79 -0
  52. package/dist/server/context-contribution.d.ts +10 -0
  53. package/dist/server/context-contribution.d.ts.map +1 -0
  54. package/dist/server/context.d.ts.map +1 -1
  55. package/dist/server/index.d.ts +2 -1
  56. package/dist/server/index.d.ts.map +1 -1
  57. package/dist/server/index.js +133 -88
  58. package/dist/server/lifecycle.d.ts +4 -0
  59. package/dist/server/lifecycle.d.ts.map +1 -0
  60. package/dist/server/middleware/auth.d.ts +35 -7
  61. package/dist/server/middleware/auth.d.ts.map +1 -1
  62. package/dist/server/process-signal-common.d.ts +6 -0
  63. package/dist/server/process-signal-common.d.ts.map +1 -0
  64. package/dist/server/process-signals.d.ts.map +1 -1
  65. package/dist/server/types.d.ts +1 -1
  66. package/dist/server/types.d.ts.map +1 -1
  67. package/dist/testing/surface-conformance.d.ts +47 -0
  68. package/dist/testing/surface-conformance.d.ts.map +1 -0
  69. package/dist/testing/surface-manifest.d.ts +156 -0
  70. package/dist/testing/surface-manifest.d.ts.map +1 -0
  71. package/dist/testing.d.ts +2 -0
  72. package/dist/testing.d.ts.map +1 -1
  73. package/dist/testing.js +366 -3
  74. package/dist/tools/agent.d.ts.map +1 -1
  75. package/dist/tools/async-operation.d.ts +168 -0
  76. package/dist/tools/async-operation.d.ts.map +1 -0
  77. package/dist/tools/cli-args.d.ts +1 -1
  78. package/dist/tools/cli-args.d.ts.map +1 -1
  79. package/dist/tools/cli-command.d.ts +29 -0
  80. package/dist/tools/cli-command.d.ts.map +1 -0
  81. package/dist/tools/cli-format.d.ts +2 -2
  82. package/dist/tools/cli-format.d.ts.map +1 -1
  83. package/dist/tools/cli-wait.d.ts +11 -2
  84. package/dist/tools/cli-wait.d.ts.map +1 -1
  85. package/dist/tools/cli.d.ts +13 -6
  86. package/dist/tools/cli.d.ts.map +1 -1
  87. package/dist/tools/define-download-tool.d.ts +18 -0
  88. package/dist/tools/define-download-tool.d.ts.map +1 -0
  89. package/dist/tools/define-upload-tool.d.ts +16 -0
  90. package/dist/tools/define-upload-tool.d.ts.map +1 -0
  91. package/dist/tools/define-view-file-tool.d.ts +10 -0
  92. package/dist/tools/define-view-file-tool.d.ts.map +1 -0
  93. package/dist/tools/define-wait-tool.d.ts +22 -0
  94. package/dist/tools/define-wait-tool.d.ts.map +1 -0
  95. package/dist/tools/download-core.d.ts +20 -0
  96. package/dist/tools/download-core.d.ts.map +1 -0
  97. package/dist/tools/execute.d.ts +3 -1
  98. package/dist/tools/execute.d.ts.map +1 -1
  99. package/dist/tools/lifecycle.d.ts +4 -0
  100. package/dist/tools/lifecycle.d.ts.map +1 -0
  101. package/dist/tools/mcp-round.d.ts +0 -1
  102. package/dist/tools/mcp-round.d.ts.map +1 -1
  103. package/dist/tools/mcp-stdio-signals.d.ts +33 -0
  104. package/dist/tools/mcp-stdio-signals.d.ts.map +1 -0
  105. package/dist/tools/mount-download.d.ts +10 -4
  106. package/dist/tools/mount-download.d.ts.map +1 -1
  107. package/dist/tools/mount-upload.d.ts +4 -2
  108. package/dist/tools/mount-upload.d.ts.map +1 -1
  109. package/dist/tools/mount-wait.d.ts.map +1 -1
  110. package/dist/tools/native-definition.d.ts +15 -0
  111. package/dist/tools/native-definition.d.ts.map +1 -0
  112. package/dist/tools/runtime-tool.d.ts +5 -4
  113. package/dist/tools/runtime-tool.d.ts.map +1 -1
  114. package/dist/tools/surface.d.ts.map +1 -1
  115. package/dist/tools/upload-core.d.ts +4 -0
  116. package/dist/tools/upload-core.d.ts.map +1 -0
  117. package/dist/tools/view-file.d.ts +84 -23
  118. package/dist/tools/view-file.d.ts.map +1 -1
  119. package/dist/tools/wait-core.d.ts +16 -2
  120. package/dist/tools/wait-core.d.ts.map +1 -1
  121. package/dist/tools.d.ts +12 -3
  122. package/dist/tools.d.ts.map +1 -1
  123. package/dist/tools.js +827 -495
  124. package/llms-full.txt +720 -53
  125. package/package.json +10 -2
@@ -1,23 +1,33 @@
1
1
  import {
2
- isWithinDir,
2
+ isWithinDir
3
+ } from "./index-sbdmyz75.js";
4
+ import {
5
+ assertToolName,
6
+ assertUniqueToolName,
7
+ hasUsableChars,
8
+ toToolName
9
+ } from "./index-k4dftwf7.js";
10
+ import {
3
11
  jsonSchemaFields,
4
12
  toJsonSchema
5
- } from "./index-yxpe3phd.js";
13
+ } from "./index-cby4ar3v.js";
6
14
  import {
7
- AppError,
8
- STITCH_ERROR_STATUS,
9
15
  formatZodError,
10
16
  getRequestContext,
11
- isStitchErrorCode,
12
- isUnsafeKey,
13
17
  normalizeError,
14
18
  runWithRequestContext,
15
- safeJsonParse,
16
19
  validateDeclaredOutput
17
- } from "./index-173tvqcp.js";
20
+ } from "./index-v4bbq3p4.js";
21
+ import {
22
+ AppError,
23
+ STITCH_ERROR_STATUS,
24
+ isStitchErrorCode,
25
+ isUnsafeKey,
26
+ safeJsonParse
27
+ } from "./index-h60df7rj.js";
18
28
  import {
19
29
  isRecord
20
- } from "./index-bmnhqtya.js";
30
+ } from "./index-7eq9va6e.js";
21
31
 
22
32
  // src/tools/coerce.ts
23
33
  import { z } from "zod";
@@ -671,7 +681,12 @@ async function runToolMethod(method, toolName, rawArgs, context, hooks, lifecycl
671
681
  }
672
682
  try {
673
683
  const resolved = await extension.resolve({ ...rawArgs, ...parsed.data });
674
- callContext = { ...context, ...resolved, source: context.source };
684
+ callContext = {
685
+ ...context,
686
+ ...resolved,
687
+ source: context.source,
688
+ ...context.mcp !== undefined && { mcp: context.mcp }
689
+ };
675
690
  hookContext = callContext;
676
691
  callArgs = Object.fromEntries(Object.entries(rawArgs).filter(([key]) => !extensionKeys.has(key)));
677
692
  } catch (err) {
@@ -764,71 +779,6 @@ async function runToolMethod(method, toolName, rawArgs, context, hooks, lifecycl
764
779
  }
765
780
  }
766
781
 
767
- // src/tools/names.ts
768
- var SINGULAR_EXCEPTIONS = new Set([
769
- "analytics",
770
- "status",
771
- "stats",
772
- "settings",
773
- "media",
774
- "progress",
775
- "news"
776
- ]);
777
- var TOOL_NAME_RE = /^[a-zA-Z0-9_-]{1,64}$/;
778
- function normalizeService(value) {
779
- return value.replace(/[^a-zA-Z0-9_]/g, "_");
780
- }
781
- function normalizeMethod(value) {
782
- return value.replace(/[^a-zA-Z0-9_-]/g, "_");
783
- }
784
- function singularize(name) {
785
- if (SINGULAR_EXCEPTIONS.has(name))
786
- return name;
787
- if (name.endsWith("ies"))
788
- return `${name.slice(0, -3)}y`;
789
- if (name.endsWith("s") && !name.endsWith("ss"))
790
- return name.slice(0, -1);
791
- return name;
792
- }
793
- function singularizeTail(name) {
794
- const cut = name.lastIndexOf("_");
795
- if (cut === -1)
796
- return singularize(name);
797
- return `${name.slice(0, cut + 1)}${singularize(name.slice(cut + 1))}`;
798
- }
799
- function hasUsableChars(value) {
800
- return /[a-zA-Z0-9]/.test(value);
801
- }
802
- function assertToolName(name, serviceName, key) {
803
- const where = `service "${serviceName}", method "${key}"`;
804
- if (!TOOL_NAME_RE.test(name)) {
805
- const why = name.length > 64 ? `is ${name.length} characters (max 64) — set an explicit \`toolName\`` : "must match [a-zA-Z0-9_-]";
806
- throw new Error(`Tool name "${name}" (${where}) ${why}`);
807
- }
808
- }
809
- function toToolName(serviceName, methodName) {
810
- const normalized = normalizeService(serviceName);
811
- const singular = singularizeTail(normalized);
812
- const method = normalizeMethod(methodName);
813
- if (method === "list")
814
- return `list_${normalized}`;
815
- if (method === "get")
816
- return `get_${singular}`;
817
- if (method === "create")
818
- return `create_${singular}`;
819
- if (method === "update")
820
- return `update_${singular}`;
821
- if (method === "delete")
822
- return `delete_${singular}`;
823
- const snake = method.replace(/([A-Z])/g, "_$1").toLowerCase();
824
- return `${snake}_${singular}`.replace(/^_/, "");
825
- }
826
- function assertUniqueToolName(name, taken, surface) {
827
- if (taken) {
828
- throw new Error(`Duplicate ${surface} "${name}" across mounted operations`);
829
- }
830
- }
831
-
832
782
  // src/tools/presentation.ts
833
783
  import { z as z3 } from "zod";
834
784
  function withoutDialect(schema) {
@@ -1020,6 +970,100 @@ function formatToolError(result, toolName, errorHint) {
1020
970
  return err;
1021
971
  }
1022
972
 
973
+ // src/tools/runtime-tool.ts
974
+ function defineRuntimeTool(definition) {
975
+ if (definition.transports?.length === 0) {
976
+ throw new Error(`Runtime tool "${definition.name}" must expose at least one transport`);
977
+ }
978
+ return definition;
979
+ }
980
+ function createRuntimeToolFactory(config) {
981
+ function parseContext(context) {
982
+ const parsed = config.context.parse(context);
983
+ return {
984
+ ...context,
985
+ ...parsed,
986
+ params: undefined,
987
+ input: context.input,
988
+ ...context.mcp !== undefined && { mcp: context.mcp }
989
+ };
990
+ }
991
+ function define(definition) {
992
+ if (definition.output !== undefined) {
993
+ const { action: action2, method: method2, meta: meta2, handler: handler2, ...tool2 } = definition;
994
+ const identity2 = {
995
+ serviceName: config.serviceName,
996
+ action: action2,
997
+ method: method2,
998
+ ...config.scope !== undefined && { scope: config.scope },
999
+ ...(meta2 ?? config.meta) !== undefined && { meta: meta2 ?? config.meta }
1000
+ };
1001
+ return defineRuntimeTool({
1002
+ ...tool2,
1003
+ identity: identity2,
1004
+ handler: (context) => handler2(parseContext(context))
1005
+ });
1006
+ }
1007
+ const { action, method, meta, handler, ...tool } = definition;
1008
+ const identity = {
1009
+ serviceName: config.serviceName,
1010
+ action,
1011
+ method,
1012
+ ...config.scope !== undefined && { scope: config.scope },
1013
+ ...(meta ?? config.meta) !== undefined && { meta: meta ?? config.meta }
1014
+ };
1015
+ return defineRuntimeTool({
1016
+ ...tool,
1017
+ identity,
1018
+ handler: (context) => handler(parseContext(context))
1019
+ });
1020
+ }
1021
+ return { define };
1022
+ }
1023
+ function runtimeToolSupports(definition, transport) {
1024
+ if (definition.transports?.length === 0) {
1025
+ throw new Error(`Runtime tool "${definition.name}" must expose at least one transport`);
1026
+ }
1027
+ if (!definition.transports)
1028
+ return transport !== "CLI";
1029
+ return definition.transports.includes(transport);
1030
+ }
1031
+ function runtimeToolIdentity(definition) {
1032
+ return {
1033
+ method: definition.identity.method,
1034
+ desc: definition.description,
1035
+ serviceName: definition.identity.serviceName,
1036
+ key: definition.identity.action,
1037
+ toolName: definition.name,
1038
+ scope: definition.identity.scope,
1039
+ meta: definition.identity.meta,
1040
+ annotations: definition.annotations,
1041
+ ui: definition.ui,
1042
+ mcp: definition.mcp
1043
+ };
1044
+ }
1045
+ function runtimeToolMountable(definition, assertName = true) {
1046
+ if (assertName) {
1047
+ assertToolName(definition.name, definition.identity.serviceName, definition.identity.action);
1048
+ }
1049
+ const method = {
1050
+ ...runtimeToolIdentity(definition),
1051
+ inputSchema: definition.input,
1052
+ outputSchema: definition.output,
1053
+ handler: definition.handler
1054
+ };
1055
+ return {
1056
+ method,
1057
+ name: definition.name,
1058
+ argumentSchema: definition.input,
1059
+ presentationSchema: buildToolPresentationSchema({
1060
+ inputSchema: definition.input,
1061
+ unrepresentable: "any"
1062
+ }),
1063
+ shouldExtend: false
1064
+ };
1065
+ }
1066
+
1023
1067
  // src/tools/cli-args.ts
1024
1068
  import { z as z5 } from "zod";
1025
1069
  var NUMERIC_VALUE = /^-(\d+\.?\d*|\.\d+)$/;
@@ -1341,6 +1385,50 @@ function parseCliArgs(argv, schema, config = {}) {
1341
1385
  return { toolArgs, options };
1342
1386
  }
1343
1387
 
1388
+ // src/tools/cli-command.ts
1389
+ function defineCliCommand(definition) {
1390
+ return definition;
1391
+ }
1392
+ function cliCommandPresentationSchema(definition) {
1393
+ return buildToolPresentationSchema({
1394
+ inputSchema: definition.input,
1395
+ unrepresentable: "any"
1396
+ });
1397
+ }
1398
+ async function executeCliCommand(definition, rawArgs, options, writers, coerceJson) {
1399
+ let parsed;
1400
+ try {
1401
+ parsed = definition.input.safeParse(coerceJson ? coerceJsonArgs(rawArgs, definition.input) : rawArgs);
1402
+ } catch (error) {
1403
+ return toolResultFromError(error);
1404
+ }
1405
+ if (!parsed.success) {
1406
+ return {
1407
+ ok: false,
1408
+ code: "VALIDATION_ERROR",
1409
+ details: { message: `Invalid input: ${formatZodError(parsed.error)}` }
1410
+ };
1411
+ }
1412
+ try {
1413
+ const data = await definition.handler({
1414
+ input: parsed.data,
1415
+ options,
1416
+ ...writers
1417
+ });
1418
+ const checked = validateDeclaredOutput(definition.output, data);
1419
+ if (!checked.ok) {
1420
+ return {
1421
+ ok: false,
1422
+ code: "INTERNAL_SERVER_ERROR",
1423
+ details: { message: checked.message }
1424
+ };
1425
+ }
1426
+ return { ok: true, data: checked.data };
1427
+ } catch (error) {
1428
+ return toolResultFromError(error);
1429
+ }
1430
+ }
1431
+
1344
1432
  // src/tools/cli-format.ts
1345
1433
  var DEFAULT_EXIT_CODES = {
1346
1434
  VALIDATION_ERROR: 1,
@@ -1351,19 +1439,21 @@ var DEFAULT_EXIT_CODES = {
1351
1439
  CONFLICT: 5,
1352
1440
  RATE_LIMITED: 6,
1353
1441
  TIMEOUT: 7,
1442
+ WAIT_FAILED: 1,
1354
1443
  INTERNAL_SERVER_ERROR: 1
1355
1444
  };
1356
1445
  function emitResult(result, writers, opts) {
1357
1446
  if (result.ok) {
1358
1447
  if (result.data === undefined)
1359
1448
  return 0;
1360
- const text = opts.json ? JSON.stringify(result.data) : JSON.stringify(result.data, null, 2);
1361
- writers.stdout(`${text}
1449
+ const text2 = opts.json ? JSON.stringify(result.data) : JSON.stringify(result.data, null, 2);
1450
+ writers.stdout(`${text2}
1362
1451
  `);
1363
1452
  return 0;
1364
1453
  }
1365
1454
  const error = formatToolError(result, opts.toolName, opts.errorHint);
1366
- writers.stderr(`${JSON.stringify(error, null, 2)}
1455
+ const text = opts.json ? JSON.stringify(error) : JSON.stringify(error, null, 2);
1456
+ writers.stderr(`${text}
1367
1457
  `);
1368
1458
  const codes = opts.exitCodes ?? DEFAULT_EXIT_CODES;
1369
1459
  return codes[result.code] ?? 1;
@@ -1372,45 +1462,140 @@ function emitResult(result, writers, opts) {
1372
1462
  // src/tools/wait-core.ts
1373
1463
  var DEFAULT_BACKOFF = [2, 3, 5, 5, 8, 10];
1374
1464
  var DEFAULT_TIMEOUT = 600;
1375
- var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1465
+
1466
+ class WaitTimeoutError extends Error {
1467
+ constructor() {
1468
+ super("wait deadline elapsed before the first snapshot");
1469
+ this.name = "WaitTimeoutError";
1470
+ }
1471
+ }
1472
+ var defaultSleep = (ms, signal) => {
1473
+ signal?.throwIfAborted();
1474
+ return new Promise((resolve, reject) => {
1475
+ let settled = false;
1476
+ const settle = (action) => {
1477
+ if (settled)
1478
+ return;
1479
+ settled = true;
1480
+ signal?.removeEventListener("abort", onAbort);
1481
+ action();
1482
+ };
1483
+ const timer = setTimeout(() => {
1484
+ settle(resolve);
1485
+ }, ms);
1486
+ timer.unref?.();
1487
+ const onAbort = () => {
1488
+ clearTimeout(timer);
1489
+ settle(() => reject(signal?.reason ?? new Error("aborted")));
1490
+ };
1491
+ signal?.addEventListener("abort", onAbort, { once: true });
1492
+ if (signal?.aborted)
1493
+ onAbort();
1494
+ });
1495
+ };
1376
1496
  async function pollUntil(params) {
1377
1497
  const backoff = params.backoff?.length ? params.backoff : DEFAULT_BACKOFF;
1378
1498
  const lastBackoff = backoff[backoff.length - 1] ?? 5;
1379
1499
  const timeoutSec = params.timeoutSec ?? DEFAULT_TIMEOUT;
1380
1500
  const sleep = params.sleepFn ?? defaultSleep;
1381
- const startedAt = Date.now();
1501
+ const now = params.nowFn ?? (() => performance.now());
1502
+ const startedAt = now();
1503
+ const deadline = startedAt + timeoutSec * 1000;
1504
+ let last;
1382
1505
  for (let attempt = 0;; attempt++) {
1383
- const state = await params.poll();
1506
+ params.signal?.throwIfAborted();
1507
+ const remainingBeforePoll = Math.max(0, deadline - now());
1508
+ if (remainingBeforePoll === 0 && last) {
1509
+ return { state: last.state, timedOut: true };
1510
+ }
1511
+ const deadlineController = new AbortController;
1512
+ const onCallerAbort = () => deadlineController.abort(params.signal?.reason);
1513
+ params.signal?.addEventListener("abort", onCallerAbort, { once: true });
1514
+ const deadlineTimer = setTimeout(() => deadlineController.abort(new WaitTimeoutError), remainingBeforePoll);
1515
+ deadlineTimer.unref?.();
1516
+ let state;
1517
+ try {
1518
+ state = await params.poll(deadlineController.signal);
1519
+ } catch (error) {
1520
+ if (params.signal?.aborted)
1521
+ throw params.signal.reason ?? error;
1522
+ if (deadlineController.signal.aborted && now() >= deadline) {
1523
+ if (last)
1524
+ return { state: last.state, timedOut: true };
1525
+ throw new WaitTimeoutError;
1526
+ }
1527
+ throw error;
1528
+ } finally {
1529
+ clearTimeout(deadlineTimer);
1530
+ params.signal?.removeEventListener("abort", onCallerAbort);
1531
+ }
1532
+ params.signal?.throwIfAborted();
1533
+ last = { state };
1384
1534
  if (params.done(state))
1385
1535
  return { state, timedOut: false };
1386
- const elapsedSec = (Date.now() - startedAt) / 1000;
1536
+ const elapsedMs = now() - startedAt;
1537
+ const elapsedSec = elapsedMs / 1000;
1387
1538
  params.onTick?.(attempt + 1, elapsedSec);
1388
1539
  if (elapsedSec >= timeoutSec)
1389
1540
  return { state, timedOut: true };
1390
1541
  const waitSec = backoff[Math.min(attempt, backoff.length - 1)] ?? lastBackoff;
1391
- await sleep(waitSec * 1000);
1542
+ await sleep(Math.min(waitSec * 1000, Math.max(0, deadline - now())), params.signal);
1392
1543
  }
1393
1544
  }
1545
+ function runWaitOperation(params) {
1546
+ return pollUntil({
1547
+ poll: (signal) => params.poll(params.input, signal),
1548
+ done: params.done,
1549
+ backoff: params.backoff,
1550
+ timeoutSec: params.timeoutSec,
1551
+ sleepFn: params.sleepFn,
1552
+ onTick: params.onTick,
1553
+ signal: params.signal,
1554
+ nowFn: params.nowFn
1555
+ });
1556
+ }
1394
1557
 
1395
1558
  // src/tools/cli-wait.ts
1396
1559
  var DEFAULT_TIMEOUT2 = 600;
1560
+ function waitFailure(tool, result) {
1561
+ return {
1562
+ ok: false,
1563
+ code: "WAIT_FAILED",
1564
+ details: {
1565
+ message: `"${tool}" reached a terminal failed state`,
1566
+ result
1567
+ }
1568
+ };
1569
+ }
1397
1570
  async function pollUntilDone(params) {
1398
1571
  const { initial, wait, call } = params;
1399
1572
  if (!initial.ok)
1400
1573
  return initial;
1574
+ if (wait.failed?.(initial.data))
1575
+ return waitFailure(wait.tool, initial.data);
1401
1576
  if (wait.done(initial.data))
1402
1577
  return initial;
1403
1578
  const pollArgs = wait.poll(initial.data);
1404
1579
  if (!pollArgs)
1405
1580
  return initial;
1406
1581
  const timeoutSec = params.timeoutSec ?? wait.timeout ?? DEFAULT_TIMEOUT2;
1582
+ let failedState;
1407
1583
  const { state, timedOut } = await pollUntil({
1408
1584
  poll: () => call(wait.tool, pollArgs),
1409
- done: (result) => !result.ok || wait.done(result.data),
1585
+ done: (result) => {
1586
+ if (!result.ok)
1587
+ return true;
1588
+ if (wait.failed?.(result.data)) {
1589
+ failedState = result;
1590
+ return true;
1591
+ }
1592
+ return wait.done(result.data);
1593
+ },
1410
1594
  backoff: wait.backoff,
1411
1595
  timeoutSec,
1412
1596
  sleepFn: params.sleepFn,
1413
- onTick: params.onTick
1597
+ onTick: params.onTick,
1598
+ signal: params.signal
1414
1599
  });
1415
1600
  if (timedOut) {
1416
1601
  return {
@@ -1419,6 +1604,8 @@ async function pollUntilDone(params) {
1419
1604
  details: { message: `Timed out after ${timeoutSec}s waiting for "${wait.tool}"` }
1420
1605
  };
1421
1606
  }
1607
+ if (state === failedState && state.ok)
1608
+ return waitFailure(wait.tool, state.data);
1422
1609
  return state;
1423
1610
  }
1424
1611
 
@@ -1733,9 +1920,56 @@ async function writeDownload(root, target, data) {
1733
1920
  await writeFile(target, data);
1734
1921
  }
1735
1922
 
1923
+ // src/tools/surface.ts
1924
+ function duplicateLabel(transport) {
1925
+ if (transport === "MCP")
1926
+ return "MCP tool name";
1927
+ if (transport === "AGENT")
1928
+ return "agent tool name";
1929
+ return "CLI command";
1930
+ }
1931
+ function collectToolSurface({
1932
+ surface,
1933
+ transport,
1934
+ assertUniqueNames = true,
1935
+ ...collectConfig
1936
+ }) {
1937
+ const entries = [];
1938
+ const names = new Set;
1939
+ const append = (entry) => {
1940
+ if (assertUniqueNames) {
1941
+ assertUniqueToolName(entry.mountable.name, names.has(entry.mountable.name), duplicateLabel(transport));
1942
+ }
1943
+ names.add(entry.mountable.name);
1944
+ entries.push(entry);
1945
+ };
1946
+ for (const service of surface.services ?? []) {
1947
+ for (const mountable of collectTools(service, transport, collectConfig)) {
1948
+ append({
1949
+ kind: "contract",
1950
+ service: service.name,
1951
+ action: mountable.method.key,
1952
+ mountable
1953
+ });
1954
+ }
1955
+ }
1956
+ for (const definition of surface.runtimeTools ?? []) {
1957
+ if (!runtimeToolSupports(definition, transport))
1958
+ continue;
1959
+ append({
1960
+ kind: "runtime",
1961
+ service: definition.identity.serviceName,
1962
+ action: definition.identity.action,
1963
+ mountable: runtimeToolMountable(definition, collectConfig.assertNames),
1964
+ definition
1965
+ });
1966
+ }
1967
+ return entries;
1968
+ }
1969
+
1736
1970
  // src/tools/cli.ts
1737
1971
  var GLOBAL_OPTIONS = [
1738
- ["--json", "Emit raw JSON on stdout (for piping / scripts)"],
1972
+ ["--json", "Emit compact success/error JSON records (for scripts)"],
1739
1973
  ["--wait", "Block-poll an async result to a terminal state"],
1740
1974
  ["--wait-timeout <s>", "Override the --wait timeout in seconds"],
1741
1975
  ["--output-dir <dir>", "Download result media into a directory"],
@@ -1810,7 +2044,7 @@ function collectPassthrough(toolArgs, field, knownKeys) {
1810
2044
  const base = passthroughBase(toolArgs[field]);
1811
2045
  toolArgs[field] = base ? { ...base, ...bag } : bag;
1812
2046
  }
1813
- function renderTopHelp(name, version, tools) {
2047
+ function renderTopHelp(name, version, commands) {
1814
2048
  const lines = [
1815
2049
  `${name} ${version}`,
1816
2050
  "",
@@ -1818,9 +2052,9 @@ function renderTopHelp(name, version, tools) {
1818
2052
  "",
1819
2053
  "Commands:"
1820
2054
  ];
1821
- const width = Math.max(0, ...[...tools.keys()].map((k) => k.length));
1822
- for (const [command, tool] of tools) {
1823
- lines.push(` ${padRight(command, width)} ${summarize(tool.method.desc)}`);
2055
+ const width = Math.max(0, ...[...commands.keys()].map((key) => key.length));
2056
+ for (const [command, descriptor] of commands) {
2057
+ lines.push(` ${padRight(command, width)} ${summarize(descriptor.description)}`);
1824
2058
  }
1825
2059
  lines.push("", "Global options:");
1826
2060
  const optWidth = Math.max(...GLOBAL_OPTIONS.map(([flag]) => flag.length));
@@ -1831,16 +2065,42 @@ function renderTopHelp(name, version, tools) {
1831
2065
  `)}
1832
2066
  `;
1833
2067
  }
1834
- function renderCommandHelp(name, command, tool) {
1835
- const lines = [tool.method.desc, "", `Usage: ${name} ${command} [args] [--flags]`, ""];
1836
- const fields = jsonSchemaFields(tool.presentationSchema);
2068
+ function renderCommandHelp(name, command, descriptor) {
2069
+ const fields = jsonSchemaFields(descriptor.presentationSchema);
2070
+ const fieldsByName = new Map(fields.map((field) => [field.name, field]));
2071
+ const positionals = [];
2072
+ for (const [fieldName, info] of describeSchemaFields(descriptor.argumentSchema)) {
2073
+ if (info.kind === "boolean")
2074
+ continue;
2075
+ const field = fieldsByName.get(fieldName);
2076
+ if (field)
2077
+ positionals.push(field);
2078
+ }
2079
+ const positionalSyntax = new Map(positionals.map((field) => [
2080
+ field.name,
2081
+ field.required ? `<${field.name}>` : `[${field.name}]`
2082
+ ]));
2083
+ const usage = [
2084
+ `Usage: ${name} ${command}`,
2085
+ ...positionals.map((field) => positionalSyntax.get(field.name) ?? field.name),
2086
+ "[--flags]"
2087
+ ].join(" ");
2088
+ const lines = [descriptor.description, "", usage, ""];
1837
2089
  if (fields.length > 0) {
1838
2090
  lines.push("Arguments:");
1839
- const width = Math.max(...fields.map((f) => f.name.length));
2091
+ const labels = new Map(fields.map((field) => {
2092
+ const positional = positionalSyntax.get(field.name);
2093
+ return [
2094
+ field.name,
2095
+ positional ? `${positional} | --${field.name}` : `--${field.name}`
2096
+ ];
2097
+ }));
2098
+ const width = Math.max(...fields.map((field) => labels.get(field.name)?.length ?? 0));
1840
2099
  for (const f of fields) {
1841
2100
  const req = f.required ? " (required)" : "";
1842
2101
  const desc = f.description ? ` — ${f.description}` : "";
1843
- lines.push(` --${padRight(f.name, width)} <${typeLabel(f.schema)}>${req}${desc}`);
2102
+ const label = labels.get(f.name) ?? `--${f.name}`;
2103
+ lines.push(` ${padRight(label, width)} <${typeLabel(f.schema)}>${req}${desc}`);
1844
2104
  }
1845
2105
  lines.push("");
1846
2106
  }
@@ -1848,6 +2108,55 @@ function renderCommandHelp(name, command, tool) {
1848
2108
  `)}
1849
2109
  `;
1850
2110
  }
2111
+ function managedDescriptor(tool) {
2112
+ return {
2113
+ description: tool.method.desc,
2114
+ argumentSchema: tool.argumentSchema,
2115
+ presentationSchema: tool.presentationSchema
2116
+ };
2117
+ }
2118
+ function nativeDescriptor(definition) {
2119
+ return {
2120
+ description: definition.description,
2121
+ argumentSchema: definition.input,
2122
+ presentationSchema: cliCommandPresentationSchema(definition)
2123
+ };
2124
+ }
2125
+ function assertCommandShape(name, descriptor, exists) {
2126
+ assertUniqueToolName(name, exists, "CLI command");
2127
+ if (name === "help" || name === "version") {
2128
+ throw new Error(`[stitchkit] CLI command "${name}" is reserved`);
2129
+ }
2130
+ const conflicting = jsonSchemaFields(descriptor.presentationSchema).map((field) => field.name).filter((field) => RESERVED_CLI_OPTIONS.has(field));
2131
+ if (conflicting.length > 0) {
2132
+ throw new Error(`[stitchkit] CLI command "${name}" declares reserved option field(s): ${conflicting.join(", ")}`);
2133
+ }
2134
+ }
2135
+ async function prepareInvocation(command, commandArgv, descriptor, config, readStdin) {
2136
+ let parsed;
2137
+ try {
2138
+ parsed = parseCliArgs(commandArgv, descriptor.argumentSchema, {
2139
+ allowUnknown: config.passthrough?.[command] !== undefined,
2140
+ knownFields: jsonSchemaFields(descriptor.presentationSchema).map((field) => field.name)
2141
+ });
2142
+ } catch (error) {
2143
+ if (!(error instanceof CliArgumentError))
2144
+ throw error;
2145
+ return { ok: false, message: error.message };
2146
+ }
2147
+ const { toolArgs, options } = parsed;
2148
+ const firstUnset = jsonSchemaFields(descriptor.presentationSchema).find((field) => field.required && !(field.name in toolArgs) && field.schema.type !== "boolean");
2149
+ if (firstUnset) {
2150
+ const piped = await readStdin();
2151
+ if (piped !== null)
2152
+ toolArgs[firstUnset.name] = piped;
2153
+ }
2154
+ const passthroughField = config.passthrough?.[command];
2155
+ if (passthroughField) {
2156
+ collectPassthrough(toolArgs, passthroughField, objectShapeKeys(descriptor.argumentSchema));
2157
+ }
2158
+ return { ok: true, toolArgs, options };
2159
+ }
1851
2160
  var DEFAULT_DOWNLOAD_MAX_BYTES = 100 * 1024 * 1024;
1852
2161
  async function downloadResults(files, dir, stderr, quiet, allowPrivate, maxBytes, timeoutMs) {
1853
2162
  const root = resolve(dir);
@@ -1886,66 +2195,121 @@ async function createCli(config) {
1886
2195
  const exit = config.exit ?? ((code) => void process.exit(code));
1887
2196
  const argv = config.argv ?? process.argv.slice(2);
1888
2197
  const readStdin = config.stdin ?? readPipedStdin;
1889
- const auth = config.auth === undefined ? undefined : await config.auth;
1890
- const services = typeof config.services === "function" ? config.services(auth) : config.services;
1891
- const context = config.context?.(auth);
1892
- const tools = new Map;
1893
- for (const service of services) {
1894
- for (const mountable of collectTools(service, "CLI")) {
1895
- assertUniqueToolName(mountable.name, tools.has(mountable.name), "CLI command");
1896
- if (mountable.name === "help" || mountable.name === "version") {
1897
- throw new Error(`[stitchkit] CLI command "${mountable.name}" is reserved`);
1898
- }
1899
- const conflicting = jsonSchemaFields(mountable.presentationSchema).map((field) => field.name).filter((name) => RESERVED_CLI_OPTIONS.has(name));
1900
- if (conflicting.length > 0) {
1901
- throw new Error(`[stitchkit] CLI command "${mountable.name}" declares reserved option field(s): ${conflicting.join(", ")}`);
1902
- }
1903
- tools.set(mountable.name, mountable);
1904
- }
2198
+ if (config.auth !== undefined && config.resolveAuth !== undefined) {
2199
+ throw new Error("[stitchkit] createCli: use either auth or resolveAuth, not both");
1905
2200
  }
1906
- const runTool = createToolRunner({
1907
- source: "cli",
1908
- context,
1909
- hooks: config.hooks,
1910
- lifecycle: config.lifecycle,
1911
- errorHint: config.errorHint,
1912
- coerceJsonArgs: config.coerceJsonArgs
1913
- });
1914
- const [command, ...commandArgv] = argv;
1915
- if (command === undefined || command === "help" || command === "--help" || command === "-h") {
1916
- stdout(renderTopHelp(config.name, config.version, tools));
1917
- return exit(0);
2201
+ const nativeCommands = new Map;
2202
+ const nativeHelp = new Map;
2203
+ for (const definition of config.commands ?? []) {
2204
+ const descriptor2 = nativeDescriptor(definition);
2205
+ assertCommandShape(definition.name, descriptor2, nativeCommands.has(definition.name));
2206
+ nativeCommands.set(definition.name, definition);
2207
+ nativeHelp.set(definition.name, descriptor2);
1918
2208
  }
2209
+ const [command, ...commandArgv] = argv;
1919
2210
  if (command === "--version" || command === "version") {
1920
2211
  stdout(`${config.name} ${config.version}
1921
2212
  `);
1922
2213
  return exit(0);
1923
2214
  }
1924
- const tool = tools.get(command);
2215
+ const beforeSeparator = commandArgv.indexOf("--") === -1 ? commandArgv : commandArgv.slice(0, commandArgv.indexOf("--"));
2216
+ const helpRequested = beforeSeparator.includes("--help") || beforeSeparator.includes("-h");
2217
+ const native = command === undefined ? undefined : nativeCommands.get(command);
2218
+ if (native && command !== undefined) {
2219
+ const descriptor2 = nativeHelp.get(command);
2220
+ if (!descriptor2)
2221
+ throw new Error("[stitchkit] native CLI descriptor invariant failed");
2222
+ if (helpRequested) {
2223
+ stdout(renderCommandHelp(config.name, command, descriptor2));
2224
+ return exit(0);
2225
+ }
2226
+ const prepared2 = await prepareInvocation(command, commandArgv, descriptor2, config, readStdin);
2227
+ if (!prepared2.ok) {
2228
+ stderr(`${prepared2.message}
2229
+ `);
2230
+ return exit(2);
2231
+ }
2232
+ const { toolArgs: toolArgs2, options: options2 } = prepared2;
2233
+ if (options2.help) {
2234
+ stdout(renderCommandHelp(config.name, command, descriptor2));
2235
+ return exit(0);
2236
+ }
2237
+ if (options2.wait) {
2238
+ stderr(`--wait is not configured for native command "${command}"
2239
+ `);
2240
+ return exit(2);
2241
+ }
2242
+ if (options2.waitTimeout !== undefined) {
2243
+ stderr(`--wait-timeout requires --wait
2244
+ `);
2245
+ return exit(2);
2246
+ }
2247
+ if (options2.outputDir !== undefined) {
2248
+ stderr(`--output-dir is not configured for native commands
2249
+ `);
2250
+ return exit(2);
2251
+ }
2252
+ if (options2.dryRun) {
2253
+ stdout(`${JSON.stringify({ command, args: toolArgs2 }, null, 2)}
2254
+ `);
2255
+ return exit(0);
2256
+ }
2257
+ const result2 = await executeCliCommand(native, toolArgs2, options2, { stdout, stderr }, config.coerceJsonArgs ?? true);
2258
+ const exitCode2 = emitResult(result2, { stdout, stderr }, {
2259
+ json: options2.json,
2260
+ toolName: command,
2261
+ errorHint: config.errorHint,
2262
+ exitCodes: { ...DEFAULT_EXIT_CODES, ...config.exitCodes }
2263
+ });
2264
+ return exit(exitCode2);
2265
+ }
2266
+ let authPromise;
2267
+ const resolveIdentity = () => {
2268
+ authPromise ??= Promise.resolve(config.resolveAuth ? config.resolveAuth() : config.auth);
2269
+ return authPromise;
2270
+ };
2271
+ const dynamicSurface = typeof config.services === "function" || typeof config.runtimeTools === "function";
2272
+ const buildManagedSurface = async (forExecution) => {
2273
+ const auth = dynamicSurface || forExecution ? await resolveIdentity() : undefined;
2274
+ const services = typeof config.services === "function" ? config.services(auth) : config.services ?? [];
2275
+ const runtimeTools = typeof config.runtimeTools === "function" ? config.runtimeTools(auth) : config.runtimeTools ?? [];
2276
+ const tools = new Map;
2277
+ const help = new Map(nativeHelp);
2278
+ for (const { mountable } of collectToolSurface({
2279
+ surface: { services, runtimeTools },
2280
+ transport: "CLI"
2281
+ })) {
2282
+ const descriptor2 = managedDescriptor(mountable);
2283
+ assertCommandShape(mountable.name, descriptor2, help.has(mountable.name));
2284
+ tools.set(mountable.name, mountable);
2285
+ help.set(mountable.name, descriptor2);
2286
+ }
2287
+ return { auth, help, tools };
2288
+ };
2289
+ const topLevelHelp = command === undefined || command === "help" || command === "--help" || command === "-h";
2290
+ const managed = await buildManagedSurface(!topLevelHelp && !helpRequested);
2291
+ if (topLevelHelp) {
2292
+ stdout(renderTopHelp(config.name, config.version, managed.help));
2293
+ return exit(0);
2294
+ }
2295
+ const tool = managed.tools.get(command);
1925
2296
  if (!tool) {
1926
2297
  stderr(`Unknown command "${command}". Run "${config.name} --help" for the command list.
1927
2298
  `);
1928
2299
  return exit(1);
1929
2300
  }
1930
- const beforeSeparator = commandArgv.indexOf("--") === -1 ? commandArgv : commandArgv.slice(0, commandArgv.indexOf("--"));
1931
- if (beforeSeparator.includes("--help") || beforeSeparator.includes("-h")) {
1932
- stdout(renderCommandHelp(config.name, command, tool));
2301
+ const descriptor = managedDescriptor(tool);
2302
+ if (helpRequested) {
2303
+ stdout(renderCommandHelp(config.name, command, descriptor));
1933
2304
  return exit(0);
1934
2305
  }
1935
- let parsed;
1936
- try {
1937
- parsed = parseCliArgs(commandArgv, tool.argumentSchema, {
1938
- allowUnknown: config.passthrough?.[command] !== undefined,
1939
- knownFields: jsonSchemaFields(tool.presentationSchema).map((field) => field.name)
1940
- });
1941
- } catch (error) {
1942
- if (!(error instanceof CliArgumentError))
1943
- throw error;
1944
- stderr(`${error.message}
2306
+ const prepared = await prepareInvocation(command, commandArgv, descriptor, config, readStdin);
2307
+ if (!prepared.ok) {
2308
+ stderr(`${prepared.message}
1945
2309
  `);
1946
2310
  return exit(2);
1947
2311
  }
1948
- const { toolArgs, options } = parsed;
2312
+ const { toolArgs, options } = prepared;
1949
2313
  if (options.wait && !config.wait?.[command]) {
1950
2314
  stderr(`--wait is not configured for command "${command}"
1951
2315
  `);
@@ -1962,25 +2326,22 @@ async function createCli(config) {
1962
2326
  return exit(2);
1963
2327
  }
1964
2328
  if (options.help) {
1965
- stdout(renderCommandHelp(config.name, command, tool));
2329
+ stdout(renderCommandHelp(config.name, command, descriptor));
1966
2330
  return exit(0);
1967
2331
  }
1968
- const fields = jsonSchemaFields(safeInputSchema(tool));
1969
- const firstUnset = fields.find((f) => f.required && !(f.name in toolArgs) && f.schema.type !== "boolean");
1970
- if (firstUnset) {
1971
- const piped = await readStdin();
1972
- if (piped !== null)
1973
- toolArgs[firstUnset.name] = piped;
1974
- }
1975
- const passthroughField = config.passthrough?.[command];
1976
- if (passthroughField) {
1977
- collectPassthrough(toolArgs, passthroughField, objectShapeKeys(tool.argumentSchema));
1978
- }
1979
2332
  if (options.dryRun) {
1980
2333
  stdout(`${JSON.stringify({ command, args: toolArgs }, null, 2)}
1981
2334
  `);
1982
2335
  return exit(0);
1983
2336
  }
2337
+ const runTool = createToolRunner({
2338
+ source: "cli",
2339
+ context: { ...config.context?.(managed.auth), signal: config.signal },
2340
+ hooks: config.hooks,
2341
+ lifecycle: config.lifecycle,
2342
+ errorHint: config.errorHint,
2343
+ coerceJsonArgs: config.coerceJsonArgs
2344
+ });
1984
2345
  let result;
1985
2346
  try {
1986
2347
  result = await runTool(tool, toolArgs);
@@ -1993,7 +2354,7 @@ async function createCli(config) {
1993
2354
  initial: result,
1994
2355
  wait: waitConfig,
1995
2356
  call: async (toolName, args) => {
1996
- const pollTool = tools.get(toolName);
2357
+ const pollTool = managed.tools.get(toolName);
1997
2358
  if (!pollTool) {
1998
2359
  return {
1999
2360
  ok: false,
@@ -2005,7 +2366,8 @@ async function createCli(config) {
2005
2366
  },
2006
2367
  timeoutSec: options.waitTimeout,
2007
2368
  onTick: options.quiet ? undefined : (attempt) => stderr(`waiting… (poll ${attempt})
2008
- `)
2369
+ `),
2370
+ signal: config.signal
2009
2371
  });
2010
2372
  }
2011
2373
  let downloadsOk = true;
@@ -2020,8 +2382,5 @@ async function createCli(config) {
2020
2382
  });
2021
2383
  return exit(downloadsOk ? exitCode : 1);
2022
2384
  }
2023
- function safeInputSchema(tool) {
2024
- return tool.presentationSchema;
2025
- }
2026
2385
 
2027
- export { coerceJsonArgs, toolResultFromError, toolErrorFromResult, executeToolMethod, assertToolName, assertUniqueToolName, flattenToolJsonSchema, buildToolPresentationSchema, isObjectPresentationSchema, presentationMetadata, collectTools, createToolRunner, formatToolError, fetchGuarded, fetchPinnedDocument, readCapped, writeDownload, parseCliArgs, DEFAULT_EXIT_CODES, emitResult, pollUntil, pollUntilDone, createCli };
2386
+ export { coerceJsonArgs, toolResultFromError, toolErrorFromResult, executeToolMethod, flattenToolJsonSchema, buildToolPresentationSchema, isObjectPresentationSchema, presentationMetadata, collectTools, createToolRunner, formatToolError, defineRuntimeTool, createRuntimeToolFactory, collectToolSurface, WaitTimeoutError, runWaitOperation, fetchGuarded, fetchPinnedDocument, readCapped, parseCliArgs, defineCliCommand, DEFAULT_EXIT_CODES, emitResult, pollUntilDone, createCli };