sim 2.1.6-preview.84.1 → 2.1.6-preview.90.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 +18 -1
  2. package/dist/index.js +495 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -256,9 +256,26 @@ The main environment variables are:
256
256
  | `SIM_API_KEY` | API key, usually for CI |
257
257
  | `SIM_WORKSPACE` | Workspace to target |
258
258
  | `SIM_OUTPUT` | `table`, `json`, `yaml`, or `text` |
259
- | `SIM_CONFIG_DIR` | Directory containing CLI config and credentials |
259
+ | `SIM_CONFIG_DIR` | Base directory for CLI config, credentials, and the update cache |
260
260
  | `SIM_TIMEOUT_SECONDS` | Per-request timeout; `0` waits indefinitely |
261
261
  | `SIM_DEBUG` | Print request diagnostics to stderr |
262
+ | `SIM_NO_UPDATE_CHECK` | Turn off the update notice |
263
+
264
+ On eligible interactive invocations, `sim` uses a daily cache before asking
265
+ `registry.npmjs.org` what is published under the `latest` tag and prints one
266
+ line on stderr when a newer version exists. Prerelease installs are skipped
267
+ entirely. The cache lives in `~/.sim` by default and follows `SIM_CONFIG_DIR`;
268
+ without a writable cache, each eligible invocation checks again. Concurrent
269
+ invocations can also perform duplicate checks. The registry request has a
270
+ one-second deadline; the short-lived request process is terminated on expiry.
271
+ Apart from the configured registry URL, it sends only its own version and never
272
+ your Sim API key. If `npm_config_registry` points at a private mirror, its query
273
+ string is preserved, including any query-string credentials. Registry URLs
274
+ containing username/password userinfo are rejected. Set
275
+ `SIM_NO_UPDATE_CHECK=1` to turn it off. Empty or whitespace-only registry values
276
+ use the public default; non-empty malformed or non-HTTP(S) values fail closed.
277
+ The full list of cases where it stays quiet is in the
278
+ [configuration guide](https://docs.sim.ai/cli/configuration).
262
279
 
263
280
  ## Documentation
264
281
 
package/dist/index.js CHANGED
@@ -2310,6 +2310,9 @@ function configPath() {
2310
2310
  function credentialsPath() {
2311
2311
  return process.env.SIM_CREDENTIALS_FILE || join(configDir(), "credentials");
2312
2312
  }
2313
+ function updateCachePath() {
2314
+ return join(configDir(), "update-check.json");
2315
+ }
2313
2316
  // src/config/profile.ts
2314
2317
  import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2315
2318
  import { dirname } from "node:path";
@@ -7465,6 +7468,47 @@ var V2_OPERATIONS = {
7465
7468
  }
7466
7469
  }
7467
7470
  },
7471
+ createSandbox: {
7472
+ method: "POST",
7473
+ path: "/api/v2/sandboxes",
7474
+ pathParams: [],
7475
+ responseMode: "json",
7476
+ summary: "Create Sandbox",
7477
+ personalKeyOnly: true,
7478
+ body: {
7479
+ workspaceId: {
7480
+ kind: "string",
7481
+ required: true,
7482
+ describe: "Workspace in which to create the sandbox."
7483
+ },
7484
+ name: {
7485
+ kind: "string",
7486
+ required: true,
7487
+ describe: "Display name, unique within the workspace; 1 to 64 characters."
7488
+ },
7489
+ language: {
7490
+ kind: "enum",
7491
+ required: true,
7492
+ values: ["javascript", "python"],
7493
+ describe: "Dependency ecosystem: `javascript` installs from npm, `python` from PyPI."
7494
+ },
7495
+ dependencies: {
7496
+ kind: "array",
7497
+ default: [],
7498
+ describe: "Package specifiers installed into the sandbox, one per entry."
7499
+ },
7500
+ cliTools: {
7501
+ kind: "array",
7502
+ default: [],
7503
+ describe: "Pinned managed CLI ids installed into the sandbox, at most 10, no duplicates."
7504
+ },
7505
+ systemPackages: {
7506
+ kind: "array",
7507
+ default: [],
7508
+ describe: "Debian packages installed into the sandbox, one per entry."
7509
+ }
7510
+ }
7511
+ },
7468
7512
  createServiceAccountCredential: {
7469
7513
  method: "POST",
7470
7514
  path: "/api/v2/credentials",
@@ -7982,6 +8026,18 @@ var V2_OPERATIONS = {
7982
8026
  }
7983
8027
  }
7984
8028
  },
8029
+ deleteSandbox: {
8030
+ method: "DELETE",
8031
+ path: "/api/v2/sandboxes/[sandboxId]",
8032
+ pathParams: ["sandboxId"],
8033
+ pathParamDocs: { sandboxId: "Unique sandbox identifier." },
8034
+ responseMode: "json",
8035
+ summary: "Delete Sandbox",
8036
+ personalKeyOnly: true,
8037
+ query: {
8038
+ workspaceId: { kind: "string", required: true, describe: "Workspace that owns the sandbox." }
8039
+ }
8040
+ },
7985
8041
  deleteSecret: {
7986
8042
  method: "DELETE",
7987
8043
  path: "/api/v2/secrets/[name]",
@@ -8675,6 +8731,17 @@ var V2_OPERATIONS = {
8675
8731
  workspaceId: { kind: "string", required: true, describe: "Workspace that owns the table." }
8676
8732
  }
8677
8733
  },
8734
+ getSandbox: {
8735
+ method: "GET",
8736
+ path: "/api/v2/sandboxes/[sandboxId]",
8737
+ pathParams: ["sandboxId"],
8738
+ pathParamDocs: { sandboxId: "Unique sandbox identifier." },
8739
+ responseMode: "json",
8740
+ summary: "Get Sandbox",
8741
+ query: {
8742
+ workspaceId: { kind: "string", required: true, describe: "Workspace that owns the sandbox." }
8743
+ }
8744
+ },
8678
8745
  getSkill: {
8679
8746
  method: "GET",
8680
8747
  path: "/api/v2/skills/[skillId]",
@@ -9825,6 +9892,41 @@ var V2_OPERATIONS = {
9825
9892
  }
9826
9893
  }
9827
9894
  },
9895
+ listSandboxes: {
9896
+ method: "GET",
9897
+ path: "/api/v2/sandboxes",
9898
+ pathParams: [],
9899
+ responseMode: "json",
9900
+ summary: "List Sandboxes",
9901
+ query: {
9902
+ workspaceId: { kind: "string", required: true, describe: "Workspace that owns the sandbox." },
9903
+ search: {
9904
+ kind: "string",
9905
+ describe: "Case-insensitive substring match against the sandbox name."
9906
+ },
9907
+ sortBy: {
9908
+ kind: "enum",
9909
+ values: ["name", "createdAt", "updatedAt"],
9910
+ default: "name",
9911
+ describe: "Field used to sort the result. Sorting by `name` is case-sensitive and follows the storage collation, so do not rely on a case-insensitive order."
9912
+ },
9913
+ sortOrder: {
9914
+ kind: "enum",
9915
+ values: ["asc", "desc"],
9916
+ default: "asc",
9917
+ describe: "Sort direction."
9918
+ },
9919
+ limit: {
9920
+ kind: "integer",
9921
+ default: 50,
9922
+ describe: "Maximum sandboxes to return per page. Must be a whole number from 1 to 100. Defaults to 50."
9923
+ },
9924
+ cursor: {
9925
+ kind: "string",
9926
+ describe: "Opaque cursor from the previous page. Send it back with the same sort and filters; only `limit` may change. Change anything else and pagination must restart without a cursor."
9927
+ }
9928
+ }
9929
+ },
9828
9930
  listSecrets: {
9829
9931
  method: "GET",
9830
9932
  path: "/api/v2/secrets",
@@ -11374,6 +11476,39 @@ var V2_OPERATIONS = {
11374
11476
  limit: { kind: "integer", describe: "Maximum matching rows to update." }
11375
11477
  }
11376
11478
  },
11479
+ updateSandbox: {
11480
+ method: "PATCH",
11481
+ path: "/api/v2/sandboxes/[sandboxId]",
11482
+ pathParams: ["sandboxId"],
11483
+ pathParamDocs: { sandboxId: "Unique sandbox identifier." },
11484
+ responseMode: "json",
11485
+ summary: "Update Sandbox",
11486
+ personalKeyOnly: true,
11487
+ body: {
11488
+ workspaceId: { kind: "string", required: true, describe: "Workspace that owns the sandbox." },
11489
+ name: {
11490
+ kind: "string",
11491
+ describe: "New display name, unique within the workspace; 1 to 64 characters."
11492
+ },
11493
+ language: {
11494
+ kind: "enum",
11495
+ values: ["javascript", "python"],
11496
+ describe: "Replacement dependency ecosystem. The whole spec is revalidated against it, so a Python dependency list does not survive a switch to JavaScript."
11497
+ },
11498
+ dependencies: {
11499
+ kind: "array",
11500
+ describe: "Replacement package list; replaces the whole list."
11501
+ },
11502
+ cliTools: {
11503
+ kind: "array",
11504
+ describe: "Replacement managed CLI list; replaces the whole list."
11505
+ },
11506
+ systemPackages: {
11507
+ kind: "array",
11508
+ describe: "Replacement Debian package list; replaces the whole list."
11509
+ }
11510
+ }
11511
+ },
11377
11512
  updateSkill: {
11378
11513
  method: "PATCH",
11379
11514
  path: "/api/v2/skills/[skillId]",
@@ -12307,6 +12442,9 @@ var CLI_CONTRACT = {
12307
12442
  confirm: "This revokes the explicit skill editor grant for the selected email."
12308
12443
  },
12309
12444
  deleteCustomTool: { confirm: "This deletes the custom tool." },
12445
+ deleteSandbox: {
12446
+ confirm: "This deletes the sandbox; Function blocks that select it fail until re-pointed."
12447
+ },
12310
12448
  deleteMcpServer: {
12311
12449
  confirm: "This removes the MCP server and the tools it provides."
12312
12450
  },
@@ -12593,6 +12731,20 @@ var CLI_CONTRACT = {
12593
12731
  importWorkflow: { flags: { folderPath: FOLDER_PATH_FLAG } },
12594
12732
  createCustomTool: { flags: { schema: { json: true, describe: CUSTOM_TOOL_SCHEMA_HELP } } },
12595
12733
  updateCustomTool: { flags: { schema: { json: true, describe: CUSTOM_TOOL_SCHEMA_HELP } } },
12734
+ createSandbox: {
12735
+ flags: {
12736
+ dependencies: { list: true, manifest: true },
12737
+ cliTools: { list: true },
12738
+ systemPackages: { list: true, manifest: true }
12739
+ }
12740
+ },
12741
+ updateSandbox: {
12742
+ flags: {
12743
+ dependencies: { list: true, manifest: true },
12744
+ cliTools: { list: true },
12745
+ systemPackages: { list: true, manifest: true }
12746
+ }
12747
+ },
12596
12748
  listTables: {
12597
12749
  flags: { folderPath: FOLDER_PATH_FLAG },
12598
12750
  columns: [
@@ -12734,6 +12886,15 @@ var CLI_CONTRACT = {
12734
12886
  { header: "updated", path: "updatedAt", format: "timestamp" }
12735
12887
  ]
12736
12888
  },
12889
+ listSandboxes: {
12890
+ columns: [
12891
+ { header: "id" },
12892
+ { header: "name" },
12893
+ { header: "language" },
12894
+ { header: "status", path: "buildStatus" },
12895
+ { header: "updated", path: "updatedAt", format: "timestamp" }
12896
+ ]
12897
+ },
12737
12898
  listCredentials: {
12738
12899
  columns: [
12739
12900
  { header: "id" },
@@ -13439,7 +13600,11 @@ function readArgumentSource(raw, flagName) {
13439
13600
  throw new SimApiError(`--${flagName} cannot read ${path}: ${error.message}${literalAtHint(error, path)}`, 0);
13440
13601
  }
13441
13602
  }
13442
- function readListValues(raw, flagName) {
13603
+ function isManifestNoise(line) {
13604
+ const trimmed = line.trim();
13605
+ return trimmed === "" || trimmed.startsWith("#");
13606
+ }
13607
+ function readListValues(raw, flagName, manifest = false) {
13443
13608
  const arguments_ = Array.isArray(raw) ? raw : [raw];
13444
13609
  const values = arguments_.flatMap((argument) => {
13445
13610
  if (typeof argument !== "string") {
@@ -13451,10 +13616,11 @@ function readListValues(raw, flagName) {
13451
13616
  const lines = source.text.split(/\r?\n/);
13452
13617
  if (lines.at(-1) === "")
13453
13618
  lines.pop();
13454
- if (lines.length === 0) {
13619
+ const kept = manifest ? lines.filter((line) => !isManifestNoise(line)) : lines;
13620
+ if (kept.length === 0) {
13455
13621
  throw new SimApiError(`--${flagName}${source.from} contains no values`, 0);
13456
13622
  }
13457
- return lines.map((line, index) => {
13623
+ return kept.map((line, index) => {
13458
13624
  const value = line.trim();
13459
13625
  if (!value) {
13460
13626
  throw new SimApiError(`--${flagName}${source.from} has an empty value on line ${index + 1}`, 0);
@@ -13499,7 +13665,7 @@ function coerce(raw, field, flag, flagName) {
13499
13665
  if (raw === undefined)
13500
13666
  return;
13501
13667
  if (flag.list) {
13502
- const values = readListValues(raw, flagName).map((value) => flag.folderPath ? encodeFolderPath(value) : value);
13668
+ const values = readListValues(raw, flagName, flag.manifest === true).map((value) => flag.folderPath ? encodeFolderPath(value) : value);
13503
13669
  return field.kind === "string" ? values.join(",") : values;
13504
13670
  }
13505
13671
  if (flag.rowCap)
@@ -13678,7 +13844,7 @@ function addFieldOption(command, operation, field, descriptor, slot, paginates,
13678
13844
  const placeholder = takesList ? "<value...>" : flag.rowCap ? "<n>" : wantsJson ? "<json|@file>" : "<value>";
13679
13845
  const choices = flag.choices ?? descriptor.values;
13680
13846
  const literalNull = slot === "body" && !takesList && !wantsJson;
13681
- const describe = `${documented}${takesList ? " (space-separated, or @path / @- with one value per line; @@value for a literal leading @)" : wantsJson ? " (JSON, or @path / @- to read a file or stdin)" : ""}${descriptor.required ? " (required)" : ""}${literalNull ? literalNullHint(documented, name) : ""}`;
13847
+ const describe = `${documented}${takesList ? flag.manifest ? " (space-separated, or @path / @- with one value per line; in a file, blank lines and # comments are ignored, while inline values are sent as typed and may not be empty; @@value for a literal leading @)" : " (space-separated, or @path / @- with one value per line; @@value for a literal leading @)" : wantsJson ? " (JSON, or @path / @- to read a file or stdin)" : ""}${descriptor.required ? " (required)" : ""}${literalNull ? literalNullHint(documented, name) : ""}`;
13682
13848
  const renamedFrom = flag.renamedFrom ?? [];
13683
13849
  const option = new Option(`${short}--${name} ${placeholder}`, describe);
13684
13850
  if (flag.hidden)
@@ -14421,6 +14587,7 @@ var GROUP_ALIASES = {
14421
14587
  knowledge: "kb",
14422
14588
  logs: "log",
14423
14589
  "mcp-servers": "mcp-server",
14590
+ sandboxes: "sandbox",
14424
14591
  secrets: "secret",
14425
14592
  skills: "skill",
14426
14593
  tables: "table",
@@ -16454,6 +16621,328 @@ function attachSecretCommands(program2) {
16454
16621
  secrets.command("set").argument("<name>", "Secret name, as referenced in workflows").description(describeOperation(V2_OPERATIONS.setSecret, "Create or replace a named secret")).addOption(new Option("--scope <scope>", "Secret ownership scope (required)").choices([...SECRET_SCOPES]).makeOptionMandatory()).option("--value <value|@file>", "Secret value. Passing it inline exposes it to shell history and process listings; @path reads it from a file and @- from stdin, verbatim — a trailing newline is part of the value, so write the file with printf rather than echo. Prefix a literal leading @ with a second one").option("--description <description>", "What the secret is for, shown to teammates; workspace scope only. Omit to leave an existing description unchanged").option("--unredacted", `${V2_OPERATIONS.setSecret.body.unredacted.describe} Pass --no-unredacted to restore redaction`).option("--no-unredacted", "Send --unredacted as false").on("option:unredacted", () => redactionSpellings.add("--unredacted")).on("option:no-unredacted", () => redactionSpellings.add("--no-unredacted")).action((name, options, command) => setSecret(name, options, command, redactionSpellings));
16455
16622
  }
16456
16623
 
16624
+ // src/update/check.ts
16625
+ import { spawn as spawn2 } from "node:child_process";
16626
+ import {
16627
+ closeSync,
16628
+ constants as constants2,
16629
+ fstatSync,
16630
+ lstatSync,
16631
+ mkdirSync as mkdirSync2,
16632
+ openSync,
16633
+ readSync as readSync2,
16634
+ renameSync,
16635
+ unlinkSync,
16636
+ writeFileSync as writeFileSync2
16637
+ } from "node:fs";
16638
+ import { dirname as dirname3 } from "node:path";
16639
+ import { fileURLToPath } from "node:url";
16640
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
16641
+ var REGISTRY_TIMEOUT_MS = 1000;
16642
+ var DEFAULT_REGISTRY = "https://registry.npmjs.org";
16643
+ var PACKAGE_NAME = "sim";
16644
+ var DIST_TAGS_PATH = `-/package/${PACKAGE_NAME}/dist-tags`;
16645
+ var MAX_RESPONSE_BYTES = 64 * 1024;
16646
+ var MAX_CACHE_BYTES = 4 * 1024;
16647
+ var STABLE_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
16648
+ function parseStableVersion(version) {
16649
+ const match = STABLE_VERSION_PATTERN.exec(version);
16650
+ if (!match)
16651
+ return null;
16652
+ const parsed = [Number(match[1]), Number(match[2]), Number(match[3])];
16653
+ return parsed.every(Number.isSafeInteger) ? parsed : null;
16654
+ }
16655
+ function isNewerVersion(candidate, current) {
16656
+ if (candidate[0] !== current[0])
16657
+ return candidate[0] > current[0];
16658
+ if (candidate[1] !== current[1])
16659
+ return candidate[1] > current[1];
16660
+ return candidate[2] > current[2];
16661
+ }
16662
+ var CI_VARIABLES = [
16663
+ "CI",
16664
+ "GITHUB_ACTIONS",
16665
+ "JENKINS_URL",
16666
+ "TEAMCITY_VERSION",
16667
+ "BUILDKITE"
16668
+ ];
16669
+ var CACHE_VERSION = 1;
16670
+ var cacheWriteSequence = 0;
16671
+ function isEnabled(value) {
16672
+ if (value === undefined)
16673
+ return false;
16674
+ const normalized = value.trim().toLowerCase();
16675
+ return normalized !== "" && normalized !== "0" && normalized !== "false";
16676
+ }
16677
+ function isProjectLocalInstall(modulePath, cwd) {
16678
+ const normalizedModulePath = normalizeModulePath(modulePath);
16679
+ const nodeModulesIndex = normalizedModulePath.indexOf("/node_modules/");
16680
+ if (nodeModulesIndex < 0)
16681
+ return false;
16682
+ const installRoot = normalizedModulePath.slice(0, nodeModulesIndex);
16683
+ const workingDirectory = normalizeModulePath(cwd).replace(/\/+$/, "");
16684
+ return workingDirectory === installRoot || workingDirectory.startsWith(`${installRoot}/`);
16685
+ }
16686
+ function isUnadvisableInstall(modulePath, env2, cwd) {
16687
+ const normalized = normalizeModulePath(modulePath);
16688
+ return env2.npm_command === "exec" || normalized.includes("/_npx/") || normalized.includes("/packages/sim-cli/") || isProjectLocalInstall(modulePath, cwd);
16689
+ }
16690
+ function normalizeModulePath(modulePath) {
16691
+ return modulePath.replace(/\\/g, "/").toLowerCase();
16692
+ }
16693
+ function registryUrl(env2) {
16694
+ const fallback = new URL(DIST_TAGS_PATH, DEFAULT_REGISTRY);
16695
+ const configured = env2.npm_config_registry?.trim();
16696
+ if (!configured)
16697
+ return fallback;
16698
+ try {
16699
+ const base = new URL(configured);
16700
+ if (base.protocol !== "http:" && base.protocol !== "https:")
16701
+ return null;
16702
+ if (base.username || base.password)
16703
+ return null;
16704
+ base.pathname = `${base.pathname.replace(/\/$/, "")}/${DIST_TAGS_PATH}`;
16705
+ return base;
16706
+ } catch {
16707
+ return null;
16708
+ }
16709
+ }
16710
+ var REGISTRY_REQUEST_SCRIPT = `
16711
+ let input = ''
16712
+ process.stdin.setEncoding('utf8')
16713
+ for await (const chunk of process.stdin) input += chunk
16714
+
16715
+ try {
16716
+ const { url, headers, maxResponseBytes, timeoutMs } = JSON.parse(input)
16717
+ const deadline = setTimeout(() => process.exit(1), timeoutMs)
16718
+ const response = await fetch(url, { headers, redirect: 'error' })
16719
+ const declared = Number(response.headers.get('content-length'))
16720
+
16721
+ if (!response.ok || !response.body || (Number.isFinite(declared) && declared > maxResponseBytes)) {
16722
+ process.exit(1)
16723
+ }
16724
+
16725
+ const reader = response.body.getReader()
16726
+ const chunks = []
16727
+ let seen = 0
16728
+
16729
+ while (true) {
16730
+ const { done, value } = await reader.read()
16731
+ if (done) break
16732
+ seen += value.byteLength
16733
+ if (seen > maxResponseBytes) {
16734
+ process.exit(1)
16735
+ }
16736
+ chunks.push(Buffer.from(value))
16737
+ }
16738
+
16739
+ clearTimeout(deadline)
16740
+ process.stdout.write(Buffer.concat(chunks), () => process.exit(0))
16741
+ } catch {
16742
+ process.exit(1)
16743
+ }
16744
+ `;
16745
+ function registryProcessEnv() {
16746
+ const env2 = { ...process.env };
16747
+ for (const key of Object.keys(env2)) {
16748
+ const normalized = key.toLowerCase();
16749
+ if (normalized === "npm_config_registry" || normalized === "sim_api_key")
16750
+ delete env2[key];
16751
+ }
16752
+ return env2;
16753
+ }
16754
+ function requestRegistry(url, { headers, maxResponseBytes, timeoutMs }) {
16755
+ return new Promise((resolve3, reject) => {
16756
+ const proxyArguments = process.execArgv.filter((argument) => argument === "--use-env-proxy" || argument === "--no-use-env-proxy");
16757
+ const child = spawn2(process.execPath, [...proxyArguments, "--input-type=module", "--eval", REGISTRY_REQUEST_SCRIPT], {
16758
+ env: registryProcessEnv(),
16759
+ killSignal: "SIGKILL",
16760
+ stdio: ["pipe", "pipe", "ignore"],
16761
+ timeout: timeoutMs,
16762
+ windowsHide: true
16763
+ });
16764
+ const chunks = [];
16765
+ let failed = false;
16766
+ let seen = 0;
16767
+ child.stdout.on("data", (chunk) => {
16768
+ seen += chunk.byteLength;
16769
+ if (seen > maxResponseBytes) {
16770
+ failed = true;
16771
+ child.kill("SIGKILL");
16772
+ return;
16773
+ }
16774
+ chunks.push(chunk);
16775
+ });
16776
+ child.stdout.on("error", () => {
16777
+ failed = true;
16778
+ child.kill("SIGKILL");
16779
+ });
16780
+ child.stdin.on("error", () => {});
16781
+ child.once("error", reject);
16782
+ child.once("close", (code) => {
16783
+ resolve3(code === 0 && !failed ? Buffer.concat(chunks).toString("utf8") : null);
16784
+ });
16785
+ child.stdin.end(JSON.stringify({ headers, maxResponseBytes, timeoutMs, url: url.href }));
16786
+ });
16787
+ }
16788
+ async function fetchDistTags(env2, request) {
16789
+ try {
16790
+ const url = registryUrl(env2);
16791
+ if (!url)
16792
+ return null;
16793
+ const text2 = await request(url, {
16794
+ headers: { accept: "application/json", "user-agent": `${PACKAGE_NAME}-cli/${CLI_VERSION}` },
16795
+ maxResponseBytes: MAX_RESPONSE_BYTES,
16796
+ timeoutMs: REGISTRY_TIMEOUT_MS
16797
+ });
16798
+ if (text2 === null || Buffer.byteLength(text2) > MAX_RESPONSE_BYTES)
16799
+ return null;
16800
+ const body = JSON.parse(text2);
16801
+ if (typeof body !== "object" || body === null || Array.isArray(body))
16802
+ return null;
16803
+ const tags = {};
16804
+ for (const [tag, version] of Object.entries(body)) {
16805
+ if (typeof version === "string")
16806
+ tags[tag] = version;
16807
+ }
16808
+ return tags;
16809
+ } catch {
16810
+ return null;
16811
+ }
16812
+ }
16813
+ function readCache(path) {
16814
+ let descriptor = null;
16815
+ try {
16816
+ if (!lstatSync(path).isFile())
16817
+ return null;
16818
+ descriptor = openSync(path, constants2.O_RDONLY | constants2.O_NONBLOCK | constants2.O_NOFOLLOW);
16819
+ const descriptorStats = fstatSync(descriptor);
16820
+ if (!descriptorStats.isFile() || descriptorStats.size > MAX_CACHE_BYTES) {
16821
+ return null;
16822
+ }
16823
+ const buffer = Buffer.allocUnsafe(MAX_CACHE_BYTES + 1);
16824
+ let bytesRead = 0;
16825
+ while (bytesRead < buffer.byteLength) {
16826
+ const count = readSync2(descriptor, buffer, bytesRead, buffer.byteLength - bytesRead, bytesRead);
16827
+ if (count === 0)
16828
+ break;
16829
+ bytesRead += count;
16830
+ }
16831
+ if (bytesRead > MAX_CACHE_BYTES)
16832
+ return null;
16833
+ const parsed = JSON.parse(buffer.subarray(0, bytesRead).toString("utf8"));
16834
+ if (typeof parsed !== "object" || parsed === null)
16835
+ return null;
16836
+ const entry = parsed;
16837
+ if (entry.version !== CACHE_VERSION)
16838
+ return null;
16839
+ if (typeof entry.checkedAt !== "string" || Number.isNaN(Date.parse(entry.checkedAt)))
16840
+ return null;
16841
+ return {
16842
+ version: CACHE_VERSION,
16843
+ checkedAt: entry.checkedAt
16844
+ };
16845
+ } catch {
16846
+ return null;
16847
+ } finally {
16848
+ if (descriptor !== null) {
16849
+ try {
16850
+ closeSync(descriptor);
16851
+ } catch {}
16852
+ }
16853
+ }
16854
+ }
16855
+ function writeCache(path, entry) {
16856
+ let descriptor = null;
16857
+ let temporaryCreated = false;
16858
+ const temporaryPath = `${path}.${process.pid}.${Date.now()}.${cacheWriteSequence++}.tmp`;
16859
+ try {
16860
+ mkdirSync2(dirname3(path), { recursive: true, mode: 448 });
16861
+ descriptor = openSync(temporaryPath, "wx", 420);
16862
+ temporaryCreated = true;
16863
+ writeFileSync2(descriptor, `${JSON.stringify(entry, null, 2)}
16864
+ `);
16865
+ closeSync(descriptor);
16866
+ descriptor = null;
16867
+ renameSync(temporaryPath, path);
16868
+ temporaryCreated = false;
16869
+ } catch {} finally {
16870
+ if (descriptor !== null) {
16871
+ try {
16872
+ closeSync(descriptor);
16873
+ } catch {}
16874
+ }
16875
+ if (temporaryCreated) {
16876
+ try {
16877
+ unlinkSync(temporaryPath);
16878
+ } catch {}
16879
+ }
16880
+ }
16881
+ }
16882
+ function isFresh(entry, now) {
16883
+ const age = now.getTime() - Date.parse(entry.checkedAt);
16884
+ return age >= 0 && age < CHECK_INTERVAL_MS;
16885
+ }
16886
+ function upgradeCommand(modulePath = fileURLToPath(import.meta.url), env2 = process.env) {
16887
+ const target = `${PACKAGE_NAME}@latest`;
16888
+ const normalized = normalizeModulePath(modulePath);
16889
+ if (normalized.includes(".bun/install/global"))
16890
+ return `bun add -g ${target}`;
16891
+ if (normalized.includes("/pnpm/") || normalized.includes("/.pnpm/")) {
16892
+ return `pnpm add -g ${target}`;
16893
+ }
16894
+ if (normalized.includes("/.yarn/") || normalized.includes("/yarn/")) {
16895
+ return `yarn global add ${target}`;
16896
+ }
16897
+ const agent = env2.npm_config_user_agent ?? "";
16898
+ if (agent.startsWith("pnpm/"))
16899
+ return `pnpm add -g ${target}`;
16900
+ if (agent.startsWith("yarn/"))
16901
+ return `yarn global add ${target}`;
16902
+ if (agent.startsWith("bun/"))
16903
+ return `bun add -g ${target}`;
16904
+ return `npm install -g ${target}`;
16905
+ }
16906
+ async function announceUpdateIfAvailable(options = {}) {
16907
+ try {
16908
+ const env2 = options.env ?? process.env;
16909
+ const isTty = options.isTty ?? process.stderr.isTTY === true;
16910
+ const modulePath = options.modulePath ?? fileURLToPath(import.meta.url);
16911
+ const cwd = options.cwd ?? process.cwd();
16912
+ const now = options.now ?? new Date;
16913
+ if (isEnabled(env2.SIM_NO_UPDATE_CHECK))
16914
+ return;
16915
+ if (!isTty)
16916
+ return;
16917
+ if (CI_VARIABLES.some((variable) => isEnabled(env2[variable])))
16918
+ return;
16919
+ if (isUnadvisableInstall(modulePath, env2, cwd))
16920
+ return;
16921
+ const currentVersion = options.currentVersion ?? CLI_VERSION;
16922
+ const current = parseStableVersion(currentVersion);
16923
+ if (!current)
16924
+ return;
16925
+ const cachePath = updateCachePath();
16926
+ const cached = readCache(cachePath);
16927
+ if (cached && isFresh(cached, now))
16928
+ return;
16929
+ const tags = await fetchDistTags(env2, options.registryRequest ?? requestRegistry);
16930
+ const latest = tags?.latest ?? null;
16931
+ const available = latest ? parseStableVersion(latest) : null;
16932
+ writeCache(cachePath, {
16933
+ version: CACHE_VERSION,
16934
+ checkedAt: now.toISOString()
16935
+ });
16936
+ if (!latest || !available)
16937
+ return;
16938
+ if (!isNewerVersion(available, current))
16939
+ return;
16940
+ const write = options.write ?? ((message) => void process.stderr.write(message));
16941
+ write(`Update available: sim ${currentVersion} → ${latest}. Run: ${upgradeCommand(modulePath, env2)}
16942
+ `);
16943
+ } catch {}
16944
+ }
16945
+
16457
16946
  // src/program.ts
16458
16947
  var PROGRAM_DESCRIPTION = "Talk to the Sim API from your terminal";
16459
16948
  var HELP_EPILOGUE = `
@@ -16533,6 +17022,7 @@ function buildProgram(options = {}) {
16533
17022
  attachProtocolCommands(program2);
16534
17023
  attachSecretCommands(program2);
16535
17024
  program2.addHelpText("after", HELP_EPILOGUE);
17025
+ program2.hook("preAction", () => announceUpdateIfAvailable());
16536
17026
  refuseHelpAfterUnknownCommand(program2);
16537
17027
  assertNoReservedProgramFlags(program2);
16538
17028
  return program2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sim",
3
- "version": "2.1.6-preview.84.1",
3
+ "version": "2.1.6-preview.90.1",
4
4
  "description": "Sim CLI - talk to the Sim API from your terminal",
5
5
  "type": "module",
6
6
  "bin": {