sim 2.0.0-preview.17.1 → 2.0.0-preview.21.1
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/README.md +5 -0
- package/dist/index.js +826 -122
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,6 +57,11 @@ Each setting resolves independently, first match wins:
|
|
|
57
57
|
| --- | --- |
|
|
58
58
|
| 1 | Command-line flag (`--endpoint`, `--workspace`, `--output`) |
|
|
59
59
|
| 2 | Environment (`SIM_ENDPOINT`, `SIM_API_KEY`, `SIM_WORKSPACE`, `SIM_OUTPUT`) |
|
|
60
|
+
|
|
61
|
+
`SIM_TIMEOUT_SECONDS` bounds each request (default `3600`, `0` waits
|
|
62
|
+
indefinitely) and `SIM_DEBUG=1` traces requests to stderr. Node ignores
|
|
63
|
+
`HTTPS_PROXY` unless `NODE_USE_ENV_PROXY=1` is also set, on Node 22.21+ or
|
|
64
|
+
24.5+; the CLI warns when a proxy is configured but will not be used.
|
|
60
65
|
| 3 | `~/.sim/config` / `~/.sim/credentials` for the selected profile |
|
|
61
66
|
| 4 | Built-in default (`https://www.sim.ai`, `table`) |
|
|
62
67
|
|
package/dist/index.js
CHANGED
|
@@ -2539,6 +2539,53 @@ function readPackageVersion() {
|
|
|
2539
2539
|
var CLI_VERSION = readPackageVersion();
|
|
2540
2540
|
var USER_AGENT = `sim-cli/${CLI_VERSION} node/${process.versions.node} (${process.platform}; ${process.arch})`;
|
|
2541
2541
|
|
|
2542
|
+
// src/http/environment.ts
|
|
2543
|
+
var reported = new Set;
|
|
2544
|
+
function once(key, message) {
|
|
2545
|
+
if (reported.has(key))
|
|
2546
|
+
return;
|
|
2547
|
+
reported.add(key);
|
|
2548
|
+
process.stderr.write(`warning: ${message}
|
|
2549
|
+
`);
|
|
2550
|
+
}
|
|
2551
|
+
var PROXY_VARIABLES = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
|
|
2552
|
+
var PROXY_SUPPORT = { 22: 21, 24: 5 };
|
|
2553
|
+
var FIRST_SUPPORTED_MAJOR = 24;
|
|
2554
|
+
function runtimeCanProxy(version) {
|
|
2555
|
+
const [major, minor] = version.replace(/^v/, "").split(".").map(Number);
|
|
2556
|
+
if (!Number.isFinite(major) || !Number.isFinite(minor))
|
|
2557
|
+
return false;
|
|
2558
|
+
const firstSupportedMinor = PROXY_SUPPORT[major];
|
|
2559
|
+
if (firstSupportedMinor !== undefined)
|
|
2560
|
+
return minor >= firstSupportedMinor;
|
|
2561
|
+
return major > FIRST_SUPPORTED_MAJOR;
|
|
2562
|
+
}
|
|
2563
|
+
function warnIfProxyIgnored(env2 = process.env, version = process.version) {
|
|
2564
|
+
const variable = PROXY_VARIABLES.find((name) => env2[name]);
|
|
2565
|
+
if (!variable)
|
|
2566
|
+
return;
|
|
2567
|
+
if (env2.NODE_USE_ENV_PROXY && runtimeCanProxy(version))
|
|
2568
|
+
return;
|
|
2569
|
+
once("proxy", runtimeCanProxy(version) ? `${variable} is set but Node only uses it when NODE_USE_ENV_PROXY=1. Re-run with NODE_USE_ENV_PROXY=1 to route through the proxy.` : `${variable} is set but Node ${version} cannot use it. Upgrade to Node 22.21 or 24.5 and set NODE_USE_ENV_PROXY=1 to route through the proxy.`);
|
|
2570
|
+
}
|
|
2571
|
+
var LOOPBACK = new Set(["localhost", "127.0.0.1", "[::1]", "::1", "0.0.0.0"]);
|
|
2572
|
+
function isLoopback(hostname) {
|
|
2573
|
+
return LOOPBACK.has(hostname) || hostname.endsWith(".localhost");
|
|
2574
|
+
}
|
|
2575
|
+
function warnIfKeyOverCleartext(endpoint, hasApiKey) {
|
|
2576
|
+
if (!hasApiKey)
|
|
2577
|
+
return;
|
|
2578
|
+
let url;
|
|
2579
|
+
try {
|
|
2580
|
+
url = new URL(endpoint);
|
|
2581
|
+
} catch {
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
if (url.protocol !== "http:" || isLoopback(url.hostname))
|
|
2585
|
+
return;
|
|
2586
|
+
once("cleartext", `sending your API key to ${url.host} over http. Anything on the path can read it — use https unless this network is trusted.`);
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2542
2589
|
// src/http/client.ts
|
|
2543
2590
|
class SimApiError extends Error {
|
|
2544
2591
|
status;
|
|
@@ -2623,6 +2670,51 @@ function dropUnionBranchNoise(issues) {
|
|
|
2623
2670
|
const kept = issues.filter((issue) => !issues.some((other) => rejectsAKeyThatValidated(issue, other)));
|
|
2624
2671
|
return kept.length > 0 ? kept : issues;
|
|
2625
2672
|
}
|
|
2673
|
+
var DEFAULT_TIMEOUT_SECONDS = 3600;
|
|
2674
|
+
var MAX_TIMEOUT_MS = 2 ** 31 - 1;
|
|
2675
|
+
var RAISE_TIMEOUT_HINT = "Raise SIM_TIMEOUT_SECONDS, or set it to 0 to wait indefinitely.";
|
|
2676
|
+
function isRequestTimeout(error) {
|
|
2677
|
+
return error instanceof DOMException && error.name === "TimeoutError";
|
|
2678
|
+
}
|
|
2679
|
+
function resolveTimeoutMs(env2 = process.env) {
|
|
2680
|
+
const raw = env2.SIM_TIMEOUT_SECONDS;
|
|
2681
|
+
if (raw === undefined || raw.trim() === "")
|
|
2682
|
+
return DEFAULT_TIMEOUT_SECONDS * 1000;
|
|
2683
|
+
const seconds = Number(raw);
|
|
2684
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
2685
|
+
throw new SimApiError(`Invalid SIM_TIMEOUT_SECONDS "${raw}". Use a non-negative number of seconds, or 0 to disable.`, 0);
|
|
2686
|
+
}
|
|
2687
|
+
const ms = seconds === 0 ? 0 : Math.max(1, Math.round(seconds * 1000));
|
|
2688
|
+
if (ms > MAX_TIMEOUT_MS) {
|
|
2689
|
+
throw new SimApiError(`SIM_TIMEOUT_SECONDS "${raw}" is longer than Node can wait (${Math.floor(MAX_TIMEOUT_MS / 1000)}s). Use 0 to wait indefinitely.`, 0);
|
|
2690
|
+
}
|
|
2691
|
+
return ms;
|
|
2692
|
+
}
|
|
2693
|
+
function combineSignals(caller, timeout) {
|
|
2694
|
+
if (!caller)
|
|
2695
|
+
return timeout;
|
|
2696
|
+
if (!timeout)
|
|
2697
|
+
return caller;
|
|
2698
|
+
if (typeof AbortSignal.any === "function")
|
|
2699
|
+
return AbortSignal.any([caller, timeout]);
|
|
2700
|
+
const controller = new AbortController;
|
|
2701
|
+
for (const signal of [caller, timeout]) {
|
|
2702
|
+
if (signal.aborted) {
|
|
2703
|
+
controller.abort(signal.reason);
|
|
2704
|
+
break;
|
|
2705
|
+
}
|
|
2706
|
+
signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
|
|
2707
|
+
}
|
|
2708
|
+
return controller.signal;
|
|
2709
|
+
}
|
|
2710
|
+
function debugEnabled(env2 = process.env) {
|
|
2711
|
+
const raw = env2.SIM_DEBUG;
|
|
2712
|
+
return raw !== undefined && raw !== "" && raw !== "0" && raw.toLowerCase() !== "false";
|
|
2713
|
+
}
|
|
2714
|
+
function traceRequest(method, url, status, startedAt) {
|
|
2715
|
+
process.stderr.write(`${source_default.dim(`[sim] ${method} ${url} → ${status} ${Math.round(performance.now() - startedAt)}ms`)}
|
|
2716
|
+
`);
|
|
2717
|
+
}
|
|
2626
2718
|
function formatApiErrorDetails(details) {
|
|
2627
2719
|
const issues = [];
|
|
2628
2720
|
const seen = new Set;
|
|
@@ -2706,10 +2798,18 @@ class SimClient {
|
|
|
2706
2798
|
const apiKey = this.resolveApiKey(options.auth);
|
|
2707
2799
|
const url = buildUrl(this.profile.endpoint, path, options.query);
|
|
2708
2800
|
const hasBody = options.body !== undefined;
|
|
2801
|
+
const method = options.method ?? "GET";
|
|
2802
|
+
warnIfProxyIgnored();
|
|
2803
|
+
warnIfKeyOverCleartext(this.profile.endpoint, Boolean(apiKey));
|
|
2804
|
+
const timeoutMs = resolveTimeoutMs();
|
|
2805
|
+
const timeout = timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined;
|
|
2806
|
+
const signal = combineSignals(options.signal, timeout);
|
|
2807
|
+
const trace = debugEnabled();
|
|
2808
|
+
const startedAt = performance.now();
|
|
2709
2809
|
let response;
|
|
2710
2810
|
try {
|
|
2711
2811
|
response = await fetch(url, {
|
|
2712
|
-
method
|
|
2812
|
+
method,
|
|
2713
2813
|
headers: {
|
|
2714
2814
|
...apiKey ? { "x-api-key": apiKey } : {},
|
|
2715
2815
|
accept: "application/json",
|
|
@@ -2718,15 +2818,22 @@ class SimClient {
|
|
|
2718
2818
|
...options.headers
|
|
2719
2819
|
},
|
|
2720
2820
|
body: hasBody ? JSON.stringify(options.body) : undefined,
|
|
2721
|
-
signal
|
|
2821
|
+
signal,
|
|
2722
2822
|
redirect: "manual"
|
|
2723
2823
|
});
|
|
2724
2824
|
} catch (cause) {
|
|
2825
|
+
if (trace)
|
|
2826
|
+
traceRequest(method, url, "failed", startedAt);
|
|
2725
2827
|
if (options.signal?.aborted) {
|
|
2726
2828
|
throw new SimApiError("Request cancelled.", 0);
|
|
2727
2829
|
}
|
|
2830
|
+
if (timeout?.aborted) {
|
|
2831
|
+
throw new SimApiError(`${url} did not answer within ${timeoutMs / 1000}s. ${RAISE_TIMEOUT_HINT}`, 0);
|
|
2832
|
+
}
|
|
2728
2833
|
throw new SimApiError(`Could not reach ${this.profile.endpoint}: ${cause.message}`, 0);
|
|
2729
2834
|
}
|
|
2835
|
+
if (trace)
|
|
2836
|
+
traceRequest(method, url, response.status, startedAt);
|
|
2730
2837
|
if (REDIRECT_STATUSES.has(response.status))
|
|
2731
2838
|
throw this.toRedirectError(url, path, response);
|
|
2732
2839
|
if (!response.ok) {
|
|
@@ -2768,16 +2875,16 @@ function redirectEndpoint(endpoint, requestPath, target) {
|
|
|
2768
2875
|
return suggested === endpoint.replace(/\/+$/, "") ? null : suggested;
|
|
2769
2876
|
}
|
|
2770
2877
|
function pageProgress() {
|
|
2771
|
-
let
|
|
2878
|
+
let reported2 = false;
|
|
2772
2879
|
return {
|
|
2773
2880
|
advance: (fetched) => {
|
|
2774
2881
|
if (!process.stderr.isTTY)
|
|
2775
2882
|
return;
|
|
2776
|
-
|
|
2883
|
+
reported2 = true;
|
|
2777
2884
|
process.stderr.write(`\r${source_default.dim(`fetched ${fetched}…`)}\x1B[K`);
|
|
2778
2885
|
},
|
|
2779
2886
|
finish: () => {
|
|
2780
|
-
if (
|
|
2887
|
+
if (reported2)
|
|
2781
2888
|
process.stderr.write("\r\x1B[K");
|
|
2782
2889
|
}
|
|
2783
2890
|
};
|
|
@@ -11064,7 +11171,7 @@ function attachCredentialCommands(program2) {
|
|
|
11064
11171
|
}
|
|
11065
11172
|
|
|
11066
11173
|
// src/commands/protocol/files-get.ts
|
|
11067
|
-
import { once } from "node:events";
|
|
11174
|
+
import { once as once2 } from "node:events";
|
|
11068
11175
|
import { createWriteStream } from "node:fs";
|
|
11069
11176
|
import { link, lstat, mkdtemp, readlink, rename, rm } from "node:fs/promises";
|
|
11070
11177
|
import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
|
|
@@ -11079,6 +11186,9 @@ function printProtocolResult(format, result) {
|
|
|
11079
11186
|
|
|
11080
11187
|
// src/commands/protocol/files-get.ts
|
|
11081
11188
|
function writeFailure(path, error) {
|
|
11189
|
+
if (isRequestTimeout(error)) {
|
|
11190
|
+
return new SimApiError(`Downloading ${path} timed out. ${RAISE_TIMEOUT_HINT}`, 0);
|
|
11191
|
+
}
|
|
11082
11192
|
const code = error.code;
|
|
11083
11193
|
if (code === "EEXIST") {
|
|
11084
11194
|
return new SimApiError(`${path} already exists. Pass --force to overwrite it, or choose another output path.`, 0);
|
|
@@ -11169,7 +11279,7 @@ async function streamToStdout(body, output = process.stdout) {
|
|
|
11169
11279
|
if (done)
|
|
11170
11280
|
return;
|
|
11171
11281
|
if (!output.write(value))
|
|
11172
|
-
await
|
|
11282
|
+
await once2(output, "drain");
|
|
11173
11283
|
}
|
|
11174
11284
|
} finally {
|
|
11175
11285
|
reader.releaseLock();
|
|
@@ -11429,6 +11539,278 @@ function attachKnowledgeDocumentUpload(documents) {
|
|
|
11429
11539
|
});
|
|
11430
11540
|
}
|
|
11431
11541
|
|
|
11542
|
+
// src/commands/protocol/logs-follow.ts
|
|
11543
|
+
var DEFAULT_BACKLOG = 10;
|
|
11544
|
+
var DEFAULT_INTERVAL_SECONDS = 3;
|
|
11545
|
+
var MIN_INTERVAL_SECONDS = 0.1;
|
|
11546
|
+
var MAX_BACKOFF_MS = 30000;
|
|
11547
|
+
var POLL_PAGE_SIZE = 100;
|
|
11548
|
+
var MAX_PAGES_PER_POLL = 10;
|
|
11549
|
+
var MAX_REMEMBERED_RUNS = 5000;
|
|
11550
|
+
var MAX_CELL_WIDTH2 = 60;
|
|
11551
|
+
var WAIT_SLICE_MS = 250;
|
|
11552
|
+
var RETRYABLE_CLIENT_STATUSES = new Set([408, 425, 429]);
|
|
11553
|
+
var ERASE_LINE = `${String.fromCharCode(27)}[K`;
|
|
11554
|
+
function collect(value, previous) {
|
|
11555
|
+
return [...previous, value];
|
|
11556
|
+
}
|
|
11557
|
+
function at2(row, path) {
|
|
11558
|
+
return path.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : undefined, row);
|
|
11559
|
+
}
|
|
11560
|
+
function renderCell2(value, format) {
|
|
11561
|
+
switch (format) {
|
|
11562
|
+
case "timestamp":
|
|
11563
|
+
return timestamp2(value);
|
|
11564
|
+
case "duration":
|
|
11565
|
+
return duration(value);
|
|
11566
|
+
case "bytes":
|
|
11567
|
+
return bytes(value);
|
|
11568
|
+
case "bool":
|
|
11569
|
+
return bool2(value);
|
|
11570
|
+
case "cost":
|
|
11571
|
+
return typeof value === "number" ? `$${value.toFixed(4)}` : text(null);
|
|
11572
|
+
default:
|
|
11573
|
+
return text(typeof value === "object" && value !== null ? JSON.stringify(value) : value);
|
|
11574
|
+
}
|
|
11575
|
+
}
|
|
11576
|
+
var COLUMNS = (CLI_CONTRACT.listLogs?.columns ?? []).map((spec) => ({
|
|
11577
|
+
header: spec.header,
|
|
11578
|
+
value: (row) => renderCell2(at2(row, spec.path ?? spec.header), spec.format)
|
|
11579
|
+
}));
|
|
11580
|
+
function oneLine2(value) {
|
|
11581
|
+
return value.replace(/\s*[\r\n\t]+\s*/g, " ");
|
|
11582
|
+
}
|
|
11583
|
+
function pad2(value, width) {
|
|
11584
|
+
return value + " ".repeat(Math.max(0, width - visibleWidth(value)));
|
|
11585
|
+
}
|
|
11586
|
+
function clamp2(value, width) {
|
|
11587
|
+
if (visibleWidth(value) <= width || visibleWidth(value) !== value.length)
|
|
11588
|
+
return value;
|
|
11589
|
+
return `${value.slice(0, Math.max(1, width - 1))}…`;
|
|
11590
|
+
}
|
|
11591
|
+
function createTableWriter() {
|
|
11592
|
+
let widths = null;
|
|
11593
|
+
return (rows) => {
|
|
11594
|
+
const lines = rows.map((row) => COLUMNS.map((column) => oneLine2(column.value(row))));
|
|
11595
|
+
if (!widths) {
|
|
11596
|
+
widths = COLUMNS.map((column, index) => Math.min(MAX_CELL_WIDTH2, Math.max(visibleWidth(column.header), ...lines.map((line) => visibleWidth(line[index])))));
|
|
11597
|
+
const header = widths;
|
|
11598
|
+
console.log(source_default.dim(COLUMNS.map((column, index) => pad2(column.header.toUpperCase(), header[index])).join(" ").trimEnd()));
|
|
11599
|
+
}
|
|
11600
|
+
const locked = widths;
|
|
11601
|
+
for (const line of lines) {
|
|
11602
|
+
console.log(line.map((cell, index) => pad2(clamp2(cell, locked[index]), locked[index])).join(" ").trimEnd());
|
|
11603
|
+
}
|
|
11604
|
+
};
|
|
11605
|
+
}
|
|
11606
|
+
function createWriter(format) {
|
|
11607
|
+
if (format === "json") {
|
|
11608
|
+
return (rows) => {
|
|
11609
|
+
for (const row of rows)
|
|
11610
|
+
console.log(JSON.stringify(row));
|
|
11611
|
+
};
|
|
11612
|
+
}
|
|
11613
|
+
if (format === "yaml") {
|
|
11614
|
+
return (rows) => {
|
|
11615
|
+
for (const row of rows) {
|
|
11616
|
+
console.log(`---
|
|
11617
|
+
${dump(row, { lineWidth: 0, noRefs: true }).trimEnd()}`);
|
|
11618
|
+
}
|
|
11619
|
+
};
|
|
11620
|
+
}
|
|
11621
|
+
if (format === "text") {
|
|
11622
|
+
return (rows) => {
|
|
11623
|
+
if (rows.length > 0)
|
|
11624
|
+
printList("text", rows, COLUMNS);
|
|
11625
|
+
};
|
|
11626
|
+
}
|
|
11627
|
+
return createTableWriter();
|
|
11628
|
+
}
|
|
11629
|
+
function followStatus() {
|
|
11630
|
+
let reported2 = false;
|
|
11631
|
+
return {
|
|
11632
|
+
note: (message) => {
|
|
11633
|
+
if (!process.stderr.isTTY)
|
|
11634
|
+
return;
|
|
11635
|
+
reported2 = true;
|
|
11636
|
+
process.stderr.write(`\r${source_default.dim(message)}${ERASE_LINE}`);
|
|
11637
|
+
},
|
|
11638
|
+
warn: (message) => {
|
|
11639
|
+
if (reported2) {
|
|
11640
|
+
reported2 = false;
|
|
11641
|
+
process.stderr.write(`\r${ERASE_LINE}`);
|
|
11642
|
+
}
|
|
11643
|
+
process.stderr.write(`warning: ${message}
|
|
11644
|
+
`);
|
|
11645
|
+
},
|
|
11646
|
+
clear: () => {
|
|
11647
|
+
if (!reported2)
|
|
11648
|
+
return;
|
|
11649
|
+
reported2 = false;
|
|
11650
|
+
process.stderr.write(`\r${ERASE_LINE}`);
|
|
11651
|
+
}
|
|
11652
|
+
};
|
|
11653
|
+
}
|
|
11654
|
+
function watchForInterrupt() {
|
|
11655
|
+
let stopped = false;
|
|
11656
|
+
const stop = () => {
|
|
11657
|
+
stopped = true;
|
|
11658
|
+
};
|
|
11659
|
+
process.on("SIGINT", stop);
|
|
11660
|
+
process.on("SIGTERM", stop);
|
|
11661
|
+
return {
|
|
11662
|
+
interrupted: () => stopped,
|
|
11663
|
+
dispose: () => {
|
|
11664
|
+
process.off("SIGINT", stop);
|
|
11665
|
+
process.off("SIGTERM", stop);
|
|
11666
|
+
}
|
|
11667
|
+
};
|
|
11668
|
+
}
|
|
11669
|
+
async function waitFor(ms, interrupted) {
|
|
11670
|
+
let remaining = ms;
|
|
11671
|
+
while (remaining > 0 && !interrupted()) {
|
|
11672
|
+
const step = Math.min(WAIT_SLICE_MS, remaining);
|
|
11673
|
+
await sleep(step);
|
|
11674
|
+
remaining -= step;
|
|
11675
|
+
}
|
|
11676
|
+
}
|
|
11677
|
+
function isUnprinted(state, row) {
|
|
11678
|
+
if (state.seen.has(row.runId))
|
|
11679
|
+
return false;
|
|
11680
|
+
return state.floor === null || row.startedAt >= state.floor;
|
|
11681
|
+
}
|
|
11682
|
+
function remember(state, rows) {
|
|
11683
|
+
for (const row of rows)
|
|
11684
|
+
state.seen.set(row.runId, row.startedAt);
|
|
11685
|
+
let excess = state.seen.size - MAX_REMEMBERED_RUNS;
|
|
11686
|
+
if (excess <= 0)
|
|
11687
|
+
return;
|
|
11688
|
+
for (const [runId, startedAt] of state.seen) {
|
|
11689
|
+
if (excess <= 0)
|
|
11690
|
+
break;
|
|
11691
|
+
if (state.floor === null || startedAt > state.floor)
|
|
11692
|
+
state.floor = startedAt;
|
|
11693
|
+
state.seen.delete(runId);
|
|
11694
|
+
excess -= 1;
|
|
11695
|
+
}
|
|
11696
|
+
}
|
|
11697
|
+
async function collectUnprinted(client, path, query, state, pageSize, maxPages) {
|
|
11698
|
+
const rows = [];
|
|
11699
|
+
let cursor = null;
|
|
11700
|
+
let truncated = false;
|
|
11701
|
+
for (let page = 0;page < maxPages; page += 1) {
|
|
11702
|
+
const response = await client.request(path, {
|
|
11703
|
+
query: { ...query, limit: pageSize, cursor }
|
|
11704
|
+
});
|
|
11705
|
+
const page_rows = response?.data ?? [];
|
|
11706
|
+
const unprinted = page_rows.filter((row) => isUnprinted(state, row));
|
|
11707
|
+
rows.push(...unprinted);
|
|
11708
|
+
cursor = response?.nextCursor ?? null;
|
|
11709
|
+
if (!cursor || page_rows.length === 0 || unprinted.length < page_rows.length)
|
|
11710
|
+
break;
|
|
11711
|
+
if (page === maxPages - 1)
|
|
11712
|
+
truncated = true;
|
|
11713
|
+
}
|
|
11714
|
+
return { rows, truncated };
|
|
11715
|
+
}
|
|
11716
|
+
function isTransient(error) {
|
|
11717
|
+
if (!(error instanceof SimApiError))
|
|
11718
|
+
return false;
|
|
11719
|
+
if (error.status === 0 || error.status >= 500)
|
|
11720
|
+
return true;
|
|
11721
|
+
return RETRYABLE_CLIENT_STATUSES.has(error.status);
|
|
11722
|
+
}
|
|
11723
|
+
function nonNegativeInteger(raw, flag) {
|
|
11724
|
+
const value = Number(raw);
|
|
11725
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
11726
|
+
throw new SimApiError(`${flag} must be a non-negative integer`, 0);
|
|
11727
|
+
}
|
|
11728
|
+
return value;
|
|
11729
|
+
}
|
|
11730
|
+
function intervalMs(raw) {
|
|
11731
|
+
const seconds = Number(raw);
|
|
11732
|
+
if (!Number.isFinite(seconds) || seconds < MIN_INTERVAL_SECONDS) {
|
|
11733
|
+
throw new SimApiError(`--interval must be at least ${MIN_INTERVAL_SECONDS} seconds`, 0);
|
|
11734
|
+
}
|
|
11735
|
+
return Math.round(seconds * 1000);
|
|
11736
|
+
}
|
|
11737
|
+
function inSeconds(ms) {
|
|
11738
|
+
return Math.round(ms / 100) / 10;
|
|
11739
|
+
}
|
|
11740
|
+
function attachLogsFollow(logs) {
|
|
11741
|
+
logs.command("follow").description("Watch runs as they arrive, printing each new run once").option("--workflow <id>", "Only follow runs of this workflow (repeatable)", collect, []).option("--folder <path>", "Only follow runs of workflows in this folder (repeatable)", collect, []).option("--trigger <type>", "Only follow runs with this trigger type (repeatable)", collect, []).addOption(new Option("--level <level>", "Only follow runs at this severity").choices([
|
|
11742
|
+
...V2_OPERATIONS.listLogs.query.level.values
|
|
11743
|
+
])).addOption(new Option("--details <level>", "Response detail level; full names each run’s workflow").choices([...V2_OPERATIONS.listLogs.query.details.values]).default("full")).option("-n, --lines <count>", "Recent runs to print before watching", String(DEFAULT_BACKLOG)).option("--interval <seconds>", "Seconds between polls", String(DEFAULT_INTERVAL_SECONDS)).addHelpText("after", `
|
|
11744
|
+
Each run prints once, when it is first seen, so its status is the status it had
|
|
11745
|
+
at that moment. With --output json every run is a JSON object on its own line
|
|
11746
|
+
(JSONL) rather than a member of an array, because a follow never ends and so can
|
|
11747
|
+
never close one; --output yaml emits a --- separated document stream. Progress
|
|
11748
|
+
and retries go to stderr, leaving stdout a clean stream of rows. Ctrl-C stops the
|
|
11749
|
+
follow.
|
|
11750
|
+
|
|
11751
|
+
Examples:
|
|
11752
|
+
$ sim logs follow --level error
|
|
11753
|
+
$ sim logs follow --workflow wf_123 -n 0
|
|
11754
|
+
$ sim --output json logs follow | jq -r '.runId'
|
|
11755
|
+
`).action(async (options, command) => {
|
|
11756
|
+
const lines = nonNegativeInteger(options.lines, "--lines");
|
|
11757
|
+
const delay = intervalMs(options.interval);
|
|
11758
|
+
const { client, profile } = clientFrom(command);
|
|
11759
|
+
const path = V2_OPERATIONS.listLogs.path;
|
|
11760
|
+
const query = {
|
|
11761
|
+
workspaceId: client.requireWorkspace(),
|
|
11762
|
+
workflowIds: options.workflow?.length ? options.workflow.join(",") : undefined,
|
|
11763
|
+
folderPaths: options.folder?.length ? options.folder.map(encodeFolderPath).join(",") : undefined,
|
|
11764
|
+
triggers: options.trigger?.length ? options.trigger.join(",") : undefined,
|
|
11765
|
+
level: options.level,
|
|
11766
|
+
details: options.details,
|
|
11767
|
+
order: "desc"
|
|
11768
|
+
};
|
|
11769
|
+
const write = createWriter(profile.output);
|
|
11770
|
+
const status = followStatus();
|
|
11771
|
+
const interrupt = watchForInterrupt();
|
|
11772
|
+
const state = { seen: new Map, floor: null };
|
|
11773
|
+
try {
|
|
11774
|
+
const seed = await collectUnprinted(client, path, query, state, Math.max(lines, 1), 1);
|
|
11775
|
+
remember(state, seed.rows);
|
|
11776
|
+
state.floor = seed.rows.at(-1)?.startedAt ?? null;
|
|
11777
|
+
if (seed.truncated && seed.rows.length < lines) {
|
|
11778
|
+
status.warn(`asked for ${lines} earlier runs but a page holds ${seed.rows.length}; following from there — see sim logs list for more`);
|
|
11779
|
+
}
|
|
11780
|
+
write(lines > 0 ? seed.rows.slice(0, lines).reverse() : []);
|
|
11781
|
+
let failures = 0;
|
|
11782
|
+
while (!interrupt.interrupted()) {
|
|
11783
|
+
await waitFor(failures === 0 ? delay : Math.min(delay * 2 ** failures, MAX_BACKOFF_MS), interrupt.interrupted);
|
|
11784
|
+
if (interrupt.interrupted())
|
|
11785
|
+
break;
|
|
11786
|
+
let fresh;
|
|
11787
|
+
try {
|
|
11788
|
+
fresh = await collectUnprinted(client, path, query, state, POLL_PAGE_SIZE, MAX_PAGES_PER_POLL);
|
|
11789
|
+
} catch (error) {
|
|
11790
|
+
if (!isTransient(error))
|
|
11791
|
+
throw error;
|
|
11792
|
+
failures += 1;
|
|
11793
|
+
const next = Math.min(delay * 2 ** failures, MAX_BACKOFF_MS);
|
|
11794
|
+
status.note(`poll failed (${error.message}); retrying in ${inSeconds(next)}s…`);
|
|
11795
|
+
continue;
|
|
11796
|
+
}
|
|
11797
|
+
failures = 0;
|
|
11798
|
+
status.clear();
|
|
11799
|
+
if (fresh.truncated) {
|
|
11800
|
+
status.warn(`more than ${MAX_PAGES_PER_POLL * POLL_PAGE_SIZE} runs arrived at once; older ones were skipped — see sim logs list`);
|
|
11801
|
+
}
|
|
11802
|
+
if (fresh.rows.length === 0)
|
|
11803
|
+
continue;
|
|
11804
|
+
remember(state, fresh.rows);
|
|
11805
|
+
write(fresh.rows.reverse());
|
|
11806
|
+
}
|
|
11807
|
+
} finally {
|
|
11808
|
+
status.clear();
|
|
11809
|
+
interrupt.dispose();
|
|
11810
|
+
}
|
|
11811
|
+
});
|
|
11812
|
+
}
|
|
11813
|
+
|
|
11432
11814
|
// src/runtime/options.ts
|
|
11433
11815
|
var DEFAULT_LIMIT = 100;
|
|
11434
11816
|
function describeField(flag, descriptor, name, field) {
|
|
@@ -11518,7 +11900,7 @@ function addOperationOptions(command, operation, commandSpec, operationSpec) {
|
|
|
11518
11900
|
}
|
|
11519
11901
|
|
|
11520
11902
|
// src/commands/protocol/resource-directory.ts
|
|
11521
|
-
var
|
|
11903
|
+
var COLUMNS2 = [
|
|
11522
11904
|
{ header: "kind", value: (entry) => text(entry.kind) },
|
|
11523
11905
|
{ header: "name", value: (entry) => text(entry.name) },
|
|
11524
11906
|
{
|
|
@@ -11584,7 +11966,7 @@ function attachResourceDirectoryCommands(group, config) {
|
|
|
11584
11966
|
listResources(client, config, workspaceId, folderPath, options.search, limit)
|
|
11585
11967
|
]);
|
|
11586
11968
|
const entries = entriesFor(config, folders, resources);
|
|
11587
|
-
printList(profile.output, entries.slice(0, limit),
|
|
11969
|
+
printList(profile.output, entries.slice(0, limit), COLUMNS2);
|
|
11588
11970
|
});
|
|
11589
11971
|
group.command("mkdir").argument("<path>", "Folder path to create; the leading / is optional").allowExcessArguments(false).description(`Create a ${config.kind} directory at a path`).action(async (path, _options, command) => {
|
|
11590
11972
|
const { client, profile } = clientFrom(command);
|
|
@@ -11613,17 +11995,17 @@ function jsonFlag(raw, flagName, kind) {
|
|
|
11613
11995
|
}
|
|
11614
11996
|
async function watchImport(client, workspaceId, job) {
|
|
11615
11997
|
let current = job;
|
|
11616
|
-
let
|
|
11998
|
+
let reported2 = -1;
|
|
11617
11999
|
while (!IMPORT_SETTLED.has(current.status)) {
|
|
11618
12000
|
await sleep2(IMPORT_POLL_MS);
|
|
11619
12001
|
const next = await client.request(`/api/v2/tables/imports/${encodeURIComponent(current.id)}`, { query: { workspaceId } });
|
|
11620
12002
|
current = next.data;
|
|
11621
|
-
if (process.stderr.isTTY && current.rowsProcessed !==
|
|
11622
|
-
|
|
11623
|
-
process.stderr.write(`\r${source_default.dim(`${current.status}… ${
|
|
12003
|
+
if (process.stderr.isTTY && current.rowsProcessed !== reported2) {
|
|
12004
|
+
reported2 = current.rowsProcessed;
|
|
12005
|
+
process.stderr.write(`\r${source_default.dim(`${current.status}… ${reported2} rows`)}\x1B[K`);
|
|
11624
12006
|
}
|
|
11625
12007
|
}
|
|
11626
|
-
if (process.stderr.isTTY &&
|
|
12008
|
+
if (process.stderr.isTTY && reported2 >= 0)
|
|
11627
12009
|
process.stderr.write("\r\x1B[K");
|
|
11628
12010
|
return current;
|
|
11629
12011
|
}
|
|
@@ -11718,6 +12100,427 @@ function attachTableImport(tables) {
|
|
|
11718
12100
|
});
|
|
11719
12101
|
}
|
|
11720
12102
|
|
|
12103
|
+
// src/runtime/renamed.ts
|
|
12104
|
+
var warned = new Set;
|
|
12105
|
+
function warn(kind, from, to) {
|
|
12106
|
+
const key = `${kind}:${from}`;
|
|
12107
|
+
if (warned.has(key))
|
|
12108
|
+
return;
|
|
12109
|
+
warned.add(key);
|
|
12110
|
+
process.stderr.write(`warning: ${kind} "${from}" has been renamed to "${to}". The old name still works.
|
|
12111
|
+
`);
|
|
12112
|
+
}
|
|
12113
|
+
function warnRenamedCommand(from, to) {
|
|
12114
|
+
warn("command", `sim ${from}`, `sim ${to}`);
|
|
12115
|
+
}
|
|
12116
|
+
function warnRenamedFlag(from, to) {
|
|
12117
|
+
warn("flag", `--${from}`, `--${to}`);
|
|
12118
|
+
}
|
|
12119
|
+
|
|
12120
|
+
// src/runtime/execute.ts
|
|
12121
|
+
function cursorSlot(operationSpec) {
|
|
12122
|
+
if (operationSpec.query && "cursor" in operationSpec.query)
|
|
12123
|
+
return "query";
|
|
12124
|
+
if (operationSpec.body && "cursor" in operationSpec.body)
|
|
12125
|
+
return "body";
|
|
12126
|
+
return null;
|
|
12127
|
+
}
|
|
12128
|
+
function foldRenamedFlags(operation, commandSpec, flags) {
|
|
12129
|
+
for (const [field, flag] of Object.entries(commandSpec.flags ?? {})) {
|
|
12130
|
+
if (!flag.renamedFrom?.length)
|
|
12131
|
+
continue;
|
|
12132
|
+
const current = flagNameFor(operation, field);
|
|
12133
|
+
for (const previous of flag.renamedFrom) {
|
|
12134
|
+
const supplied = flags[camel(previous)];
|
|
12135
|
+
if (supplied === undefined)
|
|
12136
|
+
continue;
|
|
12137
|
+
if (flags[camel(current)] !== undefined) {
|
|
12138
|
+
throw new SimApiError(`--${previous} is the former name of --${current}; pass one, not both`, 0);
|
|
12139
|
+
}
|
|
12140
|
+
warnRenamedFlag(previous, current);
|
|
12141
|
+
flags[camel(current)] = supplied;
|
|
12142
|
+
}
|
|
12143
|
+
}
|
|
12144
|
+
}
|
|
12145
|
+
async function executeOperation(operation, commandSpec, operationSpec, invocation) {
|
|
12146
|
+
const host = invocation[invocation.length - 1];
|
|
12147
|
+
const inheritedFlags = host.optsWithGlobals();
|
|
12148
|
+
const flags = {
|
|
12149
|
+
...inheritedFlags.workspace === undefined ? {} : { workspace: inheritedFlags.workspace },
|
|
12150
|
+
...inheritedFlags.allWorkspaces === undefined ? {} : { allWorkspaces: inheritedFlags.allWorkspaces },
|
|
12151
|
+
...invocation[invocation.length - 2]
|
|
12152
|
+
};
|
|
12153
|
+
const pathPositionalCount = operationSpec.pathParams.filter((param) => !commandSpec.pathFlags?.[param] && !isProfileWorkspacePath(commandSpec, param)).length;
|
|
12154
|
+
const positional = invocation.slice(0, pathPositionalCount);
|
|
12155
|
+
const requestFlags = { ...flags };
|
|
12156
|
+
for (const [index, field] of (commandSpec.positionals ?? []).entries()) {
|
|
12157
|
+
requestFlags[camel(flagNameFor(operation, field))] = invocation[pathPositionalCount + index];
|
|
12158
|
+
}
|
|
12159
|
+
foldRenamedFlags(operation, commandSpec, requestFlags);
|
|
12160
|
+
if (commandSpec.confirm && !requestFlags.yes) {
|
|
12161
|
+
throw new SimApiError(`${commandSpec.confirm} Re-run with --yes to confirm.`, 0);
|
|
12162
|
+
}
|
|
12163
|
+
if (commandSpec.allWorkspaces && requestFlags.allWorkspaces && requestFlags.workspace) {
|
|
12164
|
+
throw new SimApiError("--all-workspaces cannot be combined with --workspace", 0);
|
|
12165
|
+
}
|
|
12166
|
+
const { client, profile } = clientFrom(host);
|
|
12167
|
+
const hasWorkspaceField = Boolean(operationSpec.query && PROFILE_INJECTED_FIELD in operationSpec.query || operationSpec.body && PROFILE_INJECTED_FIELD in operationSpec.body);
|
|
12168
|
+
const omitsWorkspace = commandSpec.allWorkspaces && requestFlags.allWorkspaces === true;
|
|
12169
|
+
const request = buildRequest(operation, positional, requestFlags, hasWorkspaceField && !omitsWorkspace ? client.requireWorkspace() : profile.workspaceId);
|
|
12170
|
+
const paging = cursorSlot(operationSpec);
|
|
12171
|
+
if (paging) {
|
|
12172
|
+
const rawLimit = Number.parseInt(String(requestFlags.limit ?? DEFAULT_LIMIT), 10);
|
|
12173
|
+
if (Number.isNaN(rawLimit) || rawLimit < 0) {
|
|
12174
|
+
throw new SimApiError("--limit must be a non-negative number", 0);
|
|
12175
|
+
}
|
|
12176
|
+
const limit = rawLimit === 0 ? Number.POSITIVE_INFINITY : rawLimit;
|
|
12177
|
+
const pageSize = Math.min(Number.isFinite(limit) ? limit : DEFAULT_LIMIT, DEFAULT_LIMIT);
|
|
12178
|
+
const pageLimit = "limit" in (operationSpec[paging] ?? {}) ? { limit: pageSize } : {};
|
|
12179
|
+
const rows = [];
|
|
12180
|
+
const progress = pageProgress();
|
|
12181
|
+
let cursor = null;
|
|
12182
|
+
try {
|
|
12183
|
+
do {
|
|
12184
|
+
const page = await client.request(request.path, {
|
|
12185
|
+
method: operationSpec.method,
|
|
12186
|
+
query: paging === "query" ? { ...request.query, ...pageLimit, cursor } : request.query,
|
|
12187
|
+
body: paging === "body" ? { ...request.body ?? {}, ...pageLimit, ...cursor ? { cursor } : {} } : request.body
|
|
12188
|
+
});
|
|
12189
|
+
rows.push(...page.data);
|
|
12190
|
+
cursor = page.nextCursor;
|
|
12191
|
+
if (cursor && rows.length < limit)
|
|
12192
|
+
progress.advance(rows.length);
|
|
12193
|
+
} while (cursor && rows.length < limit);
|
|
12194
|
+
} finally {
|
|
12195
|
+
progress.finish();
|
|
12196
|
+
}
|
|
12197
|
+
renderPage(profile.output, Number.isFinite(limit) ? rows.slice(0, limit) : rows, commandSpec);
|
|
12198
|
+
return;
|
|
12199
|
+
}
|
|
12200
|
+
const result = await client.request(request.path, {
|
|
12201
|
+
method: operationSpec.method,
|
|
12202
|
+
query: request.query,
|
|
12203
|
+
body: request.body
|
|
12204
|
+
});
|
|
12205
|
+
renderResult(operation, profile.output, result?.data ?? result, commandSpec, {
|
|
12206
|
+
expandedTrace: requestFlags.trace === true
|
|
12207
|
+
});
|
|
12208
|
+
}
|
|
12209
|
+
|
|
12210
|
+
// src/commands/protocol/workflow-run-follow.ts
|
|
12211
|
+
var AGENT_STREAM_PROTOCOL_HEADER = "x-sim-stream-protocol";
|
|
12212
|
+
var AGENT_STREAM_PROTOCOL_V1 = "agent-events-v1";
|
|
12213
|
+
var DONE_SENTINEL = "[DONE]";
|
|
12214
|
+
function isRecord(value) {
|
|
12215
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12216
|
+
}
|
|
12217
|
+
function stringField(frame, key) {
|
|
12218
|
+
const value = frame[key];
|
|
12219
|
+
return typeof value === "string" ? value : null;
|
|
12220
|
+
}
|
|
12221
|
+
async function* sseData(body) {
|
|
12222
|
+
const reader = body.getReader();
|
|
12223
|
+
const decoder = new TextDecoder;
|
|
12224
|
+
let buffer = "";
|
|
12225
|
+
try {
|
|
12226
|
+
while (true) {
|
|
12227
|
+
const { done, value } = await reader.read();
|
|
12228
|
+
buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
|
|
12229
|
+
const lines = buffer.split(`
|
|
12230
|
+
`);
|
|
12231
|
+
buffer = done ? "" : lines.pop() ?? "";
|
|
12232
|
+
for (const rawLine of lines) {
|
|
12233
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
12234
|
+
if (!line.startsWith("data:"))
|
|
12235
|
+
continue;
|
|
12236
|
+
const payload = line.slice(5).startsWith(" ") ? line.slice(6) : line.slice(5);
|
|
12237
|
+
if (payload.length > 0)
|
|
12238
|
+
yield payload;
|
|
12239
|
+
}
|
|
12240
|
+
if (done)
|
|
12241
|
+
return;
|
|
12242
|
+
}
|
|
12243
|
+
} finally {
|
|
12244
|
+
reader.releaseLock();
|
|
12245
|
+
}
|
|
12246
|
+
}
|
|
12247
|
+
|
|
12248
|
+
class Commentary {
|
|
12249
|
+
sink;
|
|
12250
|
+
atLineStart = true;
|
|
12251
|
+
constructor(sink) {
|
|
12252
|
+
this.sink = sink;
|
|
12253
|
+
}
|
|
12254
|
+
inline(text2) {
|
|
12255
|
+
if (text2.length === 0)
|
|
12256
|
+
return;
|
|
12257
|
+
this.sink.write(text2);
|
|
12258
|
+
this.atLineStart = text2.endsWith(`
|
|
12259
|
+
`);
|
|
12260
|
+
}
|
|
12261
|
+
line(text2) {
|
|
12262
|
+
this.sink.write(`${this.atLineStart ? "" : `
|
|
12263
|
+
`}${text2}
|
|
12264
|
+
`);
|
|
12265
|
+
this.atLineStart = true;
|
|
12266
|
+
}
|
|
12267
|
+
endLine() {
|
|
12268
|
+
if (this.atLineStart)
|
|
12269
|
+
return;
|
|
12270
|
+
this.sink.write(`
|
|
12271
|
+
`);
|
|
12272
|
+
this.atLineStart = true;
|
|
12273
|
+
}
|
|
12274
|
+
}
|
|
12275
|
+
function toolNotice(frame) {
|
|
12276
|
+
const name = safeOneLine(stringField(frame, "name") ?? "tool");
|
|
12277
|
+
if (frame.phase === "start")
|
|
12278
|
+
return source_default.dim(`→ ${name}`);
|
|
12279
|
+
const status = stringField(frame, "status");
|
|
12280
|
+
if (status && status !== "success")
|
|
12281
|
+
return source_default.yellow(`✗ ${name} (${safeOneLine(status)})`);
|
|
12282
|
+
return source_default.dim(`✓ ${name}`);
|
|
12283
|
+
}
|
|
12284
|
+
async function renderRunStream(body, options) {
|
|
12285
|
+
const commentary = new Commentary(options.stderr);
|
|
12286
|
+
let final = null;
|
|
12287
|
+
for await (const payload of sseData(body)) {
|
|
12288
|
+
let frame;
|
|
12289
|
+
try {
|
|
12290
|
+
frame = JSON.parse(payload);
|
|
12291
|
+
} catch {
|
|
12292
|
+
continue;
|
|
12293
|
+
}
|
|
12294
|
+
if (frame === DONE_SENTINEL)
|
|
12295
|
+
break;
|
|
12296
|
+
if (!isRecord(frame))
|
|
12297
|
+
continue;
|
|
12298
|
+
if (frame.event === undefined && typeof frame.chunk === "string") {
|
|
12299
|
+
commentary.inline(sanitize(frame.chunk));
|
|
12300
|
+
continue;
|
|
12301
|
+
}
|
|
12302
|
+
switch (frame.event) {
|
|
12303
|
+
case "chunk_reset":
|
|
12304
|
+
commentary.line(source_default.dim("… retracted; that turn resolved to tool calls"));
|
|
12305
|
+
break;
|
|
12306
|
+
case "thinking":
|
|
12307
|
+
if (options.includeThinking && typeof frame.data === "string") {
|
|
12308
|
+
commentary.inline(source_default.dim(sanitize(frame.data)));
|
|
12309
|
+
}
|
|
12310
|
+
break;
|
|
12311
|
+
case "tool":
|
|
12312
|
+
if (options.includeToolCalls)
|
|
12313
|
+
commentary.line(toolNotice(frame));
|
|
12314
|
+
break;
|
|
12315
|
+
case "stream_error":
|
|
12316
|
+
commentary.line(source_default.yellow(`warning: ${safeOneLine(stringField(frame, "error") ?? "stream read failed")}`));
|
|
12317
|
+
break;
|
|
12318
|
+
case "error":
|
|
12319
|
+
commentary.endLine();
|
|
12320
|
+
throw new SimApiError(safeOneLine(stringField(frame, "error") ?? "The workflow run failed."), 0);
|
|
12321
|
+
case "final":
|
|
12322
|
+
if (isRecord(frame.data))
|
|
12323
|
+
final = frame.data;
|
|
12324
|
+
break;
|
|
12325
|
+
default:
|
|
12326
|
+
break;
|
|
12327
|
+
}
|
|
12328
|
+
}
|
|
12329
|
+
commentary.endLine();
|
|
12330
|
+
if (!final) {
|
|
12331
|
+
throw new SimApiError("The run stream ended before the workflow reported a result. The run may still be in progress — check: sim workflows runs list", 0);
|
|
12332
|
+
}
|
|
12333
|
+
return final;
|
|
12334
|
+
}
|
|
12335
|
+
async function followRun(workflowId, command) {
|
|
12336
|
+
const flags = command.optsWithGlobals();
|
|
12337
|
+
if (flags.async === true) {
|
|
12338
|
+
throw new SimApiError("--follow streams a run as it happens and --async returns before it starts; pass one, not both", 0);
|
|
12339
|
+
}
|
|
12340
|
+
const includeThinking = flags.includeThinking === true;
|
|
12341
|
+
const includeToolCalls = flags.includeToolCalls === true;
|
|
12342
|
+
const negotiates = includeThinking || includeToolCalls;
|
|
12343
|
+
const { client, profile } = clientFrom(command);
|
|
12344
|
+
const operation = V2_OPERATIONS.executeWorkflow;
|
|
12345
|
+
const request = buildRequest("executeWorkflow", [workflowId], flags, profile.workspaceId);
|
|
12346
|
+
const response = await client.requestRaw(request.path, {
|
|
12347
|
+
method: "POST",
|
|
12348
|
+
query: request.query,
|
|
12349
|
+
body: {
|
|
12350
|
+
...request.body ?? {},
|
|
12351
|
+
stream: true,
|
|
12352
|
+
...includeThinking ? { includeThinking: true } : {},
|
|
12353
|
+
...includeToolCalls ? { includeToolCalls: true } : {}
|
|
12354
|
+
},
|
|
12355
|
+
headers: {
|
|
12356
|
+
accept: "text/event-stream",
|
|
12357
|
+
...negotiates ? { [AGENT_STREAM_PROTOCOL_HEADER]: AGENT_STREAM_PROTOCOL_V1 } : {}
|
|
12358
|
+
}
|
|
12359
|
+
});
|
|
12360
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
12361
|
+
if (!contentType.toLowerCase().includes("text/event-stream")) {
|
|
12362
|
+
await response.body?.cancel();
|
|
12363
|
+
throw new SimApiError(`${operation.path} answered ${contentType || "an unknown content type"} instead of an event stream. This deployment may predate streaming runs — re-run without --follow.`, response.status);
|
|
12364
|
+
}
|
|
12365
|
+
if (!response.body) {
|
|
12366
|
+
throw new SimApiError("The run stream had no body.", response.status);
|
|
12367
|
+
}
|
|
12368
|
+
const final = await renderRunStream(response.body, {
|
|
12369
|
+
includeThinking,
|
|
12370
|
+
includeToolCalls,
|
|
12371
|
+
stderr: process.stderr
|
|
12372
|
+
});
|
|
12373
|
+
renderResult("executeWorkflow", profile.output, final, CLI_CONTRACT.executeWorkflow ?? {});
|
|
12374
|
+
if (final.success === false) {
|
|
12375
|
+
throw new SimApiError(safeOneLine(typeof final.error === "string" ? final.error : "The workflow run failed."), 0);
|
|
12376
|
+
}
|
|
12377
|
+
}
|
|
12378
|
+
function followOrDelegate(previous) {
|
|
12379
|
+
return async (workflowId, _options, command) => {
|
|
12380
|
+
const flags = command.optsWithGlobals();
|
|
12381
|
+
if (flags.follow !== true) {
|
|
12382
|
+
if (flags.includeThinking === true || flags.includeToolCalls === true) {
|
|
12383
|
+
throw new SimApiError("--include-thinking and --include-tool-calls describe a stream; add --follow", 0);
|
|
12384
|
+
}
|
|
12385
|
+
if (previous) {
|
|
12386
|
+
await previous(command.processedArgs);
|
|
12387
|
+
return;
|
|
12388
|
+
}
|
|
12389
|
+
await executeOperation("executeWorkflow", CLI_CONTRACT.executeWorkflow ?? {}, V2_OPERATIONS.executeWorkflow, [workflowId, command.opts(), command]);
|
|
12390
|
+
return;
|
|
12391
|
+
}
|
|
12392
|
+
await followRun(workflowId, command);
|
|
12393
|
+
};
|
|
12394
|
+
}
|
|
12395
|
+
function attachWorkflowRunFollow(workflows) {
|
|
12396
|
+
const run = workflows.commands.find((command) => command.name() === "run");
|
|
12397
|
+
if (!run) {
|
|
12398
|
+
throw new Error("workflows run must be registered before --follow can be attached to it");
|
|
12399
|
+
}
|
|
12400
|
+
const held = run._actionHandler;
|
|
12401
|
+
const previous = typeof held === "function" ? held : null;
|
|
12402
|
+
run.option("--follow", "Stream the run as it happens; progress on stderr, result on stdout. The stream reports only success and output, so the result omits the run id and timings a non-streaming run returns").option("--include-thinking", "Show model reasoning while following (requires --follow)").option("--include-tool-calls", "Show tool calls while following (requires --follow)").action(followOrDelegate(previous));
|
|
12403
|
+
}
|
|
12404
|
+
|
|
12405
|
+
// src/commands/protocol/workflow-run-wait.ts
|
|
12406
|
+
var TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled"]);
|
|
12407
|
+
var WAIT_EXIT_CODES = {
|
|
12408
|
+
completed: 0,
|
|
12409
|
+
failed: 1,
|
|
12410
|
+
cancelled: 2,
|
|
12411
|
+
paused: 3,
|
|
12412
|
+
timeout: 4
|
|
12413
|
+
};
|
|
12414
|
+
var FIRST_POLL_DELAY_MS = 2000;
|
|
12415
|
+
var MAX_POLL_DELAY_MS = 15000;
|
|
12416
|
+
var POLL_BACKOFF_FACTOR = 2;
|
|
12417
|
+
var DEFAULT_WAIT_TIMEOUT_SECONDS = 3600;
|
|
12418
|
+
var WAIT_TIMEOUT_FLAG = "--wait-timeout <seconds>";
|
|
12419
|
+
function isRecord2(value) {
|
|
12420
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12421
|
+
}
|
|
12422
|
+
function optionalString(value) {
|
|
12423
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
12424
|
+
}
|
|
12425
|
+
function readRun(raw) {
|
|
12426
|
+
const run = isRecord2(raw) && isRecord2(raw.data) ? raw.data : raw;
|
|
12427
|
+
if (!isRecord2(run) || typeof run.status !== "string") {
|
|
12428
|
+
throw new SimApiError("Run status response carried no status.", 0);
|
|
12429
|
+
}
|
|
12430
|
+
const paused = isRecord2(run.paused) ? run.paused : null;
|
|
12431
|
+
return {
|
|
12432
|
+
status: run.status,
|
|
12433
|
+
pauseKind: paused ? optionalString(paused.pauseKind) : null,
|
|
12434
|
+
resumeAt: paused ? optionalString(paused.resumeAt) : null,
|
|
12435
|
+
contextId: paused ? optionalString(paused.contextId) : null
|
|
12436
|
+
};
|
|
12437
|
+
}
|
|
12438
|
+
function classify(snapshot) {
|
|
12439
|
+
if (snapshot.status === "paused")
|
|
12440
|
+
return snapshot.pauseKind === "time" ? null : "paused";
|
|
12441
|
+
if (!TERMINAL_STATUSES.has(snapshot.status))
|
|
12442
|
+
return null;
|
|
12443
|
+
return snapshot.status === "completed" ? "completed" : snapshot.status === "cancelled" ? "cancelled" : "failed";
|
|
12444
|
+
}
|
|
12445
|
+
function waitProgress() {
|
|
12446
|
+
let reported2 = false;
|
|
12447
|
+
return {
|
|
12448
|
+
advance: (status, elapsedMs) => {
|
|
12449
|
+
if (!process.stderr.isTTY)
|
|
12450
|
+
return;
|
|
12451
|
+
reported2 = true;
|
|
12452
|
+
process.stderr.write(`\r${source_default.dim(`${status} — waiting ${Math.round(elapsedMs / 1000)}s…`)}\x1B[K`);
|
|
12453
|
+
},
|
|
12454
|
+
finish: () => {
|
|
12455
|
+
if (!reported2)
|
|
12456
|
+
return;
|
|
12457
|
+
reported2 = false;
|
|
12458
|
+
process.stderr.write("\r\x1B[K");
|
|
12459
|
+
}
|
|
12460
|
+
};
|
|
12461
|
+
}
|
|
12462
|
+
function parseWaitTimeout(raw) {
|
|
12463
|
+
const seconds = Number(raw);
|
|
12464
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
12465
|
+
throw new SimApiError(`Invalid ${WAIT_TIMEOUT_FLAG} "${raw}". Use a non-negative number of seconds, or 0 to wait indefinitely.`, 0);
|
|
12466
|
+
}
|
|
12467
|
+
return seconds;
|
|
12468
|
+
}
|
|
12469
|
+
function explain(outcome, runId, workflowId, snapshot) {
|
|
12470
|
+
if (outcome === "completed")
|
|
12471
|
+
return null;
|
|
12472
|
+
if (outcome === "failed")
|
|
12473
|
+
return `Run ${runId} failed.`;
|
|
12474
|
+
if (outcome === "cancelled")
|
|
12475
|
+
return `Run ${runId} was cancelled.`;
|
|
12476
|
+
const context = snapshot.contextId ? ` --context ${snapshot.contextId}` : "";
|
|
12477
|
+
return `Run ${runId} is paused waiting for input. Resume it: sim workflows runs resume ${runId} --workflow ${workflowId}${context}`;
|
|
12478
|
+
}
|
|
12479
|
+
function runSpec() {
|
|
12480
|
+
return CLI_CONTRACT.getWorkflowRun ?? {};
|
|
12481
|
+
}
|
|
12482
|
+
function attachWorkflowRunWait(runs) {
|
|
12483
|
+
runs.command("wait").argument("<runId>", V2_OPERATIONS.getWorkflowRun.pathParamDocs?.runId).description("Wait for a run to reach a terminal state, then show it").addOption(new Option("--workflow <workflowId>", "Workflow ID (required)").makeOptionMandatory()).addOption(new Option(WAIT_TIMEOUT_FLAG, `Give up after this many seconds, or 0 to wait indefinitely (default: ${DEFAULT_WAIT_TIMEOUT_SECONDS}). Bounds the whole wait; SIM_TIMEOUT_SECONDS bounds one request`)).action(async (runId, options, command) => {
|
|
12484
|
+
const timeoutSeconds = options.waitTimeout === undefined ? DEFAULT_WAIT_TIMEOUT_SECONDS : parseWaitTimeout(options.waitTimeout);
|
|
12485
|
+
const { client, profile } = clientFrom(command);
|
|
12486
|
+
const operation = V2_OPERATIONS.getWorkflowRun;
|
|
12487
|
+
const path = resolvePath(operation.path, { id: options.workflow, runId });
|
|
12488
|
+
const startedAt = Date.now();
|
|
12489
|
+
const deadline = timeoutSeconds === 0 ? Number.POSITIVE_INFINITY : startedAt + timeoutSeconds * 1000;
|
|
12490
|
+
const progress = waitProgress();
|
|
12491
|
+
let delayMs = FIRST_POLL_DELAY_MS;
|
|
12492
|
+
try {
|
|
12493
|
+
while (true) {
|
|
12494
|
+
const raw = await client.request(path, { method: operation.method });
|
|
12495
|
+
const snapshot = readRun(raw);
|
|
12496
|
+
const outcome = classify(snapshot);
|
|
12497
|
+
if (outcome) {
|
|
12498
|
+
progress.finish();
|
|
12499
|
+
renderResult("getWorkflowRun", profile.output, raw, runSpec());
|
|
12500
|
+
const message = explain(outcome, runId, options.workflow, snapshot);
|
|
12501
|
+
if (message)
|
|
12502
|
+
console.error(source_default.red(message));
|
|
12503
|
+
process.exitCode = WAIT_EXIT_CODES[outcome];
|
|
12504
|
+
return;
|
|
12505
|
+
}
|
|
12506
|
+
const remainingMs = deadline - Date.now();
|
|
12507
|
+
if (remainingMs <= 0) {
|
|
12508
|
+
progress.finish();
|
|
12509
|
+
renderResult("getWorkflowRun", profile.output, raw, runSpec());
|
|
12510
|
+
console.error(source_default.red(`Timed out after ${timeoutSeconds}s waiting for run ${runId} (status: ${snapshot.status}${snapshot.resumeAt ? `, resuming at ${snapshot.resumeAt}` : ""}). Raise ${WAIT_TIMEOUT_FLAG}, or set it to 0 to wait indefinitely.`));
|
|
12511
|
+
process.exitCode = WAIT_EXIT_CODES.timeout;
|
|
12512
|
+
return;
|
|
12513
|
+
}
|
|
12514
|
+
progress.advance(snapshot.status, Date.now() - startedAt);
|
|
12515
|
+
await sleep(Math.min(delayMs, remainingMs));
|
|
12516
|
+
delayMs = Math.min(delayMs * POLL_BACKOFF_FACTOR, MAX_POLL_DELAY_MS);
|
|
12517
|
+
}
|
|
12518
|
+
} finally {
|
|
12519
|
+
progress.finish();
|
|
12520
|
+
}
|
|
12521
|
+
});
|
|
12522
|
+
}
|
|
12523
|
+
|
|
11721
12524
|
// src/commands/protocol/index.ts
|
|
11722
12525
|
function group(program2, name) {
|
|
11723
12526
|
const existing = program2.commands.find((command) => command.name() === name);
|
|
@@ -11753,12 +12556,16 @@ function attachProtocolCommands(program2) {
|
|
|
11753
12556
|
folders: "listTableFolders",
|
|
11754
12557
|
createFolder: "createTableFolder"
|
|
11755
12558
|
});
|
|
11756
|
-
|
|
12559
|
+
const workflows = group(program2, "workflows");
|
|
12560
|
+
attachResourceDirectoryCommands(workflows, {
|
|
11757
12561
|
kind: "workflow",
|
|
11758
12562
|
resources: "listWorkflows",
|
|
11759
12563
|
folders: "listWorkflowFolders",
|
|
11760
12564
|
createFolder: "createWorkflowFolder"
|
|
11761
12565
|
});
|
|
12566
|
+
attachWorkflowRunFollow(workflows);
|
|
12567
|
+
attachWorkflowRunWait(group(workflows, "runs"));
|
|
12568
|
+
attachLogsFollow(group(program2, "logs"));
|
|
11762
12569
|
}
|
|
11763
12570
|
|
|
11764
12571
|
// src/terminal/secret-input.ts
|
|
@@ -11882,113 +12689,6 @@ function attachSecretCommands(program2) {
|
|
|
11882
12689
|
secrets.command("set").argument("<name>", "Secret name, as referenced in workflows").description("Create or replace a named secret").addOption(new Option("--scope <scope>", "Secret ownership scope").choices([...SECRET_SCOPES]).makeOptionMandatory()).option("--value <value>", "Secret value; visible to shell history when supplied directly").option("--description <description>", "What the secret is for, shown to teammates; workspace scope only. Omit to leave an existing description unchanged").action((name, options, command) => setSecret(name, options, command));
|
|
11883
12690
|
}
|
|
11884
12691
|
|
|
11885
|
-
// src/runtime/renamed.ts
|
|
11886
|
-
var warned = new Set;
|
|
11887
|
-
function warn(kind, from, to) {
|
|
11888
|
-
const key = `${kind}:${from}`;
|
|
11889
|
-
if (warned.has(key))
|
|
11890
|
-
return;
|
|
11891
|
-
warned.add(key);
|
|
11892
|
-
process.stderr.write(`warning: ${kind} "${from}" has been renamed to "${to}". The old name still works.
|
|
11893
|
-
`);
|
|
11894
|
-
}
|
|
11895
|
-
function warnRenamedCommand(from, to) {
|
|
11896
|
-
warn("command", `sim ${from}`, `sim ${to}`);
|
|
11897
|
-
}
|
|
11898
|
-
function warnRenamedFlag(from, to) {
|
|
11899
|
-
warn("flag", `--${from}`, `--${to}`);
|
|
11900
|
-
}
|
|
11901
|
-
|
|
11902
|
-
// src/runtime/execute.ts
|
|
11903
|
-
function cursorSlot(operationSpec) {
|
|
11904
|
-
if (operationSpec.query && "cursor" in operationSpec.query)
|
|
11905
|
-
return "query";
|
|
11906
|
-
if (operationSpec.body && "cursor" in operationSpec.body)
|
|
11907
|
-
return "body";
|
|
11908
|
-
return null;
|
|
11909
|
-
}
|
|
11910
|
-
function foldRenamedFlags(operation, commandSpec, flags) {
|
|
11911
|
-
for (const [field, flag] of Object.entries(commandSpec.flags ?? {})) {
|
|
11912
|
-
if (!flag.renamedFrom?.length)
|
|
11913
|
-
continue;
|
|
11914
|
-
const current = flagNameFor(operation, field);
|
|
11915
|
-
for (const previous of flag.renamedFrom) {
|
|
11916
|
-
const supplied = flags[camel(previous)];
|
|
11917
|
-
if (supplied === undefined)
|
|
11918
|
-
continue;
|
|
11919
|
-
if (flags[camel(current)] !== undefined) {
|
|
11920
|
-
throw new SimApiError(`--${previous} is the former name of --${current}; pass one, not both`, 0);
|
|
11921
|
-
}
|
|
11922
|
-
warnRenamedFlag(previous, current);
|
|
11923
|
-
flags[camel(current)] = supplied;
|
|
11924
|
-
}
|
|
11925
|
-
}
|
|
11926
|
-
}
|
|
11927
|
-
async function executeOperation(operation, commandSpec, operationSpec, invocation) {
|
|
11928
|
-
const host = invocation[invocation.length - 1];
|
|
11929
|
-
const inheritedFlags = host.optsWithGlobals();
|
|
11930
|
-
const flags = {
|
|
11931
|
-
...inheritedFlags.workspace === undefined ? {} : { workspace: inheritedFlags.workspace },
|
|
11932
|
-
...inheritedFlags.allWorkspaces === undefined ? {} : { allWorkspaces: inheritedFlags.allWorkspaces },
|
|
11933
|
-
...invocation[invocation.length - 2]
|
|
11934
|
-
};
|
|
11935
|
-
const pathPositionalCount = operationSpec.pathParams.filter((param) => !commandSpec.pathFlags?.[param] && !isProfileWorkspacePath(commandSpec, param)).length;
|
|
11936
|
-
const positional = invocation.slice(0, pathPositionalCount);
|
|
11937
|
-
const requestFlags = { ...flags };
|
|
11938
|
-
for (const [index, field] of (commandSpec.positionals ?? []).entries()) {
|
|
11939
|
-
requestFlags[camel(flagNameFor(operation, field))] = invocation[pathPositionalCount + index];
|
|
11940
|
-
}
|
|
11941
|
-
foldRenamedFlags(operation, commandSpec, requestFlags);
|
|
11942
|
-
if (commandSpec.confirm && !requestFlags.yes) {
|
|
11943
|
-
throw new SimApiError(`${commandSpec.confirm} Re-run with --yes to confirm.`, 0);
|
|
11944
|
-
}
|
|
11945
|
-
if (commandSpec.allWorkspaces && requestFlags.allWorkspaces && requestFlags.workspace) {
|
|
11946
|
-
throw new SimApiError("--all-workspaces cannot be combined with --workspace", 0);
|
|
11947
|
-
}
|
|
11948
|
-
const { client, profile } = clientFrom(host);
|
|
11949
|
-
const hasWorkspaceField = Boolean(operationSpec.query && PROFILE_INJECTED_FIELD in operationSpec.query || operationSpec.body && PROFILE_INJECTED_FIELD in operationSpec.body);
|
|
11950
|
-
const omitsWorkspace = commandSpec.allWorkspaces && requestFlags.allWorkspaces === true;
|
|
11951
|
-
const request = buildRequest(operation, positional, requestFlags, hasWorkspaceField && !omitsWorkspace ? client.requireWorkspace() : profile.workspaceId);
|
|
11952
|
-
const paging = cursorSlot(operationSpec);
|
|
11953
|
-
if (paging) {
|
|
11954
|
-
const rawLimit = Number.parseInt(String(requestFlags.limit ?? DEFAULT_LIMIT), 10);
|
|
11955
|
-
if (Number.isNaN(rawLimit) || rawLimit < 0) {
|
|
11956
|
-
throw new SimApiError("--limit must be a non-negative number", 0);
|
|
11957
|
-
}
|
|
11958
|
-
const limit = rawLimit === 0 ? Number.POSITIVE_INFINITY : rawLimit;
|
|
11959
|
-
const pageSize = Math.min(Number.isFinite(limit) ? limit : DEFAULT_LIMIT, DEFAULT_LIMIT);
|
|
11960
|
-
const pageLimit = "limit" in (operationSpec[paging] ?? {}) ? { limit: pageSize } : {};
|
|
11961
|
-
const rows = [];
|
|
11962
|
-
const progress = pageProgress();
|
|
11963
|
-
let cursor = null;
|
|
11964
|
-
try {
|
|
11965
|
-
do {
|
|
11966
|
-
const page = await client.request(request.path, {
|
|
11967
|
-
method: operationSpec.method,
|
|
11968
|
-
query: paging === "query" ? { ...request.query, ...pageLimit, cursor } : request.query,
|
|
11969
|
-
body: paging === "body" ? { ...request.body ?? {}, ...pageLimit, ...cursor ? { cursor } : {} } : request.body
|
|
11970
|
-
});
|
|
11971
|
-
rows.push(...page.data);
|
|
11972
|
-
cursor = page.nextCursor;
|
|
11973
|
-
if (cursor && rows.length < limit)
|
|
11974
|
-
progress.advance(rows.length);
|
|
11975
|
-
} while (cursor && rows.length < limit);
|
|
11976
|
-
} finally {
|
|
11977
|
-
progress.finish();
|
|
11978
|
-
}
|
|
11979
|
-
renderPage(profile.output, Number.isFinite(limit) ? rows.slice(0, limit) : rows, commandSpec);
|
|
11980
|
-
return;
|
|
11981
|
-
}
|
|
11982
|
-
const result = await client.request(request.path, {
|
|
11983
|
-
method: operationSpec.method,
|
|
11984
|
-
query: request.query,
|
|
11985
|
-
body: request.body
|
|
11986
|
-
});
|
|
11987
|
-
renderResult(operation, profile.output, result?.data ?? result, commandSpec, {
|
|
11988
|
-
expandedTrace: requestFlags.trace === true
|
|
11989
|
-
});
|
|
11990
|
-
}
|
|
11991
|
-
|
|
11992
12692
|
// src/runtime/build.ts
|
|
11993
12693
|
var GROUP_ALIASES = {
|
|
11994
12694
|
"audit-logs": "audit-log",
|
|
@@ -12248,6 +12948,10 @@ async function main() {
|
|
|
12248
12948
|
console.error(source_default.red(`Error: ${sanitize(error.message)}`));
|
|
12249
12949
|
process.exit(1);
|
|
12250
12950
|
}
|
|
12951
|
+
if (isRequestTimeout(error)) {
|
|
12952
|
+
console.error(source_default.red(`Error: the request timed out. ${RAISE_TIMEOUT_HINT}`));
|
|
12953
|
+
process.exit(1);
|
|
12954
|
+
}
|
|
12251
12955
|
if (error instanceof SimApiError) {
|
|
12252
12956
|
console.error(source_default.red(`Error: ${sanitize(error.message)}`));
|
|
12253
12957
|
if (error.code)
|