sim 2.1.13 → 2.1.14-preview.117.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 +48 -4
  2. package/dist/index.js +1054 -402
  3. package/package.json +5 -2
package/dist/index.js CHANGED
@@ -6946,35 +6946,588 @@ var {
6946
6946
  safeDump
6947
6947
  } = yaml;
6948
6948
 
6949
+ // src/update/install.ts
6950
+ import { spawn as spawn2 } from "node:child_process";
6951
+ import { readFileSync as readFileSync2, realpathSync } from "node:fs";
6952
+ import { homedir as homedir2 } from "node:os";
6953
+ import { dirname as dirname2, isAbsolute, join as join2 } from "node:path";
6954
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
6955
+
6956
+ // ../utils/src/errors.ts
6957
+ function getErrorMessage(value, fallback) {
6958
+ if (value instanceof Error)
6959
+ return value.message;
6960
+ if (typeof value === "string" && value.length > 0)
6961
+ return value;
6962
+ return fallback ?? String(value);
6963
+ }
6964
+
6965
+ // ../utils/src/object.ts
6966
+ function omit(obj, keys) {
6967
+ const result = { ...obj };
6968
+ for (const key of keys) {
6969
+ delete result[key];
6970
+ }
6971
+ return result;
6972
+ }
6973
+
6974
+ // src/update/install.ts
6975
+ var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
6976
+
6977
+ // src/update/check.ts
6978
+ import { spawn } from "node:child_process";
6979
+ import { fileURLToPath } from "node:url";
6980
+
6981
+ // src/config/json-file.ts
6982
+ import {
6983
+ closeSync,
6984
+ constants,
6985
+ fstatSync,
6986
+ lstatSync,
6987
+ mkdirSync,
6988
+ openSync,
6989
+ readSync,
6990
+ renameSync,
6991
+ unlinkSync,
6992
+ writeFileSync
6993
+ } from "node:fs";
6994
+ import { dirname } from "node:path";
6995
+ var writeSequence = 0;
6996
+ function readJsonFile(path, maxBytes) {
6997
+ let descriptor = null;
6998
+ try {
6999
+ if (!lstatSync(path).isFile())
7000
+ return null;
7001
+ descriptor = openSync(path, constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW);
7002
+ const stats = fstatSync(descriptor);
7003
+ if (!stats.isFile() || stats.size > maxBytes)
7004
+ return null;
7005
+ const buffer = Buffer.allocUnsafe(maxBytes + 1);
7006
+ let bytesRead = 0;
7007
+ while (bytesRead < buffer.byteLength) {
7008
+ const count = readSync(descriptor, buffer, bytesRead, buffer.byteLength - bytesRead, bytesRead);
7009
+ if (count === 0)
7010
+ break;
7011
+ bytesRead += count;
7012
+ }
7013
+ if (bytesRead > maxBytes)
7014
+ return null;
7015
+ return JSON.parse(buffer.subarray(0, bytesRead).toString("utf8"));
7016
+ } catch {
7017
+ return null;
7018
+ } finally {
7019
+ if (descriptor !== null) {
7020
+ try {
7021
+ closeSync(descriptor);
7022
+ } catch {}
7023
+ }
7024
+ }
7025
+ }
7026
+ function writeJsonFile(path, value, mode = 420) {
7027
+ let descriptor = null;
7028
+ let temporaryCreated = false;
7029
+ const temporaryPath = `${path}.${process.pid}.${Date.now()}.${writeSequence++}.tmp`;
7030
+ try {
7031
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
7032
+ descriptor = openSync(temporaryPath, "wx", mode);
7033
+ temporaryCreated = true;
7034
+ writeFileSync(descriptor, `${JSON.stringify(value, null, 2)}
7035
+ `);
7036
+ closeSync(descriptor);
7037
+ descriptor = null;
7038
+ renameSync(temporaryPath, path);
7039
+ temporaryCreated = false;
7040
+ } catch {} finally {
7041
+ if (descriptor !== null) {
7042
+ try {
7043
+ closeSync(descriptor);
7044
+ } catch {}
7045
+ }
7046
+ if (temporaryCreated) {
7047
+ try {
7048
+ unlinkSync(temporaryPath);
7049
+ } catch {}
7050
+ }
7051
+ }
7052
+ }
7053
+
6949
7054
  // src/config/paths.ts
6950
7055
  import { homedir } from "node:os";
6951
7056
  import { join } from "node:path";
6952
7057
  function configDir() {
6953
7058
  return process.env.SIM_CONFIG_DIR || join(homedir(), ".sim");
6954
7059
  }
6955
- function configPath() {
6956
- return process.env.SIM_CONFIG_FILE || join(configDir(), "config");
7060
+ function configPath() {
7061
+ return process.env.SIM_CONFIG_FILE || join(configDir(), "config");
7062
+ }
7063
+ function credentialsPath() {
7064
+ return process.env.SIM_CREDENTIALS_FILE || join(configDir(), "credentials");
7065
+ }
7066
+ function updateCachePath() {
7067
+ return join(configDir(), "update-check.json");
7068
+ }
7069
+ function telemetryStatePath() {
7070
+ return join(configDir(), "telemetry.json");
7071
+ }
7072
+
7073
+ // src/environment.ts
7074
+ var CI_VARIABLES = [
7075
+ "CI",
7076
+ "GITHUB_ACTIONS",
7077
+ "JENKINS_URL",
7078
+ "TEAMCITY_VERSION",
7079
+ "BUILDKITE"
7080
+ ];
7081
+ function isEnabled(value) {
7082
+ if (value === undefined)
7083
+ return false;
7084
+ const normalized = value.trim().toLowerCase();
7085
+ return normalized !== "" && normalized !== "0" && normalized !== "false";
7086
+ }
7087
+ function isCi(env = process.env) {
7088
+ return CI_VARIABLES.some((variable) => isEnabled(env[variable]));
7089
+ }
7090
+ function proxyExecArgv() {
7091
+ return process.execArgv.filter((argument) => argument === "--use-env-proxy" || argument === "--no-use-env-proxy");
7092
+ }
7093
+ function childProcessEnv(strip, extra = {}) {
7094
+ const env = { ...process.env };
7095
+ const stripped = new Set(strip.map((name) => name.toLowerCase()));
7096
+ for (const key of Object.keys(env)) {
7097
+ if (stripped.has(key.toLowerCase()))
7098
+ delete env[key];
7099
+ }
7100
+ return { ...env, ...extra };
7101
+ }
7102
+
7103
+ // src/version.ts
7104
+ import { readFileSync } from "node:fs";
7105
+ function readPackageVersion() {
7106
+ const metadata = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
7107
+ if (typeof metadata !== "object" || metadata === null || !("version" in metadata) || typeof metadata.version !== "string") {
7108
+ throw new Error("CLI package metadata is missing a valid version");
7109
+ }
7110
+ return metadata.version;
7111
+ }
7112
+ var CLI_VERSION = readPackageVersion();
7113
+ var USER_AGENT = `sim-cli/${CLI_VERSION} node/${process.versions.node} (${process.platform}; ${process.arch})`;
7114
+
7115
+ // src/update/check.ts
7116
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
7117
+ var REGISTRY_TIMEOUT_MS = 1000;
7118
+ var DEFAULT_REGISTRY = "https://registry.npmjs.org";
7119
+ var PACKAGE_NAME = "sim";
7120
+ var DIST_TAGS_PATH = `-/package/${PACKAGE_NAME}/dist-tags`;
7121
+ var MAX_RESPONSE_BYTES = 64 * 1024;
7122
+ var MAX_CACHE_BYTES = 4 * 1024;
7123
+ var STABLE_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
7124
+ function parseStableVersion(version) {
7125
+ const match = STABLE_VERSION_PATTERN.exec(version);
7126
+ if (!match)
7127
+ return null;
7128
+ const parsed = [Number(match[1]), Number(match[2]), Number(match[3])];
7129
+ return parsed.every(Number.isSafeInteger) ? parsed : null;
7130
+ }
7131
+ function isNewerVersion(candidate, current) {
7132
+ if (candidate[0] !== current[0])
7133
+ return candidate[0] > current[0];
7134
+ if (candidate[1] !== current[1])
7135
+ return candidate[1] > current[1];
7136
+ return candidate[2] > current[2];
7137
+ }
7138
+ var CACHE_VERSION = 1;
7139
+ function isProjectLocalInstall(modulePath, cwd) {
7140
+ const normalizedModulePath = normalizeModulePath(modulePath);
7141
+ const nodeModulesIndex = normalizedModulePath.indexOf("/node_modules/");
7142
+ if (nodeModulesIndex < 0)
7143
+ return false;
7144
+ const installRoot = normalizedModulePath.slice(0, nodeModulesIndex);
7145
+ const workingDirectory = normalizeModulePath(cwd).replace(/\/+$/, "");
7146
+ return workingDirectory === installRoot || workingDirectory.startsWith(`${installRoot}/`);
7147
+ }
7148
+ function isUnadvisableInstall(modulePath, env, cwd) {
7149
+ const normalized = normalizeModulePath(modulePath);
7150
+ return env.npm_command === "exec" || normalized.includes("/_npx/") || normalized.includes("/packages/sim-cli/") || isProjectLocalInstall(modulePath, cwd);
7151
+ }
7152
+ function normalizeModulePath(modulePath) {
7153
+ return modulePath.replace(/\\/g, "/").toLowerCase();
7154
+ }
7155
+ function registryUrl(env) {
7156
+ const fallback = new URL(DIST_TAGS_PATH, DEFAULT_REGISTRY);
7157
+ const configured = env.npm_config_registry?.trim();
7158
+ if (!configured)
7159
+ return fallback;
7160
+ try {
7161
+ const base = new URL(configured);
7162
+ if (base.protocol !== "http:" && base.protocol !== "https:")
7163
+ return null;
7164
+ if (base.username || base.password)
7165
+ return null;
7166
+ base.pathname = `${base.pathname.replace(/\/$/, "")}/${DIST_TAGS_PATH}`;
7167
+ return base;
7168
+ } catch {
7169
+ return null;
7170
+ }
7171
+ }
7172
+ var REGISTRY_REQUEST_SCRIPT = `
7173
+ let input = ''
7174
+ process.stdin.setEncoding('utf8')
7175
+ for await (const chunk of process.stdin) input += chunk
7176
+
7177
+ try {
7178
+ const { url, headers, maxResponseBytes, timeoutMs } = JSON.parse(input)
7179
+ const deadline = setTimeout(() => process.exit(1), timeoutMs)
7180
+ const response = await fetch(url, { headers, redirect: 'error' })
7181
+ const declared = Number(response.headers.get('content-length'))
7182
+
7183
+ if (!response.ok || !response.body || (Number.isFinite(declared) && declared > maxResponseBytes)) {
7184
+ process.exit(1)
7185
+ }
7186
+
7187
+ const reader = response.body.getReader()
7188
+ const chunks = []
7189
+ let seen = 0
7190
+
7191
+ while (true) {
7192
+ const { done, value } = await reader.read()
7193
+ if (done) break
7194
+ seen += value.byteLength
7195
+ if (seen > maxResponseBytes) {
7196
+ process.exit(1)
7197
+ }
7198
+ chunks.push(Buffer.from(value))
7199
+ }
7200
+
7201
+ clearTimeout(deadline)
7202
+ process.stdout.write(Buffer.concat(chunks), () => process.exit(0))
7203
+ } catch {
7204
+ process.exit(1)
7205
+ }
7206
+ `;
7207
+ function requestRegistry(url, { headers, maxResponseBytes, timeoutMs }) {
7208
+ return new Promise((resolve, reject) => {
7209
+ const child = spawn(process.execPath, [...proxyExecArgv(), "--input-type=module", "--eval", REGISTRY_REQUEST_SCRIPT], {
7210
+ env: childProcessEnv(["npm_config_registry", "sim_api_key"]),
7211
+ killSignal: "SIGKILL",
7212
+ stdio: ["pipe", "pipe", "ignore"],
7213
+ timeout: timeoutMs,
7214
+ windowsHide: true
7215
+ });
7216
+ const chunks = [];
7217
+ let failed = false;
7218
+ let seen = 0;
7219
+ child.stdout.on("data", (chunk) => {
7220
+ seen += chunk.byteLength;
7221
+ if (seen > maxResponseBytes) {
7222
+ failed = true;
7223
+ child.kill("SIGKILL");
7224
+ return;
7225
+ }
7226
+ chunks.push(chunk);
7227
+ });
7228
+ child.stdout.on("error", () => {
7229
+ failed = true;
7230
+ child.kill("SIGKILL");
7231
+ });
7232
+ child.stdin.on("error", () => {});
7233
+ child.once("error", reject);
7234
+ child.once("close", (code) => {
7235
+ resolve(code === 0 && !failed ? Buffer.concat(chunks).toString("utf8") : null);
7236
+ });
7237
+ child.stdin.end(JSON.stringify({ headers, maxResponseBytes, timeoutMs, url: url.href }));
7238
+ });
7239
+ }
7240
+ async function fetchDistTags(env, request) {
7241
+ try {
7242
+ const url = registryUrl(env);
7243
+ if (!url)
7244
+ return null;
7245
+ const text = await request(url, {
7246
+ headers: { accept: "application/json", "user-agent": `${PACKAGE_NAME}-cli/${CLI_VERSION}` },
7247
+ maxResponseBytes: MAX_RESPONSE_BYTES,
7248
+ timeoutMs: REGISTRY_TIMEOUT_MS
7249
+ });
7250
+ if (text === null || Buffer.byteLength(text) > MAX_RESPONSE_BYTES)
7251
+ return null;
7252
+ const body = JSON.parse(text);
7253
+ if (typeof body !== "object" || body === null || Array.isArray(body))
7254
+ return null;
7255
+ const tags = {};
7256
+ for (const [tag, version] of Object.entries(body)) {
7257
+ if (typeof version === "string")
7258
+ tags[tag] = version;
7259
+ }
7260
+ return tags;
7261
+ } catch {
7262
+ return null;
7263
+ }
7264
+ }
7265
+ function readCache(path) {
7266
+ const parsed = readJsonFile(path, MAX_CACHE_BYTES);
7267
+ if (typeof parsed !== "object" || parsed === null)
7268
+ return null;
7269
+ const entry = parsed;
7270
+ if (entry.version !== CACHE_VERSION)
7271
+ return null;
7272
+ if (typeof entry.checkedAt !== "string" || Number.isNaN(Date.parse(entry.checkedAt)))
7273
+ return null;
7274
+ return { version: CACHE_VERSION, checkedAt: entry.checkedAt };
7275
+ }
7276
+ function writeCache(path, entry) {
7277
+ writeJsonFile(path, entry);
7278
+ }
7279
+ function isFresh(entry, now) {
7280
+ const age = now.getTime() - Date.parse(entry.checkedAt);
7281
+ return age >= 0 && age < CHECK_INTERVAL_MS;
7282
+ }
7283
+ function upgradeCommand(modulePath = fileURLToPath(import.meta.url), env = process.env) {
7284
+ const target = `${PACKAGE_NAME}@latest`;
7285
+ const normalized = normalizeModulePath(modulePath);
7286
+ if (normalized.includes(".bun/install/global"))
7287
+ return `bun add -g ${target}`;
7288
+ if (normalized.includes("/pnpm/") || normalized.includes("/.pnpm/")) {
7289
+ return `pnpm add -g ${target}`;
7290
+ }
7291
+ if (normalized.includes("/.yarn/") || normalized.includes("/yarn/")) {
7292
+ return `yarn global add ${target}`;
7293
+ }
7294
+ const agent = env.npm_config_user_agent ?? "";
7295
+ if (agent.startsWith("pnpm/"))
7296
+ return `pnpm add -g ${target}`;
7297
+ if (agent.startsWith("yarn/"))
7298
+ return `yarn global add ${target}`;
7299
+ if (agent.startsWith("bun/"))
7300
+ return `bun add -g ${target}`;
7301
+ return `npm install -g ${target}`;
7302
+ }
7303
+ async function announceUpdateIfAvailable(options = {}) {
7304
+ try {
7305
+ const env = options.env ?? process.env;
7306
+ const isTty = options.isTty ?? process.stderr.isTTY === true;
7307
+ const modulePath = options.modulePath ?? fileURLToPath(import.meta.url);
7308
+ const cwd = options.cwd ?? process.cwd();
7309
+ const now = options.now ?? new Date;
7310
+ if (isEnabled(env.SIM_NO_UPDATE_CHECK))
7311
+ return;
7312
+ if (!isTty)
7313
+ return;
7314
+ if (isCi(env))
7315
+ return;
7316
+ if (isUnadvisableInstall(modulePath, env, cwd))
7317
+ return;
7318
+ const currentVersion = options.currentVersion ?? CLI_VERSION;
7319
+ const current = parseStableVersion(currentVersion);
7320
+ if (!current)
7321
+ return;
7322
+ const cachePath = updateCachePath();
7323
+ const cached = readCache(cachePath);
7324
+ if (cached && isFresh(cached, now))
7325
+ return;
7326
+ const tags = await fetchDistTags(env, options.registryRequest ?? requestRegistry);
7327
+ const latest = tags?.latest ?? null;
7328
+ const available = latest ? parseStableVersion(latest) : null;
7329
+ writeCache(cachePath, {
7330
+ version: CACHE_VERSION,
7331
+ checkedAt: now.toISOString()
7332
+ });
7333
+ if (!latest || !available)
7334
+ return;
7335
+ if (!isNewerVersion(available, current))
7336
+ return;
7337
+ const write = options.write ?? ((message) => void process.stderr.write(message));
7338
+ write(`Update available: sim ${currentVersion} → ${latest}. Run: sim update
7339
+ `);
7340
+ } catch {}
6957
7341
  }
6958
- function credentialsPath() {
6959
- return process.env.SIM_CREDENTIALS_FILE || join(configDir(), "credentials");
7342
+
7343
+ // src/update/install.ts
7344
+ class CliUpdateError extends Error {
7345
+ }
7346
+ var PACKAGE_MANAGERS = ["npm", "pnpm", "bun", "yarn"];
7347
+ var runPackageManager = (manager, args, { env, capture }) => new Promise((resolve, reject) => {
7348
+ const child = spawn2(manager, args, {
7349
+ cwd: homedir2(),
7350
+ env,
7351
+ shell: process.platform === "win32",
7352
+ stdio: ["ignore", capture ? "pipe" : process.stderr, process.stderr],
7353
+ timeout: capture ? 1e4 : 5 * 60000,
7354
+ killSignal: "SIGKILL",
7355
+ windowsHide: true
7356
+ });
7357
+ let output = "";
7358
+ child.stdout?.setEncoding("utf8").on("data", (chunk) => {
7359
+ output += chunk;
7360
+ if (Buffer.byteLength(output) > 64 * 1024) {
7361
+ child.kill("SIGKILL");
7362
+ reject(new CliUpdateError(`${manager} returned too much output while checking Sim.`));
7363
+ }
7364
+ });
7365
+ child.once("error", (error) => {
7366
+ reject(new CliUpdateError(`Could not run ${manager}: ${getErrorMessage(error)}`));
7367
+ });
7368
+ child.once("close", (code, signal) => {
7369
+ if (code !== 0) {
7370
+ reject(new CliUpdateError(`${manager} ${args.join(" ")} failed (${signal ?? `exit ${code}`}). Resolve the package-manager error and run sim update again.`));
7371
+ return;
7372
+ }
7373
+ resolve(output.trim());
7374
+ });
7375
+ });
7376
+ function parseReleaseVersion(version) {
7377
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(preview|dev)\.(0|[1-9]\d*)\.(0|[1-9]\d*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(version);
7378
+ if (version.length > 256 || !match) {
7379
+ throw new CliUpdateError(`Cannot determine the release channel for Sim ${version}.`);
7380
+ }
7381
+ const precedence = [BigInt(match[1]), BigInt(match[2]), BigInt(match[3])];
7382
+ if (match[4])
7383
+ precedence.push(BigInt(match[5]), BigInt(match[6]));
7384
+ return {
7385
+ channel: match[4] === "preview" ? "staging" : match[4] === "dev" ? "dev" : "latest",
7386
+ precedence
7387
+ };
6960
7388
  }
6961
- function updateCachePath() {
6962
- return join(configDir(), "update-check.json");
7389
+ function compareReleases(candidate, current) {
7390
+ for (const [index, component] of candidate.precedence.entries()) {
7391
+ if (component !== current.precedence[index])
7392
+ return component > current.precedence[index] ? 1 : -1;
7393
+ }
7394
+ return 0;
7395
+ }
7396
+ function resolveInstallationPath(path) {
7397
+ try {
7398
+ return realpathSync(path);
7399
+ } catch (cause) {
7400
+ throw new CliUpdateError(`Cannot access the Sim installation: ${getErrorMessage(cause)}`, {
7401
+ cause
7402
+ });
7403
+ }
7404
+ }
7405
+ function readInstalledVersion(entrypoint) {
7406
+ const manifestPath = join2(dirname2(dirname2(resolveInstallationPath(entrypoint))), "package.json");
7407
+ let manifest;
7408
+ try {
7409
+ manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
7410
+ } catch (cause) {
7411
+ throw new CliUpdateError(`Cannot read the installed Sim manifest: ${getErrorMessage(cause)}`, {
7412
+ cause
7413
+ });
7414
+ }
7415
+ if (typeof manifest !== "object" || manifest === null || !("name" in manifest) || manifest.name !== "sim" || !("version" in manifest) || typeof manifest.version !== "string") {
7416
+ throw new CliUpdateError("The installed Sim manifest must name the sim package and its version.");
7417
+ }
7418
+ return manifest.version;
7419
+ }
7420
+ function parseRegistryVersion(output, manager) {
7421
+ let version;
7422
+ try {
7423
+ if (manager === "yarn") {
7424
+ const events = output.split(/\r?\n/).filter((line) => line.trim()).map((line) => JSON.parse(line));
7425
+ const inspections = events.filter((event) => typeof event === "object" && event !== null && ("type" in event) && event.type === "inspect" && ("data" in event));
7426
+ if (inspections.length === 1)
7427
+ version = inspections[0].data;
7428
+ } else {
7429
+ version = JSON.parse(output);
7430
+ }
7431
+ } catch (cause) {
7432
+ throw new CliUpdateError(`${manager} returned invalid registry JSON.`, { cause });
7433
+ }
7434
+ if (typeof version !== "string") {
7435
+ throw new CliUpdateError(`${manager} did not resolve a single Sim release version.`);
7436
+ }
7437
+ return version;
7438
+ }
7439
+ async function installUpdate(options = {}) {
7440
+ const modulePath = resolveInstallationPath(options.modulePath ?? fileURLToPath2(import.meta.url));
7441
+ const env = omit(options.env ?? process.env, ["SIM_API_KEY"]);
7442
+ const normalized = modulePath.replaceAll("\\", "/").toLowerCase();
7443
+ if (env.npm_command === "exec" || normalized.includes("/_npx/") || normalized.includes("/bunx-") || !normalized.endsWith("/node_modules/sim/dist/index.js")) {
7444
+ throw new CliUpdateError("sim update requires a global installation. Update project dependencies with their package manager, or use sim@latest with your package runner.");
7445
+ }
7446
+ const manager = options.packageManager ?? upgradeCommand(modulePath, env).split(" ")[0];
7447
+ if (!PACKAGE_MANAGERS.includes(manager)) {
7448
+ throw new CliUpdateError("Cannot determine which package manager installed Sim.");
7449
+ }
7450
+ const packageManager = manager;
7451
+ const run = options.run ?? runPackageManager;
7452
+ const currentVersion = options.currentVersion ?? CLI_VERSION;
7453
+ const current = parseReleaseVersion(currentVersion);
7454
+ const target = current.channel;
7455
+ const write = options.write ?? ((message) => void process.stderr.write(message));
7456
+ env.SIM_NO_UPDATE_CHECK = "1";
7457
+ const locateArgs = packageManager === "bun" ? ["pm", "bin", "-g"] : packageManager === "yarn" ? ["global", "dir", "--silent"] : ["root", "-g"];
7458
+ const directory = await run(packageManager, locateArgs, { env, capture: true });
7459
+ if (!isAbsolute(directory) || /[\r\n]/.test(directory)) {
7460
+ throw new CliUpdateError(`${packageManager} did not return a valid global installation path.`);
7461
+ }
7462
+ const installedEntry = packageManager === "bun" ? join2(directory, "sim") : join2(directory, ...packageManager === "yarn" ? ["node_modules"] : [], "sim/dist/index.js");
7463
+ if (resolveInstallationPath(installedEntry) !== modulePath) {
7464
+ throw new CliUpdateError(`${packageManager} would update a different Sim installation. Use the package manager and global configuration that installed this copy, or select --package-manager.`);
7465
+ }
7466
+ const release = await import_proper_lockfile.lock(dirname2(dirname2(modulePath)), { retries: 0, realpath: false }).catch((cause) => {
7467
+ throw new CliUpdateError(`Cannot lock Sim for update: ${getErrorMessage(cause)}`, { cause });
7468
+ });
7469
+ let updateFailed = false;
7470
+ try {
7471
+ if (readInstalledVersion(installedEntry) !== currentVersion) {
7472
+ throw new CliUpdateError("The Sim installation changed while starting the update. Run sim update again.");
7473
+ }
7474
+ const version = parseRegistryVersion(await run(packageManager, [
7475
+ packageManager === "bun" || packageManager === "yarn" ? "info" : "view",
7476
+ `sim@${target}`,
7477
+ "version",
7478
+ "--json"
7479
+ ], { env, capture: true }), packageManager);
7480
+ const candidate = parseReleaseVersion(version);
7481
+ if (candidate.channel !== target) {
7482
+ throw new CliUpdateError("The registry resolved Sim to a different release channel.");
7483
+ }
7484
+ const comparison = compareReleases(candidate, current);
7485
+ if (comparison < 0) {
7486
+ throw new CliUpdateError(`Refusing to downgrade Sim ${currentVersion} to ${version}. Check your package-manager registry settings.`);
7487
+ }
7488
+ if (comparison === 0) {
7489
+ write(`Sim ${currentVersion} is already up to date.
7490
+ `);
7491
+ return;
7492
+ }
7493
+ write(`Updating Sim ${currentVersion} with ${packageManager} (sim@${version})…
7494
+ `);
7495
+ const args = packageManager === "npm" ? ["install", "-g", `sim@${version}`] : packageManager === "yarn" ? ["global", "add", `sim@${version}`] : ["add", "-g", `sim@${version}`];
7496
+ await run(packageManager, args, { env, capture: false });
7497
+ if (readInstalledVersion(installedEntry) !== version) {
7498
+ throw new CliUpdateError("The package manager did not install the expected Sim version.");
7499
+ }
7500
+ write(`Updated Sim ${currentVersion} → ${version}. The next invocation will use the new version.
7501
+ `);
7502
+ } catch (error) {
7503
+ updateFailed = true;
7504
+ throw error;
7505
+ } finally {
7506
+ await release().catch((cause) => {
7507
+ const message = `Cannot release the Sim update lock: ${getErrorMessage(cause)}`;
7508
+ if (updateFailed) {
7509
+ write(`${message}
7510
+ `);
7511
+ } else {
7512
+ throw new CliUpdateError(message, { cause });
7513
+ }
7514
+ });
7515
+ }
6963
7516
  }
6964
7517
  // src/config/profile.ts
6965
- var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
7518
+ var import_proper_lockfile2 = __toESM(require_proper_lockfile(), 1);
6966
7519
  import { AsyncLocalStorage } from "node:async_hooks";
6967
7520
  import { createHash } from "node:crypto";
6968
7521
  import {
6969
7522
  chmodSync,
6970
7523
  existsSync,
6971
- mkdirSync,
6972
- readFileSync,
6973
- renameSync,
7524
+ mkdirSync as mkdirSync2,
7525
+ readFileSync as readFileSync3,
7526
+ renameSync as renameSync2,
6974
7527
  rmSync,
6975
- writeFileSync
7528
+ writeFileSync as writeFileSync2
6976
7529
  } from "node:fs";
6977
- import { dirname } from "node:path";
7530
+ import { dirname as dirname3 } from "node:path";
6978
7531
 
6979
7532
  // src/config/ini.ts
6980
7533
  class ProfileConfigError extends Error {
@@ -7130,16 +7683,16 @@ function configSectionName(profile) {
7130
7683
  function readIni(path) {
7131
7684
  if (!existsSync(path))
7132
7685
  return { preamble: [], sections: [] };
7133
- return parseIni(readFileSync(path, "utf8"));
7686
+ return parseIni(readFileSync3(path, "utf8"));
7134
7687
  }
7135
7688
  function writeIni(path, doc, secret) {
7136
- mkdirSync(dirname(path), { recursive: true, mode: 448 });
7689
+ mkdirSync2(dirname3(path), { recursive: true, mode: 448 });
7137
7690
  const temporary = `${path}.${process.pid}.${temporaryFileSequence++}.tmp`;
7138
7691
  try {
7139
- writeFileSync(temporary, serializeIni(doc), { mode: secret ? 384 : 420 });
7692
+ writeFileSync2(temporary, serializeIni(doc), { mode: secret ? 384 : 420 });
7140
7693
  if (secret)
7141
7694
  chmodSync(temporary, 384);
7142
- renameSync(temporary, path);
7695
+ renameSync2(temporary, path);
7143
7696
  } catch (error) {
7144
7697
  rmSync(temporary, { force: true });
7145
7698
  throw error;
@@ -7301,10 +7854,10 @@ var heldLock = new AsyncLocalStorage;
7301
7854
  async function withProfileLoginLease(profile, work) {
7302
7855
  const digest = createHash("sha256").update(profile, "utf8").digest("hex");
7303
7856
  const path = `${credentialsPath()}.login-${digest}`;
7304
- mkdirSync(dirname(path), { recursive: true, mode: 448 });
7857
+ mkdirSync2(dirname3(path), { recursive: true, mode: 448 });
7305
7858
  let release;
7306
7859
  try {
7307
- release = await import_proper_lockfile.lock(path, {
7860
+ release = await import_proper_lockfile2.lock(path, {
7308
7861
  realpath: false,
7309
7862
  stale: CREDENTIALS_LOCK_STALE_MS,
7310
7863
  update: CREDENTIALS_LOCK_STALE_MS / 3,
@@ -7326,10 +7879,10 @@ async function withCredentialsLock(work) {
7326
7879
  if (heldLock.getStore())
7327
7880
  return work();
7328
7881
  const path = credentialsPath();
7329
- mkdirSync(dirname(path), { recursive: true, mode: 448 });
7882
+ mkdirSync2(dirname3(path), { recursive: true, mode: 448 });
7330
7883
  let release;
7331
7884
  try {
7332
- release = await import_proper_lockfile.lock(path, {
7885
+ release = await import_proper_lockfile2.lock(path, {
7333
7886
  realpath: false,
7334
7887
  stale: CREDENTIALS_LOCK_STALE_MS,
7335
7888
  update: CREDENTIALS_LOCK_STALE_MS / 3,
@@ -7468,17 +8021,190 @@ function resolveProfile(overrides = {}) {
7468
8021
  }
7469
8022
  };
7470
8023
  }
7471
- // src/version.ts
7472
- import { readFileSync as readFileSync2 } from "node:fs";
7473
- function readPackageVersion() {
7474
- const metadata = JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8"));
7475
- if (typeof metadata !== "object" || metadata === null || !("version" in metadata) || typeof metadata.version !== "string") {
7476
- throw new Error("CLI package metadata is missing a valid version");
7477
- }
7478
- return metadata.version;
8024
+ // ../utils/src/client-info.ts
8025
+ var CLIENT_INFO_HEADER = "x-sim-client-info";
8026
+ var SIM_SURFACES = ["web", "desktop", "cli", "sdk-js", "sdk-python"];
8027
+ var TOKEN_PATTERN = /^[A-Za-z0-9._+-]+$/;
8028
+ var OS_KEY = "os";
8029
+ var ARCH_KEY = "arch";
8030
+ var AGENT_KEY = "agent";
8031
+ var SURFACE_SET = new Set(SIM_SURFACES);
8032
+ function isToken(value) {
8033
+ return TOKEN_PATTERN.test(value);
8034
+ }
8035
+ function product(name, version) {
8036
+ if (!isToken(name))
8037
+ throw new Error(`Client info token name is not a valid token: ${name}`);
8038
+ if (version === undefined)
8039
+ return name;
8040
+ if (!isToken(version))
8041
+ throw new Error(`Client info token version is not a valid token: ${version}`);
8042
+ return `${name}/${version}`;
8043
+ }
8044
+ function formatClientInfo(info) {
8045
+ const tokens = [product(info.surface, info.version)];
8046
+ if (info.runtime)
8047
+ tokens.push(product(info.runtime.name, info.runtime.version));
8048
+ if (info.os)
8049
+ tokens.push(product(OS_KEY, info.os));
8050
+ if (info.arch)
8051
+ tokens.push(product(ARCH_KEY, info.arch));
8052
+ if (info.agent)
8053
+ tokens.push(product(AGENT_KEY, info.agent));
8054
+ return tokens.join("; ");
8055
+ }
8056
+
8057
+ // src/telemetry/coding-agent.ts
8058
+ var AGENT_NAME_PATTERN = /^[a-z0-9_-]+$/i;
8059
+ var MAX_AGENT_NAME_LENGTH = 64;
8060
+ var anyOf = (...variables) => (env) => variables.some((variable) => Boolean(env[variable]));
8061
+ var AGENT_MARKERS = [
8062
+ { name: "amp", matches: (env) => env.AGENT === "amp" || Boolean(env.AMP_CURRENT_THREAD_ID) },
8063
+ {
8064
+ name: "codex",
8065
+ matches: anyOf("CODEX_THREAD_ID", "CODEX_SANDBOX", "CODEX_CI", "CODEX_SANDBOX_NETWORK_DISABLED")
8066
+ },
8067
+ { name: "gemini-cli", matches: anyOf("GEMINI_CLI") },
8068
+ { name: "opencode", matches: anyOf("OPENCODE") },
8069
+ { name: "antigravity", matches: anyOf("ANTIGRAVITY_AGENT") },
8070
+ { name: "augment", matches: anyOf("AUGMENT_AGENT") },
8071
+ { name: "cline", matches: anyOf("CLINE_ACTIVE") },
8072
+ { name: "cowork", matches: anyOf("CLAUDE_CODE_IS_COWORK") },
8073
+ { name: "claude-code", matches: anyOf("CLAUDECODE", "CLAUDE_CODE") },
8074
+ {
8075
+ name: "cursor",
8076
+ matches: (env) => anyOf("CURSOR_AGENT", "CURSOR_TRACE_ID")(env) || env.CURSOR_EXTENSION_HOST_ROLE === "agent-exec"
8077
+ },
8078
+ { name: "warp", matches: anyOf("OZ_RUN_ID") },
8079
+ { name: "pi", matches: anyOf("PI_CODING_AGENT") },
8080
+ { name: "crush", matches: anyOf("CRUSH") }
8081
+ ];
8082
+ function declaredAgentName(value) {
8083
+ const trimmed = value?.trim().toLowerCase();
8084
+ if (!trimmed || trimmed.length > MAX_AGENT_NAME_LENGTH)
8085
+ return;
8086
+ return AGENT_NAME_PATTERN.test(trimmed) ? trimmed : undefined;
8087
+ }
8088
+ function detectCodingAgent(env = process.env) {
8089
+ const declared = declaredAgentName(env.AI_AGENT);
8090
+ if (declared)
8091
+ return declared;
8092
+ const generic = declaredAgentName(env.AGENT);
8093
+ if (generic && generic !== "1")
8094
+ return generic;
8095
+ return AGENT_MARKERS.find((marker) => marker.matches(env))?.name;
8096
+ }
8097
+
8098
+ // src/telemetry/policy.ts
8099
+ var DO_NOT_TRACK_VARIABLE = "DO_NOT_TRACK";
8100
+ var TELEMETRY_DISABLED_VARIABLE = "SIM_TELEMETRY_DISABLED";
8101
+ function telemetryStatus({ env, state, configured }) {
8102
+ if (isEnabled(env[DO_NOT_TRACK_VARIABLE]))
8103
+ return { enabled: false, reason: "do_not_track" };
8104
+ if (isEnabled(env[TELEMETRY_DISABLED_VARIABLE]))
8105
+ return { enabled: false, reason: "environment" };
8106
+ if (state.enabled === false)
8107
+ return { enabled: false, reason: "setting" };
8108
+ if (!configured)
8109
+ return { enabled: false, reason: "unconfigured" };
8110
+ return { enabled: true };
8111
+ }
8112
+
8113
+ // ../utils/src/id.ts
8114
+ function generateId() {
8115
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
8116
+ return crypto.randomUUID();
8117
+ }
8118
+ const bytes = new Uint8Array(16);
8119
+ crypto.getRandomValues(bytes);
8120
+ bytes[6] = bytes[6] & 15 | 64;
8121
+ bytes[8] = bytes[8] & 63 | 128;
8122
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
8123
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
8124
+ }
8125
+
8126
+ // src/telemetry/state.ts
8127
+ var SESSION_IDLE_MS = 30 * 60 * 1000;
8128
+ var MAX_STATE_BYTES = 4 * 1024;
8129
+ var STATE_VERSION = 1;
8130
+ var STATE_FILE_MODE = 384;
8131
+ function isIsoTimestamp(value) {
8132
+ return typeof value === "string" && !Number.isNaN(Date.parse(value));
8133
+ }
8134
+ function parseSession(value) {
8135
+ if (typeof value !== "object" || value === null)
8136
+ return;
8137
+ const session = value;
8138
+ if (typeof session.id !== "string" || !session.id)
8139
+ return;
8140
+ if (!isIsoTimestamp(session.lastActiveAt))
8141
+ return;
8142
+ if (!Number.isSafeInteger(session.sequence) || session.sequence < 0)
8143
+ return;
8144
+ return {
8145
+ id: session.id,
8146
+ lastActiveAt: session.lastActiveAt,
8147
+ sequence: session.sequence
8148
+ };
8149
+ }
8150
+ function initialTelemetryState() {
8151
+ return { version: STATE_VERSION, deviceId: generateId() };
8152
+ }
8153
+ function readTelemetryState(path = telemetryStatePath()) {
8154
+ const parsed = readJsonFile(path, MAX_STATE_BYTES);
8155
+ if (typeof parsed !== "object" || parsed === null)
8156
+ return null;
8157
+ const state = parsed;
8158
+ if (state.version !== STATE_VERSION)
8159
+ return null;
8160
+ if (typeof state.deviceId !== "string" || !state.deviceId)
8161
+ return null;
8162
+ const result = { version: STATE_VERSION, deviceId: state.deviceId };
8163
+ if (typeof state.enabled === "boolean")
8164
+ result.enabled = state.enabled;
8165
+ if (isIsoTimestamp(state.noticeShownAt))
8166
+ result.noticeShownAt = state.noticeShownAt;
8167
+ const session = parseSession(state.session);
8168
+ if (session)
8169
+ result.session = session;
8170
+ return result;
8171
+ }
8172
+ function loadTelemetryState(path = telemetryStatePath()) {
8173
+ return readTelemetryState(path) ?? initialTelemetryState();
8174
+ }
8175
+ function writeTelemetryState(state, path = telemetryStatePath()) {
8176
+ writeJsonFile(path, state, STATE_FILE_MODE);
8177
+ }
8178
+ function nextSession(state, now) {
8179
+ const current = state.session;
8180
+ const idleFor = current ? now.getTime() - Date.parse(current.lastActiveAt) : Number.NaN;
8181
+ if (current && idleFor >= 0 && idleFor < SESSION_IDLE_MS) {
8182
+ return { id: current.id, lastActiveAt: now.toISOString(), sequence: current.sequence + 1 };
8183
+ }
8184
+ return { id: generateId(), lastActiveAt: now.toISOString(), sequence: 1 };
8185
+ }
8186
+
8187
+ // src/telemetry/client-info.ts
8188
+ var cached;
8189
+ function clientInfoHeader(env = process.env) {
8190
+ if (env !== process.env)
8191
+ return buildClientInfoHeader(env);
8192
+ cached ??= buildClientInfoHeader(env);
8193
+ return cached;
8194
+ }
8195
+ function buildClientInfoHeader(env) {
8196
+ return formatClientInfo({
8197
+ surface: "cli",
8198
+ version: CLI_VERSION,
8199
+ runtime: { name: "node", version: process.versions.node },
8200
+ os: process.platform,
8201
+ arch: process.arch,
8202
+ ...reportingAllowed(env) ? { agent: detectCodingAgent(env) } : {}
8203
+ });
8204
+ }
8205
+ function reportingAllowed(env) {
8206
+ return telemetryStatus({ env, state: loadTelemetryState(), configured: true }).enabled;
7479
8207
  }
7480
- var CLI_VERSION = readPackageVersion();
7481
- var USER_AGENT = `sim-cli/${CLI_VERSION} node/${process.versions.node} (${process.platform}; ${process.arch})`;
7482
8208
 
7483
8209
  // src/http/environment.ts
7484
8210
  var reported = new Set;
@@ -7818,6 +8544,7 @@ class SimClient {
7818
8544
  ...credential?.kind === "oauth" ? { authorization: `Bearer ${credential.oauth.accessToken}` } : {},
7819
8545
  accept: "application/json",
7820
8546
  "user-agent": USER_AGENT,
8547
+ [CLIENT_INFO_HEADER]: clientInfoHeader(),
7821
8548
  ...hasBody ? { "content-type": "application/json" } : {},
7822
8549
  ...options.headers
7823
8550
  },
@@ -8466,20 +9193,18 @@ var {
8466
9193
  Help
8467
9194
  } = import__.default;
8468
9195
 
9196
+ // src/commands/update.ts
9197
+ function updateCommand() {
9198
+ return new Command("update").description("Update this global CLI installation to the newest release on its channel").addOption(new Option("--package-manager <manager>", "Package manager that installed this copy").choices(["npm", "pnpm", "bun", "yarn"])).action(async (options) => {
9199
+ await installUpdate({ packageManager: options.packageManager });
9200
+ });
9201
+ }
9202
+
8469
9203
  // src/commands/auth.ts
8470
- import { spawn } from "node:child_process";
9204
+ import { spawn as spawn3 } from "node:child_process";
8471
9205
  import { randomBytes as randomBytes3 } from "node:crypto";
8472
9206
  import { createInterface } from "node:readline/promises";
8473
9207
 
8474
- // ../utils/src/errors.ts
8475
- function getErrorMessage(value, fallback) {
8476
- if (value instanceof Error)
8477
- return value.message;
8478
- if (typeof value === "string" && value.length > 0)
8479
- return value;
8480
- return fallback ?? String(value);
8481
- }
8482
-
8483
9208
  // src/auth/device-flow.ts
8484
9209
  import { createHash as createHash3, randomBytes as randomBytes2, randomInt } from "node:crypto";
8485
9210
 
@@ -14627,7 +15352,7 @@ var MAX_INTERACTIVE_WORKSPACES = 1000;
14627
15352
  function openBrowser(url) {
14628
15353
  const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : [process.platform === "darwin" ? "open" : "xdg-open", [url]];
14629
15354
  try {
14630
- const child = spawn(command, args, { stdio: "ignore", detached: true });
15355
+ const child = spawn3(command, args, { stdio: "ignore", detached: true });
14631
15356
  child.on("error", () => {});
14632
15357
  child.unref();
14633
15358
  } catch {}
@@ -17094,7 +17819,7 @@ function attachWorkspaceOperationWait(operations) {
17094
17819
  }
17095
17820
 
17096
17821
  // src/runtime/request.ts
17097
- import { closeSync, existsSync as existsSync2, fstatSync, openSync, readSync } from "node:fs";
17822
+ import { closeSync as closeSync2, existsSync as existsSync2, fstatSync as fstatSync2, openSync as openSync2, readSync as readSync2 } from "node:fs";
17098
17823
  var PROFILE_INJECTED_FIELD = "workspaceId";
17099
17824
  function isProfileWorkspacePath(commandSpec, param) {
17100
17825
  return commandSpec.profileWorkspacePath === true && param === PROFILE_INJECTED_FIELD;
@@ -17158,7 +17883,7 @@ function readArgumentDescriptor(descriptor) {
17158
17883
  for (;; ) {
17159
17884
  let read;
17160
17885
  try {
17161
- read = readSync(descriptor, buffer, 0, buffer.length, null);
17886
+ read = readSync2(descriptor, buffer, 0, buffer.length, null);
17162
17887
  } catch (error) {
17163
17888
  const code = error.code;
17164
17889
  if (code === "EAGAIN") {
@@ -17200,13 +17925,13 @@ function readArgumentSource(raw, flagName) {
17200
17925
  }
17201
17926
  }
17202
17927
  try {
17203
- const descriptor = openSync(path, "r");
17928
+ const descriptor = openSync2(path, "r");
17204
17929
  try {
17205
- if (fstatSync(descriptor).size > MAX_JSON_ARGUMENT_BYTES)
17930
+ if (fstatSync2(descriptor).size > MAX_JSON_ARGUMENT_BYTES)
17206
17931
  throw new SimApiError("JSON input exceeds 10 MiB", 0);
17207
17932
  return { text: readArgumentDescriptor(descriptor), from: ` (read from ${path})` };
17208
17933
  } finally {
17209
- closeSync(descriptor);
17934
+ closeSync2(descriptor);
17210
17935
  }
17211
17936
  } catch (error) {
17212
17937
  throw new SimApiError(`--${flagName} cannot read ${path}: ${error.message}${literalAtHint(error, path)}`, 0);
@@ -18511,7 +19236,7 @@ Examples:
18511
19236
  import { once as once2 } from "node:events";
18512
19237
  import { createWriteStream, rmSync as rmSync2 } from "node:fs";
18513
19238
  import { link, lstat, mkdtemp, readlink, rename, rm } from "node:fs/promises";
18514
- import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
19239
+ import { dirname as dirname4, join as join3, resolve as resolve2 } from "node:path";
18515
19240
  import { Readable } from "node:stream";
18516
19241
  import { pipeline } from "node:stream/promises";
18517
19242
  function writeFailure(path, error) {
@@ -18543,7 +19268,7 @@ async function forcedPublicationTarget(target) {
18543
19268
  }
18544
19269
  if (!metadata.isSymbolicLink())
18545
19270
  return candidate;
18546
- candidate = resolve2(dirname2(candidate), await readlink(candidate));
19271
+ candidate = resolve2(dirname4(candidate), await readlink(candidate));
18547
19272
  }
18548
19273
  }
18549
19274
  function normalizedWriteFailure(target, error) {
@@ -18596,8 +19321,8 @@ async function saveStagedFile(body, target, force) {
18596
19321
  try {
18597
19322
  try {
18598
19323
  const publicationTarget = force ? await forcedPublicationTarget(target) : target;
18599
- temporaryDirectory = await mkdtemp(join2(dirname2(publicationTarget), ".sim-download-"));
18600
- const temporaryPath = join2(temporaryDirectory, "payload");
19324
+ temporaryDirectory = await mkdtemp(join3(dirname4(publicationTarget), ".sim-download-"));
19325
+ const temporaryPath = join3(temporaryDirectory, "payload");
18601
19326
  await streamToFile(body, createWriteStream(temporaryPath, { flags: "wx" }), target);
18602
19327
  if (force) {
18603
19328
  await rename(temporaryPath, publicationTarget);
@@ -18695,7 +19420,7 @@ function attachFileGet(files) {
18695
19420
  }
18696
19421
 
18697
19422
  // src/transfer/local-file.ts
18698
- import { constants } from "node:fs";
19423
+ import { constants as constants2 } from "node:fs";
18699
19424
  import { access, stat } from "node:fs/promises";
18700
19425
  import { basename } from "node:path";
18701
19426
  var CONTENT_TYPES = {
@@ -18736,7 +19461,7 @@ async function localFile(path, override) {
18736
19461
  const stats = await stat(path);
18737
19462
  if (!stats.isFile())
18738
19463
  throw new SimApiError(`${path} is not a regular file`, 0);
18739
- await access(path, constants.R_OK);
19464
+ await access(path, constants2.R_OK);
18740
19465
  size = stats.size;
18741
19466
  } catch (error) {
18742
19467
  if (error instanceof SimApiError)
@@ -18907,7 +19632,7 @@ function attachKnowledgeDocumentUpload(documents) {
18907
19632
  }
18908
19633
 
18909
19634
  // src/commands/protocol/knowledge-export.ts
18910
- import { basename as basename2, join as join3 } from "node:path";
19635
+ import { basename as basename2, join as join4 } from "node:path";
18911
19636
  function attachmentFileName(contentDisposition) {
18912
19637
  if (!contentDisposition)
18913
19638
  return null;
@@ -18949,7 +19674,7 @@ function attachKnowledgeExport(knowledge) {
18949
19674
  await streamToStdout(response.body);
18950
19675
  return;
18951
19676
  }
18952
- const target = options.outputFile ?? join3(process.cwd(), attachmentFileName(response.headers.get("content-disposition")) ?? `${knowledgeBaseId}.simkb.zip`);
19677
+ const target = options.outputFile ?? join4(process.cwd(), attachmentFileName(response.headers.get("content-disposition")) ?? `${knowledgeBaseId}.simkb.zip`);
18953
19678
  await saveToFile(response.body, target, Boolean(options.force));
18954
19679
  printProtocolResult(profile.output, {
18955
19680
  id: knowledgeBaseId,
@@ -20087,341 +20812,252 @@ async function setSecret(name, options, command, redactionSpellings) {
20087
20812
  body: {
20088
20813
  workspaceId: client.requireWorkspace(),
20089
20814
  scope: options.scope,
20090
- ...value === undefined ? {} : { value },
20091
- description,
20092
- ...unredacted === undefined ? {} : { unredacted }
20093
- }
20094
- });
20095
- renderResult("setSecret", profile.output, response.data, SECRET_RESULT);
20096
- }
20097
- function attachSecretCommands(program) {
20098
- const secrets = program.commands.find((command) => command.name() === "secrets");
20099
- if (!secrets)
20100
- throw new Error("The generated secrets command group is missing");
20101
- const redactionSpellings = new Set;
20102
- 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));
20103
- }
20104
-
20105
- // src/update/check.ts
20106
- import { spawn as spawn2 } from "node:child_process";
20107
- import {
20108
- closeSync as closeSync2,
20109
- constants as constants2,
20110
- fstatSync as fstatSync2,
20111
- lstatSync,
20112
- mkdirSync as mkdirSync2,
20113
- openSync as openSync2,
20114
- readSync as readSync2,
20115
- renameSync as renameSync2,
20116
- unlinkSync,
20117
- writeFileSync as writeFileSync2
20118
- } from "node:fs";
20119
- import { dirname as dirname3 } from "node:path";
20120
- import { fileURLToPath } from "node:url";
20121
- var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
20122
- var REGISTRY_TIMEOUT_MS = 1000;
20123
- var DEFAULT_REGISTRY = "https://registry.npmjs.org";
20124
- var PACKAGE_NAME = "sim";
20125
- var DIST_TAGS_PATH = `-/package/${PACKAGE_NAME}/dist-tags`;
20126
- var MAX_RESPONSE_BYTES = 64 * 1024;
20127
- var MAX_CACHE_BYTES = 4 * 1024;
20128
- var STABLE_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
20129
- function parseStableVersion(version) {
20130
- const match = STABLE_VERSION_PATTERN.exec(version);
20131
- if (!match)
20132
- return null;
20133
- const parsed = [Number(match[1]), Number(match[2]), Number(match[3])];
20134
- return parsed.every(Number.isSafeInteger) ? parsed : null;
20135
- }
20136
- function isNewerVersion(candidate, current) {
20137
- if (candidate[0] !== current[0])
20138
- return candidate[0] > current[0];
20139
- if (candidate[1] !== current[1])
20140
- return candidate[1] > current[1];
20141
- return candidate[2] > current[2];
20142
- }
20143
- var CI_VARIABLES = [
20144
- "CI",
20145
- "GITHUB_ACTIONS",
20146
- "JENKINS_URL",
20147
- "TEAMCITY_VERSION",
20148
- "BUILDKITE"
20149
- ];
20150
- var CACHE_VERSION = 1;
20151
- var cacheWriteSequence = 0;
20152
- function isEnabled(value) {
20153
- if (value === undefined)
20154
- return false;
20155
- const normalized = value.trim().toLowerCase();
20156
- return normalized !== "" && normalized !== "0" && normalized !== "false";
20157
- }
20158
- function isProjectLocalInstall(modulePath, cwd) {
20159
- const normalizedModulePath = normalizeModulePath(modulePath);
20160
- const nodeModulesIndex = normalizedModulePath.indexOf("/node_modules/");
20161
- if (nodeModulesIndex < 0)
20162
- return false;
20163
- const installRoot = normalizedModulePath.slice(0, nodeModulesIndex);
20164
- const workingDirectory = normalizeModulePath(cwd).replace(/\/+$/, "");
20165
- return workingDirectory === installRoot || workingDirectory.startsWith(`${installRoot}/`);
20166
- }
20167
- function isUnadvisableInstall(modulePath, env, cwd) {
20168
- const normalized = normalizeModulePath(modulePath);
20169
- return env.npm_command === "exec" || normalized.includes("/_npx/") || normalized.includes("/packages/sim-cli/") || isProjectLocalInstall(modulePath, cwd);
20170
- }
20171
- function normalizeModulePath(modulePath) {
20172
- return modulePath.replace(/\\/g, "/").toLowerCase();
20173
- }
20174
- function registryUrl(env) {
20175
- const fallback = new URL(DIST_TAGS_PATH, DEFAULT_REGISTRY);
20176
- const configured = env.npm_config_registry?.trim();
20177
- if (!configured)
20178
- return fallback;
20179
- try {
20180
- const base = new URL(configured);
20181
- if (base.protocol !== "http:" && base.protocol !== "https:")
20182
- return null;
20183
- if (base.username || base.password)
20184
- return null;
20185
- base.pathname = `${base.pathname.replace(/\/$/, "")}/${DIST_TAGS_PATH}`;
20186
- return base;
20187
- } catch {
20188
- return null;
20189
- }
20815
+ ...value === undefined ? {} : { value },
20816
+ description,
20817
+ ...unredacted === undefined ? {} : { unredacted }
20818
+ }
20819
+ });
20820
+ renderResult("setSecret", profile.output, response.data, SECRET_RESULT);
20190
20821
  }
20191
- var REGISTRY_REQUEST_SCRIPT = `
20192
- let input = ''
20193
- process.stdin.setEncoding('utf8')
20194
- for await (const chunk of process.stdin) input += chunk
20195
-
20822
+ function attachSecretCommands(program) {
20823
+ const secrets = program.commands.find((command) => command.name() === "secrets");
20824
+ if (!secrets)
20825
+ throw new Error("The generated secrets command group is missing");
20826
+ const redactionSpellings = new Set;
20827
+ 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));
20828
+ }
20829
+ // src/telemetry/transport.ts
20830
+ import { spawn as spawn4 } from "node:child_process";
20831
+ var BUILT_IN_KEY = process.env.SIM_CLI_TELEMETRY_KEY;
20832
+ var BUILT_IN_HOST = process.env.SIM_CLI_TELEMETRY_HOST;
20833
+ var DEFAULT_INGEST_HOST = "https://us.i.posthog.com";
20834
+ var CAPTURE_PATH = "/i/v0/e/";
20835
+ var SEND_TIMEOUT_MS = 5000;
20836
+ var PAYLOAD_VARIABLE = "SIM_TELEMETRY_CAPTURE";
20837
+ function builtInIngestTarget() {
20838
+ if (!BUILT_IN_KEY)
20839
+ return;
20840
+ return { key: BUILT_IN_KEY, host: BUILT_IN_HOST || DEFAULT_INGEST_HOST };
20841
+ }
20842
+ var SEND_SCRIPT = `
20196
20843
  try {
20197
- const { url, headers, maxResponseBytes, timeoutMs } = JSON.parse(input)
20844
+ const { url, body, timeoutMs } = JSON.parse(process.env[${JSON.stringify(PAYLOAD_VARIABLE)}])
20198
20845
  const deadline = setTimeout(() => process.exit(1), timeoutMs)
20199
- const response = await fetch(url, { headers, redirect: 'error' })
20200
- const declared = Number(response.headers.get('content-length'))
20201
-
20202
- if (!response.ok || !response.body || (Number.isFinite(declared) && declared > maxResponseBytes)) {
20203
- process.exit(1)
20204
- }
20205
-
20206
- const reader = response.body.getReader()
20207
- const chunks = []
20208
- let seen = 0
20209
-
20210
- while (true) {
20211
- const { done, value } = await reader.read()
20212
- if (done) break
20213
- seen += value.byteLength
20214
- if (seen > maxResponseBytes) {
20215
- process.exit(1)
20216
- }
20217
- chunks.push(Buffer.from(value))
20218
- }
20219
-
20846
+ await fetch(url, {
20847
+ method: 'POST',
20848
+ headers: { 'content-type': 'application/json' },
20849
+ body,
20850
+ redirect: 'error',
20851
+ })
20220
20852
  clearTimeout(deadline)
20221
- process.stdout.write(Buffer.concat(chunks), () => process.exit(0))
20853
+ process.exit(0)
20222
20854
  } catch {
20223
20855
  process.exit(1)
20224
20856
  }
20225
20857
  `;
20226
- function registryProcessEnv() {
20227
- const env = { ...process.env };
20228
- for (const key of Object.keys(env)) {
20229
- const normalized = key.toLowerCase();
20230
- if (normalized === "npm_config_registry" || normalized === "sim_api_key")
20231
- delete env[key];
20232
- }
20233
- return env;
20234
- }
20235
- function requestRegistry(url, { headers, maxResponseBytes, timeoutMs }) {
20236
- return new Promise((resolve, reject) => {
20237
- const proxyArguments = process.execArgv.filter((argument) => argument === "--use-env-proxy" || argument === "--no-use-env-proxy");
20238
- const child = spawn2(process.execPath, [...proxyArguments, "--input-type=module", "--eval", REGISTRY_REQUEST_SCRIPT], {
20239
- env: registryProcessEnv(),
20240
- killSignal: "SIGKILL",
20241
- stdio: ["pipe", "pipe", "ignore"],
20242
- timeout: timeoutMs,
20243
- windowsHide: true
20244
- });
20245
- const chunks = [];
20246
- let failed = false;
20247
- let seen = 0;
20248
- child.stdout.on("data", (chunk) => {
20249
- seen += chunk.byteLength;
20250
- if (seen > maxResponseBytes) {
20251
- failed = true;
20252
- child.kill("SIGKILL");
20253
- return;
20254
- }
20255
- chunks.push(chunk);
20256
- });
20257
- child.stdout.on("error", () => {
20258
- failed = true;
20259
- child.kill("SIGKILL");
20260
- });
20261
- child.stdin.on("error", () => {});
20262
- child.once("error", reject);
20263
- child.once("close", (code) => {
20264
- resolve(code === 0 && !failed ? Buffer.concat(chunks).toString("utf8") : null);
20265
- });
20266
- child.stdin.end(JSON.stringify({ headers, maxResponseBytes, timeoutMs, url: url.href }));
20858
+ function sendCapture(target, request, spawnSender = spawn4) {
20859
+ const payload = JSON.stringify({
20860
+ url: new URL(CAPTURE_PATH, target.host).href,
20861
+ body: JSON.stringify(request),
20862
+ timeoutMs: SEND_TIMEOUT_MS
20267
20863
  });
20268
- }
20269
- async function fetchDistTags(env, request) {
20270
20864
  try {
20271
- const url = registryUrl(env);
20272
- if (!url)
20273
- return null;
20274
- const text = await request(url, {
20275
- headers: { accept: "application/json", "user-agent": `${PACKAGE_NAME}-cli/${CLI_VERSION}` },
20276
- maxResponseBytes: MAX_RESPONSE_BYTES,
20277
- timeoutMs: REGISTRY_TIMEOUT_MS
20865
+ const child = spawnSender(process.execPath, [...proxyExecArgv(), "--input-type=module", "--eval", SEND_SCRIPT], {
20866
+ detached: true,
20867
+ env: childProcessEnv(["sim_api_key"], { [PAYLOAD_VARIABLE]: payload }),
20868
+ stdio: "ignore",
20869
+ windowsHide: true
20278
20870
  });
20279
- if (text === null || Buffer.byteLength(text) > MAX_RESPONSE_BYTES)
20280
- return null;
20281
- const body = JSON.parse(text);
20282
- if (typeof body !== "object" || body === null || Array.isArray(body))
20283
- return null;
20284
- const tags = {};
20285
- for (const [tag, version] of Object.entries(body)) {
20286
- if (typeof version === "string")
20287
- tags[tag] = version;
20288
- }
20289
- return tags;
20290
- } catch {
20291
- return null;
20871
+ child.once("error", () => {});
20872
+ child.unref();
20873
+ } catch {}
20874
+ }
20875
+
20876
+ // src/telemetry/invocation.ts
20877
+ var COMMAND_EVENT = "cli_command_executed";
20878
+ var LIBRARY_NAME = "sim-cli";
20879
+ var EXCLUDED_ROOT_COMMAND = "telemetry";
20880
+ var USAGE_DATA_DOCS_URL = "https://docs.sim.ai/cli/usage-data";
20881
+ var FIRST_RUN_NOTICE = [
20882
+ "Sim collects anonymous usage data to improve the CLI: which commands run, whether",
20883
+ "they succeed, and how long they take. Nothing you type is sent.",
20884
+ `Learn more: ${USAGE_DATA_DOCS_URL}`,
20885
+ "Turn it off: sim telemetry disable",
20886
+ ""
20887
+ ].join(`
20888
+ `);
20889
+ function commandPath2(command) {
20890
+ const names = [];
20891
+ for (let current = command;current?.parent; current = current.parent) {
20892
+ names.unshift(current.name());
20292
20893
  }
20894
+ return names;
20293
20895
  }
20294
- function readCache(path) {
20295
- let descriptor = null;
20296
- try {
20297
- if (!lstatSync(path).isFile())
20298
- return null;
20299
- descriptor = openSync2(path, constants2.O_RDONLY | constants2.O_NONBLOCK | constants2.O_NOFOLLOW);
20300
- const descriptorStats = fstatSync2(descriptor);
20301
- if (!descriptorStats.isFile() || descriptorStats.size > MAX_CACHE_BYTES) {
20302
- return null;
20303
- }
20304
- const buffer = Buffer.allocUnsafe(MAX_CACHE_BYTES + 1);
20305
- let bytesRead = 0;
20306
- while (bytesRead < buffer.byteLength) {
20307
- const count = readSync2(descriptor, buffer, bytesRead, buffer.byteLength - bytesRead, bytesRead);
20308
- if (count === 0)
20309
- break;
20310
- bytesRead += count;
20311
- }
20312
- if (bytesRead > MAX_CACHE_BYTES)
20313
- return null;
20314
- const parsed = JSON.parse(buffer.subarray(0, bytesRead).toString("utf8"));
20315
- if (typeof parsed !== "object" || parsed === null)
20316
- return null;
20317
- const entry = parsed;
20318
- if (entry.version !== CACHE_VERSION)
20319
- return null;
20320
- if (typeof entry.checkedAt !== "string" || Number.isNaN(Date.parse(entry.checkedAt)))
20321
- return null;
20322
- return {
20323
- version: CACHE_VERSION,
20324
- checkedAt: entry.checkedAt
20325
- };
20326
- } catch {
20327
- return null;
20328
- } finally {
20329
- if (descriptor !== null) {
20330
- try {
20331
- closeSync2(descriptor);
20332
- } catch {}
20896
+ function typedFlags(command) {
20897
+ const flags = new Set;
20898
+ for (let current = command;current; current = current.parent) {
20899
+ for (const option of current.options) {
20900
+ if (current.getOptionValueSource(option.attributeName()) !== "cli")
20901
+ continue;
20902
+ const name = option.long ?? option.short;
20903
+ if (name)
20904
+ flags.add(name);
20333
20905
  }
20334
20906
  }
20907
+ return [...flags];
20335
20908
  }
20336
- function writeCache(path, entry) {
20337
- let descriptor = null;
20338
- let temporaryCreated = false;
20339
- const temporaryPath = `${path}.${process.pid}.${Date.now()}.${cacheWriteSequence++}.tmp`;
20909
+ function endpointKind(command) {
20340
20910
  try {
20341
- mkdirSync2(dirname3(path), { recursive: true, mode: 448 });
20342
- descriptor = openSync2(temporaryPath, "wx", 420);
20343
- temporaryCreated = true;
20344
- writeFileSync2(descriptor, `${JSON.stringify(entry, null, 2)}
20345
- `);
20346
- closeSync2(descriptor);
20347
- descriptor = null;
20348
- renameSync2(temporaryPath, path);
20349
- temporaryCreated = false;
20350
- } catch {} finally {
20351
- if (descriptor !== null) {
20352
- try {
20353
- closeSync2(descriptor);
20354
- } catch {}
20355
- }
20356
- if (temporaryCreated) {
20357
- try {
20358
- unlinkSync(temporaryPath);
20359
- } catch {}
20360
- }
20911
+ const hostname = new URL(profileFrom(command).endpoint).hostname;
20912
+ return hostname === "sim.ai" || hostname.endsWith(".sim.ai") ? "hosted" : "self_hosted";
20913
+ } catch {
20914
+ return;
20361
20915
  }
20362
20916
  }
20363
- function isFresh(entry, now) {
20364
- const age = now.getTime() - Date.parse(entry.checkedAt);
20365
- return age >= 0 && age < CHECK_INTERVAL_MS;
20366
- }
20367
- function upgradeCommand(modulePath = fileURLToPath(import.meta.url), env = process.env) {
20368
- const target = `${PACKAGE_NAME}@latest`;
20369
- const normalized = normalizeModulePath(modulePath);
20370
- if (normalized.includes(".bun/install/global"))
20371
- return `bun add -g ${target}`;
20372
- if (normalized.includes("/pnpm/") || normalized.includes("/.pnpm/")) {
20373
- return `pnpm add -g ${target}`;
20374
- }
20375
- if (normalized.includes("/.yarn/") || normalized.includes("/yarn/")) {
20376
- return `yarn global add ${target}`;
20917
+ function failureProperties(error) {
20918
+ if (error === undefined)
20919
+ return {};
20920
+ if (!(error instanceof Error))
20921
+ return { error_name: "unknown" };
20922
+ const properties = { error_name: error.name };
20923
+ if (error instanceof SimApiError) {
20924
+ if (error.code)
20925
+ properties.error_code = error.code;
20926
+ if (error.status > 0)
20927
+ properties.http_status = error.status;
20928
+ }
20929
+ return properties;
20930
+ }
20931
+ var listenForProcessExit = (listener) => {
20932
+ process.once("exit", listener);
20933
+ };
20934
+ function createCommandTelemetry(options = {}) {
20935
+ const env = options.env ?? process.env;
20936
+ const ingestTarget = options.ingestTarget ?? builtInIngestTarget;
20937
+ const send = options.send ?? sendCapture;
20938
+ const now = options.now ?? (() => new Date);
20939
+ const elapsed = options.elapsed ?? (() => performance.now());
20940
+ const stdoutIsTty = options.stdoutIsTty ?? process.stdout.isTTY === true;
20941
+ const stderrIsTty = options.stderrIsTty ?? process.stderr.isTTY === true;
20942
+ const write = options.write ?? ((message) => void process.stderr.write(message));
20943
+ const onExit = options.onExit ?? listenForProcessExit;
20944
+ let recorded;
20945
+ function isReportable(state) {
20946
+ return telemetryStatus({ env, state, configured: ingestTarget() !== undefined }).enabled;
20947
+ }
20948
+ function showNoticeIfDue(state) {
20949
+ if (state.noticeShownAt || !stderrIsTty || isCi(env))
20950
+ return false;
20951
+ write(FIRST_RUN_NOTICE);
20952
+ writeTelemetryState({ ...state, noticeShownAt: now().toISOString() });
20953
+ return true;
20377
20954
  }
20378
- const agent = env.npm_config_user_agent ?? "";
20379
- if (agent.startsWith("pnpm/"))
20380
- return `pnpm add -g ${target}`;
20381
- if (agent.startsWith("yarn/"))
20382
- return `yarn global add ${target}`;
20383
- if (agent.startsWith("bun/"))
20384
- return `bun add -g ${target}`;
20385
- return `npm install -g ${target}`;
20386
- }
20387
- async function announceUpdateIfAvailable(options = {}) {
20388
- try {
20389
- const env = options.env ?? process.env;
20390
- const isTty = options.isTty ?? process.stderr.isTTY === true;
20391
- const modulePath = options.modulePath ?? fileURLToPath(import.meta.url);
20392
- const cwd = options.cwd ?? process.cwd();
20393
- const now = options.now ?? new Date;
20394
- if (isEnabled(env.SIM_NO_UPDATE_CHECK))
20395
- return;
20396
- if (!isTty)
20397
- return;
20398
- if (CI_VARIABLES.some((variable) => isEnabled(env[variable])))
20399
- return;
20400
- if (isUnadvisableInstall(modulePath, env, cwd))
20955
+ function complete(outcome) {
20956
+ const invocation = recorded;
20957
+ recorded = undefined;
20958
+ if (!invocation || invocation.noticeShown)
20401
20959
  return;
20402
- const currentVersion = options.currentVersion ?? CLI_VERSION;
20403
- const current = parseStableVersion(currentVersion);
20404
- if (!current)
20960
+ const target = ingestTarget();
20961
+ if (!target)
20405
20962
  return;
20406
- const cachePath = updateCachePath();
20407
- const cached = readCache(cachePath);
20408
- if (cached && isFresh(cached, now))
20963
+ const state = loadTelemetryState();
20964
+ if (!isReportable(state))
20409
20965
  return;
20410
- const tags = await fetchDistTags(env, options.registryRequest ?? requestRegistry);
20411
- const latest = tags?.latest ?? null;
20412
- const available = latest ? parseStableVersion(latest) : null;
20413
- writeCache(cachePath, {
20414
- version: CACHE_VERSION,
20415
- checkedAt: now.toISOString()
20966
+ const timestamp = now();
20967
+ const session = nextSession(state, timestamp);
20968
+ writeTelemetryState({ ...state, session });
20969
+ const properties = {
20970
+ $lib: LIBRARY_NAME,
20971
+ $lib_version: CLI_VERSION,
20972
+ $process_person_profile: false,
20973
+ $session_id: session.id,
20974
+ session_sequence: session.sequence,
20975
+ surface: "cli",
20976
+ command: invocation.command,
20977
+ flags: invocation.flags,
20978
+ arg_count: invocation.argCount,
20979
+ exit_code: outcome.exitCode,
20980
+ duration_ms: Math.round(elapsed()),
20981
+ ...failureProperties(outcome.error),
20982
+ cli_version: CLI_VERSION,
20983
+ node_version: process.versions.node,
20984
+ os: process.platform,
20985
+ arch: process.arch,
20986
+ is_tty: stdoutIsTty,
20987
+ is_ci: isCi(env)
20988
+ };
20989
+ const kind = endpointKind(invocation.action);
20990
+ if (kind)
20991
+ properties.endpoint_kind = kind;
20992
+ const agent = detectCodingAgent(env);
20993
+ if (agent)
20994
+ properties.coding_agent = agent;
20995
+ send(target, {
20996
+ api_key: target.key,
20997
+ event: COMMAND_EVENT,
20998
+ distinct_id: state.deviceId,
20999
+ timestamp: timestamp.toISOString(),
21000
+ properties
20416
21001
  });
20417
- if (!latest || !available)
20418
- return;
20419
- if (!isNewerVersion(available, current))
20420
- return;
20421
- const write = options.write ?? ((message) => void process.stderr.write(message));
20422
- write(`Update available: sim ${currentVersion} → ${latest}. Run: ${upgradeCommand(modulePath, env)}
20423
- `);
20424
- } catch {}
21002
+ }
21003
+ return {
21004
+ observe(program) {
21005
+ program.hook("preAction", (_root, action) => {
21006
+ const path = commandPath2(action);
21007
+ if (path[0] === EXCLUDED_ROOT_COMMAND || !isReportable({}))
21008
+ return;
21009
+ const state = loadTelemetryState();
21010
+ if (!isReportable(state))
21011
+ return;
21012
+ recorded = {
21013
+ action,
21014
+ command: path.join(" "),
21015
+ flags: typedFlags(action),
21016
+ argCount: action.args.length,
21017
+ state,
21018
+ noticeShown: showNoticeIfDue(state)
21019
+ };
21020
+ });
21021
+ onExit((exitCode) => complete({ exitCode }));
21022
+ },
21023
+ complete
21024
+ };
21025
+ }
21026
+ // src/commands/telemetry.ts
21027
+ function describe(status) {
21028
+ if (status.enabled)
21029
+ return "Usage reporting is on.";
21030
+ switch (status.reason) {
21031
+ case "do_not_track":
21032
+ return `Usage reporting is off: ${DO_NOT_TRACK_VARIABLE} is set.`;
21033
+ case "environment":
21034
+ return `Usage reporting is off: ${TELEMETRY_DISABLED_VARIABLE} is set.`;
21035
+ case "setting":
21036
+ return "Usage reporting is off. Turn it on with: sim telemetry enable";
21037
+ case "unconfigured":
21038
+ return "Usage reporting is off: this build has no reporting destination.";
21039
+ }
21040
+ }
21041
+ function currentStatus() {
21042
+ return telemetryStatus({
21043
+ env: process.env,
21044
+ state: loadTelemetryState(),
21045
+ configured: builtInIngestTarget() !== undefined
21046
+ });
21047
+ }
21048
+ function setEnabled(enabled) {
21049
+ writeTelemetryState({ ...loadTelemetryState(), enabled });
21050
+ console.log(describe(currentStatus()));
21051
+ }
21052
+ function telemetryCommand() {
21053
+ const telemetry = new Command("telemetry").description("Control anonymous usage reporting");
21054
+ telemetry.command("status").description("Show whether usage reporting is on, and why not if it is off").action(() => {
21055
+ console.log(describe(currentStatus()));
21056
+ console.log(`Learn more: ${USAGE_DATA_DOCS_URL}`);
21057
+ });
21058
+ telemetry.command("enable").description("Turn usage reporting on for this machine").action(() => setEnabled(true));
21059
+ telemetry.command("disable").description("Turn usage reporting off for this machine").action(() => setEnabled(false));
21060
+ return telemetry;
20425
21061
  }
20426
21062
 
20427
21063
  // src/program.ts
@@ -20497,6 +21133,9 @@ function buildProgram(options = {}) {
20497
21133
  program.addCommand(whoamiCommand());
20498
21134
  program.addCommand(profilesCommand());
20499
21135
  program.addCommand(configureCommand());
21136
+ const update = updateCommand();
21137
+ program.addCommand(update);
21138
+ program.addCommand(telemetryCommand());
20500
21139
  for (const command of buildGeneratedCommands()) {
20501
21140
  program.addCommand(command);
20502
21141
  }
@@ -20504,54 +21143,67 @@ function buildProgram(options = {}) {
20504
21143
  attachProtocolCommands(program);
20505
21144
  attachSecretCommands(program);
20506
21145
  program.addHelpText("after", HELP_EPILOGUE);
20507
- program.hook("preAction", () => announceUpdateIfAvailable());
21146
+ program.hook("preAction", async (_program, command) => {
21147
+ if (command === update)
21148
+ return;
21149
+ await announceUpdateIfAvailable();
21150
+ });
20508
21151
  refuseHelpAfterUnknownCommand(program);
20509
21152
  assertNoReservedProgramFlags(program);
20510
21153
  return program;
20511
21154
  }
20512
21155
 
20513
21156
  // src/index.ts
21157
+ function explainFailure(error, program) {
21158
+ if (error instanceof ProfileConfigError || error instanceof CliUpdateError) {
21159
+ console.error(source_default.red(`Error: ${sanitize(error.message)}`));
21160
+ return 1;
21161
+ }
21162
+ if (isRequestTimeout(error)) {
21163
+ console.error(source_default.red(`Error: the request timed out. ${RAISE_TIMEOUT_HINT}`));
21164
+ return 1;
21165
+ }
21166
+ if (error instanceof SimApiError) {
21167
+ let output = program.opts().output;
21168
+ try {
21169
+ output = clientFrom(program).profile.output;
21170
+ } catch {}
21171
+ if (output === "json" || output === "yaml") {
21172
+ const payload = {
21173
+ error: {
21174
+ code: error.code ?? "CLI_ERROR",
21175
+ message: error.message,
21176
+ ...error.details === undefined ? {} : { details: error.details }
21177
+ }
21178
+ };
21179
+ process.stderr.write(output === "json" ? `${JSON.stringify(payload)}
21180
+ ` : dump(payload));
21181
+ return error.exitCode;
21182
+ }
21183
+ console.error(source_default.red(`Error: ${sanitize(error.message)}`));
21184
+ if (error.code)
21185
+ console.error(source_default.dim(` code: ${sanitize(error.code)}`));
21186
+ if (error.details !== undefined) {
21187
+ for (const line of formatApiErrorDetails(error.details)) {
21188
+ console.error(source_default.dim(sanitize(line)));
21189
+ }
21190
+ }
21191
+ return error.exitCode;
21192
+ }
21193
+ return null;
21194
+ }
20514
21195
  async function main() {
21196
+ const telemetry = createCommandTelemetry();
20515
21197
  const program = buildProgram();
21198
+ telemetry.observe(program);
20516
21199
  try {
20517
21200
  await program.parseAsync(process.argv);
20518
21201
  } catch (error) {
20519
- if (error instanceof ProfileConfigError) {
20520
- console.error(source_default.red(`Error: ${sanitize(error.message)}`));
20521
- process.exit(1);
20522
- }
20523
- if (isRequestTimeout(error)) {
20524
- console.error(source_default.red(`Error: the request timed out. ${RAISE_TIMEOUT_HINT}`));
20525
- process.exit(1);
20526
- }
20527
- if (error instanceof SimApiError) {
20528
- let output = program.opts().output;
20529
- try {
20530
- output = clientFrom(program).profile.output;
20531
- } catch {}
20532
- if (output === "json" || output === "yaml") {
20533
- const payload = {
20534
- error: {
20535
- code: error.code ?? "CLI_ERROR",
20536
- message: error.message,
20537
- ...error.details === undefined ? {} : { details: error.details }
20538
- }
20539
- };
20540
- process.stderr.write(output === "json" ? `${JSON.stringify(payload)}
20541
- ` : dump(payload));
20542
- process.exit(error.exitCode);
20543
- }
20544
- console.error(source_default.red(`Error: ${sanitize(error.message)}`));
20545
- if (error.code)
20546
- console.error(source_default.dim(` code: ${sanitize(error.code)}`));
20547
- if (error.details !== undefined) {
20548
- for (const line of formatApiErrorDetails(error.details)) {
20549
- console.error(source_default.dim(sanitize(line)));
20550
- }
20551
- }
20552
- process.exit(error.exitCode);
20553
- }
20554
- throw error;
21202
+ const exitCode = explainFailure(error, program);
21203
+ telemetry.complete({ exitCode: exitCode ?? 1, error });
21204
+ if (exitCode === null)
21205
+ throw error;
21206
+ process.exit(exitCode);
20555
21207
  }
20556
21208
  }
20557
21209
  main();