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.
- package/CHANGELOG.md +92 -0
- package/README.md +10 -7
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/contract/client-types.d.ts +6 -3
- package/dist/contract/client-types.d.ts.map +1 -1
- package/dist/contract/define-validation.d.ts.map +1 -1
- package/dist/contract/define.d.ts +18 -12
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/endpoint-capabilities.d.ts +28 -0
- package/dist/contract/endpoint-capabilities.d.ts.map +1 -0
- package/dist/contract/factory.d.ts +49 -19
- package/dist/contract/factory.d.ts.map +1 -1
- package/dist/contract/tool-view.d.ts +1 -1
- package/dist/contract/tool-view.d.ts.map +1 -1
- package/dist/contract/transports.d.ts +5 -0
- package/dist/contract/transports.d.ts.map +1 -0
- package/dist/entrypoints/agent-runtime/coding-tools.js +9 -8
- package/dist/entrypoints/agent-runtime/harness.js +9 -8
- package/dist/entrypoints/agent-runtime/realtime.js +3 -3
- package/dist/entrypoints/agent-runtime/sandbox.js +10 -9
- package/dist/entrypoints/agent-runtime/sqlite/bun.js +12 -11
- package/dist/entrypoints/agent-runtime/sqlite/node.js +12 -11
- package/dist/entrypoints/agent-runtime/testing.js +12 -11
- package/dist/entrypoints/agent-runtime.js +22 -19
- package/dist/entrypoints/application/diagnostic-journal.js +3 -3
- package/dist/entrypoints/application/directory-inbox.js +4 -4
- package/dist/entrypoints/application/opentelemetry.js +1 -1
- package/dist/entrypoints/application.js +10 -10
- package/dist/entrypoints/cli/publish.js +4 -4
- package/dist/entrypoints/cli.js +13 -12
- package/dist/entrypoints/contract.d.ts +1 -1
- package/dist/entrypoints/contract.d.ts.map +1 -1
- package/dist/entrypoints/contract.js +6 -4
- package/dist/entrypoints/files.js +3 -3
- package/dist/entrypoints/geo/contract.d.ts +7 -0
- package/dist/entrypoints/geo/contract.d.ts.map +1 -0
- package/dist/entrypoints/geo/contract.js +6 -0
- package/dist/entrypoints/geo.js +60 -23
- package/dist/entrypoints/http.js +3 -3
- package/dist/entrypoints/index.js +9 -7
- package/dist/entrypoints/live.js +1 -1
- package/dist/entrypoints/node.js +7 -6
- package/dist/entrypoints/primitives.js +1 -1
- package/dist/entrypoints/process/owner-loss.js +1 -1
- package/dist/entrypoints/process.js +3 -3
- package/dist/entrypoints/react.js +1 -1
- package/dist/entrypoints/remote.js +4 -3
- package/dist/entrypoints/server.js +16 -15
- package/dist/entrypoints/telegram/screens.js +5 -5
- package/dist/entrypoints/telegram.js +7 -7
- package/dist/entrypoints/testing.js +7 -6
- package/dist/entrypoints/tools/agent.d.ts +9 -0
- package/dist/entrypoints/tools/agent.d.ts.map +1 -0
- package/dist/entrypoints/tools/agent.js +16 -0
- package/dist/entrypoints/tools/connections.js +7 -6
- package/dist/entrypoints/tools/contract.js +1 -1
- package/dist/entrypoints/tools/invoker.js +5 -4
- package/dist/entrypoints/tools/mcp.js +110 -9
- package/dist/entrypoints/tools/toolkit.d.ts +9 -0
- package/dist/entrypoints/tools/toolkit.d.ts.map +1 -0
- package/dist/entrypoints/tools/toolkit.js +40 -0
- package/dist/entrypoints/tools.d.ts +0 -10
- package/dist/entrypoints/tools.d.ts.map +1 -1
- package/dist/entrypoints/tools.js +38 -80
- package/dist/entrypoints/tracking/contract.js +5 -3
- package/dist/entrypoints/tracking/performance.js +3 -3
- package/dist/entrypoints/tracking.d.ts +1 -1
- package/dist/entrypoints/tracking.d.ts.map +1 -1
- package/dist/entrypoints/tracking.js +63 -83
- package/dist/entrypoints/xlsx.js +1 -1
- package/dist/geo/index.d.ts +2 -1
- package/dist/geo/index.d.ts.map +1 -1
- package/dist/geo/record.d.ts +8 -0
- package/dist/geo/record.d.ts.map +1 -0
- package/dist/geo/resolver.d.ts +1 -3
- package/dist/geo/resolver.d.ts.map +1 -1
- package/dist/geo/schema.d.ts +44 -0
- package/dist/geo/schema.d.ts.map +1 -0
- package/dist/geo/types.d.ts +2 -13
- package/dist/geo/types.d.ts.map +1 -1
- package/dist/{index-n5sv4z0w.js → index-0qyrcjfs.js} +16 -18
- package/dist/index-1psbyyhj.js +21 -0
- package/dist/{index-tsngj2b5.js → index-2fc1k9k2.js} +206 -206
- package/dist/index-3prvb42w.js +30 -0
- package/dist/{index-073nw9qr.js → index-3yb5kft5.js} +524 -522
- package/dist/{index-d9hq291n.js → index-4jpykzvw.js} +70 -81
- package/dist/index-568w258r.js +76 -0
- package/dist/index-69t14s4a.js +39 -0
- package/dist/{index-dky0dcmf.js → index-72r9k0av.js} +6 -2
- package/dist/{index-6p3frkv5.js → index-7zwtbak8.js} +9 -50
- package/dist/{index-90p34rts.js → index-8ryydawp.js} +7 -5
- package/dist/{index-vv1sch8a.js → index-8vzh5wpv.js} +13 -8
- package/dist/{index-75ns2ybj.js → index-8xyf8h2k.js} +1 -1
- package/dist/{index-xx1j310k.js → index-8yxgcyt6.js} +171 -169
- package/dist/{index-p6apg0jg.js → index-9frf01t9.js} +16 -9
- package/dist/{index-q0th8q0z.js → index-9mjrnqxs.js} +1 -3
- package/dist/{index-6f49krb5.js → index-aa50qwv4.js} +1 -1
- package/dist/{index-ega3yz4c.js → index-aqwg2dm5.js} +1 -1
- package/dist/{index-p0rf7sq9.js → index-bgcwxhxh.js} +225 -223
- package/dist/index-bph35vpy.js +77 -0
- package/dist/{index-tdpmfks8.js → index-bz479f0x.js} +1 -1
- package/dist/index-d3xdyjt6.js +38 -0
- package/dist/{index-q968hy6y.js → index-e452syyh.js} +1 -1
- package/dist/{index-86nbfysb.js → index-ff39f9dr.js} +3 -3
- package/dist/index-h1j71dr1.js +48 -0
- package/dist/{index-mgb7r8b8.js → index-jswst0h3.js} +1 -1
- package/dist/{index-095ya5hm.js → index-knmnj11j.js} +22 -191
- package/dist/{index-vwhrnp67.js → index-kz9734jc.js} +3 -3
- package/dist/{index-txg1npmg.js → index-mtwh9hyp.js} +1 -1
- package/dist/{index-dr3t5n1v.js → index-nn9qvkww.js} +46 -46
- package/dist/{index-ghtpfz4z.js → index-nyyam9ce.js} +2 -2
- package/dist/{index-qezhmp51.js → index-nz06qw5a.js} +3 -1
- package/dist/{index-1bx1xv1k.js → index-pfvde2fz.js} +5 -2
- package/dist/{index-w7jq3fz9.js → index-pq2m7t46.js} +1142 -1212
- package/dist/{index-yk4g8805.js → index-pq7rkwkk.js} +1 -30
- package/dist/{index-s80awwsw.js → index-qch1vcaa.js} +3 -1
- package/dist/{index-hdjmdwsj.js → index-qctbzmg9.js} +521 -511
- package/dist/{index-n8z3rvzw.js → index-qevqn3d3.js} +3 -5
- package/dist/{index-nqxrm176.js → index-r3db01kh.js} +1 -1
- package/dist/{index-v6vb5qx8.js → index-s3yxw6kt.js} +1 -1
- package/dist/{index-cqmqyjax.js → index-sa99p98w.js} +1 -1
- package/dist/index-sb9dr3tq.js +14 -0
- package/dist/{index-0gxvns6s.js → index-sba3e6m4.js} +1 -0
- package/dist/{index-f24bdt13.js → index-v1swr26p.js} +3 -1
- package/dist/{index-rhzkndr5.js → index-x4p3kw1p.js} +533 -530
- package/dist/{index-cycp3r3e.js → index-yb9n4pgj.js} +2 -2
- package/dist/{index-htxh46ej.js → index-ycqhyars.js} +2 -2
- package/dist/{index-9bwcthxw.js → index-yj6saztf.js} +31 -13
- package/dist/{index-f6m4c545.js → index-zrdcmg6j.js} +4 -6
- package/dist/{index-tvtacc9a.js → index-zyc64nbh.js} +1 -34
- package/dist/process/bun-child.d.ts +3 -2
- package/dist/process/bun-child.d.ts.map +1 -1
- package/dist/server/contract-method.d.ts.map +1 -1
- package/dist/tools/internal/surface-projector.d.ts.map +1 -1
- package/dist/tools/operations/async-operation-canonical-types.d.ts +1 -1
- package/dist/tracking/attribution.d.ts +21 -25
- package/dist/tracking/attribution.d.ts.map +1 -1
- package/dist/tracking/client-visit.d.ts +5 -7
- package/dist/tracking/client-visit.d.ts.map +1 -1
- package/dist/tracking/client.d.ts +2 -2
- package/dist/tracking/client.d.ts.map +1 -1
- package/dist/tracking/contract.d.ts +1 -1
- package/dist/tracking/host.d.ts +0 -1
- package/dist/tracking/host.d.ts.map +1 -1
- package/dist/tracking/privacy.d.ts +6 -0
- package/dist/tracking/privacy.d.ts.map +1 -1
- package/dist/tracking/schemas.d.ts +2 -2
- package/dist/tracking/schemas.d.ts.map +1 -1
- package/llms/agent-runtime-harness.txt +4 -3
- package/llms/agent-runtime-sandbox.txt +1 -1
- package/llms/agent-runtime.2.txt +3 -2
- package/llms/agent-runtime.3.txt +4 -46
- package/llms/agent-runtime.4.txt +113 -0
- package/llms/agent-runtime.txt +11 -13
- package/llms/cli.txt +1 -1
- package/llms/contract.txt +46 -24
- package/llms/geo-contract.txt +23 -0
- package/llms/geo.txt +40 -4
- package/llms/stitchkit.txt +16 -9
- package/llms/testing.txt +4 -6
- package/llms/tools-agent.txt +24 -0
- package/llms/tools-mcp.txt +71 -6
- package/llms/tools-toolkit.txt +23 -0
- package/llms/tools.2.txt +8 -5
- package/llms/tools.3.txt +9 -78
- package/llms/tools.txt +8 -8
- package/llms/tracking-contract.txt +1 -1
- package/llms/tracking.txt +27 -5
- package/llms/{upgrading-0.105.0-0.118.0.txt → upgrading-0.106.0-0.120.0.txt} +152 -410
- package/llms/upgrading-0.37.0-0.53.0.txt +969 -0
- package/llms/{upgrading-0.44.0-0.62.0.txt → upgrading-0.55.0-0.68.0.txt} +509 -528
- package/llms/{upgrading-0.63.0-0.83.0.txt → upgrading-0.69.0-0.95.0.txt} +506 -509
- package/llms/{upgrading-0.84.0-0.104.0.txt → upgrading-0.96.0-0.105.0.txt} +410 -506
- package/llms/upgrading.txt +5 -5
- package/llms-full.txt +475 -154
- package/llms.txt +17 -14
- package/package.json +32 -20
- package/llms/upgrading-0.37.0-0.44.0.txt +0 -444
- package/dist/{index-arjmb6de.js → index-1jwsfkfz.js} +0 -0
- 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-
|
|
17
|
+
} from "./index-x4p3kw1p.js";
|
|
18
18
|
import {
|
|
19
19
|
toolResultFromError,
|
|
20
20
|
toolErrorFromResult
|
|
21
|
-
} from "./index-
|
|
21
|
+
} from "./index-s3yxw6kt.js";
|
|
22
22
|
import {
|
|
23
23
|
createToolRunner,
|
|
24
24
|
formatToolError
|
|
25
|
-
} from "./index-
|
|
25
|
+
} from "./index-ycqhyars.js";
|
|
26
26
|
import {
|
|
27
27
|
writeContainedFile
|
|
28
|
-
} from "./index-
|
|
28
|
+
} from "./index-e452syyh.js";
|
|
29
29
|
import {
|
|
30
30
|
openPinnedDirectory,
|
|
31
31
|
isSafeEntryName,
|
|
32
32
|
containedEntryMetadata
|
|
33
|
-
} from "./index-
|
|
33
|
+
} from "./index-1jwsfkfz.js";
|
|
34
|
+
import {
|
|
35
|
+
pollUntil
|
|
36
|
+
} from "./index-bph35vpy.js";
|
|
34
37
|
import {
|
|
35
38
|
collectToolSurface
|
|
36
|
-
} from "./index-
|
|
39
|
+
} from "./index-ff39f9dr.js";
|
|
37
40
|
import {
|
|
38
41
|
fetchGuarded,
|
|
39
42
|
readCapped
|
|
40
|
-
} from "./index-
|
|
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/
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
function
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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/
|
|
877
|
-
import {
|
|
816
|
+
// src/tools/cli/download.ts
|
|
817
|
+
import { basename, resolve } from "node:path";
|
|
878
818
|
|
|
879
|
-
// src/
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
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/
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
-
|
|
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/
|
|
1294
|
-
var
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
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
|
|
947
|
+
return limits.length === 0 ? "" : ` [${limits.join(", ")}]`;
|
|
1320
948
|
}
|
|
1321
|
-
function
|
|
1322
|
-
|
|
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
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
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
|
|
1338
|
-
const
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
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
|
|
1347
|
-
|
|
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
|
|
1358
|
-
const
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
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
|
|
1367
|
-
if (
|
|
1368
|
-
return
|
|
1369
|
-
const
|
|
1370
|
-
|
|
1371
|
-
|
|
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
|
|
1376
|
-
|
|
1377
|
-
|
|
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
|
|
1385
|
-
const
|
|
1386
|
-
const
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
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
|
-
|
|
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
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
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
|
|
1427
|
-
|
|
1428
|
-
|
|
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
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
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
|
-
|
|
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/
|
|
1477
|
-
|
|
1478
|
-
|
|
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
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
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
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
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
|
-
|
|
1561
|
-
|
|
1562
|
-
if (
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
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
|
-
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1224
|
+
command: defaultCommand,
|
|
1225
|
+
commandArgv: [...globals, ...argv.slice(index)],
|
|
1226
|
+
topLevelHelp: false,
|
|
1227
|
+
version: false
|
|
1595
1228
|
};
|
|
1596
1229
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
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/
|
|
1603
|
-
|
|
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/
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
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/
|
|
1650
|
-
function
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
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
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
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/
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
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
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
[
|
|
1762
|
-
|
|
1763
|
-
|
|
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
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
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
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
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 (
|
|
1882
|
-
|
|
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
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
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
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
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 (
|
|
1927
|
-
|
|
1928
|
-
|
|
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
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
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
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
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 (
|
|
1950
|
-
|
|
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
|
-
|
|
1955
|
-
|
|
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
|
-
|
|
1969
|
-
|
|
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
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
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
|
|
1977
|
-
|
|
1978
|
-
|
|
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
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
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
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
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
|
|
2007
|
-
|
|
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 {
|
|
2386
|
+
export { defineCliCommand2, DEFAULT_EXIT_CODES2, cliExitCode2, emitResult2, CliArgumentError2, cliInvocationResult2, createCliInvoker2, routeCliArgv2, parseCliArgs2, extractCliGlobalOptions2, renderCliView2, pollUntilDone2, createCli2 };
|