stitchkit 0.118.0 → 0.119.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 (151) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/dist/browser/http.d.ts.map +1 -1
  3. package/dist/contract/client-types.d.ts +6 -3
  4. package/dist/contract/client-types.d.ts.map +1 -1
  5. package/dist/contract/define-validation.d.ts.map +1 -1
  6. package/dist/contract/define.d.ts +18 -12
  7. package/dist/contract/define.d.ts.map +1 -1
  8. package/dist/contract/endpoint-capabilities.d.ts +28 -0
  9. package/dist/contract/endpoint-capabilities.d.ts.map +1 -0
  10. package/dist/contract/factory.d.ts +49 -19
  11. package/dist/contract/factory.d.ts.map +1 -1
  12. package/dist/contract/tool-view.d.ts +1 -1
  13. package/dist/contract/tool-view.d.ts.map +1 -1
  14. package/dist/contract/transports.d.ts +5 -0
  15. package/dist/contract/transports.d.ts.map +1 -0
  16. package/dist/entrypoints/agent-runtime/coding-tools.js +6 -5
  17. package/dist/entrypoints/agent-runtime/harness.js +6 -5
  18. package/dist/entrypoints/agent-runtime/sandbox.js +6 -5
  19. package/dist/entrypoints/agent-runtime/sqlite/bun.js +9 -8
  20. package/dist/entrypoints/agent-runtime/sqlite/node.js +9 -8
  21. package/dist/entrypoints/agent-runtime/testing.js +9 -8
  22. package/dist/entrypoints/agent-runtime.js +14 -13
  23. package/dist/entrypoints/application/diagnostic-journal.js +3 -3
  24. package/dist/entrypoints/application/directory-inbox.js +4 -4
  25. package/dist/entrypoints/application/opentelemetry.js +1 -1
  26. package/dist/entrypoints/application.js +5 -5
  27. package/dist/entrypoints/cli/publish.js +4 -4
  28. package/dist/entrypoints/cli.js +8 -7
  29. package/dist/entrypoints/contract.d.ts +1 -1
  30. package/dist/entrypoints/contract.d.ts.map +1 -1
  31. package/dist/entrypoints/contract.js +6 -4
  32. package/dist/entrypoints/files.js +3 -3
  33. package/dist/entrypoints/geo/contract.d.ts +7 -0
  34. package/dist/entrypoints/geo/contract.d.ts.map +1 -0
  35. package/dist/entrypoints/geo/contract.js +6 -0
  36. package/dist/entrypoints/geo.js +60 -23
  37. package/dist/entrypoints/http.js +2 -2
  38. package/dist/entrypoints/index.js +8 -6
  39. package/dist/entrypoints/live.js +1 -1
  40. package/dist/entrypoints/node.js +7 -6
  41. package/dist/entrypoints/primitives.js +1 -1
  42. package/dist/entrypoints/process/owner-loss.js +1 -1
  43. package/dist/entrypoints/process.js +3 -3
  44. package/dist/entrypoints/react.js +1 -1
  45. package/dist/entrypoints/remote.js +4 -3
  46. package/dist/entrypoints/server.d.ts +1 -1
  47. package/dist/entrypoints/server.d.ts.map +1 -1
  48. package/dist/entrypoints/server.js +42 -27
  49. package/dist/entrypoints/telegram.js +2 -2
  50. package/dist/entrypoints/testing.js +7 -6
  51. package/dist/entrypoints/tools/connections.js +3 -2
  52. package/dist/entrypoints/tools/contract.js +1 -1
  53. package/dist/entrypoints/tools/invoker.js +5 -4
  54. package/dist/entrypoints/tools/mcp.js +7 -6
  55. package/dist/entrypoints/tools.js +17 -15
  56. package/dist/entrypoints/tracking/contract.js +5 -3
  57. package/dist/entrypoints/tracking/performance.js +3 -3
  58. package/dist/entrypoints/tracking.d.ts +1 -1
  59. package/dist/entrypoints/tracking.d.ts.map +1 -1
  60. package/dist/entrypoints/tracking.js +63 -83
  61. package/dist/entrypoints/xlsx.js +1 -1
  62. package/dist/geo/index.d.ts +2 -1
  63. package/dist/geo/index.d.ts.map +1 -1
  64. package/dist/geo/record.d.ts +8 -0
  65. package/dist/geo/record.d.ts.map +1 -0
  66. package/dist/geo/resolver.d.ts +1 -3
  67. package/dist/geo/resolver.d.ts.map +1 -1
  68. package/dist/geo/schema.d.ts +44 -0
  69. package/dist/geo/schema.d.ts.map +1 -0
  70. package/dist/geo/types.d.ts +2 -13
  71. package/dist/geo/types.d.ts.map +1 -1
  72. package/dist/{index-n5sv4z0w.js → index-1k18qzqa.js} +13 -15
  73. package/dist/{index-tsngj2b5.js → index-2dfvv1jg.js} +206 -206
  74. package/dist/{index-d9hq291n.js → index-4jpykzvw.js} +70 -81
  75. package/dist/{index-ghtpfz4z.js → index-57gqaq8b.js} +2 -2
  76. package/dist/index-69t14s4a.js +39 -0
  77. package/dist/{index-6p3frkv5.js → index-6a69xzrx.js} +4 -4
  78. package/dist/{index-dky0dcmf.js → index-72r9k0av.js} +6 -2
  79. package/dist/{index-vv1sch8a.js → index-8vzh5wpv.js} +13 -8
  80. package/dist/{index-xx1j310k.js → index-8yxgcyt6.js} +171 -169
  81. package/dist/{index-p6apg0jg.js → index-9frf01t9.js} +16 -9
  82. package/dist/{index-q0th8q0z.js → index-9mjrnqxs.js} +1 -3
  83. package/dist/{index-ega3yz4c.js → index-aqwg2dm5.js} +1 -1
  84. package/dist/{index-073nw9qr.js → index-ba7are04.js} +524 -522
  85. package/dist/{index-095ya5hm.js → index-bbvc59gw.js} +13 -11
  86. package/dist/{index-txg1npmg.js → index-cyxp8rbf.js} +1 -1
  87. package/dist/index-d3xdyjt6.js +38 -0
  88. package/dist/{index-n8z3rvzw.js → index-f4njz9j0.js} +3 -5
  89. package/dist/{index-90p34rts.js → index-fcqj44mb.js} +3 -1
  90. package/dist/{index-86nbfysb.js → index-ff39f9dr.js} +3 -3
  91. package/dist/{index-75ns2ybj.js → index-fqd4yqwd.js} +1 -1
  92. package/dist/{index-nqxrm176.js → index-g444vfz2.js} +1 -1
  93. package/dist/{index-vwhrnp67.js → index-kz9734jc.js} +3 -3
  94. package/dist/{index-dr3t5n1v.js → index-nn9qvkww.js} +46 -46
  95. package/dist/{index-qezhmp51.js → index-nz06qw5a.js} +3 -1
  96. package/dist/{index-1bx1xv1k.js → index-pfvde2fz.js} +5 -2
  97. package/dist/{index-s80awwsw.js → index-qch1vcaa.js} +3 -1
  98. package/dist/{index-cycp3r3e.js → index-rxcgp4b9.js} +2 -2
  99. package/dist/{index-w7jq3fz9.js → index-s0qjs6ee.js} +1198 -1196
  100. package/dist/{index-p0rf7sq9.js → index-s20s2mad.js} +209 -209
  101. package/dist/{index-v6vb5qx8.js → index-s3yxw6kt.js} +1 -1
  102. package/dist/{index-cqmqyjax.js → index-sa99p98w.js} +1 -1
  103. package/dist/{index-0gxvns6s.js → index-sba3e6m4.js} +1 -0
  104. package/dist/{index-f24bdt13.js → index-v1swr26p.js} +3 -1
  105. package/dist/{index-rhzkndr5.js → index-x4p3kw1p.js} +533 -530
  106. package/dist/{index-hdjmdwsj.js → index-x8d9fdeg.js} +513 -507
  107. package/dist/{index-htxh46ej.js → index-ycqhyars.js} +2 -2
  108. package/dist/{index-9bwcthxw.js → index-yj6saztf.js} +31 -13
  109. package/dist/{index-tdpmfks8.js → index-ynrf908v.js} +1 -1
  110. package/dist/{index-f6m4c545.js → index-zrdcmg6j.js} +4 -6
  111. package/dist/process/bun-child.d.ts +3 -2
  112. package/dist/process/bun-child.d.ts.map +1 -1
  113. package/dist/server/contract-method.d.ts.map +1 -1
  114. package/dist/server/file.d.ts +12 -6
  115. package/dist/server/file.d.ts.map +1 -1
  116. package/dist/tools/internal/surface-projector.d.ts.map +1 -1
  117. package/dist/tools/operations/async-operation-canonical-types.d.ts +1 -1
  118. package/dist/tracking/attribution.d.ts +21 -25
  119. package/dist/tracking/attribution.d.ts.map +1 -1
  120. package/dist/tracking/client-visit.d.ts +5 -7
  121. package/dist/tracking/client-visit.d.ts.map +1 -1
  122. package/dist/tracking/client.d.ts +2 -2
  123. package/dist/tracking/client.d.ts.map +1 -1
  124. package/dist/tracking/contract.d.ts +1 -1
  125. package/dist/tracking/host.d.ts +0 -1
  126. package/dist/tracking/host.d.ts.map +1 -1
  127. package/dist/tracking/privacy.d.ts +6 -0
  128. package/dist/tracking/privacy.d.ts.map +1 -1
  129. package/dist/tracking/schemas.d.ts +2 -2
  130. package/dist/tracking/schemas.d.ts.map +1 -1
  131. package/llms/contract.txt +46 -24
  132. package/llms/geo-contract.txt +23 -0
  133. package/llms/geo.txt +40 -4
  134. package/llms/server.2.txt +19 -6
  135. package/llms/server.5.txt +2 -1
  136. package/llms/stitchkit.txt +4 -3
  137. package/llms/testing.txt +3 -3
  138. package/llms/tools.2.txt +2 -2
  139. package/llms/tools.3.txt +1 -1
  140. package/llms/tracking-contract.txt +1 -1
  141. package/llms/tracking.txt +27 -5
  142. package/llms/{upgrading-0.105.0-0.118.0.txt → upgrading-0.106.0-0.119.0.txt} +121 -410
  143. package/llms/upgrading-0.37.0-0.53.0.txt +969 -0
  144. package/llms/{upgrading-0.44.0-0.62.0.txt → upgrading-0.55.0-0.68.0.txt} +509 -528
  145. package/llms/{upgrading-0.63.0-0.83.0.txt → upgrading-0.69.0-0.95.0.txt} +506 -509
  146. package/llms/{upgrading-0.84.0-0.104.0.txt → upgrading-0.96.0-0.105.0.txt} +410 -506
  147. package/llms/upgrading.txt +5 -5
  148. package/llms-full.txt +271 -48
  149. package/llms.txt +11 -10
  150. package/package.json +23 -19
  151. package/llms/upgrading-0.37.0-0.44.0.txt +0 -444
@@ -14,15 +14,15 @@ import {
14
14
  objectShapeKeys,
15
15
  assertUniqueToolName,
16
16
  buildToolPresentationSchema
17
- } from "./index-rhzkndr5.js";
17
+ } from "./index-x4p3kw1p.js";
18
18
  import {
19
19
  toolResultFromError,
20
20
  toolErrorFromResult
21
- } from "./index-v6vb5qx8.js";
21
+ } from "./index-s3yxw6kt.js";
22
22
  import {
23
23
  createToolRunner,
24
24
  formatToolError
25
- } from "./index-htxh46ej.js";
25
+ } from "./index-ycqhyars.js";
26
26
  import {
27
27
  writeContainedFile
28
28
  } from "./index-q968hy6y.js";
@@ -33,7 +33,7 @@ import {
33
33
  } from "./index-arjmb6de.js";
34
34
  import {
35
35
  collectToolSurface
36
- } from "./index-86nbfysb.js";
36
+ } from "./index-ff39f9dr.js";
37
37
  import {
38
38
  fetchGuarded,
39
39
  readCapped
@@ -59,6 +59,78 @@ import {
59
59
  isRecord
60
60
  } from "./index-9jfdm5tg.js";
61
61
 
62
+ // src/tools/operations/wait-core.ts
63
+ var DEFAULT_BACKOFF = [2, 3, 5, 5, 8, 10];
64
+ var DEFAULT_TIMEOUT = 600;
65
+
66
+ class WaitTimeoutError extends Error {
67
+ constructor() {
68
+ super("wait deadline elapsed before the first snapshot");
69
+ this.name = "WaitTimeoutError";
70
+ }
71
+ }
72
+ async function pollUntil(params) {
73
+ const backoff = params.backoff?.length ? params.backoff : DEFAULT_BACKOFF;
74
+ const lastBackoff = backoff[backoff.length - 1] ?? 5;
75
+ const timeoutSec = params.timeoutSec ?? DEFAULT_TIMEOUT;
76
+ const pause = params.sleepFn ?? sleep;
77
+ const now = params.nowFn ?? (() => performance.now());
78
+ const startedAt = now();
79
+ const deadline = startedAt + timeoutSec * 1000;
80
+ let last;
81
+ for (let attempt = 0;; attempt++) {
82
+ params.signal?.throwIfAborted();
83
+ const remainingBeforePoll = Math.max(0, deadline - now());
84
+ if (remainingBeforePoll === 0 && last) {
85
+ return { state: last.state, timedOut: true };
86
+ }
87
+ const deadlineController = new AbortController;
88
+ const onCallerAbort = () => deadlineController.abort(params.signal?.reason);
89
+ params.signal?.addEventListener("abort", onCallerAbort, { once: true });
90
+ const deadlineTimer = setTimeout(() => deadlineController.abort(new WaitTimeoutError), remainingBeforePoll);
91
+ deadlineTimer.unref?.();
92
+ let state;
93
+ try {
94
+ state = await params.poll(deadlineController.signal);
95
+ } catch (error) {
96
+ if (params.signal?.aborted)
97
+ throw params.signal.reason ?? error;
98
+ if (deadlineController.signal.aborted && now() >= deadline) {
99
+ if (last)
100
+ return { state: last.state, timedOut: true };
101
+ throw new WaitTimeoutError;
102
+ }
103
+ throw error;
104
+ } finally {
105
+ clearTimeout(deadlineTimer);
106
+ params.signal?.removeEventListener("abort", onCallerAbort);
107
+ }
108
+ params.signal?.throwIfAborted();
109
+ last = { state };
110
+ if (params.done(state))
111
+ return { state, timedOut: false };
112
+ const elapsedMs = now() - startedAt;
113
+ const elapsedSec = elapsedMs / 1000;
114
+ params.onTick?.(attempt + 1, elapsedSec);
115
+ if (elapsedSec >= timeoutSec)
116
+ return { state, timedOut: true };
117
+ const waitSec = backoff[Math.min(attempt, backoff.length - 1)] ?? lastBackoff;
118
+ await pause(Math.min(waitSec * 1000, Math.max(0, deadline - now())), params.signal);
119
+ }
120
+ }
121
+ function runWaitOperation(params) {
122
+ return pollUntil({
123
+ poll: (signal) => params.poll(params.input, signal),
124
+ done: params.done,
125
+ backoff: params.backoff,
126
+ timeoutSec: params.timeoutSec,
127
+ sleepFn: params.sleepFn,
128
+ onTick: params.onTick,
129
+ signal: params.signal,
130
+ nowFn: params.nowFn
131
+ });
132
+ }
133
+
62
134
  // src/tools/cli/command.ts
63
135
  function defineCliCommand2(definition) {
64
136
  return definition;
@@ -201,13 +273,15 @@ function emitPresentationFailure(result, failure, writers, opts) {
201
273
  }, writers, { json: opts.json, toolName: opts.toolName, exitCodes: opts.exitCodes });
202
274
  }
203
275
 
276
+ // src/tools/cli/args-fields.ts
277
+ import { z } from "zod";
278
+
204
279
  // src/tools/cli/argument-error.ts
205
280
  class CliArgumentError2 extends Error {
206
281
  name = "CliArgumentError";
207
282
  }
208
283
 
209
284
  // src/tools/cli/args-fields.ts
210
- import { z } from "zod";
211
285
  function appendCliOptionValue(raw, name, value, info) {
212
286
  const existing = raw.get(name);
213
287
  const repeatable = info?.kind === "array" || !name.includes(".") && info === undefined;
@@ -724,1287 +798,978 @@ function managedCliDescriptor(tool) {
724
798
  };
725
799
  }
726
800
 
727
- // src/tools/cli/args-route.ts
728
- function isOptionToken(token) {
729
- return token.startsWith("--") || /^-[A-Za-z](?:=|$)/.test(token);
730
- }
731
- function looksLikeOption(token) {
732
- return token.startsWith("--") || /^-[A-Za-z]/.test(token);
801
+ // src/tools/cli/io.ts
802
+ import { writeSync } from "node:fs";
803
+
804
+ // src/tools/cli/stdin.ts
805
+ function readPipedStdin(input = process.stdin) {
806
+ if (input.isTTY || input.readableEnded)
807
+ return Promise.resolve(null);
808
+ if (input.destroyed)
809
+ return Promise.reject(new Error("stdin closed before EOF"));
810
+ return new Promise((resolve, reject) => {
811
+ const chunks = [];
812
+ const cleanup = () => {
813
+ clearTimeout(timer);
814
+ input.pause();
815
+ input.off("data", onData);
816
+ input.off("end", onEnd);
817
+ input.off("error", onError);
818
+ input.off("close", onClose);
819
+ };
820
+ const onData = (chunk) => {
821
+ if (chunk.length === 0)
822
+ return;
823
+ clearTimeout(timer);
824
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
825
+ };
826
+ const onEnd = () => {
827
+ cleanup();
828
+ const text = Buffer.concat(chunks).toString("utf8").trim();
829
+ resolve(text.length > 0 ? text : null);
830
+ };
831
+ const onError = (error) => {
832
+ cleanup();
833
+ reject(error);
834
+ };
835
+ const onClose = () => onError(new Error("stdin closed before EOF"));
836
+ const timer = setTimeout(() => {
837
+ cleanup();
838
+ resolve(null);
839
+ }, 250);
840
+ input.on("end", onEnd);
841
+ input.on("error", onError);
842
+ input.on("close", onClose);
843
+ input.on("data", onData);
844
+ input.resume();
845
+ });
733
846
  }
734
- var BOOL_OPTIONS = new Set(["json", "wait", "quiet", "dry-run", "help", "ascending"]);
735
- var VIEW_OPTIONS = new Set(["count-by", "sum", "by", "top", "table", "sort"]);
736
- var VALUE_OPTIONS = new Set(["wait-timeout", "output-dir", ...VIEW_OPTIONS]);
737
- var RESERVED_CLI_OPTIONS = new Set([...BOOL_OPTIONS, ...VALUE_OPTIONS]);
738
- function classifyLongOptionToken(token) {
739
- if (!token.startsWith("--") || token === "--")
740
- return;
741
- const equals = token.indexOf("=");
742
- const name = equals >= 0 ? token.slice(2, equals) : token.slice(2);
743
- return {
744
- name,
745
- value: equals >= 0 ? token.slice(equals + 1) : undefined,
746
- inline: equals >= 0,
747
- globalKind: BOOL_OPTIONS.has(name) ? "boolean" : VALUE_OPTIONS.has(name) ? "value" : undefined
847
+
848
+ // src/tools/cli/io.ts
849
+ function createCliIo(config) {
850
+ let pendingWrite;
851
+ let writeFailed = false;
852
+ const writeFd = (fd, text) => {
853
+ const bytes = Buffer.from(text, "utf8");
854
+ let offset = 0;
855
+ while (offset < bytes.length) {
856
+ try {
857
+ offset += writeSync(fd, bytes, offset, bytes.length - offset);
858
+ } catch (error) {
859
+ if (isRecord(error) && error.code === "EAGAIN") {
860
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1);
861
+ continue;
862
+ }
863
+ const stream = fd === 1 ? process.stdout : process.stderr;
864
+ const rest = bytes.subarray(offset);
865
+ pendingWrite = new Promise((resolve) => {
866
+ stream.write(rest, (failure) => {
867
+ if (failure)
868
+ writeFailed = true;
869
+ resolve();
870
+ });
871
+ });
872
+ return;
873
+ }
874
+ }
748
875
  };
876
+ const stdout = config.stdout ?? ((text) => writeFd(1, text));
877
+ const stderr = config.stderr ?? ((text) => writeFd(2, text));
878
+ const exit = config.exit ?? ((code) => {
879
+ if (pendingWrite === undefined)
880
+ process.exit(code);
881
+ else
882
+ pendingWrite.then(() => process.exit(writeFailed ? 1 : code));
883
+ });
884
+ const readStdin = config.stdin ?? readPipedStdin;
885
+ return { stdout, stderr, exit, readStdin };
749
886
  }
750
- var HELP_TOKENS = new Set(["--help", "-h", "help"]);
751
- function helpFilterFrom(token, rest) {
752
- if (token?.startsWith("--help=")) {
753
- const value = token.slice("--help=".length).trim();
754
- if (value.length === 0 || isReservedBoolWord(value))
755
- return;
756
- return value;
887
+
888
+ // src/tools/cli/download.ts
889
+ import { basename, resolve } from "node:path";
890
+
891
+ // src/internal/write-download.ts
892
+ import path from "node:path";
893
+ async function writeDownload(root, name, data) {
894
+ if (!isSafeEntryName(name) || path.basename(name) !== name) {
895
+ throw new Error("download name escapes the output dir");
757
896
  }
758
- if (token === undefined || !HELP_TOKENS.has(token))
759
- return;
760
- const next = rest[0];
761
- return next !== undefined && !next.startsWith("-") ? next : undefined;
897
+ const parent = { handle: root, basename: name };
898
+ const metadata = await containedEntryMetadata(parent);
899
+ await writeContainedFile({
900
+ parent,
901
+ content: data,
902
+ replace: true,
903
+ ...metadata?.isFile() && { mode: metadata.mode }
904
+ });
762
905
  }
763
- function routeCliArgv2(argv, defaultCommand) {
764
- if (defaultCommand === undefined) {
765
- const [command, ...commandArgv] = argv;
766
- const helpFilter = helpFilterFrom(command, commandArgv);
767
- return {
768
- command,
769
- commandArgv,
770
- topLevelHelp: false,
771
- version: false,
772
- ...helpFilter !== undefined && { helpFilter }
773
- };
906
+
907
+ // src/tools/cli/download.ts
908
+ var DEFAULT_DOWNLOAD_MAX_BYTES = 100 * 1024 * 1024;
909
+ async function downloadResults(files, dir, stderr, quiet, allowPrivate, maxBytes, timeoutMs) {
910
+ const root = resolve(dir);
911
+ if (files.length === 0)
912
+ return true;
913
+ let directory;
914
+ try {
915
+ directory = await openPinnedDirectory(root, { create: true, readable: false });
916
+ } catch (error) {
917
+ stderr(`failed to prepare download directory: ${error instanceof Error ? error.message : String(error)}
918
+ `);
919
+ return false;
774
920
  }
775
- const globals = [];
776
- let index = 0;
777
- while (index < argv.length) {
778
- const token = argv[index];
779
- if (token === undefined)
780
- break;
781
- if (token === "--") {
782
- return {
783
- commandArgv: [],
784
- topLevelHelp: false,
785
- version: false,
786
- error: 'A command is required before "--"'
787
- };
788
- }
789
- const inlineHelpFilter = token.startsWith("--help=") && !isReservedBoolWord(token.slice("--help=".length));
790
- if (token === "--help" || token === "-h" || inlineHelpFilter) {
791
- const helpFilter = helpFilterFrom(token, argv.slice(index + 1));
792
- return {
793
- commandArgv: [],
794
- topLevelHelp: true,
795
- version: false,
796
- ...helpFilter !== undefined && { helpFilter }
797
- };
798
- }
799
- if (token === "--version" || token === "version") {
800
- return { commandArgv: [], topLevelHelp: false, version: true };
801
- }
802
- if (!token.startsWith("-") || token === "-") {
803
- return {
804
- command: token,
805
- commandArgv: [...globals, ...argv.slice(index + 1)],
806
- topLevelHelp: false,
807
- version: false
808
- };
809
- }
810
- if (!token.startsWith("--")) {
811
- return {
812
- command: defaultCommand,
813
- commandArgv: [...globals, ...argv.slice(index)],
814
- topLevelHelp: false,
815
- version: false
816
- };
817
- }
818
- const option = classifyLongOptionToken(token);
819
- if (!option) {
820
- return {
821
- command: defaultCommand,
822
- commandArgv: [...globals, ...argv.slice(index)],
823
- topLevelHelp: false,
824
- version: false
825
- };
826
- }
827
- if (option.globalKind === "boolean") {
828
- globals.push(token);
829
- index += 1;
830
- const next = argv[index];
831
- if (option.value === undefined && next !== undefined && separateBoolValue(next) !== undefined) {
832
- globals.push(next);
833
- index += 1;
921
+ let succeeded = true;
922
+ try {
923
+ for (const file of files) {
924
+ try {
925
+ const res = await fetchGuarded(new URL(file.url), allowPrivate, { timeoutMs });
926
+ if (!res.ok)
927
+ throw new Error(`HTTP ${res.status}`);
928
+ const buffer = await readCapped(res, maxBytes);
929
+ if (!buffer)
930
+ throw new Error(`file exceeds the ${maxBytes}-byte cap`);
931
+ const name = basename(file.name);
932
+ await writeDownload(directory, name, buffer);
933
+ if (!quiet)
934
+ stderr(`saved ${resolve(root, name)} (${(buffer.length / 1024).toFixed(0)}KB)
935
+ `);
936
+ } catch (err) {
937
+ succeeded = false;
938
+ const reason = errnoOf(err) ?? (err instanceof Error ? err.message : String(err));
939
+ stderr(`failed to download ${file.url}: ${reason}
940
+ `);
834
941
  }
835
- continue;
836
942
  }
837
- if (option.globalKind === "value") {
838
- globals.push(token);
839
- if (!option.inline) {
840
- const value = argv[index + 1];
841
- if (value === "--help" || value === "-h") {
842
- const helpFilter = helpFilterFrom(value, argv.slice(index + 2));
843
- return {
844
- commandArgv: [],
845
- topLevelHelp: true,
846
- version: false,
847
- ...helpFilter !== undefined && { helpFilter }
848
- };
849
- }
850
- if (value === "--version" || value === "version") {
851
- return { commandArgv: [], topLevelHelp: false, version: true };
852
- }
853
- if (value !== undefined)
854
- globals.push(value);
855
- index += value === undefined ? 1 : 2;
856
- } else {
857
- index += 1;
858
- }
859
- continue;
943
+ } finally {
944
+ try {
945
+ await directory.close();
946
+ } catch (error) {
947
+ succeeded = false;
948
+ stderr(`failed to close download directory: ${error instanceof Error ? error.message : String(error)}
949
+ `);
860
950
  }
861
- return {
862
- command: defaultCommand,
863
- commandArgv: [...globals, ...argv.slice(index)],
864
- topLevelHelp: false,
865
- version: false
866
- };
867
951
  }
868
- return {
869
- command: defaultCommand,
870
- commandArgv: globals,
871
- topLevelHelp: false,
872
- version: false
873
- };
952
+ return succeeded;
874
953
  }
875
954
 
876
- // src/tools/cli/args.ts
877
- import { z as z2 } from "zod";
878
-
879
- // src/tools/cli/args-json.ts
880
- function cliFieldOption(option, fields) {
881
- const json = option.inline && option.name.endsWith(":") && !fields.has(option.name);
882
- const name = json ? option.name.slice(0, -1) : option.name;
883
- return { name, json };
884
- }
885
- function appendCliFieldValue(raw, jsonFields, name, value, info, json) {
886
- appendCliOptionValue(raw, name, value, json || jsonFields.has(name) ? { kind: "other" } : info);
887
- if (json)
888
- jsonFields.add(name);
889
- }
890
- function coerceCliFieldValue(info, values, label, json) {
891
- if (!json)
892
- return coerceField(info, values, label);
893
- try {
894
- return safeJsonParse(values[0] ?? "");
895
- } catch (cause) {
896
- throw new CliArgumentError2(`${label} expects valid JSON`, { cause });
897
- }
955
+ // src/tools/cli/help.ts
956
+ var GLOBAL_OPTIONS = [
957
+ ["--json", "Emit compact success/error JSON records (for scripts)"],
958
+ ["--wait", "Block-poll an async result to a terminal state"],
959
+ ["--wait-timeout <s>", "Override the --wait timeout in seconds"],
960
+ ["--output-dir <dir>", "Download result media into a directory"],
961
+ ["--quiet", "Suppress non-essential stderr output"],
962
+ ["--dry-run", "Print the resolved call without executing it"],
963
+ ["--help, -h", "Show help for a command"],
964
+ ["--help <text>", "List only the commands matching a substring"],
965
+ ["--count-by <field>", "Count records per distinct value of a field"],
966
+ ["--sum <field>", "Total a numeric field, optionally grouped by --by"],
967
+ ["--sort <field>", "Order the records by a field, largest first"],
968
+ ["--ascending", "Flip --sort to smallest first"],
969
+ ["--top <n>", "Keep the n leading entries of the view asked for"],
970
+ ["--table <a,b>", "Render the named fields as an aligned table"]
971
+ ];
972
+ function typeLabel(schema) {
973
+ if (Array.isArray(schema.enum))
974
+ return schema.enum.join("|");
975
+ if (schema.type === "array")
976
+ return "value";
977
+ if (typeof schema.type === "string")
978
+ return schema.type;
979
+ return "value";
898
980
  }
899
-
900
- // src/tools/cli/args-tokens.ts
901
- function readCliTokens(argv, fields, fieldFor, config) {
902
- const options = {
903
- json: false,
904
- wait: false,
905
- quiet: false,
906
- dryRun: false,
907
- help: false
908
- };
909
- const flags = new Map;
910
- const jsonFields = new Set;
911
- const literalBooleanFlags = new Set;
912
- const frameworkFlags = new Map;
913
- const viewFlags = new Map;
914
- let ascending = false;
915
- const positionals = [];
916
- const pushFlag = (name, value, json = false) => {
917
- appendCliFieldValue(flags, jsonFields, name, value, fieldFor(name), json);
918
- };
919
- let optionsEnded = false;
920
- for (let i = 0;i < argv.length; i++) {
921
- const tok = argv[i];
922
- if (tok === undefined)
923
- continue;
924
- if (!optionsEnded && tok === "--") {
925
- optionsEnded = true;
981
+ function constraintLabel(schema) {
982
+ const limits = [];
983
+ const bounds = [
984
+ ["minimum", ">="],
985
+ ["exclusiveMinimum", ">"],
986
+ ["maximum", "<="],
987
+ ["exclusiveMaximum", "<"],
988
+ ["minLength", "length >="],
989
+ ["maxLength", "length <="],
990
+ ["minItems", "items >="],
991
+ ["maxItems", "items <="]
992
+ ];
993
+ for (const [keyword, label] of bounds) {
994
+ const bound = schema[keyword];
995
+ if (typeof bound !== "number")
926
996
  continue;
927
- }
928
- if (optionsEnded || !looksLikeOption(tok)) {
929
- positionals.push(tok);
997
+ if (schema.type === "integer" && Math.abs(bound) === Number.MAX_SAFE_INTEGER)
930
998
  continue;
931
- }
932
- if (tok === "-h") {
933
- options.help = true;
999
+ limits.push(`${label}${bound}`);
1000
+ }
1001
+ const compositions = [
1002
+ ["anyOf", "any of", " | "],
1003
+ ["oneOf", "one of", " | "],
1004
+ ["allOf", "all of", " & "]
1005
+ ];
1006
+ for (const [keyword, label, separator] of compositions) {
1007
+ const members = schema[keyword];
1008
+ if (!Array.isArray(members))
934
1009
  continue;
935
- }
936
- if (!tok.startsWith("--")) {
937
- const match = /^-([A-Za-z])(?:=(.*))?$/.exec(tok);
938
- const alias = match?.[1];
939
- const field = alias === undefined ? undefined : config.optionAliases?.get(alias);
940
- if (!alias || !field)
941
- throw new CliArgumentError2(`Unknown option "${tok}"`);
942
- const inline = match?.[2];
943
- const info = fieldFor(field);
944
- if (info?.kind === "boolean") {
945
- const separate = inline === undefined ? separateBoolValue(argv[i + 1]) : undefined;
946
- if (separate !== undefined)
947
- i++;
948
- pushFlag(field, inline ?? String(separate ?? true));
949
- if (inline !== undefined)
950
- parseReservedBool(field, inline);
951
- literalBooleanFlags.add(field);
952
- continue;
953
- }
954
- let value = inline;
955
- if (value === undefined) {
956
- const next = argv[i + 1];
957
- if (next !== undefined && !isOptionToken(next)) {
958
- value = next;
959
- i++;
960
- } else {
961
- throw new CliArgumentError2(`--${field} requires a value`);
962
- }
963
- }
964
- pushFlag(field, value);
1010
+ const branches = members.map((member) => {
1011
+ const constraints = isRecord(member) ? constraintLabel(member) : "";
1012
+ const type = isRecord(member) ? typeLabel(member) : member === false ? "no value" : "any value";
1013
+ return { constraints, text: `${type}${constraints}` };
1014
+ });
1015
+ if (branches.every((branch) => branch.constraints === ""))
965
1016
  continue;
966
- }
967
- const option = classifyLongOptionToken(tok);
968
- if (!option)
969
- throw new CliArgumentError2(`Unknown option "${tok}"`);
970
- const { name, json } = cliFieldOption(option, fields);
971
- let { value } = option;
972
- if (name.length === 0)
973
- throw new CliArgumentError2("Invalid empty option name");
974
- if (json && RESERVED_CLI_OPTIONS.has(name)) {
975
- throw new CliArgumentError2(`--${name} is a framework option and does not accept :=JSON`);
976
- }
977
- if (option.globalKind === "boolean") {
978
- const separate = value === undefined ? separateBoolValue(argv[i + 1]) : undefined;
979
- if (separate !== undefined)
980
- i++;
981
- if (name !== "help") {
982
- appendCliOptionValue(frameworkFlags, name, value ?? String(separate ?? true), {
983
- kind: "boolean"
984
- });
985
- }
986
- const enabled = value === undefined ? separate ?? true : parseReservedBool(name, value);
987
- if (name === "dry-run")
988
- options.dryRun = enabled;
989
- else if (name === "json")
990
- options.json = enabled;
991
- else if (name === "wait")
992
- options.wait = enabled;
993
- else if (name === "quiet")
994
- options.quiet = enabled;
995
- else if (name === "ascending")
996
- ascending = enabled;
997
- else
998
- options.help = options.help || enabled;
999
- continue;
1000
- }
1001
- if (option.globalKind === "value") {
1002
- const next = argv[i + 1];
1003
- value = value ?? next;
1004
- if (value === undefined || !option.inline && isOptionToken(value)) {
1005
- throw new CliArgumentError2(`--${name} requires a value`);
1006
- }
1007
- if (!option.inline)
1008
- i++;
1009
- appendCliOptionValue(frameworkFlags, name, value, { kind: "string" });
1010
- if (name === "wait-timeout") {
1011
- const timeout = Number(value);
1012
- if (!Number.isFinite(timeout) || timeout <= 0) {
1013
- throw new CliArgumentError2("--wait-timeout must be a positive number");
1014
- }
1015
- options.waitTimeout = timeout;
1016
- } else if (VIEW_OPTIONS.has(name)) {
1017
- viewFlags.set(name, value);
1018
- } else {
1019
- options.outputDir = value;
1020
- }
1021
- continue;
1022
- }
1023
- if (value === undefined && name.startsWith("no-") && fieldFor(name.slice(3))?.kind === "boolean") {
1024
- pushFlag(name.slice(3), "false");
1025
- literalBooleanFlags.add(name.slice(3));
1026
- continue;
1027
- }
1028
- const unsafeSegment = name.split(".").find(isUnsafeKey);
1029
- if (unsafeSegment !== undefined) {
1030
- throw new CliArgumentError2(`Unsafe option name "--${name}"`);
1031
- }
1032
- const info = fieldFor(name);
1033
- const rootName = name.split(".")[0] ?? name;
1034
- if (!fields.has(rootName) && !config.allowUnknown) {
1035
- throw new CliArgumentError2(`Unknown option "--${name}"`);
1036
- }
1037
- if (value === undefined) {
1038
- if (info?.kind === "boolean") {
1039
- const separate = separateBoolValue(argv[i + 1]);
1040
- if (separate !== undefined)
1041
- i++;
1042
- pushFlag(name, String(separate ?? true));
1043
- literalBooleanFlags.add(name);
1044
- continue;
1045
- }
1046
- const next = argv[i + 1];
1047
- if (next !== undefined && !isOptionToken(next)) {
1048
- value = next;
1049
- i++;
1050
- } else {
1051
- throw new CliArgumentError2(`--${name} requires a value`);
1052
- }
1053
- }
1054
- pushFlag(name, value, json);
1017
+ limits.push(`${label}: ${branches.map((branch) => branch.text).join(separator)}`);
1055
1018
  }
1056
- return {
1057
- options,
1058
- flags,
1059
- jsonFields,
1060
- literalBooleanFlags,
1061
- viewFlags,
1062
- ascending,
1063
- positionals
1064
- };
1019
+ return limits.length === 0 ? "" : ` [${limits.join(", ")}]`;
1065
1020
  }
1066
-
1067
- // src/tools/cli/args-view.ts
1068
- function resolveCliView(flags, ascending) {
1069
- if (flags.size === 0) {
1070
- if (ascending) {
1071
- throw new CliArgumentError2("--ascending orders a record view; pass --sort with it");
1021
+ function fieldTypeLabel(schema) {
1022
+ return `<${typeLabel(schema)}>${constraintLabel(schema)}`;
1023
+ }
1024
+ function nullableField(schema) {
1025
+ if (schema.type === "null")
1026
+ return true;
1027
+ if (Array.isArray(schema.type) && schema.type.includes("null"))
1028
+ return true;
1029
+ return Array.isArray(schema.anyOf) && schema.anyOf.some((member) => isRecord(member) && nullableField(member));
1030
+ }
1031
+ function jsonValueHelp(fields) {
1032
+ const example = fields.find((field) => nullableField(field.schema))?.name;
1033
+ return [
1034
+ "Assign a whole JSON value with --field:=JSON; the declared schema still validates it.",
1035
+ ...example === undefined ? [] : [`JSON null: --${example}:=null. The literal string stays --${example} null.`]
1036
+ ];
1037
+ }
1038
+ function padRight(text, width) {
1039
+ return text.length >= width ? text : text + " ".repeat(width - text.length);
1040
+ }
1041
+ function summarize(desc) {
1042
+ const firstLine = desc.split(`
1043
+ `)[0]?.trim() ?? "";
1044
+ const firstSentence = firstLine.split(". ")[0]?.trim() ?? firstLine;
1045
+ const max = 72;
1046
+ return firstSentence.length > max ? `${firstSentence.slice(0, max - 1).trimEnd()}…` : firstSentence;
1047
+ }
1048
+ function applicationOptionLines(fields) {
1049
+ if (fields.length === 0)
1050
+ return [];
1051
+ const labels = new Map(fields.map((field) => [field.name, `--${field.name} ${fieldTypeLabel(field.schema)}`]));
1052
+ const width = Math.max(...fields.map((field) => labels.get(field.name)?.length ?? 0));
1053
+ return [
1054
+ "",
1055
+ "Application options:",
1056
+ ...fields.map((field) => ` ${padRight(labels.get(field.name) ?? `--${field.name}`, width)} ${field.description ?? ""}`.trimEnd())
1057
+ ];
1058
+ }
1059
+ function filterCommands(commands, filter) {
1060
+ const needle = filter.toLowerCase();
1061
+ return new Map([...commands].filter(([command, descriptor]) => command.toLowerCase().includes(needle) || descriptor.description.toLowerCase().includes(needle)));
1062
+ }
1063
+ function renderTopHelp(input) {
1064
+ const { name, version, defaultCommand } = input;
1065
+ const commands = input.filter === undefined ? input.commands : filterCommands(input.commands, input.filter);
1066
+ const lines = [
1067
+ `${name} ${version}`,
1068
+ "",
1069
+ `Usage: ${name} ${defaultCommand ? "[command]" : "<command>"} [args] [--flags]`,
1070
+ "",
1071
+ input.filter === undefined ? "Commands:" : `Commands matching "${input.filter}" (${commands.size} of ${input.commands.size}):`
1072
+ ];
1073
+ const width = Math.max(0, ...[...commands.keys()].map((key) => key.length));
1074
+ for (const [command, descriptor] of commands) {
1075
+ lines.push(` ${padRight(command, width)} ${summarize(descriptor.description)}${command === defaultCommand ? " (default)" : ""}${descriptor.unavailableReason !== undefined ? " [unavailable]" : ""}`);
1076
+ if (descriptor.unavailableReason !== undefined) {
1077
+ lines.push(` Unavailable: ${descriptor.unavailableReason}`);
1072
1078
  }
1073
- return;
1074
1079
  }
1075
- const countBy = flags.get("count-by");
1076
- const sum = flags.get("sum");
1077
- const table = flags.get("table");
1078
- const by = flags.get("by");
1079
- const rawTop = flags.get("top");
1080
- const named = [
1081
- ["--count-by", countBy],
1082
- ["--sum", sum]
1083
- ].filter(([, value]) => value !== undefined);
1084
- if (named.length > 1) {
1085
- throw new CliArgumentError2(`${named.map(([flag]) => flag).join(" and ")} ask for different shapes — pass one`);
1080
+ if (input.unavailable !== undefined) {
1081
+ lines.push("", `Managed commands are unavailable: ${input.unavailable}`);
1086
1082
  }
1087
- let top;
1088
- if (rawTop !== undefined) {
1089
- top = Number(rawTop);
1090
- if (!Number.isInteger(top) || top <= 0) {
1091
- throw new CliArgumentError2("--top must be a positive whole number");
1092
- }
1083
+ if (input.filter !== undefined) {
1084
+ lines.push("", `Run "${name} <command> --help" for command-specific flags.`);
1085
+ return `${lines.join(`
1086
+ `)}
1087
+ `;
1093
1088
  }
1094
- const sort = flags.get("sort");
1095
- if (table !== undefined || sort !== undefined) {
1096
- if (countBy !== undefined || sum !== undefined) {
1097
- throw new CliArgumentError2(`${table !== undefined ? "--table" : "--sort"} lists records; --count-by and --sum aggregate them — pass one`);
1089
+ lines.push("", "Global options:");
1090
+ const optWidth = Math.max(...GLOBAL_OPTIONS.map(([flag]) => flag.length));
1091
+ for (const [flag, desc] of GLOBAL_OPTIONS)
1092
+ lines.push(` ${padRight(flag, optWidth)} ${desc}`);
1093
+ lines.push(...applicationOptionLines(input.applicationOptions));
1094
+ lines.push("", ...jsonValueHelp(input.applicationOptions));
1095
+ lines.push("", `Run "${name} <command> --help" for command-specific flags.`);
1096
+ return `${lines.join(`
1097
+ `)}
1098
+ `;
1099
+ }
1100
+ function renderCommandHelp(name, command, descriptor, applicationOptions = []) {
1101
+ const fields = jsonSchemaFields(descriptor.presentationSchema);
1102
+ const fieldsByName = new Map(fields.map((field) => [field.name, field]));
1103
+ const kinds = describeSchemaFields(descriptor.argumentSchema);
1104
+ const positionals = [];
1105
+ const positionalNames = descriptor.positionals ?? [...kinds].filter(([, info]) => info.kind !== "boolean").map(([fieldName]) => fieldName);
1106
+ for (const fieldName of positionalNames) {
1107
+ const field = fieldsByName.get(fieldName);
1108
+ if (field)
1109
+ positionals.push(field);
1110
+ }
1111
+ const variadicTail = descriptor.positionals !== undefined && kinds.get(descriptor.positionals[descriptor.positionals.length - 1] ?? "")?.kind === "array" ? descriptor.positionals[descriptor.positionals.length - 1] : undefined;
1112
+ const positionalSyntax = new Map(positionals.map((field) => {
1113
+ const label = field.name === variadicTail ? `${field.name}...` : field.name;
1114
+ return [field.name, field.required ? `<${label}>` : `[${label}]`];
1115
+ }));
1116
+ const usage = [
1117
+ `Usage: ${name} ${command}`,
1118
+ ...positionals.map((field) => positionalSyntax.get(field.name) ?? field.name),
1119
+ "[--flags]"
1120
+ ].join(" ");
1121
+ const lines = [descriptor.description, "", usage, ""];
1122
+ if (descriptor.unavailableReason !== undefined) {
1123
+ lines.push(`Unavailable: ${descriptor.unavailableReason}`, "");
1124
+ }
1125
+ if (fields.length > 0) {
1126
+ lines.push("Arguments:");
1127
+ const labels = new Map(fields.map((field) => {
1128
+ const positional = positionalSyntax.get(field.name);
1129
+ const alias = descriptor.aliases.get(field.name);
1130
+ const option = alias ? `-${alias}, --${field.name}` : `--${field.name}`;
1131
+ return [field.name, positional ? `${positional} | ${option}` : option];
1132
+ }));
1133
+ const width = Math.max(...fields.map((field) => labels.get(field.name)?.length ?? 0));
1134
+ for (const f of fields) {
1135
+ const req = f.required ? " (required)" : "";
1136
+ const desc = f.description ? ` — ${f.description}` : "";
1137
+ const label = labels.get(f.name) ?? `--${f.name}`;
1138
+ lines.push(` ${padRight(label, width)} ${fieldTypeLabel(f.schema)}${req}${desc}`);
1098
1139
  }
1099
- if (by !== undefined) {
1100
- throw new CliArgumentError2("--by groups a view; a record view orders with --sort");
1140
+ const flag = [...kinds].find(([, info]) => info.kind === "boolean")?.[0];
1141
+ if (flag !== undefined) {
1142
+ lines.push("", `A boolean flag is on alone or takes true/false: --${flag}, --${flag} false, --no-${flag}`);
1101
1143
  }
1102
- if (top !== undefined && sort === undefined) {
1103
- throw new CliArgumentError2("--top needs --sort here: unordered records have no n largest");
1144
+ const arrays = [...kinds].filter(([, info]) => info.kind === "array");
1145
+ for (const [field] of arrays) {
1146
+ lines.push("", `Repeat --${field} <value> for each element, or assign the whole array with --${field}:=JSON.`);
1104
1147
  }
1105
- let fields;
1106
- if (table !== undefined) {
1107
- fields = table.split(",").map((field) => field.trim()).filter(Boolean);
1108
- if (fields.length === 0)
1109
- throw new CliArgumentError2("--table needs at least one field");
1148
+ if (arrays.length > 0 && descriptor.positionals === undefined) {
1149
+ lines.push("Array options cannot be combined with automatic positional arguments; name other fields with flags.");
1110
1150
  }
1111
- return {
1112
- kind: "records",
1113
- ...sort !== undefined && { sort },
1114
- ...ascending && { ascending },
1115
- ...top !== undefined && { top },
1116
- ...fields && { table: fields }
1117
- };
1118
- }
1119
- if (ascending) {
1120
- throw new CliArgumentError2("--ascending orders a record view; pass --sort with it");
1151
+ lines.push("", ...jsonValueHelp(fields), "");
1121
1152
  }
1122
- if (sum !== undefined) {
1123
- if (top !== undefined && by === undefined) {
1124
- throw new CliArgumentError2("--top needs --by: a single sum has nothing to rank");
1125
- }
1153
+ const applicationLines = applicationOptionLines(applicationOptions);
1154
+ if (applicationLines.length > 0)
1155
+ lines.push(...applicationLines.slice(1), "");
1156
+ return `${lines.join(`
1157
+ `)}
1158
+ `;
1159
+ }
1160
+
1161
+ // src/tools/cli/args-route.ts
1162
+ function isOptionToken(token) {
1163
+ return token.startsWith("--") || /^-[A-Za-z](?:=|$)/.test(token);
1164
+ }
1165
+ function looksLikeOption(token) {
1166
+ return token.startsWith("--") || /^-[A-Za-z]/.test(token);
1167
+ }
1168
+ var BOOL_OPTIONS = new Set(["json", "wait", "quiet", "dry-run", "help", "ascending"]);
1169
+ var VIEW_OPTIONS = new Set(["count-by", "sum", "by", "top", "table", "sort"]);
1170
+ var VALUE_OPTIONS = new Set(["wait-timeout", "output-dir", ...VIEW_OPTIONS]);
1171
+ var RESERVED_CLI_OPTIONS = new Set([...BOOL_OPTIONS, ...VALUE_OPTIONS]);
1172
+ function classifyLongOptionToken(token) {
1173
+ if (!token.startsWith("--") || token === "--")
1174
+ return;
1175
+ const equals = token.indexOf("=");
1176
+ const name = equals >= 0 ? token.slice(2, equals) : token.slice(2);
1177
+ return {
1178
+ name,
1179
+ value: equals >= 0 ? token.slice(equals + 1) : undefined,
1180
+ inline: equals >= 0,
1181
+ globalKind: BOOL_OPTIONS.has(name) ? "boolean" : VALUE_OPTIONS.has(name) ? "value" : undefined
1182
+ };
1183
+ }
1184
+ var HELP_TOKENS = new Set(["--help", "-h", "help"]);
1185
+ function helpFilterFrom(token, rest) {
1186
+ if (token?.startsWith("--help=")) {
1187
+ const value = token.slice("--help=".length).trim();
1188
+ if (value.length === 0 || isReservedBoolWord(value))
1189
+ return;
1190
+ return value;
1191
+ }
1192
+ if (token === undefined || !HELP_TOKENS.has(token))
1193
+ return;
1194
+ const next = rest[0];
1195
+ return next !== undefined && !next.startsWith("-") ? next : undefined;
1196
+ }
1197
+ function routeCliArgv2(argv, defaultCommand) {
1198
+ if (defaultCommand === undefined) {
1199
+ const [command, ...commandArgv] = argv;
1200
+ const helpFilter = helpFilterFrom(command, commandArgv);
1126
1201
  return {
1127
- kind: "sum",
1128
- field: sum,
1129
- ...by !== undefined && { by },
1130
- ...top !== undefined && { top }
1202
+ command,
1203
+ commandArgv,
1204
+ topLevelHelp: false,
1205
+ version: false,
1206
+ ...helpFilter !== undefined && { helpFilter }
1131
1207
  };
1132
1208
  }
1133
- if (countBy !== undefined) {
1134
- if (by !== undefined) {
1135
- throw new CliArgumentError2("--count-by already names the grouping field; drop --by");
1136
- }
1137
- return { kind: "count", field: countBy, ...top !== undefined && { top } };
1138
- }
1139
- if (by !== undefined) {
1140
- if (top === undefined) {
1141
- throw new CliArgumentError2("--by groups a view; pass --count-by, --sum or --top with it");
1209
+ const globals = [];
1210
+ let index = 0;
1211
+ while (index < argv.length) {
1212
+ const token = argv[index];
1213
+ if (token === undefined)
1214
+ break;
1215
+ if (token === "--") {
1216
+ return {
1217
+ commandArgv: [],
1218
+ topLevelHelp: false,
1219
+ version: false,
1220
+ error: 'A command is required before "--"'
1221
+ };
1142
1222
  }
1143
- return { kind: "count", field: by, top };
1144
- }
1145
- throw new CliArgumentError2("--top needs --sort, --by, --count-by or --sum to rank");
1146
- }
1147
-
1148
- // src/tools/cli/args.ts
1149
- function buildToolArgs(tokens, fields, fieldFor, declaredPositionals, allowUnknown) {
1150
- const { flags, jsonFields, literalBooleanFlags, positionals } = tokens;
1151
- if (declaredPositionals === undefined && positionals.length > 0) {
1152
- const arrayFlag = [...flags.keys()].find((key) => fieldFor(key)?.kind === "array");
1153
- if (arrayFlag !== undefined) {
1154
- throw new CliArgumentError2(`--${arrayFlag} cannot be combined with automatic positional arguments. Repeat --${arrayFlag} for each element or use --${arrayFlag}:=JSON; name other fields with flags, or declare a positional policy.`);
1223
+ const inlineHelpFilter = token.startsWith("--help=") && !isReservedBoolWord(token.slice("--help=".length));
1224
+ if (token === "--help" || token === "-h" || inlineHelpFilter) {
1225
+ const helpFilter = helpFilterFrom(token, argv.slice(index + 1));
1226
+ return {
1227
+ commandArgv: [],
1228
+ topLevelHelp: true,
1229
+ version: false,
1230
+ ...helpFilter !== undefined && { helpFilter }
1231
+ };
1155
1232
  }
1156
- }
1157
- const toolArgs = {};
1158
- const fillable = declaredPositionals === undefined ? [...fields.entries()].filter(([, info]) => info.kind !== "boolean").map(([name]) => name) : [...declaredPositionals];
1159
- const variadicTail = declaredPositionals !== undefined && fields.get(fillable[fillable.length - 1] ?? "")?.kind === "array" ? fillable[fillable.length - 1] : undefined;
1160
- let pi = 0;
1161
- for (const key of fillable) {
1162
- if (pi >= positionals.length)
1163
- break;
1164
- if (key === variadicTail) {
1165
- if (flags.has(key)) {
1166
- throw new CliArgumentError2(`--${key} conflicts with the positional values for "${key}" — pass one form, not both`);
1167
- }
1168
- toolArgs[key] = coerceField(fields.get(key), positionals.slice(pi), `<${key}>`);
1169
- pi = positionals.length;
1170
- break;
1233
+ if (token === "--version" || token === "version") {
1234
+ return { commandArgv: [], topLevelHelp: false, version: true };
1171
1235
  }
1172
- if (flags.has(key))
1173
- continue;
1174
- const value = positionals[pi++];
1175
- if (value !== undefined) {
1176
- toolArgs[key] = coerceField(fields.get(key), [value], `<${key}>`);
1236
+ if (!token.startsWith("-") || token === "-") {
1237
+ return {
1238
+ command: token,
1239
+ commandArgv: [...globals, ...argv.slice(index + 1)],
1240
+ topLevelHelp: false,
1241
+ version: false
1242
+ };
1177
1243
  }
1178
- }
1179
- if (pi < positionals.length) {
1180
- throw new CliArgumentError2(`Unexpected positional argument "${positionals[pi]}"`);
1181
- }
1182
- const dottedRoots = new Map;
1183
- for (const key of flags.keys()) {
1184
- const dot = key.indexOf(".");
1185
- const exactLiteralBoolean = literalBooleanFlags.has(key) && fields.has(key);
1186
- if (dot > 0 && !exactLiteralBoolean) {
1187
- dottedRoots.set(key.slice(0, dot), key);
1244
+ if (!token.startsWith("--")) {
1245
+ return {
1246
+ command: defaultCommand,
1247
+ commandArgv: [...globals, ...argv.slice(index)],
1248
+ topLevelHelp: false,
1249
+ version: false
1250
+ };
1188
1251
  }
1189
- }
1190
- for (const [root, dotted] of dottedRoots) {
1191
- if (flags.has(root)) {
1192
- throw new CliArgumentError2(`--${root} conflicts with --${dotted} — pass one form, not both`);
1252
+ const option = classifyLongOptionToken(token);
1253
+ if (!option) {
1254
+ return {
1255
+ command: defaultCommand,
1256
+ commandArgv: [...globals, ...argv.slice(index)],
1257
+ topLevelHelp: false,
1258
+ version: false
1259
+ };
1193
1260
  }
1194
- }
1195
- for (const key of jsonFields) {
1196
- for (const other of flags.keys()) {
1197
- if (other.startsWith(`${key}.`) || key.startsWith(`${other}.`)) {
1198
- throw new CliArgumentError2(`--${key} conflicts with --${other} — pass one form, not both`);
1261
+ if (option.globalKind === "boolean") {
1262
+ globals.push(token);
1263
+ index += 1;
1264
+ const next = argv[index];
1265
+ if (option.value === undefined && next !== undefined && separateBoolValue(next) !== undefined) {
1266
+ globals.push(next);
1267
+ index += 1;
1199
1268
  }
1269
+ continue;
1200
1270
  }
1201
- }
1202
- for (const [key, values] of flags) {
1203
- const info = fieldFor(key);
1204
- const exactLiteralBoolean = literalBooleanFlags.has(key) && fields.has(key);
1205
- if (key.includes(".") && !exactLiteralBoolean) {
1206
- const root = key.split(".")[0] ?? key;
1207
- const value = !jsonFields.has(key) && info === undefined && allowUnknown && !fields.has(root) ? looseCoerce(values[0] ?? "") : coerceCliFieldValue(info, values, `--${key}`, jsonFields.has(key));
1208
- setNested(toolArgs, key.split("."), value);
1271
+ if (option.globalKind === "value") {
1272
+ globals.push(token);
1273
+ if (!option.inline) {
1274
+ const value = argv[index + 1];
1275
+ if (value === "--help" || value === "-h") {
1276
+ const helpFilter = helpFilterFrom(value, argv.slice(index + 2));
1277
+ return {
1278
+ commandArgv: [],
1279
+ topLevelHelp: true,
1280
+ version: false,
1281
+ ...helpFilter !== undefined && { helpFilter }
1282
+ };
1283
+ }
1284
+ if (value === "--version" || value === "version") {
1285
+ return { commandArgv: [], topLevelHelp: false, version: true };
1286
+ }
1287
+ if (value !== undefined)
1288
+ globals.push(value);
1289
+ index += value === undefined ? 1 : 2;
1290
+ } else {
1291
+ index += 1;
1292
+ }
1209
1293
  continue;
1210
1294
  }
1211
- toolArgs[key] = coerceCliFieldValue(info, values, `--${key}`, jsonFields.has(key));
1212
- }
1213
- return toolArgs;
1214
- }
1215
- function parseCliArgs2(argv, schema, config = {}) {
1216
- const fields = describeSchemaFields(schema);
1217
- for (const name of config.knownFields ?? []) {
1218
- if (!fields.has(name))
1219
- fields.set(name, { kind: "other" });
1295
+ return {
1296
+ command: defaultCommand,
1297
+ commandArgv: [...globals, ...argv.slice(index)],
1298
+ topLevelHelp: false,
1299
+ version: false
1300
+ };
1220
1301
  }
1221
- const fieldFor = (name) => fields.get(name) ?? (name.includes(".") ? describeSchemaPath(schema, name.split(".")) : undefined);
1222
- const tokens = readCliTokens(argv, fields, fieldFor, config);
1223
- const { options } = tokens;
1224
- const view = resolveCliView(tokens.viewFlags, tokens.ascending);
1225
- if (view)
1226
- options.view = view;
1227
1302
  return {
1228
- toolArgs: buildToolArgs(tokens, fields, fieldFor, config.positionals, config.allowUnknown === true),
1229
- options
1303
+ command: defaultCommand,
1304
+ commandArgv: globals,
1305
+ topLevelHelp: false,
1306
+ version: false
1230
1307
  };
1231
1308
  }
1232
- function refuseGlobalOption(issue) {
1233
- const field = issue?.path[0];
1234
- const where = typeof field === "string" ? `--${field}` : "application option";
1235
- throw new CliArgumentError2(`${where}: ${issue?.message ?? "invalid value"}`);
1309
+
1310
+ // src/tools/cli/command-shape.ts
1311
+ function commandShapeIssue(name, descriptor, exists, passthroughField, applicationGlobals = new Set) {
1312
+ assertUniqueToolName(name, exists, "CLI command");
1313
+ if (name === "help" || name === "version") {
1314
+ throw new Error(`[stitchkit] CLI command "${name}" is reserved`);
1315
+ }
1316
+ if (passthroughField !== undefined && descriptor.aliases.has(passthroughField)) {
1317
+ throw new Error(`[stitchkit] CLI command "${name}" cannot alias passthrough field "${passthroughField}"`);
1318
+ }
1319
+ const fieldNames = jsonSchemaFields(descriptor.presentationSchema).map((field) => field.name);
1320
+ const conflicting = fieldNames.filter((field) => RESERVED_CLI_OPTIONS.has(field));
1321
+ if (conflicting.length > 0) {
1322
+ return `[stitchkit] CLI command "${name}" declares reserved option field(s): ${conflicting.join(", ")}`;
1323
+ }
1324
+ const shadowed = fieldNames.filter((field) => applicationGlobals.has(field));
1325
+ if (shadowed.length > 0) {
1326
+ return `[stitchkit] CLI command "${name}" declares field(s) shadowed by application global options: ${shadowed.join(", ")}`;
1327
+ }
1236
1328
  }
1237
- function extractCliGlobalOptions2(argv, schema) {
1238
- if (schema === undefined)
1239
- return { argv: [...argv], globals: {} };
1240
- const fields = describeSchemaFields(schema);
1241
- const rest = [];
1242
- const raw = new Map;
1329
+
1330
+ // src/tools/cli/managed-surface.ts
1331
+ async function buildManagedSurface(session, typedGlobals, forExecution) {
1332
+ const { config, nativeHelp, applicationGlobalNames } = session;
1333
+ let authPromise;
1334
+ const resolveIdentity = () => {
1335
+ authPromise ??= (async () => config.resolveAuth ? await config.resolveAuth(typedGlobals) : await config.auth)();
1336
+ return authPromise;
1337
+ };
1338
+ const dynamicSurface = typeof config.services === "function" || typeof config.runtimeTools === "function";
1339
+ let auth;
1340
+ if (dynamicSurface || forExecution) {
1341
+ try {
1342
+ auth = await resolveIdentity();
1343
+ } catch (error) {
1344
+ const failure = toolResultFromError(error);
1345
+ const normalized = toolErrorFromResult(failure);
1346
+ return {
1347
+ resolved: false,
1348
+ failure,
1349
+ reason: `${normalized.code}: ${normalized.message}`,
1350
+ help: new Map(nativeHelp)
1351
+ };
1352
+ }
1353
+ }
1354
+ const { tools, help } = buildCliSurface(config, auth, nativeHelp, applicationGlobalNames, commandShapeIssue);
1355
+ return { resolved: true, auth, help, tools };
1356
+ }
1357
+
1358
+ // src/tools/cli/args.ts
1359
+ import { z as z2 } from "zod";
1360
+
1361
+ // src/tools/cli/args-json.ts
1362
+ function cliFieldOption(option, fields) {
1363
+ const json = option.inline && option.name.endsWith(":") && !fields.has(option.name);
1364
+ const name = json ? option.name.slice(0, -1) : option.name;
1365
+ return { name, json };
1366
+ }
1367
+ function appendCliFieldValue(raw, jsonFields, name, value, info, json) {
1368
+ appendCliOptionValue(raw, name, value, json || jsonFields.has(name) ? { kind: "other" } : info);
1369
+ if (json)
1370
+ jsonFields.add(name);
1371
+ }
1372
+ function coerceCliFieldValue(info, values, label, json) {
1373
+ if (!json)
1374
+ return coerceField(info, values, label);
1375
+ try {
1376
+ return safeJsonParse(values[0] ?? "");
1377
+ } catch (cause) {
1378
+ throw new CliArgumentError2(`${label} expects valid JSON`, { cause });
1379
+ }
1380
+ }
1381
+
1382
+ // src/tools/cli/args-tokens.ts
1383
+ function readCliTokens(argv, fields, fieldFor, config) {
1384
+ const options = {
1385
+ json: false,
1386
+ wait: false,
1387
+ quiet: false,
1388
+ dryRun: false,
1389
+ help: false
1390
+ };
1391
+ const flags = new Map;
1243
1392
  const jsonFields = new Set;
1244
- let ended = false;
1393
+ const literalBooleanFlags = new Set;
1394
+ const frameworkFlags = new Map;
1395
+ const viewFlags = new Map;
1396
+ let ascending = false;
1397
+ const positionals = [];
1398
+ const pushFlag = (name, value, json = false) => {
1399
+ appendCliFieldValue(flags, jsonFields, name, value, fieldFor(name), json);
1400
+ };
1401
+ let optionsEnded = false;
1245
1402
  for (let i = 0;i < argv.length; i++) {
1246
- const token = argv[i];
1247
- if (token === undefined)
1403
+ const tok = argv[i];
1404
+ if (tok === undefined)
1248
1405
  continue;
1249
- if (!ended && token === "--")
1250
- ended = true;
1251
- const option = ended ? undefined : classifyLongOptionToken(token);
1252
- const field = option ? cliFieldOption(option, fields) : undefined;
1253
- const info = field && option?.globalKind === undefined ? fields.get(field.name) : undefined;
1254
- if (!option || !field || !info) {
1255
- rest.push(token);
1406
+ if (!optionsEnded && tok === "--") {
1407
+ optionsEnded = true;
1408
+ continue;
1409
+ }
1410
+ if (optionsEnded || !looksLikeOption(tok)) {
1411
+ positionals.push(tok);
1412
+ continue;
1413
+ }
1414
+ if (tok === "-h") {
1415
+ options.help = true;
1416
+ continue;
1417
+ }
1418
+ if (!tok.startsWith("--")) {
1419
+ const match = /^-([A-Za-z])(?:=(.*))?$/.exec(tok);
1420
+ const alias = match?.[1];
1421
+ const field = alias === undefined ? undefined : config.optionAliases?.get(alias);
1422
+ if (!alias || !field)
1423
+ throw new CliArgumentError2(`Unknown option "${tok}"`);
1424
+ const inline = match?.[2];
1425
+ const info = fieldFor(field);
1426
+ if (info?.kind === "boolean") {
1427
+ const separate = inline === undefined ? separateBoolValue(argv[i + 1]) : undefined;
1428
+ if (separate !== undefined)
1429
+ i++;
1430
+ pushFlag(field, inline ?? String(separate ?? true));
1431
+ if (inline !== undefined)
1432
+ parseReservedBool(field, inline);
1433
+ literalBooleanFlags.add(field);
1434
+ continue;
1435
+ }
1436
+ let value = inline;
1437
+ if (value === undefined) {
1438
+ const next = argv[i + 1];
1439
+ if (next !== undefined && !isOptionToken(next)) {
1440
+ value = next;
1441
+ i++;
1442
+ } else {
1443
+ throw new CliArgumentError2(`--${field} requires a value`);
1444
+ }
1445
+ }
1446
+ pushFlag(field, value);
1256
1447
  continue;
1257
1448
  }
1449
+ const option = classifyLongOptionToken(tok);
1450
+ if (!option)
1451
+ throw new CliArgumentError2(`Unknown option "${tok}"`);
1452
+ const { name, json } = cliFieldOption(option, fields);
1258
1453
  let { value } = option;
1259
- if (value === undefined && info.kind === "boolean") {
1260
- const separate = separateBoolValue(argv[i + 1]);
1454
+ if (name.length === 0)
1455
+ throw new CliArgumentError2("Invalid empty option name");
1456
+ if (json && RESERVED_CLI_OPTIONS.has(name)) {
1457
+ throw new CliArgumentError2(`--${name} is a framework option and does not accept :=JSON`);
1458
+ }
1459
+ if (option.globalKind === "boolean") {
1460
+ const separate = value === undefined ? separateBoolValue(argv[i + 1]) : undefined;
1261
1461
  if (separate !== undefined)
1262
1462
  i++;
1263
- value = String(separate ?? true);
1264
- } else if (value === undefined) {
1463
+ if (name !== "help") {
1464
+ appendCliOptionValue(frameworkFlags, name, value ?? String(separate ?? true), {
1465
+ kind: "boolean"
1466
+ });
1467
+ }
1468
+ const enabled = value === undefined ? separate ?? true : parseReservedBool(name, value);
1469
+ if (name === "dry-run")
1470
+ options.dryRun = enabled;
1471
+ else if (name === "json")
1472
+ options.json = enabled;
1473
+ else if (name === "wait")
1474
+ options.wait = enabled;
1475
+ else if (name === "quiet")
1476
+ options.quiet = enabled;
1477
+ else if (name === "ascending")
1478
+ ascending = enabled;
1479
+ else
1480
+ options.help = options.help || enabled;
1481
+ continue;
1482
+ }
1483
+ if (option.globalKind === "value") {
1484
+ const next = argv[i + 1];
1485
+ value = value ?? next;
1486
+ if (value === undefined || !option.inline && isOptionToken(value)) {
1487
+ throw new CliArgumentError2(`--${name} requires a value`);
1488
+ }
1489
+ if (!option.inline)
1490
+ i++;
1491
+ appendCliOptionValue(frameworkFlags, name, value, { kind: "string" });
1492
+ if (name === "wait-timeout") {
1493
+ const timeout = Number(value);
1494
+ if (!Number.isFinite(timeout) || timeout <= 0) {
1495
+ throw new CliArgumentError2("--wait-timeout must be a positive number");
1496
+ }
1497
+ options.waitTimeout = timeout;
1498
+ } else if (VIEW_OPTIONS.has(name)) {
1499
+ viewFlags.set(name, value);
1500
+ } else {
1501
+ options.outputDir = value;
1502
+ }
1503
+ continue;
1504
+ }
1505
+ if (value === undefined && name.startsWith("no-") && fieldFor(name.slice(3))?.kind === "boolean") {
1506
+ pushFlag(name.slice(3), "false");
1507
+ literalBooleanFlags.add(name.slice(3));
1508
+ continue;
1509
+ }
1510
+ const unsafeSegment = name.split(".").find(isUnsafeKey);
1511
+ if (unsafeSegment !== undefined) {
1512
+ throw new CliArgumentError2(`Unsafe option name "--${name}"`);
1513
+ }
1514
+ const info = fieldFor(name);
1515
+ const rootName = name.split(".")[0] ?? name;
1516
+ if (!fields.has(rootName) && !config.allowUnknown) {
1517
+ throw new CliArgumentError2(`Unknown option "--${name}"`);
1518
+ }
1519
+ if (value === undefined) {
1520
+ if (info?.kind === "boolean") {
1521
+ const separate = separateBoolValue(argv[i + 1]);
1522
+ if (separate !== undefined)
1523
+ i++;
1524
+ pushFlag(name, String(separate ?? true));
1525
+ literalBooleanFlags.add(name);
1526
+ continue;
1527
+ }
1265
1528
  const next = argv[i + 1];
1266
1529
  if (next !== undefined && !isOptionToken(next)) {
1267
1530
  value = next;
1268
1531
  i++;
1269
1532
  } else {
1270
- throw new CliArgumentError2(`--${option.name} requires a value`);
1533
+ throw new CliArgumentError2(`--${name} requires a value`);
1271
1534
  }
1272
1535
  }
1273
- appendCliFieldValue(raw, jsonFields, field.name, value, info, field.json);
1274
- }
1275
- const args = {};
1276
- for (const [name, values] of raw) {
1277
- args[name] = coerceCliFieldValue(fields.get(name), values, `--${name}`, jsonFields.has(name));
1278
- }
1279
- let coerced;
1280
- try {
1281
- coerced = coerceJsonArgs2(args, schema);
1282
- } catch (error) {
1283
- if (error instanceof z2.ZodError)
1284
- refuseGlobalOption(error.issues[0]);
1285
- throw error;
1536
+ pushFlag(name, value, json);
1286
1537
  }
1287
- const parsed = schema.safeParse(coerced);
1288
- if (!parsed.success)
1289
- refuseGlobalOption(parsed.error.issues[0]);
1290
- return { argv: rest, globals: parsed.data };
1538
+ return {
1539
+ options,
1540
+ flags,
1541
+ jsonFields,
1542
+ literalBooleanFlags,
1543
+ viewFlags,
1544
+ ascending,
1545
+ positionals
1546
+ };
1291
1547
  }
1292
1548
 
1293
- // src/tools/cli/view.ts
1294
- var ABSENT = "(absent)";
1295
- function selectRecords(data) {
1296
- if (Array.isArray(data))
1297
- return data;
1298
- if (isRecord(data)) {
1299
- const arrays = Object.entries(data).filter(([, value]) => Array.isArray(value));
1300
- const only = arrays[0];
1301
- if (arrays.length === 1 && only && Array.isArray(only[1]))
1302
- return only[1];
1303
- if (arrays.length > 1) {
1304
- throw new CliArgumentError2(`an aggregate needs one collection, but the result carries several: ${arrays.map(([key]) => key).join(", ")} — name the shape you want with jq instead`);
1549
+ // src/tools/cli/args-view.ts
1550
+ function resolveCliView(flags, ascending) {
1551
+ if (flags.size === 0) {
1552
+ if (ascending) {
1553
+ throw new CliArgumentError2("--ascending orders a record view; pass --sort with it");
1305
1554
  }
1555
+ return;
1306
1556
  }
1307
- throw new CliArgumentError2("an aggregate needs a collection; this result is not one");
1308
- }
1309
- function isAbsent(value) {
1310
- return value === undefined || value === null;
1311
- }
1312
- function readField(record, path) {
1313
- let current = record;
1314
- for (const segment of path) {
1315
- if (!isRecord(current) || !Object.hasOwn(current, segment))
1316
- return;
1317
- current = current[segment];
1318
- }
1319
- return current;
1320
- }
1321
- function fieldPath(field) {
1322
- const path = field.split(".");
1323
- if (path.some((segment) => segment.length === 0 || isUnsafeKey(segment))) {
1324
- throw new CliArgumentError2(`"${field}" is not a readable field path`);
1325
- }
1326
- return path;
1327
- }
1328
- function availableFields(records) {
1329
- const keys = new Set;
1330
- for (const record of records) {
1331
- if (isRecord(record))
1332
- for (const key of Object.keys(record))
1333
- keys.add(key);
1334
- }
1335
- return [...keys].sort();
1336
- }
1337
- function assertFieldPresent(records, field) {
1338
- const path = fieldPath(field);
1339
- const present = records.some((record) => readField(record, path) !== undefined);
1340
- if (records.length > 0 && !present) {
1341
- const available = availableFields(records);
1342
- throw new CliArgumentError2(`no record carries the field "${field}"${available.length > 0 ? ` — available: ${available.join(", ")}` : ""}`);
1343
- }
1344
- return path;
1345
- }
1346
- function groupKey(value) {
1347
- if (value === undefined)
1348
- return ABSENT;
1349
- if (value === null)
1350
- return "null";
1351
- if (value === "null" || value === ABSENT)
1352
- return JSON.stringify(value);
1353
- if (typeof value === "object")
1354
- return JSON.stringify(value);
1355
- return String(value);
1356
- }
1357
- function ordered(totals, top) {
1358
- const entries = [...totals].sort(([leftKey, left], [rightKey, right]) => right === left ? leftKey.localeCompare(rightKey) : right - left);
1359
- const sliced = top === undefined ? entries : entries.slice(0, top);
1360
- return {
1361
- kind: "json",
1362
- data: Object.fromEntries(sliced),
1363
- order: sliced.map(([key]) => key)
1364
- };
1365
- }
1366
- function stringifyGroups(data, order, compact) {
1367
- if (!isRecord(data) || order.length === 0)
1368
- return JSON.stringify(data);
1369
- const lines = order.map((key) => `${JSON.stringify(key)}:${compact ? "" : " "}${JSON.stringify(data[key])}`);
1370
- return compact ? `{${lines.join(",")}}` : `{
1371
- ${lines.join(`,
1372
- `)}
1373
- }`;
1374
- }
1375
- function numericValue(value, field) {
1376
- if (isAbsent(value))
1377
- return;
1378
- if (typeof value === "number" && Number.isFinite(value))
1379
- return value;
1380
- if (typeof value === "bigint")
1381
- return Number(value);
1382
- throw new CliArgumentError2(`--sum ${field}: "${groupKey(value)}" is not a number`);
1383
- }
1384
- function renderTable(records, columns) {
1385
- const fields = columns.map(({ field }) => field);
1386
- const cell = (value) => {
1387
- if (value === undefined)
1388
- return "";
1389
- if (value === null)
1390
- return "null";
1391
- if (typeof value === "object")
1392
- return JSON.stringify(value);
1393
- return String(value);
1394
- };
1395
- const rows = records.map((record) => columns.map(({ path }) => cell(readField(record, path))));
1396
- const widths = fields.map((field, column) => Math.max(field.length, ...rows.map((row) => row[column]?.length ?? 0)));
1397
- const line = (cells) => cells.map((value, column) => value.padEnd(widths[column] ?? value.length)).join(" ").trimEnd();
1398
- return `${[line(fields), line(widths.map((width) => "-".repeat(width))), ...rows.map(line)].join(`
1399
- `)}
1400
- `;
1401
- }
1402
- function compareFieldValues(left, right) {
1403
- if (isAbsent(left))
1404
- return isAbsent(right) ? 0 : 1;
1405
- if (isAbsent(right))
1406
- return -1;
1407
- if (typeof left === "number" && typeof right === "number")
1408
- return left - right;
1409
- if (typeof left === "bigint" || typeof right === "bigint") {
1410
- return Number(left) - Number(right);
1411
- }
1412
- return groupKey(left).localeCompare(groupKey(right));
1413
- }
1414
- function renderRecords(records, view) {
1415
- let selected = [...records];
1416
- if (view.sort !== undefined) {
1417
- const path = assertFieldPresent(records, view.sort);
1418
- const direction = view.ascending ? 1 : -1;
1419
- selected.sort((left, right) => {
1420
- const leftValue = readField(left, path);
1421
- const rightValue = readField(right, path);
1422
- const order = compareFieldValues(leftValue, rightValue);
1423
- return isAbsent(leftValue) || isAbsent(rightValue) ? order : order * direction;
1424
- });
1557
+ const countBy = flags.get("count-by");
1558
+ const sum = flags.get("sum");
1559
+ const table = flags.get("table");
1560
+ const by = flags.get("by");
1561
+ const rawTop = flags.get("top");
1562
+ const named = [
1563
+ ["--count-by", countBy],
1564
+ ["--sum", sum]
1565
+ ].filter(([, value]) => value !== undefined);
1566
+ if (named.length > 1) {
1567
+ throw new CliArgumentError2(`${named.map(([flag]) => flag).join(" and ")} ask for different shapes — pass one`);
1425
1568
  }
1426
- const columns = view.table?.map((field) => ({
1427
- field,
1428
- path: assertFieldPresent(records, field)
1429
- }));
1430
- if (view.top !== undefined)
1431
- selected = selected.slice(0, view.top);
1432
- return columns ? { kind: "text", text: renderTable(selected, columns) } : { kind: "json", data: selected };
1433
- }
1434
- function refuseEnvelopeFields(data, view) {
1435
- if (!isRecord(data))
1436
- return;
1437
- const fields = view.kind === "records" ? [...view.sort === undefined ? [] : [view.sort], ...view.table ?? []] : [view.field, ...view.kind === "sum" && view.by !== undefined ? [view.by] : []];
1438
- for (const field of fields) {
1439
- const first = field.split(".")[0] ?? "";
1440
- if (Object.hasOwn(data, first) && !Array.isArray(data[first])) {
1441
- throw new CliArgumentError2(`"${field}" belongs to the result object, not to its empty collection`);
1569
+ let top;
1570
+ if (rawTop !== undefined) {
1571
+ top = Number(rawTop);
1572
+ if (!Number.isInteger(top) || top <= 0) {
1573
+ throw new CliArgumentError2("--top must be a positive whole number");
1442
1574
  }
1443
1575
  }
1444
- }
1445
- function renderCliView2(data, view) {
1446
- const records = selectRecords(data);
1447
- if (records.length === 0)
1448
- refuseEnvelopeFields(data, view);
1449
- if (view.kind === "records")
1450
- return renderRecords(records, view);
1451
- if (view.kind === "count") {
1452
- const path = assertFieldPresent(records, view.field);
1453
- const totals = new Map;
1454
- for (const record of records) {
1455
- const key = groupKey(readField(record, path));
1456
- totals.set(key, (totals.get(key) ?? 0) + 1);
1576
+ const sort = flags.get("sort");
1577
+ if (table !== undefined || sort !== undefined) {
1578
+ if (countBy !== undefined || sum !== undefined) {
1579
+ throw new CliArgumentError2(`${table !== undefined ? "--table" : "--sort"} lists records; --count-by and --sum aggregate them — pass one`);
1457
1580
  }
1458
- return ordered(totals, view.top);
1459
- }
1460
- const path = assertFieldPresent(records, view.field);
1461
- if (view.by === undefined) {
1462
- let total = 0;
1463
- for (const record of records)
1464
- total += numericValue(readField(record, path), view.field) ?? 0;
1465
- return { kind: "json", data: total };
1466
- }
1467
- const byPath = assertFieldPresent(records, view.by);
1468
- const totals = new Map;
1469
- for (const record of records) {
1470
- const key = groupKey(readField(record, byPath));
1471
- totals.set(key, (totals.get(key) ?? 0) + (numericValue(readField(record, path), view.field) ?? 0));
1472
- }
1473
- return ordered(totals, view.top);
1474
- }
1475
-
1476
- // src/tools/operations/wait-core.ts
1477
- var DEFAULT_BACKOFF = [2, 3, 5, 5, 8, 10];
1478
- var DEFAULT_TIMEOUT = 600;
1479
-
1480
- class WaitTimeoutError extends Error {
1481
- constructor() {
1482
- super("wait deadline elapsed before the first snapshot");
1483
- this.name = "WaitTimeoutError";
1484
- }
1485
- }
1486
- async function pollUntil(params) {
1487
- const backoff = params.backoff?.length ? params.backoff : DEFAULT_BACKOFF;
1488
- const lastBackoff = backoff[backoff.length - 1] ?? 5;
1489
- const timeoutSec = params.timeoutSec ?? DEFAULT_TIMEOUT;
1490
- const pause = params.sleepFn ?? sleep;
1491
- const now = params.nowFn ?? (() => performance.now());
1492
- const startedAt = now();
1493
- const deadline = startedAt + timeoutSec * 1000;
1494
- let last;
1495
- for (let attempt = 0;; attempt++) {
1496
- params.signal?.throwIfAborted();
1497
- const remainingBeforePoll = Math.max(0, deadline - now());
1498
- if (remainingBeforePoll === 0 && last) {
1499
- return { state: last.state, timedOut: true };
1581
+ if (by !== undefined) {
1582
+ throw new CliArgumentError2("--by groups a view; a record view orders with --sort");
1500
1583
  }
1501
- const deadlineController = new AbortController;
1502
- const onCallerAbort = () => deadlineController.abort(params.signal?.reason);
1503
- params.signal?.addEventListener("abort", onCallerAbort, { once: true });
1504
- const deadlineTimer = setTimeout(() => deadlineController.abort(new WaitTimeoutError), remainingBeforePoll);
1505
- deadlineTimer.unref?.();
1506
- let state;
1507
- try {
1508
- state = await params.poll(deadlineController.signal);
1509
- } catch (error) {
1510
- if (params.signal?.aborted)
1511
- throw params.signal.reason ?? error;
1512
- if (deadlineController.signal.aborted && now() >= deadline) {
1513
- if (last)
1514
- return { state: last.state, timedOut: true };
1515
- throw new WaitTimeoutError;
1516
- }
1517
- throw error;
1518
- } finally {
1519
- clearTimeout(deadlineTimer);
1520
- params.signal?.removeEventListener("abort", onCallerAbort);
1584
+ if (top !== undefined && sort === undefined) {
1585
+ throw new CliArgumentError2("--top needs --sort here: unordered records have no n largest");
1521
1586
  }
1522
- params.signal?.throwIfAborted();
1523
- last = { state };
1524
- if (params.done(state))
1525
- return { state, timedOut: false };
1526
- const elapsedMs = now() - startedAt;
1527
- const elapsedSec = elapsedMs / 1000;
1528
- params.onTick?.(attempt + 1, elapsedSec);
1529
- if (elapsedSec >= timeoutSec)
1530
- return { state, timedOut: true };
1531
- const waitSec = backoff[Math.min(attempt, backoff.length - 1)] ?? lastBackoff;
1532
- await pause(Math.min(waitSec * 1000, Math.max(0, deadline - now())), params.signal);
1533
- }
1534
- }
1535
- function runWaitOperation(params) {
1536
- return pollUntil({
1537
- poll: (signal) => params.poll(params.input, signal),
1538
- done: params.done,
1539
- backoff: params.backoff,
1540
- timeoutSec: params.timeoutSec,
1541
- sleepFn: params.sleepFn,
1542
- onTick: params.onTick,
1543
- signal: params.signal,
1544
- nowFn: params.nowFn
1545
- });
1546
- }
1547
-
1548
- // src/tools/cli/wait.ts
1549
- var DEFAULT_TIMEOUT2 = 600;
1550
- function waitFailure(tool, result) {
1551
- return {
1552
- ok: false,
1553
- code: "WAIT_FAILED",
1554
- details: {
1555
- message: `"${tool}" reached a terminal failed state`,
1556
- result
1557
- }
1558
- };
1559
- }
1560
- async function pollUntilDone2(params) {
1561
- const { initial, wait, call } = params;
1562
- if (!initial.ok)
1563
- return initial;
1564
- if (wait.failed?.(initial.data))
1565
- return waitFailure(wait.tool, initial.data);
1566
- if (wait.done(initial.data))
1567
- return initial;
1568
- const pollArgs = wait.poll(initial.data);
1569
- if (!pollArgs)
1570
- return initial;
1571
- const timeoutSec = params.timeoutSec ?? wait.timeout ?? DEFAULT_TIMEOUT2;
1572
- let failedState;
1573
- const { state, timedOut } = await pollUntil({
1574
- poll: () => call(wait.tool, pollArgs),
1575
- done: (result) => {
1576
- if (!result.ok)
1577
- return true;
1578
- if (wait.failed?.(result.data)) {
1579
- failedState = result;
1580
- return true;
1581
- }
1582
- return wait.done(result.data);
1583
- },
1584
- backoff: wait.backoff,
1585
- timeoutSec,
1586
- sleepFn: params.sleepFn,
1587
- onTick: params.onTick,
1588
- signal: params.signal
1589
- });
1590
- if (timedOut) {
1591
- return {
1592
- ok: false,
1593
- code: "TIMEOUT",
1594
- details: { message: `Timed out after ${timeoutSec}s waiting for "${wait.tool}"` }
1595
- };
1596
- }
1597
- if (state === failedState && state.ok)
1598
- return waitFailure(wait.tool, state.data);
1599
- return state;
1600
- }
1601
-
1602
- // src/tools/cli/io.ts
1603
- import { writeSync } from "node:fs";
1604
-
1605
- // src/tools/cli/stdin.ts
1606
- function readPipedStdin(input = process.stdin) {
1607
- if (input.isTTY || input.readableEnded)
1608
- return Promise.resolve(null);
1609
- if (input.destroyed)
1610
- return Promise.reject(new Error("stdin closed before EOF"));
1611
- return new Promise((resolve, reject) => {
1612
- const chunks = [];
1613
- const cleanup = () => {
1614
- clearTimeout(timer);
1615
- input.pause();
1616
- input.off("data", onData);
1617
- input.off("end", onEnd);
1618
- input.off("error", onError);
1619
- input.off("close", onClose);
1620
- };
1621
- const onData = (chunk) => {
1622
- if (chunk.length === 0)
1623
- return;
1624
- clearTimeout(timer);
1625
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
1626
- };
1627
- const onEnd = () => {
1628
- cleanup();
1629
- const text = Buffer.concat(chunks).toString("utf8").trim();
1630
- resolve(text.length > 0 ? text : null);
1631
- };
1632
- const onError = (error) => {
1633
- cleanup();
1634
- reject(error);
1635
- };
1636
- const onClose = () => onError(new Error("stdin closed before EOF"));
1637
- const timer = setTimeout(() => {
1638
- cleanup();
1639
- resolve(null);
1640
- }, 250);
1641
- input.on("end", onEnd);
1642
- input.on("error", onError);
1643
- input.on("close", onClose);
1644
- input.on("data", onData);
1645
- input.resume();
1646
- });
1647
- }
1648
-
1649
- // src/tools/cli/io.ts
1650
- function createCliIo(config) {
1651
- let pendingWrite;
1652
- let writeFailed = false;
1653
- const writeFd = (fd, text) => {
1654
- const bytes = Buffer.from(text, "utf8");
1655
- let offset = 0;
1656
- while (offset < bytes.length) {
1657
- try {
1658
- offset += writeSync(fd, bytes, offset, bytes.length - offset);
1659
- } catch (error) {
1660
- if (isRecord(error) && error.code === "EAGAIN") {
1661
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1);
1662
- continue;
1663
- }
1664
- const stream = fd === 1 ? process.stdout : process.stderr;
1665
- const rest = bytes.subarray(offset);
1666
- pendingWrite = new Promise((resolve) => {
1667
- stream.write(rest, (failure) => {
1668
- if (failure)
1669
- writeFailed = true;
1670
- resolve();
1671
- });
1672
- });
1673
- return;
1674
- }
1675
- }
1676
- };
1677
- const stdout = config.stdout ?? ((text) => writeFd(1, text));
1678
- const stderr = config.stderr ?? ((text) => writeFd(2, text));
1679
- const exit = config.exit ?? ((code) => {
1680
- if (pendingWrite === undefined)
1681
- process.exit(code);
1682
- else
1683
- pendingWrite.then(() => process.exit(writeFailed ? 1 : code));
1684
- });
1685
- const readStdin = config.stdin ?? readPipedStdin;
1686
- return { stdout, stderr, exit, readStdin };
1687
- }
1688
-
1689
- // src/tools/cli/download.ts
1690
- import { basename, resolve } from "node:path";
1691
-
1692
- // src/internal/write-download.ts
1693
- import path from "node:path";
1694
- async function writeDownload(root, name, data) {
1695
- if (!isSafeEntryName(name) || path.basename(name) !== name) {
1696
- throw new Error("download name escapes the output dir");
1697
- }
1698
- const parent = { handle: root, basename: name };
1699
- const metadata = await containedEntryMetadata(parent);
1700
- await writeContainedFile({
1701
- parent,
1702
- content: data,
1703
- replace: true,
1704
- ...metadata?.isFile() && { mode: metadata.mode }
1705
- });
1706
- }
1707
-
1708
- // src/tools/cli/download.ts
1709
- var DEFAULT_DOWNLOAD_MAX_BYTES = 100 * 1024 * 1024;
1710
- async function downloadResults(files, dir, stderr, quiet, allowPrivate, maxBytes, timeoutMs) {
1711
- const root = resolve(dir);
1712
- if (files.length === 0)
1713
- return true;
1714
- let directory;
1715
- try {
1716
- directory = await openPinnedDirectory(root, { create: true, readable: false });
1717
- } catch (error) {
1718
- stderr(`failed to prepare download directory: ${error instanceof Error ? error.message : String(error)}
1719
- `);
1720
- return false;
1721
- }
1722
- let succeeded = true;
1723
- try {
1724
- for (const file of files) {
1725
- try {
1726
- const res = await fetchGuarded(new URL(file.url), allowPrivate, { timeoutMs });
1727
- if (!res.ok)
1728
- throw new Error(`HTTP ${res.status}`);
1729
- const buffer = await readCapped(res, maxBytes);
1730
- if (!buffer)
1731
- throw new Error(`file exceeds the ${maxBytes}-byte cap`);
1732
- const name = basename(file.name);
1733
- await writeDownload(directory, name, buffer);
1734
- if (!quiet)
1735
- stderr(`saved ${resolve(root, name)} (${(buffer.length / 1024).toFixed(0)}KB)
1736
- `);
1737
- } catch (err) {
1738
- succeeded = false;
1739
- const reason = errnoOf(err) ?? (err instanceof Error ? err.message : String(err));
1740
- stderr(`failed to download ${file.url}: ${reason}
1741
- `);
1742
- }
1743
- }
1744
- } finally {
1745
- try {
1746
- await directory.close();
1747
- } catch (error) {
1748
- succeeded = false;
1749
- stderr(`failed to close download directory: ${error instanceof Error ? error.message : String(error)}
1750
- `);
1751
- }
1752
- }
1753
- return succeeded;
1754
- }
1755
-
1756
- // src/tools/cli/help.ts
1757
- var GLOBAL_OPTIONS = [
1758
- ["--json", "Emit compact success/error JSON records (for scripts)"],
1759
- ["--wait", "Block-poll an async result to a terminal state"],
1760
- ["--wait-timeout <s>", "Override the --wait timeout in seconds"],
1761
- ["--output-dir <dir>", "Download result media into a directory"],
1762
- ["--quiet", "Suppress non-essential stderr output"],
1763
- ["--dry-run", "Print the resolved call without executing it"],
1764
- ["--help, -h", "Show help for a command"],
1765
- ["--help <text>", "List only the commands matching a substring"],
1766
- ["--count-by <field>", "Count records per distinct value of a field"],
1767
- ["--sum <field>", "Total a numeric field, optionally grouped by --by"],
1768
- ["--sort <field>", "Order the records by a field, largest first"],
1769
- ["--ascending", "Flip --sort to smallest first"],
1770
- ["--top <n>", "Keep the n leading entries of the view asked for"],
1771
- ["--table <a,b>", "Render the named fields as an aligned table"]
1772
- ];
1773
- function typeLabel(schema) {
1774
- if (Array.isArray(schema.enum))
1775
- return schema.enum.join("|");
1776
- if (schema.type === "array")
1777
- return "value";
1778
- if (typeof schema.type === "string")
1779
- return schema.type;
1780
- return "value";
1781
- }
1782
- function constraintLabel(schema) {
1783
- const limits = [];
1784
- const bounds = [
1785
- ["minimum", ">="],
1786
- ["exclusiveMinimum", ">"],
1787
- ["maximum", "<="],
1788
- ["exclusiveMaximum", "<"],
1789
- ["minLength", "length >="],
1790
- ["maxLength", "length <="],
1791
- ["minItems", "items >="],
1792
- ["maxItems", "items <="]
1793
- ];
1794
- for (const [keyword, label] of bounds) {
1795
- const bound = schema[keyword];
1796
- if (typeof bound !== "number")
1797
- continue;
1798
- if (schema.type === "integer" && Math.abs(bound) === Number.MAX_SAFE_INTEGER)
1799
- continue;
1800
- limits.push(`${label}${bound}`);
1801
- }
1802
- const compositions = [
1803
- ["anyOf", "any of", " | "],
1804
- ["oneOf", "one of", " | "],
1805
- ["allOf", "all of", " & "]
1806
- ];
1807
- for (const [keyword, label, separator] of compositions) {
1808
- const members = schema[keyword];
1809
- if (!Array.isArray(members))
1810
- continue;
1811
- const branches = members.map((member) => {
1812
- const constraints = isRecord(member) ? constraintLabel(member) : "";
1813
- const type = isRecord(member) ? typeLabel(member) : member === false ? "no value" : "any value";
1814
- return { constraints, text: `${type}${constraints}` };
1815
- });
1816
- if (branches.every((branch) => branch.constraints === ""))
1817
- continue;
1818
- limits.push(`${label}: ${branches.map((branch) => branch.text).join(separator)}`);
1819
- }
1820
- return limits.length === 0 ? "" : ` [${limits.join(", ")}]`;
1821
- }
1822
- function fieldTypeLabel(schema) {
1823
- return `<${typeLabel(schema)}>${constraintLabel(schema)}`;
1824
- }
1825
- function nullableField(schema) {
1826
- if (schema.type === "null")
1827
- return true;
1828
- if (Array.isArray(schema.type) && schema.type.includes("null"))
1829
- return true;
1830
- return Array.isArray(schema.anyOf) && schema.anyOf.some((member) => isRecord(member) && nullableField(member));
1831
- }
1832
- function jsonValueHelp(fields) {
1833
- const example = fields.find((field) => nullableField(field.schema))?.name;
1834
- return [
1835
- "Assign a whole JSON value with --field:=JSON; the declared schema still validates it.",
1836
- ...example === undefined ? [] : [`JSON null: --${example}:=null. The literal string stays --${example} null.`]
1837
- ];
1838
- }
1839
- function padRight(text, width) {
1840
- return text.length >= width ? text : text + " ".repeat(width - text.length);
1841
- }
1842
- function summarize(desc) {
1843
- const firstLine = desc.split(`
1844
- `)[0]?.trim() ?? "";
1845
- const firstSentence = firstLine.split(". ")[0]?.trim() ?? firstLine;
1846
- const max = 72;
1847
- return firstSentence.length > max ? `${firstSentence.slice(0, max - 1).trimEnd()}…` : firstSentence;
1848
- }
1849
- function applicationOptionLines(fields) {
1850
- if (fields.length === 0)
1851
- return [];
1852
- const labels = new Map(fields.map((field) => [field.name, `--${field.name} ${fieldTypeLabel(field.schema)}`]));
1853
- const width = Math.max(...fields.map((field) => labels.get(field.name)?.length ?? 0));
1854
- return [
1855
- "",
1856
- "Application options:",
1857
- ...fields.map((field) => ` ${padRight(labels.get(field.name) ?? `--${field.name}`, width)} ${field.description ?? ""}`.trimEnd())
1858
- ];
1859
- }
1860
- function filterCommands(commands, filter) {
1861
- const needle = filter.toLowerCase();
1862
- return new Map([...commands].filter(([command, descriptor]) => command.toLowerCase().includes(needle) || descriptor.description.toLowerCase().includes(needle)));
1863
- }
1864
- function renderTopHelp(input) {
1865
- const { name, version, defaultCommand } = input;
1866
- const commands = input.filter === undefined ? input.commands : filterCommands(input.commands, input.filter);
1867
- const lines = [
1868
- `${name} ${version}`,
1869
- "",
1870
- `Usage: ${name} ${defaultCommand ? "[command]" : "<command>"} [args] [--flags]`,
1871
- "",
1872
- input.filter === undefined ? "Commands:" : `Commands matching "${input.filter}" (${commands.size} of ${input.commands.size}):`
1873
- ];
1874
- const width = Math.max(0, ...[...commands.keys()].map((key) => key.length));
1875
- for (const [command, descriptor] of commands) {
1876
- lines.push(` ${padRight(command, width)} ${summarize(descriptor.description)}${command === defaultCommand ? " (default)" : ""}${descriptor.unavailableReason !== undefined ? " [unavailable]" : ""}`);
1877
- if (descriptor.unavailableReason !== undefined) {
1878
- lines.push(` Unavailable: ${descriptor.unavailableReason}`);
1587
+ let fields;
1588
+ if (table !== undefined) {
1589
+ fields = table.split(",").map((field) => field.trim()).filter(Boolean);
1590
+ if (fields.length === 0)
1591
+ throw new CliArgumentError2("--table needs at least one field");
1879
1592
  }
1593
+ return {
1594
+ kind: "records",
1595
+ ...sort !== undefined && { sort },
1596
+ ...ascending && { ascending },
1597
+ ...top !== undefined && { top },
1598
+ ...fields && { table: fields }
1599
+ };
1880
1600
  }
1881
- if (input.unavailable !== undefined) {
1882
- lines.push("", `Managed commands are unavailable: ${input.unavailable}`);
1883
- }
1884
- if (input.filter !== undefined) {
1885
- lines.push("", `Run "${name} <command> --help" for command-specific flags.`);
1886
- return `${lines.join(`
1887
- `)}
1888
- `;
1601
+ if (ascending) {
1602
+ throw new CliArgumentError2("--ascending orders a record view; pass --sort with it");
1889
1603
  }
1890
- lines.push("", "Global options:");
1891
- const optWidth = Math.max(...GLOBAL_OPTIONS.map(([flag]) => flag.length));
1892
- for (const [flag, desc] of GLOBAL_OPTIONS)
1893
- lines.push(` ${padRight(flag, optWidth)} ${desc}`);
1894
- lines.push(...applicationOptionLines(input.applicationOptions));
1895
- lines.push("", ...jsonValueHelp(input.applicationOptions));
1896
- lines.push("", `Run "${name} <command> --help" for command-specific flags.`);
1897
- return `${lines.join(`
1898
- `)}
1899
- `;
1900
- }
1901
- function renderCommandHelp(name, command, descriptor, applicationOptions = []) {
1902
- const fields = jsonSchemaFields(descriptor.presentationSchema);
1903
- const fieldsByName = new Map(fields.map((field) => [field.name, field]));
1904
- const kinds = describeSchemaFields(descriptor.argumentSchema);
1905
- const positionals = [];
1906
- const positionalNames = descriptor.positionals ?? [...kinds].filter(([, info]) => info.kind !== "boolean").map(([fieldName]) => fieldName);
1907
- for (const fieldName of positionalNames) {
1908
- const field = fieldsByName.get(fieldName);
1909
- if (field)
1910
- positionals.push(field);
1604
+ if (sum !== undefined) {
1605
+ if (top !== undefined && by === undefined) {
1606
+ throw new CliArgumentError2("--top needs --by: a single sum has nothing to rank");
1607
+ }
1608
+ return {
1609
+ kind: "sum",
1610
+ field: sum,
1611
+ ...by !== undefined && { by },
1612
+ ...top !== undefined && { top }
1613
+ };
1911
1614
  }
1912
- const variadicTail = descriptor.positionals !== undefined && kinds.get(descriptor.positionals[descriptor.positionals.length - 1] ?? "")?.kind === "array" ? descriptor.positionals[descriptor.positionals.length - 1] : undefined;
1913
- const positionalSyntax = new Map(positionals.map((field) => {
1914
- const label = field.name === variadicTail ? `${field.name}...` : field.name;
1915
- return [field.name, field.required ? `<${label}>` : `[${label}]`];
1916
- }));
1917
- const usage = [
1918
- `Usage: ${name} ${command}`,
1919
- ...positionals.map((field) => positionalSyntax.get(field.name) ?? field.name),
1920
- "[--flags]"
1921
- ].join(" ");
1922
- const lines = [descriptor.description, "", usage, ""];
1923
- if (descriptor.unavailableReason !== undefined) {
1924
- lines.push(`Unavailable: ${descriptor.unavailableReason}`, "");
1615
+ if (countBy !== undefined) {
1616
+ if (by !== undefined) {
1617
+ throw new CliArgumentError2("--count-by already names the grouping field; drop --by");
1618
+ }
1619
+ return { kind: "count", field: countBy, ...top !== undefined && { top } };
1925
1620
  }
1926
- if (fields.length > 0) {
1927
- lines.push("Arguments:");
1928
- const labels = new Map(fields.map((field) => {
1929
- const positional = positionalSyntax.get(field.name);
1930
- const alias = descriptor.aliases.get(field.name);
1931
- const option = alias ? `-${alias}, --${field.name}` : `--${field.name}`;
1932
- return [field.name, positional ? `${positional} | ${option}` : option];
1933
- }));
1934
- const width = Math.max(...fields.map((field) => labels.get(field.name)?.length ?? 0));
1935
- for (const f of fields) {
1936
- const req = f.required ? " (required)" : "";
1937
- const desc = f.description ? ` — ${f.description}` : "";
1938
- const label = labels.get(f.name) ?? `--${f.name}`;
1939
- lines.push(` ${padRight(label, width)} ${fieldTypeLabel(f.schema)}${req}${desc}`);
1621
+ if (by !== undefined) {
1622
+ if (top === undefined) {
1623
+ throw new CliArgumentError2("--by groups a view; pass --count-by, --sum or --top with it");
1940
1624
  }
1941
- const flag = [...kinds].find(([, info]) => info.kind === "boolean")?.[0];
1942
- if (flag !== undefined) {
1943
- lines.push("", `A boolean flag is on alone or takes true/false: --${flag}, --${flag} false, --no-${flag}`);
1625
+ return { kind: "count", field: by, top };
1626
+ }
1627
+ throw new CliArgumentError2("--top needs --sort, --by, --count-by or --sum to rank");
1628
+ }
1629
+
1630
+ // src/tools/cli/args.ts
1631
+ function buildToolArgs(tokens, fields, fieldFor, declaredPositionals, allowUnknown) {
1632
+ const { flags, jsonFields, literalBooleanFlags, positionals } = tokens;
1633
+ if (declaredPositionals === undefined && positionals.length > 0) {
1634
+ const arrayFlag = [...flags.keys()].find((key) => fieldFor(key)?.kind === "array");
1635
+ if (arrayFlag !== undefined) {
1636
+ throw new CliArgumentError2(`--${arrayFlag} cannot be combined with automatic positional arguments. Repeat --${arrayFlag} for each element or use --${arrayFlag}:=JSON; name other fields with flags, or declare a positional policy.`);
1944
1637
  }
1945
- const arrays = [...kinds].filter(([, info]) => info.kind === "array");
1946
- for (const [field] of arrays) {
1947
- lines.push("", `Repeat --${field} <value> for each element, or assign the whole array with --${field}:=JSON.`);
1638
+ }
1639
+ const toolArgs = {};
1640
+ const fillable = declaredPositionals === undefined ? [...fields.entries()].filter(([, info]) => info.kind !== "boolean").map(([name]) => name) : [...declaredPositionals];
1641
+ const variadicTail = declaredPositionals !== undefined && fields.get(fillable[fillable.length - 1] ?? "")?.kind === "array" ? fillable[fillable.length - 1] : undefined;
1642
+ let pi = 0;
1643
+ for (const key of fillable) {
1644
+ if (pi >= positionals.length)
1645
+ break;
1646
+ if (key === variadicTail) {
1647
+ if (flags.has(key)) {
1648
+ throw new CliArgumentError2(`--${key} conflicts with the positional values for "${key}" — pass one form, not both`);
1649
+ }
1650
+ toolArgs[key] = coerceField(fields.get(key), positionals.slice(pi), `<${key}>`);
1651
+ pi = positionals.length;
1652
+ break;
1948
1653
  }
1949
- if (arrays.length > 0 && descriptor.positionals === undefined) {
1950
- lines.push("Array options cannot be combined with automatic positional arguments; name other fields with flags.");
1654
+ if (flags.has(key))
1655
+ continue;
1656
+ const value = positionals[pi++];
1657
+ if (value !== undefined) {
1658
+ toolArgs[key] = coerceField(fields.get(key), [value], `<${key}>`);
1951
1659
  }
1952
- lines.push("", ...jsonValueHelp(fields), "");
1953
1660
  }
1954
- const applicationLines = applicationOptionLines(applicationOptions);
1955
- if (applicationLines.length > 0)
1956
- lines.push(...applicationLines.slice(1), "");
1957
- return `${lines.join(`
1958
- `)}
1959
- `;
1960
- }
1961
-
1962
- // src/tools/cli/command-shape.ts
1963
- function commandShapeIssue(name, descriptor, exists, passthroughField, applicationGlobals = new Set) {
1964
- assertUniqueToolName(name, exists, "CLI command");
1965
- if (name === "help" || name === "version") {
1966
- throw new Error(`[stitchkit] CLI command "${name}" is reserved`);
1661
+ if (pi < positionals.length) {
1662
+ throw new CliArgumentError2(`Unexpected positional argument "${positionals[pi]}"`);
1967
1663
  }
1968
- if (passthroughField !== undefined && descriptor.aliases.has(passthroughField)) {
1969
- throw new Error(`[stitchkit] CLI command "${name}" cannot alias passthrough field "${passthroughField}"`);
1664
+ const dottedRoots = new Map;
1665
+ for (const key of flags.keys()) {
1666
+ const dot = key.indexOf(".");
1667
+ const exactLiteralBoolean = literalBooleanFlags.has(key) && fields.has(key);
1668
+ if (dot > 0 && !exactLiteralBoolean) {
1669
+ dottedRoots.set(key.slice(0, dot), key);
1670
+ }
1970
1671
  }
1971
- const fieldNames = jsonSchemaFields(descriptor.presentationSchema).map((field) => field.name);
1972
- const conflicting = fieldNames.filter((field) => RESERVED_CLI_OPTIONS.has(field));
1973
- if (conflicting.length > 0) {
1974
- return `[stitchkit] CLI command "${name}" declares reserved option field(s): ${conflicting.join(", ")}`;
1672
+ for (const [root, dotted] of dottedRoots) {
1673
+ if (flags.has(root)) {
1674
+ throw new CliArgumentError2(`--${root} conflicts with --${dotted} — pass one form, not both`);
1675
+ }
1975
1676
  }
1976
- const shadowed = fieldNames.filter((field) => applicationGlobals.has(field));
1977
- if (shadowed.length > 0) {
1978
- return `[stitchkit] CLI command "${name}" declares field(s) shadowed by application global options: ${shadowed.join(", ")}`;
1677
+ for (const key of jsonFields) {
1678
+ for (const other of flags.keys()) {
1679
+ if (other.startsWith(`${key}.`) || key.startsWith(`${other}.`)) {
1680
+ throw new CliArgumentError2(`--${key} conflicts with --${other} — pass one form, not both`);
1681
+ }
1682
+ }
1683
+ }
1684
+ for (const [key, values] of flags) {
1685
+ const info = fieldFor(key);
1686
+ const exactLiteralBoolean = literalBooleanFlags.has(key) && fields.has(key);
1687
+ if (key.includes(".") && !exactLiteralBoolean) {
1688
+ const root = key.split(".")[0] ?? key;
1689
+ const value = !jsonFields.has(key) && info === undefined && allowUnknown && !fields.has(root) ? looseCoerce(values[0] ?? "") : coerceCliFieldValue(info, values, `--${key}`, jsonFields.has(key));
1690
+ setNested(toolArgs, key.split("."), value);
1691
+ continue;
1692
+ }
1693
+ toolArgs[key] = coerceCliFieldValue(info, values, `--${key}`, jsonFields.has(key));
1979
1694
  }
1695
+ return toolArgs;
1980
1696
  }
1981
-
1982
- // src/tools/cli/managed-surface.ts
1983
- async function buildManagedSurface(session, typedGlobals, forExecution) {
1984
- const { config, nativeHelp, applicationGlobalNames } = session;
1985
- let authPromise;
1986
- const resolveIdentity = () => {
1987
- authPromise ??= (async () => config.resolveAuth ? await config.resolveAuth(typedGlobals) : await config.auth)();
1988
- return authPromise;
1697
+ function parseCliArgs2(argv, schema, config = {}) {
1698
+ const fields = describeSchemaFields(schema);
1699
+ for (const name of config.knownFields ?? []) {
1700
+ if (!fields.has(name))
1701
+ fields.set(name, { kind: "other" });
1702
+ }
1703
+ const fieldFor = (name) => fields.get(name) ?? (name.includes(".") ? describeSchemaPath(schema, name.split(".")) : undefined);
1704
+ const tokens = readCliTokens(argv, fields, fieldFor, config);
1705
+ const { options } = tokens;
1706
+ const view = resolveCliView(tokens.viewFlags, tokens.ascending);
1707
+ if (view)
1708
+ options.view = view;
1709
+ return {
1710
+ toolArgs: buildToolArgs(tokens, fields, fieldFor, config.positionals, config.allowUnknown === true),
1711
+ options
1989
1712
  };
1990
- const dynamicSurface = typeof config.services === "function" || typeof config.runtimeTools === "function";
1991
- let auth;
1992
- if (dynamicSurface || forExecution) {
1993
- try {
1994
- auth = await resolveIdentity();
1995
- } catch (error) {
1996
- const failure = toolResultFromError(error);
1997
- const normalized = toolErrorFromResult(failure);
1998
- return {
1999
- resolved: false,
2000
- failure,
2001
- reason: `${normalized.code}: ${normalized.message}`,
2002
- help: new Map(nativeHelp)
2003
- };
1713
+ }
1714
+ function refuseGlobalOption(issue) {
1715
+ const field = issue?.path[0];
1716
+ const where = typeof field === "string" ? `--${field}` : "application option";
1717
+ throw new CliArgumentError2(`${where}: ${issue?.message ?? "invalid value"}`);
1718
+ }
1719
+ function extractCliGlobalOptions2(argv, schema) {
1720
+ if (schema === undefined)
1721
+ return { argv: [...argv], globals: {} };
1722
+ const fields = describeSchemaFields(schema);
1723
+ const rest = [];
1724
+ const raw = new Map;
1725
+ const jsonFields = new Set;
1726
+ let ended = false;
1727
+ for (let i = 0;i < argv.length; i++) {
1728
+ const token = argv[i];
1729
+ if (token === undefined)
1730
+ continue;
1731
+ if (!ended && token === "--")
1732
+ ended = true;
1733
+ const option = ended ? undefined : classifyLongOptionToken(token);
1734
+ const field = option ? cliFieldOption(option, fields) : undefined;
1735
+ const info = field && option?.globalKind === undefined ? fields.get(field.name) : undefined;
1736
+ if (!option || !field || !info) {
1737
+ rest.push(token);
1738
+ continue;
1739
+ }
1740
+ let { value } = option;
1741
+ if (value === undefined && info.kind === "boolean") {
1742
+ const separate = separateBoolValue(argv[i + 1]);
1743
+ if (separate !== undefined)
1744
+ i++;
1745
+ value = String(separate ?? true);
1746
+ } else if (value === undefined) {
1747
+ const next = argv[i + 1];
1748
+ if (next !== undefined && !isOptionToken(next)) {
1749
+ value = next;
1750
+ i++;
1751
+ } else {
1752
+ throw new CliArgumentError2(`--${option.name} requires a value`);
1753
+ }
2004
1754
  }
1755
+ appendCliFieldValue(raw, jsonFields, field.name, value, info, field.json);
2005
1756
  }
2006
- const { tools, help } = buildCliSurface(config, auth, nativeHelp, applicationGlobalNames, commandShapeIssue);
2007
- return { resolved: true, auth, help, tools };
1757
+ const args = {};
1758
+ for (const [name, values] of raw) {
1759
+ args[name] = coerceCliFieldValue(fields.get(name), values, `--${name}`, jsonFields.has(name));
1760
+ }
1761
+ let coerced;
1762
+ try {
1763
+ coerced = coerceJsonArgs2(args, schema);
1764
+ } catch (error) {
1765
+ if (error instanceof z2.ZodError)
1766
+ refuseGlobalOption(error.issues[0]);
1767
+ throw error;
1768
+ }
1769
+ const parsed = schema.safeParse(coerced);
1770
+ if (!parsed.success)
1771
+ refuseGlobalOption(parsed.error.issues[0]);
1772
+ return { argv: rest, globals: parsed.data };
2008
1773
  }
2009
1774
 
2010
1775
  // src/tools/cli/prepare.ts
@@ -2202,6 +1967,243 @@ function unavailableCommandJson(argv) {
2202
1967
  return false;
2203
1968
  }
2204
1969
 
1970
+ // src/tools/cli/view.ts
1971
+ var ABSENT = "(absent)";
1972
+ function selectRecords(data) {
1973
+ if (Array.isArray(data))
1974
+ return data;
1975
+ if (isRecord(data)) {
1976
+ const arrays = Object.entries(data).filter(([, value]) => Array.isArray(value));
1977
+ const only = arrays[0];
1978
+ if (arrays.length === 1 && only && Array.isArray(only[1]))
1979
+ return only[1];
1980
+ if (arrays.length > 1) {
1981
+ throw new CliArgumentError2(`an aggregate needs one collection, but the result carries several: ${arrays.map(([key]) => key).join(", ")} — name the shape you want with jq instead`);
1982
+ }
1983
+ }
1984
+ throw new CliArgumentError2("an aggregate needs a collection; this result is not one");
1985
+ }
1986
+ function isAbsent(value) {
1987
+ return value === undefined || value === null;
1988
+ }
1989
+ function readField(record, path) {
1990
+ let current = record;
1991
+ for (const segment of path) {
1992
+ if (!isRecord(current) || !Object.hasOwn(current, segment))
1993
+ return;
1994
+ current = current[segment];
1995
+ }
1996
+ return current;
1997
+ }
1998
+ function fieldPath(field) {
1999
+ const path = field.split(".");
2000
+ if (path.some((segment) => segment.length === 0 || isUnsafeKey(segment))) {
2001
+ throw new CliArgumentError2(`"${field}" is not a readable field path`);
2002
+ }
2003
+ return path;
2004
+ }
2005
+ function availableFields(records) {
2006
+ const keys = new Set;
2007
+ for (const record of records) {
2008
+ if (isRecord(record))
2009
+ for (const key of Object.keys(record))
2010
+ keys.add(key);
2011
+ }
2012
+ return [...keys].sort();
2013
+ }
2014
+ function assertFieldPresent(records, field) {
2015
+ const path = fieldPath(field);
2016
+ const present = records.some((record) => readField(record, path) !== undefined);
2017
+ if (records.length > 0 && !present) {
2018
+ const available = availableFields(records);
2019
+ throw new CliArgumentError2(`no record carries the field "${field}"${available.length > 0 ? ` — available: ${available.join(", ")}` : ""}`);
2020
+ }
2021
+ return path;
2022
+ }
2023
+ function groupKey(value) {
2024
+ if (value === undefined)
2025
+ return ABSENT;
2026
+ if (value === null)
2027
+ return "null";
2028
+ if (value === "null" || value === ABSENT)
2029
+ return JSON.stringify(value);
2030
+ if (typeof value === "object")
2031
+ return JSON.stringify(value);
2032
+ return String(value);
2033
+ }
2034
+ function ordered(totals, top) {
2035
+ const entries = [...totals].sort(([leftKey, left], [rightKey, right]) => right === left ? leftKey.localeCompare(rightKey) : right - left);
2036
+ const sliced = top === undefined ? entries : entries.slice(0, top);
2037
+ return {
2038
+ kind: "json",
2039
+ data: Object.fromEntries(sliced),
2040
+ order: sliced.map(([key]) => key)
2041
+ };
2042
+ }
2043
+ function stringifyGroups(data, order, compact) {
2044
+ if (!isRecord(data) || order.length === 0)
2045
+ return JSON.stringify(data);
2046
+ const lines = order.map((key) => `${JSON.stringify(key)}:${compact ? "" : " "}${JSON.stringify(data[key])}`);
2047
+ return compact ? `{${lines.join(",")}}` : `{
2048
+ ${lines.join(`,
2049
+ `)}
2050
+ }`;
2051
+ }
2052
+ function numericValue(value, field) {
2053
+ if (isAbsent(value))
2054
+ return;
2055
+ if (typeof value === "number" && Number.isFinite(value))
2056
+ return value;
2057
+ if (typeof value === "bigint")
2058
+ return Number(value);
2059
+ throw new CliArgumentError2(`--sum ${field}: "${groupKey(value)}" is not a number`);
2060
+ }
2061
+ function renderTable(records, columns) {
2062
+ const fields = columns.map(({ field }) => field);
2063
+ const cell = (value) => {
2064
+ if (value === undefined)
2065
+ return "";
2066
+ if (value === null)
2067
+ return "null";
2068
+ if (typeof value === "object")
2069
+ return JSON.stringify(value);
2070
+ return String(value);
2071
+ };
2072
+ const rows = records.map((record) => columns.map(({ path }) => cell(readField(record, path))));
2073
+ const widths = fields.map((field, column) => Math.max(field.length, ...rows.map((row) => row[column]?.length ?? 0)));
2074
+ const line = (cells) => cells.map((value, column) => value.padEnd(widths[column] ?? value.length)).join(" ").trimEnd();
2075
+ return `${[line(fields), line(widths.map((width) => "-".repeat(width))), ...rows.map(line)].join(`
2076
+ `)}
2077
+ `;
2078
+ }
2079
+ function compareFieldValues(left, right) {
2080
+ if (isAbsent(left))
2081
+ return isAbsent(right) ? 0 : 1;
2082
+ if (isAbsent(right))
2083
+ return -1;
2084
+ if (typeof left === "number" && typeof right === "number")
2085
+ return left - right;
2086
+ if (typeof left === "bigint" || typeof right === "bigint") {
2087
+ return Number(left) - Number(right);
2088
+ }
2089
+ return groupKey(left).localeCompare(groupKey(right));
2090
+ }
2091
+ function renderRecords(records, view) {
2092
+ let selected = [...records];
2093
+ if (view.sort !== undefined) {
2094
+ const path = assertFieldPresent(records, view.sort);
2095
+ const direction = view.ascending ? 1 : -1;
2096
+ selected.sort((left, right) => {
2097
+ const leftValue = readField(left, path);
2098
+ const rightValue = readField(right, path);
2099
+ const order = compareFieldValues(leftValue, rightValue);
2100
+ return isAbsent(leftValue) || isAbsent(rightValue) ? order : order * direction;
2101
+ });
2102
+ }
2103
+ const columns = view.table?.map((field) => ({
2104
+ field,
2105
+ path: assertFieldPresent(records, field)
2106
+ }));
2107
+ if (view.top !== undefined)
2108
+ selected = selected.slice(0, view.top);
2109
+ return columns ? { kind: "text", text: renderTable(selected, columns) } : { kind: "json", data: selected };
2110
+ }
2111
+ function refuseEnvelopeFields(data, view) {
2112
+ if (!isRecord(data))
2113
+ return;
2114
+ const fields = view.kind === "records" ? [...view.sort === undefined ? [] : [view.sort], ...view.table ?? []] : [view.field, ...view.kind === "sum" && view.by !== undefined ? [view.by] : []];
2115
+ for (const field of fields) {
2116
+ const first = field.split(".")[0] ?? "";
2117
+ if (Object.hasOwn(data, first) && !Array.isArray(data[first])) {
2118
+ throw new CliArgumentError2(`"${field}" belongs to the result object, not to its empty collection`);
2119
+ }
2120
+ }
2121
+ }
2122
+ function renderCliView2(data, view) {
2123
+ const records = selectRecords(data);
2124
+ if (records.length === 0)
2125
+ refuseEnvelopeFields(data, view);
2126
+ if (view.kind === "records")
2127
+ return renderRecords(records, view);
2128
+ if (view.kind === "count") {
2129
+ const path = assertFieldPresent(records, view.field);
2130
+ const totals = new Map;
2131
+ for (const record of records) {
2132
+ const key = groupKey(readField(record, path));
2133
+ totals.set(key, (totals.get(key) ?? 0) + 1);
2134
+ }
2135
+ return ordered(totals, view.top);
2136
+ }
2137
+ const path = assertFieldPresent(records, view.field);
2138
+ if (view.by === undefined) {
2139
+ let total = 0;
2140
+ for (const record of records)
2141
+ total += numericValue(readField(record, path), view.field) ?? 0;
2142
+ return { kind: "json", data: total };
2143
+ }
2144
+ const byPath = assertFieldPresent(records, view.by);
2145
+ const totals = new Map;
2146
+ for (const record of records) {
2147
+ const key = groupKey(readField(record, byPath));
2148
+ totals.set(key, (totals.get(key) ?? 0) + (numericValue(readField(record, path), view.field) ?? 0));
2149
+ }
2150
+ return ordered(totals, view.top);
2151
+ }
2152
+
2153
+ // src/tools/cli/wait.ts
2154
+ var DEFAULT_TIMEOUT2 = 600;
2155
+ function waitFailure(tool, result) {
2156
+ return {
2157
+ ok: false,
2158
+ code: "WAIT_FAILED",
2159
+ details: {
2160
+ message: `"${tool}" reached a terminal failed state`,
2161
+ result
2162
+ }
2163
+ };
2164
+ }
2165
+ async function pollUntilDone2(params) {
2166
+ const { initial, wait, call } = params;
2167
+ if (!initial.ok)
2168
+ return initial;
2169
+ if (wait.failed?.(initial.data))
2170
+ return waitFailure(wait.tool, initial.data);
2171
+ if (wait.done(initial.data))
2172
+ return initial;
2173
+ const pollArgs = wait.poll(initial.data);
2174
+ if (!pollArgs)
2175
+ return initial;
2176
+ const timeoutSec = params.timeoutSec ?? wait.timeout ?? DEFAULT_TIMEOUT2;
2177
+ let failedState;
2178
+ const { state, timedOut } = await pollUntil({
2179
+ poll: () => call(wait.tool, pollArgs),
2180
+ done: (result) => {
2181
+ if (!result.ok)
2182
+ return true;
2183
+ if (wait.failed?.(result.data)) {
2184
+ failedState = result;
2185
+ return true;
2186
+ }
2187
+ return wait.done(result.data);
2188
+ },
2189
+ backoff: wait.backoff,
2190
+ timeoutSec,
2191
+ sleepFn: params.sleepFn,
2192
+ onTick: params.onTick,
2193
+ signal: params.signal
2194
+ });
2195
+ if (timedOut) {
2196
+ return {
2197
+ ok: false,
2198
+ code: "TIMEOUT",
2199
+ details: { message: `Timed out after ${timeoutSec}s waiting for "${wait.tool}"` }
2200
+ };
2201
+ }
2202
+ if (state === failedState && state.ok)
2203
+ return waitFailure(wait.tool, state.data);
2204
+ return state;
2205
+ }
2206
+
2205
2207
  // src/tools/cli/run-managed.ts
2206
2208
  async function runManagedInvocation(session, route) {
2207
2209
  const { config } = session;