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.
Files changed (3) hide show
  1. package/README.md +5 -0
  2. package/dist/index.js +826 -122
  3. 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: options.method ?? "GET",
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: options.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 reported = false;
2878
+ let reported2 = false;
2772
2879
  return {
2773
2880
  advance: (fetched) => {
2774
2881
  if (!process.stderr.isTTY)
2775
2882
  return;
2776
- reported = true;
2883
+ reported2 = true;
2777
2884
  process.stderr.write(`\r${source_default.dim(`fetched ${fetched}…`)}\x1B[K`);
2778
2885
  },
2779
2886
  finish: () => {
2780
- if (reported)
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 once(output, "drain");
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 COLUMNS = [
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), COLUMNS);
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 reported = -1;
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 !== reported) {
11622
- reported = current.rowsProcessed;
11623
- process.stderr.write(`\r${source_default.dim(`${current.status}… ${reported} rows`)}\x1B[K`);
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 && reported >= 0)
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
- attachResourceDirectoryCommands(group(program2, "workflows"), {
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sim",
3
- "version": "2.0.0-preview.17.1",
3
+ "version": "2.0.0-preview.21.1",
4
4
  "description": "Sim CLI - talk to the Sim API from your terminal",
5
5
  "type": "module",
6
6
  "bin": {