sootsim 0.1.200 → 0.1.202

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 (151) hide show
  1. package/dist-cli/bin.js +5 -4
  2. package/dist-cli/chunks/{agent-RJBLYNGF.js → agent-SP6EE5XV.js} +2 -2
  3. package/dist-cli/chunks/{agent-wrapper-E6WLI6L3.js → agent-wrapper-PWZVJ3OK.js} +2 -2
  4. package/dist-cli/chunks/{app-fonts-O3VYBYQW.js → app-fonts-EMJB6QLM.js} +2 -2
  5. package/dist-cli/chunks/{assert-VFBTT2JB.js → assert-H2B6TDXT.js} +2 -2
  6. package/dist-cli/chunks/{auth-DQ6AJU2X.js → auth-SOHXBYLQ.js} +2 -2
  7. package/dist-cli/chunks/beta-CCNQQ73A.js +2 -0
  8. package/dist-cli/chunks/{camera-C2OFA3JA.js → camera-ULLCBM5H.js} +2 -2
  9. package/dist-cli/chunks/{chunk-UGJFYNMX.js → chunk-2CID4LNK.js} +3 -3
  10. package/dist-cli/chunks/chunk-2V43M57C.js +2 -0
  11. package/dist-cli/chunks/{chunk-GKXPIE6X.js → chunk-3ZENZOIX.js} +2 -2
  12. package/dist-cli/chunks/{chunk-6RHOWIGL.js → chunk-4KT2BGQE.js} +1 -1
  13. package/dist-cli/chunks/{chunk-P4PCUR4Y.js → chunk-4KVSVFRF.js} +1 -1
  14. package/dist-cli/chunks/{chunk-VACKEJ3E.js → chunk-4NM6JBYH.js} +1 -1
  15. package/dist-cli/chunks/chunk-5J63S2NM.js +1 -0
  16. package/dist-cli/chunks/{chunk-7BS4AKZT.js → chunk-5SKKMM7E.js} +1 -1
  17. package/dist-cli/chunks/chunk-6FZ6SNVP.js +4 -0
  18. package/dist-cli/chunks/{chunk-3BEGOL5T.js → chunk-6QFVYLX6.js} +2 -2
  19. package/dist-cli/chunks/{chunk-IJ6DRL7N.js → chunk-AAQVPSIV.js} +2 -2
  20. package/dist-cli/chunks/{chunk-XF3JBBPU.js → chunk-B6JB4THC.js} +2 -2
  21. package/dist-cli/chunks/{chunk-JJOS6L5H.js → chunk-BZ7AW7ZB.js} +2 -2
  22. package/dist-cli/chunks/{chunk-OCA5CK4S.js → chunk-CB7LDZKJ.js} +3 -3
  23. package/dist-cli/chunks/{chunk-6JKBWHIO.js → chunk-CGJ2U6ST.js} +1 -1
  24. package/dist-cli/chunks/chunk-DKBKU55D.js +2 -0
  25. package/dist-cli/chunks/{chunk-QSWT3OVG.js → chunk-DRJ4YWUG.js} +1 -1
  26. package/dist-cli/chunks/{chunk-XAIDHR55.js → chunk-EMOZJ773.js} +2 -2
  27. package/dist-cli/chunks/{chunk-KFGBNAV3.js → chunk-ENXHZGSL.js} +1 -1
  28. package/dist-cli/chunks/{chunk-I6DJFJQ6.js → chunk-F2LLN4U7.js} +2 -2
  29. package/dist-cli/chunks/{chunk-3PAUJH34.js → chunk-GUYKLSTX.js} +1 -1
  30. package/dist-cli/chunks/chunk-GZKT53SA.js +7 -0
  31. package/dist-cli/chunks/{chunk-DWFS6IIT.js → chunk-HZYHSLNH.js} +2 -2
  32. package/dist-cli/chunks/chunk-I6O7NBPZ.js +1 -0
  33. package/dist-cli/chunks/{chunk-U5O3F3NB.js → chunk-JP7J325R.js} +2 -2
  34. package/dist-cli/chunks/{chunk-5SB3N45C.js → chunk-K3UBHPVP.js} +1 -1
  35. package/dist-cli/chunks/{chunk-VFSOKG53.js → chunk-KRGHRSKY.js} +2 -2
  36. package/dist-cli/chunks/{chunk-VP4Y767U.js → chunk-KYGUR727.js} +2 -2
  37. package/dist-cli/chunks/{chunk-2E4DUZEV.js → chunk-LNDRRZP3.js} +2 -2
  38. package/dist-cli/chunks/{chunk-NEQRFWA3.js → chunk-OG6TFGYF.js} +2 -2
  39. package/dist-cli/chunks/{chunk-PJAKGC4A.js → chunk-PL4GXELT.js} +1 -1
  40. package/dist-cli/chunks/{chunk-MZLG4WVE.js → chunk-RJVIUQK4.js} +1 -1
  41. package/dist-cli/chunks/{chunk-4RUK2YSF.js → chunk-ROYBEBG2.js} +1 -1
  42. package/dist-cli/chunks/{chunk-42G5ZRQR.js → chunk-RS7QLHEH.js} +2 -2
  43. package/dist-cli/chunks/{chunk-LSDJTPOY.js → chunk-RWXCSRNE.js} +2 -2
  44. package/dist-cli/chunks/{chunk-NX4VSQD5.js → chunk-RZQO4Q7E.js} +1 -1
  45. package/dist-cli/chunks/{chunk-SFUQJU5Q.js → chunk-SQBX2HS7.js} +6 -6
  46. package/dist-cli/chunks/{chunk-NK4FF37S.js → chunk-TANTCV45.js} +2 -2
  47. package/dist-cli/chunks/chunk-W4Y3JJX6.js +29 -0
  48. package/dist-cli/chunks/chunk-WILIVPUU.js +567 -0
  49. package/dist-cli/chunks/{chunk-GMBG6JLV.js → chunk-WVDWIXPH.js} +2 -2
  50. package/dist-cli/chunks/{chunk-HGBMGQGP.js → chunk-Z5VMWFCY.js} +1 -1
  51. package/dist-cli/chunks/cli-version-Z4NGLJLH.js +2 -0
  52. package/dist-cli/chunks/{compat-L2E3OQW3.js → compat-YW3OJ7RX.js} +3 -3
  53. package/dist-cli/chunks/{config-K4IMJRHG.js → config-ZGOXFEX4.js} +2 -2
  54. package/dist-cli/chunks/{control-OXJ7S7UG.js → control-75JRFOYV.js} +2 -2
  55. package/dist-cli/chunks/daemon-SHFBQRHD.js +2 -0
  56. package/dist-cli/chunks/{debug-ZPKRTLGX.js → debug-4APEP64L.js} +3 -3
  57. package/dist-cli/chunks/{desktop-YA2UL7Q4.js → desktop-CS7S4CXK.js} +3 -3
  58. package/dist-cli/chunks/detox-7R7DOKQM.js +50 -0
  59. package/dist-cli/chunks/{device-M6YG7TKY.js → device-EIDKLBEK.js} +2 -2
  60. package/dist-cli/chunks/{diagnose-3OUFTELV.js → diagnose-J2F7MJAJ.js} +2 -2
  61. package/dist-cli/chunks/drivers-55L7BHDF.js +2 -0
  62. package/dist-cli/chunks/flow-AHWVTHJP.js +2 -0
  63. package/dist-cli/chunks/help-C4BTMPEE.js +2 -0
  64. package/dist-cli/chunks/home-paths-WVCREWI7.js +2 -0
  65. package/dist-cli/chunks/inspect-M4Z4SSSB.js +2 -0
  66. package/dist-cli/chunks/install-desktop-PVRUAEMA.js +2 -0
  67. package/dist-cli/chunks/{login-RNYBOMNF.js → login-EOBJ7CZ4.js} +4 -4
  68. package/dist-cli/chunks/{logout-XBW3BXL4.js → logout-RL7D66VQ.js} +2 -2
  69. package/dist-cli/chunks/maestro-F66ZIJKA.js +96 -0
  70. package/dist-cli/chunks/{mode-E353E6HN.js → mode-R2VIW66U.js} +2 -2
  71. package/dist-cli/chunks/optional-demo-registry-ILX5YV34.js +2 -0
  72. package/dist-cli/chunks/{perf-CIOBGFHU.js → perf-PGYO5BBR.js} +2 -2
  73. package/dist-cli/chunks/{record-I626AYJ5.js → record-IGDVHC67.js} +3 -3
  74. package/dist-cli/chunks/runtime-43RJGUGS.js +2 -0
  75. package/dist-cli/chunks/{screenshot-command-TTB5OOL2.js → screenshot-command-HX4GVUJM.js} +2 -2
  76. package/dist-cli/chunks/{serve-AEBEXTTW.js → serve-3TFVHYLY.js} +18 -18
  77. package/dist-cli/chunks/{setup-7QCI53ZK.js → setup-JSPPRVET.js} +3 -3
  78. package/dist-cli/chunks/{skills-7LHL5KHW.js → skills-LK3B76W7.js} +2 -2
  79. package/dist-cli/chunks/{storage-BVWLYY23.js → storage-NC5RNBUA.js} +2 -2
  80. package/dist-cli/chunks/store-3XMEKA6H.js +2 -0
  81. package/dist-cli/chunks/telemetry-CVJ5YZNS.js +2 -0
  82. package/dist-cli/chunks/test-2CXPLH5N.js +69 -0
  83. package/dist-cli/chunks/{timeline-BVR4E6PY.js → timeline-WDTEVELN.js} +2 -2
  84. package/dist-cli/chunks/upgrade-TT2IJSNJ.js +4 -0
  85. package/dist-cli/chunks/upload-4HMNB4HA.js +2 -0
  86. package/dist-cli/chunks/version-JUNH4FOR.js +4 -0
  87. package/dist-cli/chunks/{web-HYLNFAXS.js → web-BWCW26ZA.js} +2 -2
  88. package/dist-cli/chunks/{what-happened-YGRE2NFG.js → what-happened-L6EEB6MB.js} +2 -2
  89. package/dist-lib/agent-daemon-client.cjs +1 -1
  90. package/dist-lib/agent-events.cjs +1 -1
  91. package/dist-lib/agent-identity.cjs +1 -1
  92. package/dist-lib/agent-sessions.cjs +1 -1
  93. package/dist-lib/attached-projects.cjs +1 -1
  94. package/dist-lib/auth/shared-session.cjs +1 -1
  95. package/dist-lib/backend-origin.cjs +1 -1
  96. package/dist-lib/beta.cjs +1 -1
  97. package/dist-lib/beta.mjs +1 -1
  98. package/dist-lib/bridge-constants.cjs +1 -1
  99. package/dist-lib/cli-constants.cjs +1 -1
  100. package/dist-lib/config.cjs +1 -1
  101. package/dist-lib/detox/index.cjs +1 -1
  102. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  103. package/dist-lib/home-paths.cjs +32 -1
  104. package/dist-lib/host/bridge-host.cjs +51 -2
  105. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  106. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  107. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  108. package/dist-lib/host/websocket-proxy.cjs +1 -1
  109. package/dist-lib/index.cjs +1 -1
  110. package/dist-lib/jump-to-source-babel.cjs +1 -1
  111. package/dist-lib/menu.cjs +1 -1
  112. package/dist-lib/menu.mjs +1 -1
  113. package/dist-lib/metro.cjs +1 -1
  114. package/dist-lib/profiles.cjs +1 -1
  115. package/dist-lib/render-mode.cjs +1 -1
  116. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  117. package/dist-lib/sdk.cjs +1 -1
  118. package/dist-lib/sdk.mjs +1 -1
  119. package/dist-lib/skills.cjs +115 -36
  120. package/dist-lib/vite.cjs +1 -1
  121. package/package.json +1 -1
  122. package/src/home-paths.ts +48 -1
  123. package/src/host/bridge-host.ts +12 -0
  124. package/src/runtime-delivery.ts +59 -9
  125. package/src/screenshots/frame-compose.ts +21 -13
  126. package/dist-cli/chunks/beta-ZXPAAXGE.js +0 -2
  127. package/dist-cli/chunks/chunk-CB3I3VQT.js +0 -6
  128. package/dist-cli/chunks/chunk-EY2OMKPT.js +0 -1
  129. package/dist-cli/chunks/chunk-M3AUV33V.js +0 -4
  130. package/dist-cli/chunks/chunk-RJ6WD7XJ.js +0 -567
  131. package/dist-cli/chunks/chunk-TMXLUWD2.js +0 -27
  132. package/dist-cli/chunks/chunk-USDJA22M.js +0 -1
  133. package/dist-cli/chunks/chunk-VXBWUAYL.js +0 -2
  134. package/dist-cli/chunks/cli-version-E3OGD447.js +0 -2
  135. package/dist-cli/chunks/daemon-G5GBYBJ4.js +0 -2
  136. package/dist-cli/chunks/detox-LKEZBGH5.js +0 -49
  137. package/dist-cli/chunks/drivers-CTSUKCFK.js +0 -2
  138. package/dist-cli/chunks/flow-R5GJJLLW.js +0 -2
  139. package/dist-cli/chunks/help-CSSCFOBR.js +0 -2
  140. package/dist-cli/chunks/home-paths-4V6CGISP.js +0 -2
  141. package/dist-cli/chunks/inspect-NIBEAJND.js +0 -2
  142. package/dist-cli/chunks/install-desktop-7TYE6TTP.js +0 -2
  143. package/dist-cli/chunks/maestro-3EBOG357.js +0 -96
  144. package/dist-cli/chunks/optional-demo-registry-BEUP6OEZ.js +0 -2
  145. package/dist-cli/chunks/runtime-254BNZHB.js +0 -2
  146. package/dist-cli/chunks/store-6KAKNNCL.js +0 -2
  147. package/dist-cli/chunks/telemetry-EZ42CD35.js +0 -2
  148. package/dist-cli/chunks/test-BK4GZZR2.js +0 -69
  149. package/dist-cli/chunks/upgrade-AY5XC5QI.js +0 -4
  150. package/dist-cli/chunks/upload-HOF4XBAG.js +0 -2
  151. package/dist-cli/chunks/version-WQM34S4E.js +0 -4
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.200 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.202 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -10635,6 +10635,16 @@ function writeSharedConfig(patch) {
10635
10635
  import_node_fs2.default.renameSync(tmp, configFilePath());
10636
10636
  return next;
10637
10637
  }
10638
+ function runtimeUpgradeNoticePath() {
10639
+ return import_node_path.default.join(sootsimHomeDir(), "runtime-upgraded.json");
10640
+ }
10641
+ function writeRuntimeUpgradeNotice(notice) {
10642
+ ensureSootsimHome();
10643
+ const tmp = `${runtimeUpgradeNoticePath()}.tmp`;
10644
+ import_node_fs2.default.writeFileSync(tmp, `${JSON.stringify(notice)}
10645
+ `, "utf8");
10646
+ import_node_fs2.default.renameSync(tmp, runtimeUpgradeNoticePath());
10647
+ }
10638
10648
  function ensureSootsimHome() {
10639
10649
  import_node_fs2.default.mkdirSync(sootsimHomeDir(), { recursive: true });
10640
10650
  import_node_fs2.default.mkdirSync(runtimesDir(), { recursive: true });
@@ -10856,6 +10866,12 @@ function resolveRuntimeChannel(input) {
10856
10866
  const config = readConfig();
10857
10867
  return input || process.env[RUNTIME_CHANNEL_ENV] || config.runtimeChannel || "stable";
10858
10868
  }
10869
+ function resolveRuntimePin() {
10870
+ const env = process.env[RUNTIME_PIN_ENV];
10871
+ if (env && env.trim()) return env.trim();
10872
+ const config = readConfig();
10873
+ return typeof config.runtimeVersion === "string" && config.runtimeVersion.trim() ? config.runtimeVersion.trim() : null;
10874
+ }
10859
10875
  function runtimeManifestUrl(cdnOrigin) {
10860
10876
  const url = new URL(`${resolveRuntimeCdnOrigin(cdnOrigin)}/runtimes/manifest.json`);
10861
10877
  url.searchParams.set("t", String(Date.now()));
@@ -10874,7 +10890,7 @@ async function fetchRuntimeManifest(cdnOrigin) {
10874
10890
  }
10875
10891
  function resolveRuntimeVersion(manifest, opts = {}) {
10876
10892
  const channel = resolveRuntimeChannel(opts.channel);
10877
- const version = opts.version || manifest.channels[channel]?.latest;
10893
+ const version = opts.version || resolveRuntimePin() || manifest.channels[channel]?.latest;
10878
10894
  if (!version) {
10879
10895
  throw new Error(
10880
10896
  `no version specified and channel '${channel}' has no latest entry in the manifest`
@@ -10950,6 +10966,29 @@ async function updateRuntimeToLatest(opts = {}) {
10950
10966
  ensureSootsimHome();
10951
10967
  const cdnOrigin = resolveRuntimeCdnOrigin(opts.cdnOrigin);
10952
10968
  const channel = resolveRuntimeChannel(opts.channel);
10969
+ const pin = resolveRuntimePin();
10970
+ if (pin) {
10971
+ const activeVersion2 = readActiveRuntime();
10972
+ const pinInstalled = import_fs2.default.existsSync(import_path2.default.join(runtimeDir(pin), "index.html"));
10973
+ if (activeVersion2 === pin && pinInstalled) {
10974
+ return {
10975
+ checked: true,
10976
+ updated: false,
10977
+ reason: `runtime is pinned to ${pin}`,
10978
+ activeVersion: activeVersion2,
10979
+ latestVersion: pin
10980
+ };
10981
+ }
10982
+ const install2 = await installRuntime({ version: pin, channel, cdnOrigin, setActive: false });
10983
+ return {
10984
+ checked: true,
10985
+ updated: true,
10986
+ reason: `runtime is pinned to ${pin}`,
10987
+ activeVersion: pin,
10988
+ latestVersion: pin,
10989
+ install: install2
10990
+ };
10991
+ }
10953
10992
  const manifest = await fetchRuntimeManifest(cdnOrigin);
10954
10993
  const latestVersion = manifest.channels[channel]?.latest;
10955
10994
  if (!latestVersion) {
@@ -11039,7 +11078,7 @@ function extractTarball(tarPath, destDir) {
11039
11078
  });
11040
11079
  });
11041
11080
  }
11042
- var import_child_process2, import_crypto, import_fs2, import_path2, import_stream, import_promises, DEFAULT_RUNTIME_CDN_ORIGIN, RUNTIME_CDN_ORIGIN_ENV, RUNTIME_CHANNEL_ENV;
11081
+ var import_child_process2, import_crypto, import_fs2, import_path2, import_stream, import_promises, DEFAULT_RUNTIME_CDN_ORIGIN, RUNTIME_CDN_ORIGIN_ENV, RUNTIME_CHANNEL_ENV, RUNTIME_PIN_ENV;
11043
11082
  var init_runtime_delivery = __esm({
11044
11083
  "src/runtime-delivery.ts"() {
11045
11084
  "use strict";
@@ -11053,6 +11092,7 @@ var init_runtime_delivery = __esm({
11053
11092
  DEFAULT_RUNTIME_CDN_ORIGIN = "https://sootbean.com";
11054
11093
  RUNTIME_CDN_ORIGIN_ENV = "SOOTSIM_CDN_ORIGIN";
11055
11094
  RUNTIME_CHANNEL_ENV = "SOOTSIM_RUNTIME_CHANNEL";
11095
+ RUNTIME_PIN_ENV = "SOOTSIM_RUNTIME_VERSION";
11056
11096
  }
11057
11097
  });
11058
11098
 
@@ -13867,9 +13907,18 @@ var init_bridge_host = __esm({
13867
13907
  }
13868
13908
  return;
13869
13909
  }
13910
+ const previousVersion = this.activeRuntimeVersion;
13870
13911
  const active = this.setActiveRuntime(result.latestVersion);
13871
13912
  process.stderr.write(`sootsim runtime updated to ${active.version} (${reason})
13872
13913
  `);
13914
+ try {
13915
+ writeRuntimeUpgradeNotice({
13916
+ from: previousVersion,
13917
+ to: active.version,
13918
+ at: Date.now()
13919
+ });
13920
+ } catch {
13921
+ }
13873
13922
  } catch (err) {
13874
13923
  process.stderr.write(
13875
13924
  `sootsim runtime update failed (${reason}): ${err instanceof Error ? err.message : String(err)}
@@ -15542,18 +15591,18 @@ var init_ws_bridge = __esm({
15542
15591
  function getFramedScreenshotLayout(model) {
15543
15592
  const spec = (0, import_settings.getDevice)(model);
15544
15593
  const renderScale = spec.scale;
15545
- const isLegacyHardware = !spec.dynamicIsland && spec.homeIndicatorHeight === 0 && spec.cornerRadius === 0;
15594
+ const isLegacyHardware = spec.statusBarHeight > 0 && !spec.dynamicIsland && spec.homeIndicatorHeight === 0 && spec.cornerRadius === 0;
15546
15595
  const logicalScreenWidth = spec.width;
15547
15596
  const logicalScreenHeight = spec.height;
15548
- const logicalFrameWidth = isLegacyHardware ? spec.width + LEGACY_SIDE_BEZEL * 2 : spec.width + import_chrome_metrics.BEZEL_PADDING * 2;
15549
- const logicalFrameHeight = isLegacyHardware ? spec.height + LEGACY_TOP_BEZEL + LEGACY_BOTTOM_BEZEL : spec.height + import_chrome_metrics.BEZEL_PADDING * 2;
15597
+ const logicalFrameWidth = isLegacyHardware ? spec.width + import_chrome_metrics.LEGACY_SIDE_BEZEL * 2 : spec.width + spec.bezelWidth * 2;
15598
+ const logicalFrameHeight = isLegacyHardware ? spec.height + import_chrome_metrics.LEGACY_TOP_BEZEL + import_chrome_metrics.LEGACY_BOTTOM_BEZEL : spec.height + spec.bezelWidth * 2;
15550
15599
  const logicalFrameLeft = import_chrome_metrics.SIDE_BUTTON_RESERVE;
15551
15600
  const logicalFrameTop = FRAME_OUTLINE_BLEED;
15552
15601
  const logicalOuterWidth = logicalFrameWidth + logicalFrameLeft * 2;
15553
15602
  const logicalOuterHeight = logicalFrameHeight + FRAME_OUTLINE_BLEED * 2;
15554
- const logicalScreenLeft = logicalFrameLeft + (isLegacyHardware ? LEGACY_SIDE_BEZEL : import_chrome_metrics.BEZEL_PADDING);
15555
- const logicalScreenTop = isLegacyHardware ? LEGACY_TOP_BEZEL : import_chrome_metrics.BEZEL_PADDING;
15556
- const logicalFrameRadius = isLegacyHardware ? LEGACY_FRAME_RADIUS : spec.cornerRadius + import_chrome_metrics.BEZEL_PADDING;
15603
+ const logicalScreenLeft = logicalFrameLeft + (isLegacyHardware ? import_chrome_metrics.LEGACY_SIDE_BEZEL : spec.bezelWidth);
15604
+ const logicalScreenTop = isLegacyHardware ? import_chrome_metrics.LEGACY_TOP_BEZEL : spec.bezelWidth;
15605
+ const logicalFrameRadius = isLegacyHardware ? import_chrome_metrics.LEGACY_FRAME_RADIUS : spec.cornerRadius + spec.bezelWidth;
15557
15606
  const buttons = [
15558
15607
  {
15559
15608
  side: "right",
@@ -15602,10 +15651,10 @@ function getFramedScreenshotLayout(model) {
15602
15651
  metallicRingShadow: isLegacyHardware ? null : METALLIC_RING_SHADOW,
15603
15652
  buttons,
15604
15653
  legacyHomeButton: isLegacyHardware ? {
15605
- top: LEGACY_TOP_BEZEL + spec.height + (LEGACY_BOTTOM_BEZEL - LEGACY_HOME_BUTTON_SIZE) / 2,
15606
- left: spec.width / 2 - LEGACY_HOME_BUTTON_SIZE / 2 + LEGACY_SIDE_BEZEL,
15607
- size: LEGACY_HOME_BUTTON_SIZE,
15608
- ring: LEGACY_HOME_BUTTON_RING
15654
+ top: import_chrome_metrics.LEGACY_TOP_BEZEL + spec.height + (import_chrome_metrics.LEGACY_BOTTOM_BEZEL - import_chrome_metrics.LEGACY_HOME_BUTTON_SIZE) / 2,
15655
+ left: spec.width / 2 - import_chrome_metrics.LEGACY_HOME_BUTTON_SIZE / 2 + import_chrome_metrics.LEGACY_SIDE_BEZEL,
15656
+ size: import_chrome_metrics.LEGACY_HOME_BUTTON_SIZE,
15657
+ ring: import_chrome_metrics.LEGACY_HOME_BUTTON_RING
15609
15658
  } : null,
15610
15659
  showHomeIndicator: spec.homeIndicatorHeight > 0,
15611
15660
  logicalHomeIndicatorStripHeight: spec.homeIndicatorHeight
@@ -15857,18 +15906,12 @@ async function createFramedScreenshotComposer(browser) {
15857
15906
  }
15858
15907
  };
15859
15908
  }
15860
- var import_chrome_metrics, import_settings, LEGACY_FRAME_RADIUS, LEGACY_SIDE_BEZEL, LEGACY_TOP_BEZEL, LEGACY_BOTTOM_BEZEL, LEGACY_HOME_BUTTON_SIZE, LEGACY_HOME_BUTTON_RING, METALLIC_RING_SHADOW, FRAME_OUTLINE_BLEED;
15909
+ var import_chrome_metrics, import_settings, METALLIC_RING_SHADOW, FRAME_OUTLINE_BLEED;
15861
15910
  var init_frame_compose = __esm({
15862
15911
  "src/screenshots/frame-compose.ts"() {
15863
15912
  "use strict";
15864
15913
  import_chrome_metrics = require("sootsim-engine/ios/chrome-metrics");
15865
15914
  import_settings = require("sootsim-engine/settings");
15866
- LEGACY_FRAME_RADIUS = 52;
15867
- LEGACY_SIDE_BEZEL = 18;
15868
- LEGACY_TOP_BEZEL = 42;
15869
- LEGACY_BOTTOM_BEZEL = 96;
15870
- LEGACY_HOME_BUTTON_SIZE = 54;
15871
- LEGACY_HOME_BUTTON_RING = 1.5;
15872
15915
  METALLIC_RING_SHADOW = "inset 0 0 0 0.5px #000, inset 0 0 0 2px #757575, inset 0 0 0 5px #212121";
15873
15916
  FRAME_OUTLINE_BLEED = 1;
15874
15917
  }
@@ -18665,6 +18708,9 @@ var init_bridge_flow_runner = __esm({
18665
18708
  throw new Error(`unsupported flow step: ${JSON.stringify(step)}`);
18666
18709
  }
18667
18710
  }
18711
+ // live progress reporter (shell devtools "test" tab). attached by
18712
+ // runFlowPlayback; absent for programmatic callers.
18713
+ liveStatus = null;
18668
18714
  async runFlow(steps) {
18669
18715
  const onStart = [];
18670
18716
  const onComplete = [];
@@ -18681,6 +18727,15 @@ var init_bridge_flow_runner = __esm({
18681
18727
  }
18682
18728
  body.push(step);
18683
18729
  }
18730
+ if (this.liveStatus) {
18731
+ await this.liveStatus.plan(
18732
+ body.map((rawStep, index) => {
18733
+ const step = typeof rawStep === "string" ? normalizeBareStringStep(rawStep) : rawStep;
18734
+ const name = Object.keys(step).find((k) => k !== "when") ?? Object.keys(step)[0];
18735
+ return { index, name, target: this.flowStepTargetLabel(step, name) };
18736
+ })
18737
+ );
18738
+ }
18684
18739
  if (onStart.length > 0) {
18685
18740
  console.log(`[flow] onFlowStart (${onStart.length} steps)`);
18686
18741
  for (let i = 0; i < onStart.length; i++) {
@@ -18713,23 +18768,47 @@ var init_bridge_flow_runner = __esm({
18713
18768
  const explicitWait = typeof step.wait === "number" ? step.wait : 0;
18714
18769
  const waitForTimeout = typeof step.waitFor?.timeout === "number" ? step.waitFor.timeout : 0;
18715
18770
  const stepCeiling = Math.max(6e4, explicitWait + 15e3, waitForTimeout + 15e3);
18716
- if (step.runFlow || step.launchApp) {
18717
- await this.runStep(step, i);
18718
- continue;
18719
- }
18720
- await Promise.race([
18721
- this.runStep(step, i),
18722
- new Promise(
18723
- (_, reject) => setTimeout(
18724
- () => reject(
18725
- new Error(
18726
- `step ${i + 1} (${Object.keys(step)[0]}) exceeded ${stepCeiling}ms deadline`
18771
+ if (this.liveStatus) {
18772
+ await this.liveStatus.waitWhilePaused();
18773
+ await this.liveStatus.step(i, "running");
18774
+ }
18775
+ const liveStartedAtMs = Date.now();
18776
+ const reportOutcome = async (failed, error) => {
18777
+ if (!this.liveStatus) return;
18778
+ const last = this.flowTraceSteps[this.flowTraceSteps.length - 1];
18779
+ const traced = last && last.stepIndex === i && last.startedAtMs >= liveStartedAtMs;
18780
+ await this.liveStatus.step(
18781
+ i,
18782
+ failed ? "failure" : traced && last.status === "skipped" ? "skipped" : "success",
18783
+ {
18784
+ durationMs: Date.now() - liveStartedAtMs,
18785
+ error: failed ? error instanceof Error ? error.message.slice(0, 300) : String(error).slice(0, 300) : traced && last.error || void 0
18786
+ }
18787
+ );
18788
+ };
18789
+ try {
18790
+ if (step.runFlow || step.launchApp) {
18791
+ await this.runStep(step, i);
18792
+ } else {
18793
+ await Promise.race([
18794
+ this.runStep(step, i),
18795
+ new Promise(
18796
+ (_, reject) => setTimeout(
18797
+ () => reject(
18798
+ new Error(
18799
+ `step ${i + 1} (${Object.keys(step)[0]}) exceeded ${stepCeiling}ms deadline`
18800
+ )
18801
+ ),
18802
+ stepCeiling
18727
18803
  )
18728
- ),
18729
- stepCeiling
18730
- )
18731
- )
18732
- ]);
18804
+ )
18805
+ ]);
18806
+ }
18807
+ } catch (error) {
18808
+ await reportOutcome(true, error);
18809
+ throw error;
18810
+ }
18811
+ await reportOutcome(false);
18733
18812
  }
18734
18813
  }
18735
18814
  };
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.200 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.202 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sootsim",
3
- "version": "0.1.200",
3
+ "version": "0.1.202",
4
4
  "description": "sootsim CLI + vite/metro plugins + skills registry. bridge client for driving the proprietary sootsim-engine over WebSocket.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "MIT",
package/src/home-paths.ts CHANGED
@@ -15,7 +15,11 @@
15
15
  // │ └── sootsim-runtime-<version>.tar.gz
16
16
  // ├── daemon.json lockfile: pid, ports, active runtime, heartbeat
17
17
  // ├── dev-bridge.json live Vite dev bridge pointer for local CLI commands
18
- // └── config.json user prefs: update channel, cdn origin override
18
+ // ├── runtime-upgraded.json one-shot notice written by the daemon's
19
+ // │ background updater, consumed (printed + deleted)
20
+ // │ by the next interactive CLI run
21
+ // └── config.json user prefs: runtime pin, update channel, cdn
22
+ // origin override, telemetry, engine settings
19
23
 
20
24
  import { randomUUID } from 'node:crypto'
21
25
  import fs from 'node:fs'
@@ -232,6 +236,49 @@ export function writeTelemetryEnabled(enabled: boolean): void {
232
236
  writeSharedConfig({ telemetry: enabled })
233
237
  }
234
238
 
239
+ // --- background-upgrade notice --------------------------------------------
240
+ //
241
+ // the daemon auto-updates the engine runtime in the background (hourly), so
242
+ // the only place its "updated to vX" line lands is the daemon's own stderr,
243
+ // which nobody reads. it also drops a one-shot notice file here; the next
244
+ // interactive CLI invocation prints it once and deletes it, so users learn
245
+ // their engine moved without having to run `sootsim version`.
246
+
247
+ export interface RuntimeUpgradeNotice {
248
+ from: string | null
249
+ to: string
250
+ at: number
251
+ }
252
+
253
+ export function runtimeUpgradeNoticePath(): string {
254
+ return path.join(sootsimHomeDir(), 'runtime-upgraded.json')
255
+ }
256
+
257
+ export function writeRuntimeUpgradeNotice(notice: RuntimeUpgradeNotice): void {
258
+ ensureSootsimHome()
259
+ const tmp = `${runtimeUpgradeNoticePath()}.tmp`
260
+ fs.writeFileSync(tmp, `${JSON.stringify(notice)}\n`, 'utf8')
261
+ fs.renameSync(tmp, runtimeUpgradeNoticePath())
262
+ }
263
+
264
+ /** read + delete the pending upgrade notice, or null when none is pending.
265
+ * deleting first-read keeps the banner one-shot even across racing CLIs. */
266
+ export function consumeRuntimeUpgradeNotice(): RuntimeUpgradeNotice | null {
267
+ try {
268
+ const raw = fs.readFileSync(runtimeUpgradeNoticePath(), 'utf8')
269
+ fs.unlinkSync(runtimeUpgradeNoticePath())
270
+ const parsed = JSON.parse(raw) as Partial<RuntimeUpgradeNotice>
271
+ if (typeof parsed?.to !== 'string' || !parsed.to) return null
272
+ return {
273
+ from: typeof parsed.from === 'string' ? parsed.from : null,
274
+ to: parsed.to,
275
+ at: typeof parsed.at === 'number' ? parsed.at : 0,
276
+ }
277
+ } catch {
278
+ return null
279
+ }
280
+ }
281
+
235
282
  export function ensureSootsimHome(): void {
236
283
  fs.mkdirSync(sootsimHomeDir(), { recursive: true })
237
284
  fs.mkdirSync(runtimesDir(), { recursive: true })
@@ -25,6 +25,7 @@ import {
25
25
  writeActiveRuntime,
26
26
  writeDaemonLockfile,
27
27
  writeDevBridgeLockfile,
28
+ writeRuntimeUpgradeNotice,
28
29
  writeSharedConfig,
29
30
  type DaemonLockfile,
30
31
  type DevBridgeLockfile,
@@ -1283,8 +1284,19 @@ export class SootSimBridgeHost {
1283
1284
  }
1284
1285
  return
1285
1286
  }
1287
+ const previousVersion = this.activeRuntimeVersion
1286
1288
  const active = this.setActiveRuntime(result.latestVersion)
1287
1289
  process.stderr.write(`sootsim runtime updated to ${active.version} (${reason})\n`)
1290
+ // the daemon's stderr goes to a log nobody watches — drop a one-shot
1291
+ // notice so the next interactive CLI run tells the user their engine
1292
+ // moved (consumed + deleted by bin.ts).
1293
+ try {
1294
+ writeRuntimeUpgradeNotice({
1295
+ from: previousVersion,
1296
+ to: active.version,
1297
+ at: Date.now(),
1298
+ })
1299
+ } catch {}
1288
1300
  } catch (err) {
1289
1301
  process.stderr.write(
1290
1302
  `sootsim runtime update failed (${reason}): ${
@@ -17,6 +17,7 @@ import {
17
17
  export const DEFAULT_RUNTIME_CDN_ORIGIN = 'https://sootbean.com'
18
18
  export const RUNTIME_CDN_ORIGIN_ENV = 'SOOTSIM_CDN_ORIGIN'
19
19
  export const RUNTIME_CHANNEL_ENV = 'SOOTSIM_RUNTIME_CHANNEL'
20
+ export const RUNTIME_PIN_ENV = 'SOOTSIM_RUNTIME_VERSION'
20
21
 
21
22
  export interface RuntimeManifestChannel {
22
23
  latest: string
@@ -70,6 +71,7 @@ export interface RuntimeUpdateResult {
70
71
 
71
72
  interface SootsimConfig {
72
73
  runtimeChannel?: string
74
+ runtimeVersion?: string
73
75
  cdnOrigin?: string
74
76
  }
75
77
 
@@ -97,6 +99,18 @@ export function resolveRuntimeChannel(input?: string): string {
97
99
  return input || process.env[RUNTIME_CHANNEL_ENV] || config.runtimeChannel || 'stable'
98
100
  }
99
101
 
102
+ /** pinned runtime version, or null when following the channel's latest.
103
+ * env wins over config so CI can pin per-invocation; the persistent pin
104
+ * lives in ~/.sootsim/config.json (`sootsim runtime pin <version>`). */
105
+ export function resolveRuntimePin(): string | null {
106
+ const env = process.env[RUNTIME_PIN_ENV]
107
+ if (env && env.trim()) return env.trim()
108
+ const config = readConfig()
109
+ return typeof config.runtimeVersion === 'string' && config.runtimeVersion.trim()
110
+ ? config.runtimeVersion.trim()
111
+ : null
112
+ }
113
+
100
114
  export function runtimeManifestUrl(cdnOrigin?: string): string {
101
115
  const url = new URL(`${resolveRuntimeCdnOrigin(cdnOrigin)}/runtimes/manifest.json`)
102
116
  // cdn edges can keep the no-query manifest around longer than its
@@ -123,7 +137,7 @@ export function resolveRuntimeVersion(
123
137
  opts: Pick<RuntimeInstallOptions, 'version' | 'channel'> = {},
124
138
  ): { version: string; channel: string; entry: RuntimeManifestVersion } {
125
139
  const channel = resolveRuntimeChannel(opts.channel)
126
- const version = opts.version || manifest.channels[channel]?.latest
140
+ const version = opts.version || resolveRuntimePin() || manifest.channels[channel]?.latest
127
141
  if (!version) {
128
142
  throw new Error(
129
143
  `no version specified and channel '${channel}' has no latest entry in the manifest`,
@@ -211,6 +225,34 @@ export async function updateRuntimeToLatest(
211
225
  ensureSootsimHome()
212
226
  const cdnOrigin = resolveRuntimeCdnOrigin(opts.cdnOrigin)
213
227
  const channel = resolveRuntimeChannel(opts.channel)
228
+
229
+ // a pinned runtime never follows the channel. when the pin is already
230
+ // active + on disk this is a no-network no-op; otherwise install exactly
231
+ // the pinned version (it must still exist in the hosted manifest).
232
+ const pin = resolveRuntimePin()
233
+ if (pin) {
234
+ const activeVersion = readActiveRuntime()
235
+ const pinInstalled = fs.existsSync(path.join(runtimeDir(pin), 'index.html'))
236
+ if (activeVersion === pin && pinInstalled) {
237
+ return {
238
+ checked: true,
239
+ updated: false,
240
+ reason: `runtime is pinned to ${pin}`,
241
+ activeVersion,
242
+ latestVersion: pin,
243
+ }
244
+ }
245
+ const install = await installRuntime({ version: pin, channel, cdnOrigin, setActive: false })
246
+ return {
247
+ checked: true,
248
+ updated: true,
249
+ reason: `runtime is pinned to ${pin}`,
250
+ activeVersion: pin,
251
+ latestVersion: pin,
252
+ install,
253
+ }
254
+ }
255
+
214
256
  const manifest = await fetchRuntimeManifest(cdnOrigin)
215
257
  const latestVersion = manifest.channels[channel]?.latest
216
258
  if (!latestVersion) {
@@ -272,27 +314,35 @@ export async function updateRuntimeToLatest(
272
314
  export interface RuntimeUpToDateCheck {
273
315
  active: string | null
274
316
  latest: string | null
275
- // true only when a latest version is known and it is newer than the
276
- // active one (or nothing is active yet). network failures resolve to
277
- // false so callers can treat this as a best-effort hint.
317
+ // pinned version when one is set a pinned runtime is only "outdated"
318
+ // when the pin itself isn't active yet, never against channel latest.
319
+ pinned: string | null
320
+ // true only when a target version is known and it is newer than / not
321
+ // the active one (or nothing is active yet). network failures resolve
322
+ // to false so callers can treat this as a best-effort hint.
278
323
  outdated: boolean
279
324
  }
280
325
 
281
- /** read-only check: is the active runtime the latest on its channel?
282
- * unlike updateRuntimeToLatest this never downloads or activates — it's
283
- * for surfacing a "newer runtime available" hint at startup / in help. */
326
+ /** read-only check: is the active runtime the latest on its channel (or,
327
+ * when pinned, the pin)? unlike updateRuntimeToLatest this never downloads
328
+ * or activates — it's for surfacing a "newer runtime available" hint at
329
+ * startup / in help. */
284
330
  export async function checkRuntimeUpToDate(
285
331
  opts: Pick<RuntimeInstallOptions, 'channel' | 'cdnOrigin'> = {},
286
332
  ): Promise<RuntimeUpToDateCheck> {
287
333
  const active = readActiveRuntime()
334
+ const pinned = resolveRuntimePin()
335
+ if (pinned) {
336
+ return { active, latest: pinned, pinned, outdated: active !== pinned }
337
+ }
288
338
  try {
289
339
  const manifest = await fetchRuntimeManifest(resolveRuntimeCdnOrigin(opts.cdnOrigin))
290
340
  const channel = resolveRuntimeChannel(opts.channel)
291
341
  const latest = manifest.channels[channel]?.latest ?? null
292
342
  const outdated = !!(latest && (!active || compareSemver(latest, active) > 0))
293
- return { active, latest, outdated }
343
+ return { active, latest, pinned: null, outdated }
294
344
  } catch {
295
- return { active, latest: null, outdated: false }
345
+ return { active, latest: null, pinned: null, outdated: false }
296
346
  }
297
347
  }
298
348
 
@@ -1,12 +1,14 @@
1
- import { BEZEL_PADDING, SIDE_BUTTON_RESERVE } from 'sootsim-engine/ios/chrome-metrics'
1
+ import {
2
+ LEGACY_BOTTOM_BEZEL,
3
+ LEGACY_FRAME_RADIUS,
4
+ LEGACY_HOME_BUTTON_RING,
5
+ LEGACY_HOME_BUTTON_SIZE,
6
+ LEGACY_SIDE_BEZEL,
7
+ LEGACY_TOP_BEZEL,
8
+ SIDE_BUTTON_RESERVE,
9
+ } from 'sootsim-engine/ios/chrome-metrics'
2
10
  import { getDevice, type DeviceModel } from 'sootsim-engine/settings'
3
11
 
4
- const LEGACY_FRAME_RADIUS = 52
5
- const LEGACY_SIDE_BEZEL = 18
6
- const LEGACY_TOP_BEZEL = 42
7
- const LEGACY_BOTTOM_BEZEL = 96
8
- const LEGACY_HOME_BUTTON_SIZE = 54
9
- const LEGACY_HOME_BUTTON_RING = 1.5
10
12
  const METALLIC_RING_SHADOW =
11
13
  'inset 0 0 0 0.5px #000, inset 0 0 0 2px #757575, inset 0 0 0 5px #212121'
12
14
  const FRAME_OUTLINE_BLEED = 1
@@ -59,26 +61,32 @@ export interface FramedScreenshotLayout {
59
61
  export function getFramedScreenshotLayout(model: DeviceModel): FramedScreenshotLayout {
60
62
  const spec = getDevice(model)
61
63
  const renderScale = spec.scale
64
+ // legacy = home-button iPhone (SE). the canvas workspace also has no island /
65
+ // home indicator / corner radius, but no status bar, so gate on statusBar > 0
66
+ // to keep it out of the SE shell (mirrors DeviceFrame.isLegacyHardware).
62
67
  const isLegacyHardware =
63
- !spec.dynamicIsland && spec.homeIndicatorHeight === 0 && spec.cornerRadius === 0
68
+ spec.statusBarHeight > 0 &&
69
+ !spec.dynamicIsland &&
70
+ spec.homeIndicatorHeight === 0 &&
71
+ spec.cornerRadius === 0
64
72
  const logicalScreenWidth = spec.width
65
73
  const logicalScreenHeight = spec.height
66
74
  const logicalFrameWidth = isLegacyHardware
67
75
  ? spec.width + LEGACY_SIDE_BEZEL * 2
68
- : spec.width + BEZEL_PADDING * 2
76
+ : spec.width + spec.bezelWidth * 2
69
77
  const logicalFrameHeight = isLegacyHardware
70
78
  ? spec.height + LEGACY_TOP_BEZEL + LEGACY_BOTTOM_BEZEL
71
- : spec.height + BEZEL_PADDING * 2
79
+ : spec.height + spec.bezelWidth * 2
72
80
  const logicalFrameLeft = SIDE_BUTTON_RESERVE
73
81
  const logicalFrameTop = FRAME_OUTLINE_BLEED
74
82
  const logicalOuterWidth = logicalFrameWidth + logicalFrameLeft * 2
75
83
  const logicalOuterHeight = logicalFrameHeight + FRAME_OUTLINE_BLEED * 2
76
84
  const logicalScreenLeft =
77
- logicalFrameLeft + (isLegacyHardware ? LEGACY_SIDE_BEZEL : BEZEL_PADDING)
78
- const logicalScreenTop = isLegacyHardware ? LEGACY_TOP_BEZEL : BEZEL_PADDING
85
+ logicalFrameLeft + (isLegacyHardware ? LEGACY_SIDE_BEZEL : spec.bezelWidth)
86
+ const logicalScreenTop = isLegacyHardware ? LEGACY_TOP_BEZEL : spec.bezelWidth
79
87
  const logicalFrameRadius = isLegacyHardware
80
88
  ? LEGACY_FRAME_RADIUS
81
- : spec.cornerRadius + BEZEL_PADDING
89
+ : spec.cornerRadius + spec.bezelWidth
82
90
  const buttons: FrameButtonLayout[] = [
83
91
  {
84
92
  side: 'right',
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.200 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b,c,d}from"./chunk-7BS4AKZT.js";import"./chunk-6RHOWIGL.js";export{d as BETA_ASK_HEADLINE,b as BETA_LABEL,c as BETA_TAGLINE,a as IS_BETA};
@@ -1,6 +0,0 @@
1
- /*! sootsim v0.1.200 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{randomUUID as I}from"node:crypto";import t from"node:fs";import{homedir as E}from"node:os";import o from"node:path";var _="SOOTSIM_HOME",N="active",P="daemon.json",j="dev-bridge.json",F="config.json",k=3e4;function u(){let e=process.env[_];return e&&e.length>0?o.resolve(e):o.join(E(),".sootsim")}function w(){if(process.env.SOOTSIM_FORCE_DAEMON_INSTALL==="1")return!1;let e=process.env.SOOTSIM_DEV;if(e==="1"||e==="true")return!0;if(e==="0"||e==="false")return!1;let r=process.argv[1];if(!r)return!1;try{return t.realpathSync(r).includes(`${o.sep}packages${o.sep}sootsim${o.sep}`)}catch{return!1}}function T(){return process.env.SOOTSIM_FORCE_DAEMON_INSTALL==="1"?!1:process.env.IS_TAMAGUI_DEV==="1"}function q(){return w()||T()}function f(){return o.join(u(),"runtimes")}function B(e){return o.join(f(),e)}function L(){return o.join(f(),N)}function D(){return o.join(u(),"electron")}function z(){return o.join(D(),"userData")}function Q(e){return o.join(D(),e)}function C(){return o.join(u(),"profiles")}function M(){return o.join(u(),"daemon-app")}function R(){return o.join(M(),"SootSim Daemon.app")}function Z(){return o.join(R(),"Contents","MacOS","sootsim-daemon")}function $(){return o.join(u(),"cache")}function p(){return o.join(u(),P)}function m(){return o.join(u(),j)}function b(){return o.join(u(),F)}function h(){try{let e=t.readFileSync(b(),"utf8"),r=JSON.parse(e);return r&&typeof r=="object"?r:{}}catch{return{}}}function O(e){g();let r=h(),i={...r,...e};e.settings&&typeof e.settings=="object"&&(i.settings={...r.settings&&typeof r.settings=="object"?r.settings:{},...e.settings});let c=`${b()}.tmp`;return t.writeFileSync(c,`${JSON.stringify(i,null,2)}
3
- `,"utf8"),t.renameSync(c,b()),i}function ee(){return h().telemetry!==!1}function re(){let e=h().anonymousId;if(typeof e=="string"&&e.trim())return e;let r=`anon-cli-${I()}`;return O({anonymousId:r}),r}function te(e){O({telemetry:e})}function g(){t.mkdirSync(u(),{recursive:!0}),t.mkdirSync(f(),{recursive:!0}),t.mkdirSync(D(),{recursive:!0}),t.mkdirSync(C(),{recursive:!0}),t.mkdirSync($(),{recursive:!0})}function J(){try{let e=t.readFileSync(L(),"utf8").trim();return e.length>0?e:null}catch{return null}}function ne(e){t.mkdirSync(f(),{recursive:!0}),t.writeFileSync(L(),`${e}
4
- `,"utf8")}function oe(){try{return t.readdirSync(f(),{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name).sort(V)}catch{return[]}}function V(e,r){let i=s=>{let a=s.indexOf("-"),l=a>=0?s.slice(0,a):s,A=a>=0?s.slice(a+1):"",x=l.split(".").map(S=>Number.parseInt(S,10));return x.some(S=>!Number.isFinite(S))?[[Number.POSITIVE_INFINITY],s]:[x,A]},[c,n]=i(e),[v,y]=i(r);for(let s=0;s<Math.max(c.length,v.length);s++){let a=c[s]??0,l=v[s]??0;if(a!==l)return a-l}return n===y?0:n?y?n<y?-1:1:-1:1}function ie(){let e=J();if(!e)return null;let r=B(e);try{if(t.statSync(r).isDirectory())return r}catch{}return null}var d=16*1024;function H(){try{let e=t.openSync(p(),"r");try{let r=Buffer.alloc(d),i=t.readSync(e,r,0,d,0),c=r.subarray(0,i).toString("utf8"),n=JSON.parse(c);return n&&n.schema===1&&typeof n.pid=="number"&&typeof n.bridgePort=="number"&&typeof n.runtimePort=="number"&&typeof n.startedAt=="number"&&typeof n.heartbeatAt=="number"?n:null}finally{t.closeSync(e)}}catch{return null}}function se(){try{let e=t.openSync(m(),"r");try{let r=Buffer.alloc(d),i=t.readSync(e,r,0,d,0),c=r.subarray(0,i).toString("utf8"),n=JSON.parse(c);return n&&n.schema===1&&n.source==="vite-dev"&&typeof n.pid=="number"&&typeof n.bridgePort=="number"&&typeof n.runtimePort=="number"&&typeof n.cwd=="string"&&typeof n.startedAt=="number"&&typeof n.heartbeatAt=="number"&&(n.shellPort===void 0||typeof n.shellPort=="number")?n:null}finally{t.closeSync(e)}}catch{return null}}function U(e,r=Date.now()){if(!e||r-e.heartbeatAt>k)return!1;try{return process.kill(e.pid,0),!0}catch{return!1}}function ce(e,r=Date.now()){if(!e||r-e.heartbeatAt>k)return!1;try{return process.kill(e.pid,0),!0}catch{return!1}}function G(e){g();let r=`${p()}.tmp`;t.writeFileSync(r,`${JSON.stringify(e,null,2)}
5
- `,"utf8"),t.renameSync(r,p())}function ue(e){g();let r=`${m()}.tmp`;t.writeFileSync(r,`${JSON.stringify(e,null,2)}
6
- `,"utf8"),t.renameSync(r,m())}function ae(e){g();let r=H();return r&&U(r)&&r.pid!==e.pid?!1:(G(e),!0)}function fe(){try{t.unlinkSync(p())}catch{}}function le(){try{t.unlinkSync(m())}catch{}}export{_ as a,N as b,P as c,j as d,F as e,k as f,u as g,w as h,T as i,q as j,f as k,B as l,L as m,D as n,z as o,Q as p,C as q,M as r,R as s,Z as t,$ as u,p as v,m as w,b as x,h as y,O as z,ee as A,re as B,te as C,g as D,J as E,ne as F,oe as G,V as H,ie as I,H as J,se as K,U as L,ce as M,G as N,ue as O,ae as P,fe as Q,le as R};
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.200 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1,4 +0,0 @@
1
- /*! sootsim v0.1.200 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{D as v,E as f,F as R,H as w,l as p,u as S,x as O}from"./chunk-CB3I3VQT.js";import{spawn as I}from"child_process";import C from"crypto";import s from"fs";import l from"path";import{Readable as M}from"stream";import{pipeline as k}from"stream/promises";var N="https://sootbean.com",E="SOOTSIM_CDN_ORIGIN",T="SOOTSIM_RUNTIME_CHANNEL",q="https://sootsim.com/changelog";function $(){try{let t=JSON.parse(s.readFileSync(O(),"utf8"));return t&&typeof t=="object"?t:{}}catch{return{}}}function d(t){let e=$();return(t||process.env[E]||e.cdnOrigin||N).replace(/\/+$/,"")}function x(t){let e=$();return t||process.env[T]||e.runtimeChannel||"stable"}function D(t){let e=new URL(`${d(t)}/runtimes/manifest.json`);return e.searchParams.set("t",String(Date.now())),e.toString()}function U(t,e){return`${d(e)}/runtimes/sootsim-runtime-${t}.tar.gz`}async function y(t){let e=D(t),r=await fetch(e,{headers:{Accept:"application/json"}});if(!r.ok)throw new Error(`manifest fetch failed: ${r.status} ${r.statusText} (${e})`);return await r.json()}function P(t,e={}){let r=x(e.channel),n=e.version||t.channels[r]?.latest;if(!n)throw new Error(`no version specified and channel '${r}' has no latest entry in the manifest`);let i=t.versions[n];if(!i)throw new Error(`version ${n} not found in manifest; available: ${Object.keys(t.versions).slice(-10).join(", ")||"(none)"}`);return{version:n,channel:r,entry:i}}async function _(t={}){v();let e=d(t.cdnOrigin),r=await y(e),{version:n,channel:i,entry:a}=P(r,t),o=p(n),c=t.setActive!==!1;if(!t.force&&s.existsSync(l.join(o,"index.html")))return c&&R(n),{version:n,channel:i,cdnOrigin:e,runtimeDir:o,installed:!1,activated:c,manifest:r};let g=a.tarball||U(n,e),m=l.join(S(),`sootsim-runtime-${n}.tar.gz`);process.stderr.write(`sootsim: downloading runtime ${n}\u2026
3
- `),await V(g,m),process.stderr.write(`sootsim: extracting runtime ${n}\u2026
4
- `);let h=await A(m);if(h!==a.sha256)throw s.rmSync(m,{force:!0}),new Error(`sha256 mismatch for runtime ${n}: expected ${a.sha256}, actual ${h}`);let u=l.join(l.dirname(o),`.installing-${n}-${process.pid}`);s.rmSync(u,{recursive:!0,force:!0}),s.mkdirSync(u,{recursive:!0});try{if(await j(m,u),!s.existsSync(l.join(u,"index.html")))throw new Error(`extracted tarball for runtime ${n} is missing index.html`);s.rmSync(o,{recursive:!0,force:!0}),s.renameSync(u,o)}catch(b){throw s.rmSync(u,{recursive:!0,force:!0}),b}return c&&R(n),{version:n,channel:i,cdnOrigin:e,runtimeDir:o,installed:!0,activated:c,manifest:r}}async function B(t={}){v();let e=d(t.cdnOrigin),r=x(t.channel),n=await y(e),i=n.channels[r]?.latest;if(!i)return{checked:!0,updated:!1,reason:`channel '${r}' has no latest runtime`,activeVersion:f()};if(!n.versions[i])return{checked:!0,updated:!1,reason:`manifest is missing version ${i}`,activeVersion:f(),latestVersion:i};let o=f(),c=o?p(o):null,g=c?s.existsSync(l.join(c,"index.html")):!1;if(!(!o||!g||w(i,o)>0))return{checked:!0,updated:!1,reason:"active runtime is current",activeVersion:o,latestVersion:i};let h=await _({version:i,channel:r,cdnOrigin:e,setActive:!1});return{checked:!0,updated:!0,activeVersion:i,latestVersion:i,install:h}}async function K(t={}){let e=f();try{let r=await y(d(t.cdnOrigin)),n=x(t.channel),i=r.channels[n]?.latest??null,a=!!(i&&(!e||w(i,e)>0));return{active:e,latest:i,outdated:a}}catch{return{active:e,latest:null,outdated:!1}}}async function V(t,e){let r=await fetch(t);if(!r.ok||!r.body)throw new Error(`download failed: ${r.status} ${r.statusText} (${t})`);s.mkdirSync(l.dirname(e),{recursive:!0});let n=`${e}.partial`;try{await k(M.fromWeb(r.body),s.createWriteStream(n)),s.renameSync(n,e)}catch(i){try{s.unlinkSync(n)}catch{}throw i}}function A(t){return new Promise((e,r)=>{let n=C.createHash("sha256"),i=s.createReadStream(t);i.on("data",a=>n.update(a)),i.on("error",r),i.on("end",()=>e(n.digest("hex")))})}function j(t,e){return new Promise((r,n)=>{let i=I("tar",["-xzf",t,"-C",e],{stdio:["ignore","inherit","inherit"]});i.on("error",n),i.on("exit",a=>{a===0?r():n(new Error(`tar exited with code ${a}`))})})}export{N as a,q as b,d as c,x as d,y as e,_ as f,B as g,K as h};