sim 2.0.0-dev.19.1 → 2.0.0-dev.20.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 +126 -12
  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();
@@ -11613,17 +11723,17 @@ function jsonFlag(raw, flagName, kind) {
11613
11723
  }
11614
11724
  async function watchImport(client, workspaceId, job) {
11615
11725
  let current = job;
11616
- let reported = -1;
11726
+ let reported2 = -1;
11617
11727
  while (!IMPORT_SETTLED.has(current.status)) {
11618
11728
  await sleep2(IMPORT_POLL_MS);
11619
11729
  const next = await client.request(`/api/v2/tables/imports/${encodeURIComponent(current.id)}`, { query: { workspaceId } });
11620
11730
  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`);
11731
+ if (process.stderr.isTTY && current.rowsProcessed !== reported2) {
11732
+ reported2 = current.rowsProcessed;
11733
+ process.stderr.write(`\r${source_default.dim(`${current.status}… ${reported2} rows`)}\x1B[K`);
11624
11734
  }
11625
11735
  }
11626
- if (process.stderr.isTTY && reported >= 0)
11736
+ if (process.stderr.isTTY && reported2 >= 0)
11627
11737
  process.stderr.write("\r\x1B[K");
11628
11738
  return current;
11629
11739
  }
@@ -12248,6 +12358,10 @@ async function main() {
12248
12358
  console.error(source_default.red(`Error: ${sanitize(error.message)}`));
12249
12359
  process.exit(1);
12250
12360
  }
12361
+ if (isRequestTimeout(error)) {
12362
+ console.error(source_default.red(`Error: the request timed out. ${RAISE_TIMEOUT_HINT}`));
12363
+ process.exit(1);
12364
+ }
12251
12365
  if (error instanceof SimApiError) {
12252
12366
  console.error(source_default.red(`Error: ${sanitize(error.message)}`));
12253
12367
  if (error.code)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sim",
3
- "version": "2.0.0-dev.19.1",
3
+ "version": "2.0.0-dev.20.1",
4
4
  "description": "Sim CLI - talk to the Sim API from your terminal",
5
5
  "type": "module",
6
6
  "bin": {