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