sootsim 0.1.294 → 0.1.296

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 (186) hide show
  1. package/README.md +13 -4
  2. package/dist-cli/bin.js +5 -5
  3. package/dist-cli/chunks/{agent-GDFF6G7N.js → agent-3F4K6MQK.js} +2 -2
  4. package/dist-cli/chunks/{agent-wrapper-ZUKQUOD2.js → agent-wrapper-5LLVCQRZ.js} +2 -2
  5. package/dist-cli/chunks/app-fonts-LPTY7A5G.js +4 -0
  6. package/dist-cli/chunks/{assert-GXOGZUKO.js → assert-R2ZUXEMS.js} +2 -2
  7. package/dist-cli/chunks/{auth-P7FAKLHQ.js → auth-CA62Q6IS.js} +2 -2
  8. package/dist-cli/chunks/{beta-RXT5RU3A.js → beta-4AXSOQLU.js} +2 -2
  9. package/dist-cli/chunks/{camera-RNVAC4IO.js → camera-Y4RBIONE.js} +2 -2
  10. package/dist-cli/chunks/{chunk-OYYQQBKP.js → chunk-222Y7NQA.js} +1 -1
  11. package/dist-cli/chunks/chunk-2LQOLMRK.js +4 -0
  12. package/dist-cli/chunks/{chunk-AFLGWOX3.js → chunk-2XUKWIAL.js} +1 -1
  13. package/dist-cli/chunks/{chunk-DI55E6CS.js → chunk-3PAZJSIG.js} +2 -2
  14. package/dist-cli/chunks/{chunk-2M7OZ6Y4.js → chunk-4MDLYOEI.js} +2 -2
  15. package/dist-cli/chunks/{chunk-EXTV6NWM.js → chunk-4NFGOGB7.js} +2 -2
  16. package/dist-cli/chunks/chunk-54TQ3ZGI.js +30 -0
  17. package/dist-cli/chunks/chunk-5FNPAMP4.js +125 -0
  18. package/dist-cli/chunks/{chunk-4Z4URZOG.js → chunk-6C24WCDK.js} +2 -2
  19. package/dist-cli/chunks/{chunk-KWIURH7N.js → chunk-74FOMZP7.js} +2 -2
  20. package/dist-cli/chunks/{chunk-JUOQ4ZPA.js → chunk-7GQWF7HE.js} +1 -1
  21. package/dist-cli/chunks/{chunk-BXO2PTNM.js → chunk-7V3JNANH.js} +4 -4
  22. package/dist-cli/chunks/{chunk-DUHBRW7J.js → chunk-7YQXQSG7.js} +1 -1
  23. package/dist-cli/chunks/{chunk-FSRRJGYC.js → chunk-AUBP44CY.js} +2 -2
  24. package/dist-cli/chunks/chunk-B2FB6IYX.js +4 -0
  25. package/dist-cli/chunks/{chunk-FNCSUHUR.js → chunk-B2LDO55K.js} +1 -1
  26. package/dist-cli/chunks/{chunk-K53YCAQM.js → chunk-BVWIR4CD.js} +1 -1
  27. package/dist-cli/chunks/{chunk-RWUNC66S.js → chunk-C462URRQ.js} +2 -2
  28. package/dist-cli/chunks/chunk-CAAADPRH.js +10 -0
  29. package/dist-cli/chunks/{chunk-VXGCZUX4.js → chunk-CDCVWGNU.js} +3 -3
  30. package/dist-cli/chunks/{chunk-WYRLRNIP.js → chunk-CS23DQB2.js} +2 -2
  31. package/dist-cli/chunks/chunk-DUYKBCM7.js +4 -0
  32. package/dist-cli/chunks/chunk-EISE56VD.js +6 -0
  33. package/dist-cli/chunks/{chunk-VVVORIQI.js → chunk-EJ6BAJKX.js} +1 -1
  34. package/dist-cli/chunks/chunk-F2MS365Z.js +4 -0
  35. package/dist-cli/chunks/{chunk-AMKQUZA2.js → chunk-FROCFR77.js} +1 -1
  36. package/dist-cli/chunks/{chunk-I233T2T4.js → chunk-FUPQIX4U.js} +2 -2
  37. package/dist-cli/chunks/{chunk-V2MYTL4C.js → chunk-G2SO5FPN.js} +2 -2
  38. package/dist-cli/chunks/chunk-GG5PWLGC.js +4 -0
  39. package/dist-cli/chunks/chunk-GG6XOQ5P.js +15 -0
  40. package/dist-cli/chunks/chunk-HSGS2U7P.js +6 -0
  41. package/dist-cli/chunks/{chunk-PZTKQG6H.js → chunk-IR3DUBRF.js} +5 -5
  42. package/dist-cli/chunks/chunk-LHUY46GE.js +4 -0
  43. package/dist-cli/chunks/{chunk-N4MT5KEB.js → chunk-MDBR4XTV.js} +3 -3
  44. package/dist-cli/chunks/{chunk-SLMUW2WU.js → chunk-MQ6QX46T.js} +2 -2
  45. package/dist-cli/chunks/chunk-N3FZRJT7.js +4 -0
  46. package/dist-cli/chunks/{chunk-ISJ4ACWL.js → chunk-NEARYPSC.js} +11 -11
  47. package/dist-cli/chunks/chunk-P7HBJBHH.js +4 -0
  48. package/dist-cli/chunks/{chunk-24BJFJFB.js → chunk-PEUWZRXA.js} +5 -5
  49. package/dist-cli/chunks/{chunk-PSOUSJSZ.js → chunk-QIDFGREY.js} +1 -1
  50. package/dist-cli/chunks/{chunk-ZUX5PIGI.js → chunk-R65XMN4D.js} +1 -1
  51. package/dist-cli/chunks/{chunk-AO65WEF7.js → chunk-S5Z4HKWX.js} +1 -1
  52. package/dist-cli/chunks/chunk-SUJSGGSS.js +23 -0
  53. package/dist-cli/chunks/{chunk-O6I5ZGMY.js → chunk-TIOPG6QZ.js} +1 -1
  54. package/dist-cli/chunks/chunk-WJVBD2UL.js +9 -0
  55. package/dist-cli/chunks/{chunk-D2R7PDN5.js → chunk-X7B5KJ6N.js} +1 -1
  56. package/dist-cli/chunks/{chunk-7HVMAGDK.js → chunk-XMVVZCIM.js} +2 -2
  57. package/dist-cli/chunks/chunk-ZIBKGZJD.js +4 -0
  58. package/dist-cli/chunks/chunk-ZPBIHFQW.js +12 -0
  59. package/dist-cli/chunks/cleanup-4RSKYPQ7.js +25 -0
  60. package/dist-cli/chunks/{cli-version-2JGMYL4O.js → cli-version-VZFF7GGM.js} +2 -2
  61. package/dist-cli/chunks/compat-FABLN4UI.js +20 -0
  62. package/dist-cli/chunks/config-74J37LKC.js +13 -0
  63. package/dist-cli/chunks/control-VBK32Y7F.js +4 -0
  64. package/dist-cli/chunks/daemon-VFO7LRR4.js +4 -0
  65. package/dist-cli/chunks/{debug-XUGWZLIW.js → debug-AOYA2CLR.js} +3 -3
  66. package/dist-cli/chunks/{desktop-IO5HPX5Z.js → desktop-G7XWDCTM.js} +3 -3
  67. package/dist-cli/chunks/{detox-E42OEJJZ.js → detox-QXJ3L2ZI.js} +2 -2
  68. package/dist-cli/chunks/{device-XA67L2EB.js → device-NZZ5ZT3Q.js} +2 -2
  69. package/dist-cli/chunks/{diagnose-36FD6ECF.js → diagnose-5CAEJ42M.js} +2 -2
  70. package/dist-cli/chunks/disk-cleanup-GZSUZJ6Y.js +4 -0
  71. package/dist-cli/chunks/drivers-OLJCUSPO.js +4 -0
  72. package/dist-cli/chunks/{film-XARTGFIZ.js → film-C25VRUNC.js} +3 -3
  73. package/dist-cli/chunks/flow-IW3WUF5D.js +4 -0
  74. package/dist-cli/chunks/help-GEVJF2DA.js +4 -0
  75. package/dist-cli/chunks/{hidden-runtime-alias-T4ZALBE2.js → hidden-runtime-alias-NSLIK6XT.js} +2 -2
  76. package/dist-cli/chunks/home-paths-OC6JZXIM.js +4 -0
  77. package/dist-cli/chunks/inspect-KXJNI7CU.js +4 -0
  78. package/dist-cli/chunks/install-desktop-IYWPRFPB.js +4 -0
  79. package/dist-cli/chunks/{login-GF6WVVER.js → login-LB4TMM4T.js} +4 -4
  80. package/dist-cli/chunks/{logout-6F6UKUNI.js → logout-33IOBD4X.js} +2 -2
  81. package/dist-cli/chunks/{maestro-FMPKHEMX.js → maestro-EEVXX536.js} +3 -3
  82. package/dist-cli/chunks/{maestro-generate-QGPGNXI6.js → maestro-generate-ICPWVXGA.js} +3 -3
  83. package/dist-cli/chunks/{mode-JSNMAHDZ.js → mode-6IXOZDK3.js} +2 -2
  84. package/dist-cli/chunks/{optional-demo-registry-K4VERLJV.js → optional-demo-registry-KBMC7SNI.js} +2 -2
  85. package/dist-cli/chunks/{perf-PO7ARUTG.js → perf-CTTJSAIJ.js} +2 -2
  86. package/dist-cli/chunks/{permissions-XI2QUAEZ.js → permissions-BFQS4DIY.js} +2 -2
  87. package/dist-cli/chunks/{record-4CJCJAFD.js → record-MJR5ELHE.js} +3 -3
  88. package/dist-cli/chunks/report-issue-FMJRNDKH.js +29 -0
  89. package/dist-cli/chunks/runtime-PV4HH2OB.js +4 -0
  90. package/dist-cli/chunks/{screenshot-command-NDVULWMZ.js → screenshot-command-MGNASAXR.js} +3 -3
  91. package/dist-cli/chunks/{screenshots-capture-Q7UTXF3V.js → screenshots-capture-N6PANAVT.js} +2 -2
  92. package/dist-cli/chunks/serve-RS6TIO7U.js +44 -0
  93. package/dist-cli/chunks/setup-MFYGYR2F.js +32 -0
  94. package/dist-cli/chunks/{skills-NFG6VAO5.js → skills-TD35LDKK.js} +2 -2
  95. package/dist-cli/chunks/{storage-POWCOUCQ.js → storage-VCFXDJNO.js} +2 -2
  96. package/dist-cli/chunks/store-VNSTKYBQ.js +4 -0
  97. package/dist-cli/chunks/telemetry-GPNHSYFF.js +4 -0
  98. package/dist-cli/chunks/{timeline-MNHZCVAL.js → timeline-UJCZAWJV.js} +2 -2
  99. package/dist-cli/chunks/upgrade-S24NH4YG.js +7 -0
  100. package/dist-cli/chunks/upload-CCKAO2M5.js +4 -0
  101. package/dist-cli/chunks/version-IVPWT2ZQ.js +6 -0
  102. package/dist-cli/chunks/web-6BMWAWJL.js +4 -0
  103. package/dist-cli/chunks/{what-happened-H74HHGRK.js → what-happened-HRHB6ETL.js} +2 -2
  104. package/dist-lib/agent-daemon-client.cjs +2 -1
  105. package/dist-lib/agent-events.cjs +1 -1
  106. package/dist-lib/agent-identity.cjs +1 -1
  107. package/dist-lib/agent-sessions.cjs +1 -1
  108. package/dist-lib/attached-projects.cjs +1 -1
  109. package/dist-lib/auth/shared-session.cjs +1 -1
  110. package/dist-lib/backend-origin.cjs +1 -1
  111. package/dist-lib/beta.cjs +1 -1
  112. package/dist-lib/beta.mjs +1 -1
  113. package/dist-lib/bridge-constants.cjs +4 -1
  114. package/dist-lib/cli-constants.cjs +1 -1
  115. package/dist-lib/config.cjs +3 -2
  116. package/dist-lib/detox/index.cjs +1 -1
  117. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  118. package/dist-lib/home-paths.cjs +41 -14
  119. package/dist-lib/host/bridge-host.cjs +216 -84
  120. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  121. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  122. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  123. package/dist-lib/host/websocket-proxy.cjs +1 -1
  124. package/dist-lib/index.cjs +5 -2
  125. package/dist-lib/jump-to-source-babel.cjs +1 -1
  126. package/dist-lib/menu.cjs +2 -3
  127. package/dist-lib/menu.mjs +2 -3
  128. package/dist-lib/metro.cjs +1 -1
  129. package/dist-lib/profiles.cjs +1 -1
  130. package/dist-lib/render-mode.cjs +1 -1
  131. package/dist-lib/scripts/dev-server-scanner.cjs +2 -2
  132. package/dist-lib/sdk.cjs +1 -1
  133. package/dist-lib/sdk.mjs +1 -1
  134. package/dist-lib/skills.cjs +373 -159
  135. package/dist-lib/vite.cjs +3 -2
  136. package/package.json +2 -1
  137. package/scripts/postinstall.cjs +2 -78
  138. package/skills/contrast/SKILL.md +3 -2
  139. package/skills/sootsim-setup/SKILL.md +25 -0
  140. package/src/bridge-constants.ts +1 -0
  141. package/src/config.ts +4 -0
  142. package/src/disk-cleanup.ts +5 -26
  143. package/src/home-paths.ts +58 -23
  144. package/src/host/bridge-host.ts +152 -57
  145. package/src/index.ts +4 -1
  146. package/src/menu.ts +0 -1
  147. package/src/runtime-assets.ts +35 -0
  148. package/src/runtime-delivery.ts +5 -8
  149. package/src/skills/builtin/compat-check.ts +32 -65
  150. package/dist-cli/chunks/app-fonts-IMYBO6AQ.js +0 -4
  151. package/dist-cli/chunks/chunk-37ZXWXM3.js +0 -4
  152. package/dist-cli/chunks/chunk-3F2GAJ4Z.js +0 -6
  153. package/dist-cli/chunks/chunk-5S3GPTX7.js +0 -6
  154. package/dist-cli/chunks/chunk-6YN3IAWJ.js +0 -4
  155. package/dist-cli/chunks/chunk-7Q77LTPT.js +0 -15
  156. package/dist-cli/chunks/chunk-BBVBMGTT.js +0 -31
  157. package/dist-cli/chunks/chunk-CTMPC5DN.js +0 -6
  158. package/dist-cli/chunks/chunk-JFWEDISV.js +0 -120
  159. package/dist-cli/chunks/chunk-JGGXQHXH.js +0 -10
  160. package/dist-cli/chunks/chunk-OKSGM6IG.js +0 -4
  161. package/dist-cli/chunks/chunk-SPQKJDIK.js +0 -4
  162. package/dist-cli/chunks/chunk-W44PDEQH.js +0 -4
  163. package/dist-cli/chunks/chunk-WQL3BLFD.js +0 -9
  164. package/dist-cli/chunks/chunk-XYFP466E.js +0 -12
  165. package/dist-cli/chunks/chunk-ZFMJFX27.js +0 -4
  166. package/dist-cli/chunks/cleanup-74SKOD2P.js +0 -25
  167. package/dist-cli/chunks/compat-ME6HXEEU.js +0 -35
  168. package/dist-cli/chunks/config-PM67EWT3.js +0 -13
  169. package/dist-cli/chunks/control-HV7GZXAH.js +0 -4
  170. package/dist-cli/chunks/daemon-LKRHF5OK.js +0 -4
  171. package/dist-cli/chunks/disk-cleanup-DDCFGFWK.js +0 -4
  172. package/dist-cli/chunks/drivers-C2SH3DWS.js +0 -4
  173. package/dist-cli/chunks/flow-E64G5LF3.js +0 -4
  174. package/dist-cli/chunks/help-4ZSRG4F4.js +0 -4
  175. package/dist-cli/chunks/home-paths-5L7N2FLS.js +0 -4
  176. package/dist-cli/chunks/inspect-U2SV25ER.js +0 -4
  177. package/dist-cli/chunks/install-desktop-LTGVT7ID.js +0 -4
  178. package/dist-cli/chunks/runtime-SN2RWDES.js +0 -4
  179. package/dist-cli/chunks/serve-UEUIBHCK.js +0 -44
  180. package/dist-cli/chunks/setup-ZEEWYK4B.js +0 -99
  181. package/dist-cli/chunks/store-BJK7V7FL.js +0 -4
  182. package/dist-cli/chunks/telemetry-EJP5EW7K.js +0 -4
  183. package/dist-cli/chunks/upgrade-X57SIG7L.js +0 -6
  184. package/dist-cli/chunks/upload-JABM3ITZ.js +0 -4
  185. package/dist-cli/chunks/version-V4IKIDX7.js +0 -6
  186. package/dist-cli/chunks/web-F5CLF34I.js +0 -4
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.294 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! sootsim v0.1.296 | (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;
@@ -474,10 +474,10 @@ var init_registry = __esm({
474
474
  },
475
475
  {
476
476
  range: ">=4.0.0",
477
- coverage: 0.96,
478
- note: "core animations, gestures, scroll, layout transitions, keyboard, sensors, reduced motion, and native view property reads",
479
- working: "useSharedValue, useAnimatedStyle/Props/Reaction, useDerivedValue, useAnimatedScrollHandler, useScrollViewOffset, useFrameCallback, useAnimatedRef, useAnimatedKeyboard, useAnimatedSensor, useReducedMotion, entering/exiting/layout transitions, withTiming/Spring/Decay/Delay/Sequence/Repeat/Clamp, interpolate, interpolateColor, runOnJS/UI, measure, scrollTo, getViewProp (layout, opacity, zIndex, backgroundColor, boxShadow), Easing, createAnimatedComponent, Animated.View/Text/Image/ScrollView/FlatList",
480
- missing: "Reanimated 4 native CSS keyframes/transitions and direct setViewStyle remain incomplete"
477
+ coverage: 0.97,
478
+ note: "core animations, gestures, scroll, layout transitions, keyboard, sensors, reduced motion, native view property reads, and direct static style snapshots",
479
+ working: "useSharedValue, useAnimatedStyle/Props/Reaction, useDerivedValue, useAnimatedScrollHandler, useScrollViewOffset, useFrameCallback, useAnimatedRef, useAnimatedKeyboard, useAnimatedSensor, useReducedMotion, entering/exiting/layout transitions, withTiming/Spring/Decay/Delay/Sequence/Repeat/Clamp, interpolate, interpolateColor, runOnJS/UI, measure, scrollTo, getViewProp (layout, opacity, zIndex, backgroundColor, boxShadow), direct setViewStyle snapshots, Easing, createAnimatedComponent, Animated.View/Text/Image/ScrollView/FlatList",
480
+ missing: "Reanimated 4 native CSS keyframes and transitions remain incomplete"
481
481
  }
482
482
  ]
483
483
  },
@@ -10490,18 +10490,24 @@ function detectFramework(deps) {
10490
10490
  if (deps["react-native"]) return "bare";
10491
10491
  return "unknown";
10492
10492
  }
10493
- function isRNEcosystemPackage(name) {
10493
+ function isRNEcosystemPackage(name, data) {
10494
10494
  if (NON_RN_PACKAGES.has(name)) return false;
10495
- return name.startsWith("react-native") || name.startsWith("expo-") || name.startsWith("@react-native") || name.startsWith("@shopify/") || name.startsWith("@gorhom/") || name.startsWith("@callstack/") || POLYFILL_REGISTRY[name] !== void 0 || UNSUPPORTABLE.has(name);
10495
+ return name.startsWith("react-native") || name.startsWith("expo-") || name.startsWith("@react-native") || name.startsWith("@shopify/") || name.startsWith("@gorhom/") || name.startsWith("@callstack/") || data.registry[name] !== void 0 || data.unsupported.includes(name);
10496
10496
  }
10497
10497
  function scanDeps(pkg) {
10498
+ return scanDepsWithData(pkg, {
10499
+ registry: POLYFILL_REGISTRY,
10500
+ unsupported: [...UNSUPPORTABLE]
10501
+ });
10502
+ }
10503
+ function scanDepsWithData(pkg, data) {
10498
10504
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
10499
10505
  const framework = detectFramework(allDeps);
10500
10506
  const rnVersion = allDeps["react-native"] || null;
10501
10507
  const packages = [];
10502
10508
  for (const [name, version] of Object.entries(allDeps)) {
10503
- if (!isRNEcosystemPackage(name)) continue;
10504
- if (UNSUPPORTABLE.has(name)) {
10509
+ if (!isRNEcosystemPackage(name, data)) continue;
10510
+ if (data.unsupported.includes(name)) {
10505
10511
  packages.push({
10506
10512
  name,
10507
10513
  version,
@@ -10511,14 +10517,14 @@ function scanDeps(pkg) {
10511
10517
  });
10512
10518
  continue;
10513
10519
  }
10514
- const entry = POLYFILL_REGISTRY[name];
10520
+ const entry = data.registry[name];
10515
10521
  if (!entry) {
10516
10522
  packages.push({
10517
10523
  name,
10518
10524
  version,
10519
10525
  coverage: 0.3,
10520
10526
  status: "auto-stub",
10521
- note: "auto-stub (basic no-op proxy)"
10527
+ note: "not yet verified; it may work through SootSim's generic native seam"
10522
10528
  });
10523
10529
  continue;
10524
10530
  }
@@ -10539,7 +10545,7 @@ function scanDeps(pkg) {
10539
10545
  version,
10540
10546
  coverage: 0.3,
10541
10547
  status: "auto-stub",
10542
- note: "version not matched"
10548
+ note: "not yet verified for this version"
10543
10549
  });
10544
10550
  continue;
10545
10551
  }
@@ -10596,18 +10602,106 @@ var init_scan = __esm({
10596
10602
  }
10597
10603
  });
10598
10604
 
10605
+ // ../compat/src/scan-summary.ts
10606
+ function countScanResults(result) {
10607
+ const counts = {
10608
+ supported: 0,
10609
+ partial: 0,
10610
+ unsupported: 0,
10611
+ notYetVerified: 0,
10612
+ notRelevant: 0
10613
+ };
10614
+ for (const pkg of result.packages) {
10615
+ switch (pkg.status) {
10616
+ case "full":
10617
+ counts.supported++;
10618
+ break;
10619
+ case "partial":
10620
+ counts.partial++;
10621
+ break;
10622
+ case "unsupported":
10623
+ counts.unsupported++;
10624
+ break;
10625
+ case "auto-stub":
10626
+ counts.notYetVerified++;
10627
+ break;
10628
+ case "not-relevant":
10629
+ counts.notRelevant++;
10630
+ break;
10631
+ }
10632
+ }
10633
+ return counts;
10634
+ }
10635
+ function formatScanCounts(result) {
10636
+ const counts = countScanResults(result);
10637
+ return [
10638
+ `${counts.supported} supported`,
10639
+ `${counts.partial} partial`,
10640
+ `${counts.unsupported} unsupported`,
10641
+ `${counts.notYetVerified} not yet verified`
10642
+ ].join(", ");
10643
+ }
10644
+ function formatPackageStatus(pkg) {
10645
+ switch (pkg.status) {
10646
+ case "full":
10647
+ return "supported";
10648
+ case "partial":
10649
+ return "partial";
10650
+ case "unsupported":
10651
+ return "unsupported";
10652
+ case "auto-stub":
10653
+ return "not yet verified";
10654
+ case "not-relevant":
10655
+ return "not relevant at runtime";
10656
+ }
10657
+ }
10658
+ function formatDetailedScanLines(result) {
10659
+ const lines = [];
10660
+ const actionable = result.packages.filter(
10661
+ (pkg) => pkg.status === "partial" || pkg.status === "unsupported"
10662
+ );
10663
+ for (const pkg of actionable) {
10664
+ lines.push(`${formatPackageStatus(pkg)}: ${pkg.name}@${pkg.version} \u2014 ${pkg.note}`);
10665
+ }
10666
+ const unknown = result.packages.filter((pkg) => pkg.status === "auto-stub");
10667
+ if (unknown.length > 0) {
10668
+ lines.push("Not yet verified:");
10669
+ for (const pkg of unknown) {
10670
+ lines.push(` ${pkg.name}@${pkg.version} \u2014 ${pkg.note}`);
10671
+ }
10672
+ lines.push(
10673
+ "SootSim may still work. The simulator will report a concrete runtime error if it finds a gap."
10674
+ );
10675
+ }
10676
+ return lines;
10677
+ }
10678
+ var COMPAT_RESULT_CAVEAT;
10679
+ var init_scan_summary = __esm({
10680
+ "../compat/src/scan-summary.ts"() {
10681
+ "use strict";
10682
+ COMPAT_RESULT_CAVEAT = "This is a dependency-level estimate, not a guarantee that every app path works.";
10683
+ }
10684
+ });
10685
+
10599
10686
  // ../compat/src/web.ts
10600
10687
  var web_exports = {};
10601
10688
  __export(web_exports, {
10602
10689
  COMPAT_CATEGORIES: () => COMPAT_CATEGORIES,
10690
+ COMPAT_RESULT_CAVEAT: () => COMPAT_RESULT_CAVEAT,
10603
10691
  POLYFILL_REGISTRY: () => POLYFILL_REGISTRY,
10604
10692
  UNSUPPORTABLE: () => UNSUPPORTABLE,
10605
- scanDeps: () => scanDeps
10693
+ countScanResults: () => countScanResults,
10694
+ formatDetailedScanLines: () => formatDetailedScanLines,
10695
+ formatPackageStatus: () => formatPackageStatus,
10696
+ formatScanCounts: () => formatScanCounts,
10697
+ scanDeps: () => scanDeps,
10698
+ scanDepsWithData: () => scanDepsWithData
10606
10699
  });
10607
10700
  var init_web = __esm({
10608
10701
  "../compat/src/web.ts"() {
10609
10702
  "use strict";
10610
10703
  init_scan();
10704
+ init_scan_summary();
10611
10705
  init_registry();
10612
10706
  }
10613
10707
  });
@@ -10616,6 +10710,7 @@ var init_web = __esm({
10616
10710
  var bridge_constants_exports = {};
10617
10711
  __export(bridge_constants_exports, {
10618
10712
  DEFAULT_SOOTSIM_BRIDGE_PORT: () => DEFAULT_SOOTSIM_BRIDGE_PORT,
10713
+ DEFAULT_SOOTSIM_BRIDGE_URL: () => DEFAULT_SOOTSIM_BRIDGE_URL,
10619
10714
  SOOTSIM_BRIDGE_SIM_CLOSE_CODE: () => SOOTSIM_BRIDGE_SIM_CLOSE_CODE,
10620
10715
  SOOTSIM_BRIDGE_SIM_CLOSE_REASON: () => SOOTSIM_BRIDGE_SIM_CLOSE_REASON,
10621
10716
  resolveSootsimBridgePort: () => resolveSootsimBridgePort,
@@ -10714,11 +10809,12 @@ function resolveSootsimBridgeWebSocketUrl(input) {
10714
10809
  href: input.href
10715
10810
  });
10716
10811
  }
10717
- var DEFAULT_SOOTSIM_BRIDGE_PORT, CONTRAST_DEV_HTTP_BASE_PORT, SOOTSIM_SITE_DEV_HTTP_BASE_PORT, SOOTSIM_SHELL_DEV_HTTP_BASE_PORT, LOCAL_DEV_HTTP_BASE_PORTS, MAX_LOCAL_DEV_PORT_OFFSET, SOOTSIM_BRIDGE_SIM_CLOSE_CODE, SOOTSIM_BRIDGE_SIM_CLOSE_REASON;
10812
+ var DEFAULT_SOOTSIM_BRIDGE_PORT, DEFAULT_SOOTSIM_BRIDGE_URL, CONTRAST_DEV_HTTP_BASE_PORT, SOOTSIM_SITE_DEV_HTTP_BASE_PORT, SOOTSIM_SHELL_DEV_HTTP_BASE_PORT, LOCAL_DEV_HTTP_BASE_PORTS, MAX_LOCAL_DEV_PORT_OFFSET, SOOTSIM_BRIDGE_SIM_CLOSE_CODE, SOOTSIM_BRIDGE_SIM_CLOSE_REASON;
10718
10813
  var init_bridge_constants = __esm({
10719
10814
  "src/bridge-constants.ts"() {
10720
10815
  "use strict";
10721
10816
  DEFAULT_SOOTSIM_BRIDGE_PORT = 7668;
10817
+ DEFAULT_SOOTSIM_BRIDGE_URL = `http://localhost:${DEFAULT_SOOTSIM_BRIDGE_PORT}/`;
10722
10818
  CONTRAST_DEV_HTTP_BASE_PORT = 3e3;
10723
10819
  SOOTSIM_SITE_DEV_HTTP_BASE_PORT = 3001;
10724
10820
  SOOTSIM_SHELL_DEV_HTTP_BASE_PORT = 5173;
@@ -10738,7 +10834,7 @@ function hasOwnKeys(value) {
10738
10834
  }
10739
10835
  function hasSootSimConfig(config) {
10740
10836
  if (!config) return false;
10741
- return hasOwnKeys(config.modules) || hasOwnKeys(config.turboModules) || hasOwnKeys(config.nativeModules) || hasOwnKeys(config.env) || hasOwnKeys(config.network?.remap) || hasOwnKeys(config.settings) || hasOwnKeys(config.initialState);
10837
+ return typeof config.runtimeVersion === "string" && config.runtimeVersion.length > 0 || hasOwnKeys(config.modules) || hasOwnKeys(config.turboModules) || hasOwnKeys(config.nativeModules) || hasOwnKeys(config.env) || hasOwnKeys(config.network?.remap) || hasOwnKeys(config.settings) || hasOwnKeys(config.initialState);
10742
10838
  }
10743
10839
  function applySootSimConfigToUrl(url, config) {
10744
10840
  const parsed = new URL(url);
@@ -11783,7 +11879,7 @@ function readDaemonLockfile() {
11783
11879
  const bytesRead = import_node_fs2.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
11784
11880
  const raw = buf.subarray(0, bytesRead).toString("utf8");
11785
11881
  const parsed = JSON.parse(raw);
11786
- if (parsed && parsed.schema === 1 && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number") {
11882
+ if (parsed && parsed.schema === 1 && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.servedRuntimes === void 0 || Array.isArray(parsed.servedRuntimes) && parsed.servedRuntimes.every((version) => typeof version === "string"))) {
11787
11883
  return parsed;
11788
11884
  }
11789
11885
  return null;
@@ -11802,7 +11898,7 @@ function readDevBridgeLockfile() {
11802
11898
  const bytesRead = import_node_fs2.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
11803
11899
  const raw = buf.subarray(0, bytesRead).toString("utf8");
11804
11900
  const parsed = JSON.parse(raw);
11805
- if (parsed && parsed.schema === 1 && parsed.source === "vite-dev" && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.cwd === "string" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.shellPort === void 0 || typeof parsed.shellPort === "number")) {
11901
+ if (parsed && parsed.schema === 1 && parsed.source === "vite-dev" && typeof parsed.pid === "number" && typeof parsed.bridgePort === "number" && typeof parsed.runtimePort === "number" && typeof parsed.cwd === "string" && typeof parsed.startedAt === "number" && typeof parsed.heartbeatAt === "number" && (parsed.shellPort === void 0 || typeof parsed.shellPort === "number") && (parsed.servedRuntimes === void 0 || Array.isArray(parsed.servedRuntimes) && parsed.servedRuntimes.every((version) => typeof version === "string"))) {
11806
11902
  return parsed;
11807
11903
  }
11808
11904
  return null;
@@ -11885,42 +11981,10 @@ var init_home_paths = __esm({
11885
11981
  }
11886
11982
  });
11887
11983
 
11888
- // src/runtime-assets.ts
11889
- function findMonorepoRoot() {
11890
- if (_monorepoRootCache !== void 0) return _monorepoRootCache;
11891
- let dir = import_path.default.dirname((0, import_url.fileURLToPath)(__sootsim_import_meta_url));
11892
- for (let i = 0; i < 12 && dir !== import_path.default.dirname(dir); i++) {
11893
- if (import_fs.default.existsSync(import_path.default.join(dir, "packages/sootsim-engine/package.json")) && import_fs.default.existsSync(import_path.default.join(dir, "public/sootsim"))) {
11894
- _monorepoRootCache = dir;
11895
- return dir;
11896
- }
11897
- dir = import_path.default.dirname(dir);
11898
- }
11899
- _monorepoRootCache = null;
11900
- return null;
11901
- }
11902
- function resolveDevCheckoutRuntimeRoot() {
11903
- if (process.env.SOOTSIM_HOME && readActiveRuntime()) return null;
11904
- const root = findMonorepoRoot();
11905
- if (!root) return null;
11906
- const devBuild = import_path.default.join(root, "public/sootsim");
11907
- if (import_fs.default.existsSync(import_path.default.join(devBuild, "index.html")) && import_fs.default.existsSync(import_path.default.join(devBuild, "engine/index.html")) && import_fs.default.existsSync(import_path.default.join(devBuild, "engine-tenant/worker.js"))) {
11908
- return devBuild;
11909
- }
11910
- return null;
11911
- }
11912
- var import_fs, import_path, import_url, _monorepoRootCache;
11913
- var init_runtime_assets = __esm({
11914
- "src/runtime-assets.ts"() {
11915
- "use strict";
11916
- import_fs = __toESM(require("fs"), 1);
11917
- import_path = __toESM(require("path"), 1);
11918
- import_url = require("url");
11919
- init_home_paths();
11920
- }
11921
- });
11922
-
11923
11984
  // ../contrast-runtime-delivery/src/product.ts
11985
+ function isValidRuntimeVersion(value) {
11986
+ return value.length <= 100 && /^[0-9A-Za-z][0-9A-Za-z._+-]*$/.test(value);
11987
+ }
11924
11988
  function listInstalledRuntimes(product) {
11925
11989
  try {
11926
11990
  return import_node_fs3.default.readdirSync(product.runtimesDir(), { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort(compareSemver);
@@ -11983,6 +12047,7 @@ function createRuntimeDelivery(product) {
11983
12047
  return input || process.env[product.env.channel] || config.runtimeChannel || "stable";
11984
12048
  }
11985
12049
  function resolvePin() {
12050
+ if (!product.env.pin) return null;
11986
12051
  const env = process.env[product.env.pin];
11987
12052
  if (env && env.trim()) return env.trim();
11988
12053
  const config = readConfig();
@@ -12012,6 +12077,9 @@ function createRuntimeDelivery(product) {
12012
12077
  `no version specified and channel '${channel}' has no latest entry in the manifest`
12013
12078
  );
12014
12079
  }
12080
+ if (!isValidRuntimeVersion(version)) {
12081
+ throw new Error(`invalid runtime version: ${version}`);
12082
+ }
12015
12083
  const entry = manifest.versions[version];
12016
12084
  if (!entry) {
12017
12085
  throw new Error(
@@ -12027,10 +12095,11 @@ function createRuntimeDelivery(product) {
12027
12095
  const { version, channel, entry } = resolveVersion(manifest, opts);
12028
12096
  const destDir = product.runtimeDir(version);
12029
12097
  const setActive = opts.setActive !== false;
12030
- pruneLocalRetention({ protectVersions: [version] });
12098
+ const protectVersions = [version, ...opts.protectVersions ?? []];
12099
+ pruneLocalRetention({ protectVersions });
12031
12100
  if (!opts.force && import_node_fs4.default.existsSync(import_node_path4.default.join(destDir, product.installedMarker))) {
12032
12101
  if (setActive) product.writeActiveRuntime(version);
12033
- pruneLocalRetention({ protectVersions: [version] });
12102
+ pruneLocalRetention({ protectVersions });
12034
12103
  return {
12035
12104
  version,
12036
12105
  channel,
@@ -12042,29 +12111,32 @@ function createRuntimeDelivery(product) {
12042
12111
  };
12043
12112
  }
12044
12113
  const tarUrl = entry.tarball || tarballUrl(version, cdnOrigin);
12045
- const tarCachePath = import_node_path4.default.join(
12046
- product.cacheDir(),
12114
+ const downloadDir = import_node_fs4.default.mkdtempSync(
12115
+ import_node_path4.default.join(import_node_os2.default.tmpdir(), `${product.id}-runtime-download-`)
12116
+ );
12117
+ const runtimeArchivePath = import_node_path4.default.join(
12118
+ downloadDir,
12047
12119
  runtimeTarballCacheName(product, version)
12048
12120
  );
12049
- process.stderr.write(`${product.id}: downloading runtime ${version}\u2026
12050
- `);
12051
- await downloadToFile(tarUrl, tarCachePath);
12052
- process.stderr.write(`${product.id}: extracting runtime ${version}\u2026
12053
- `);
12054
12121
  const tmpDir = import_node_path4.default.join(
12055
12122
  import_node_path4.default.dirname(destDir),
12056
12123
  `.installing-${version}-${process.pid}`
12057
12124
  );
12058
12125
  import_node_fs4.default.rmSync(tmpDir, { recursive: true, force: true });
12059
12126
  try {
12060
- const actualSha = await sha256File(tarCachePath);
12127
+ process.stderr.write(`${product.id}: downloading runtime ${version}\u2026
12128
+ `);
12129
+ await downloadToFile(tarUrl, runtimeArchivePath);
12130
+ process.stderr.write(`${product.id}: extracting runtime ${version}\u2026
12131
+ `);
12132
+ const actualSha = await sha256File(runtimeArchivePath);
12061
12133
  if (actualSha !== entry.sha256) {
12062
12134
  throw new Error(
12063
12135
  `sha256 mismatch for runtime ${version}: expected ${entry.sha256}, actual ${actualSha}`
12064
12136
  );
12065
12137
  }
12066
12138
  import_node_fs4.default.mkdirSync(tmpDir, { recursive: true });
12067
- await extractTarball(tarCachePath, tmpDir);
12139
+ await extractTarball(runtimeArchivePath, tmpDir);
12068
12140
  if (!import_node_fs4.default.existsSync(import_node_path4.default.join(tmpDir, product.installedMarker))) {
12069
12141
  throw new Error(
12070
12142
  `extracted tarball for runtime ${version} is missing ${product.installedMarker}`
@@ -12076,10 +12148,10 @@ function createRuntimeDelivery(product) {
12076
12148
  import_node_fs4.default.rmSync(tmpDir, { recursive: true, force: true });
12077
12149
  throw err;
12078
12150
  } finally {
12079
- import_node_fs4.default.rmSync(tarCachePath, { force: true });
12151
+ import_node_fs4.default.rmSync(downloadDir, { recursive: true, force: true });
12080
12152
  }
12081
12153
  if (setActive) product.writeActiveRuntime(version);
12082
- pruneLocalRetention({ protectVersions: [version] });
12154
+ pruneLocalRetention({ protectVersions });
12083
12155
  return {
12084
12156
  version,
12085
12157
  channel,
@@ -12092,7 +12164,7 @@ function createRuntimeDelivery(product) {
12092
12164
  }
12093
12165
  async function updateToLatest(opts = {}) {
12094
12166
  product.ensureHome();
12095
- pruneLocalRetention();
12167
+ pruneLocalRetention({ protectVersions: opts.protectVersions });
12096
12168
  const cdnOrigin = resolveCdnOrigin(opts.cdnOrigin);
12097
12169
  const channel = resolveChannel(opts.channel);
12098
12170
  const pin = resolvePin();
@@ -12114,7 +12186,8 @@ function createRuntimeDelivery(product) {
12114
12186
  version: pin,
12115
12187
  channel,
12116
12188
  cdnOrigin,
12117
- setActive: false
12189
+ setActive: false,
12190
+ protectVersions: opts.protectVersions
12118
12191
  });
12119
12192
  return {
12120
12193
  checked: true,
@@ -12162,7 +12235,8 @@ function createRuntimeDelivery(product) {
12162
12235
  version: latestVersion,
12163
12236
  channel,
12164
12237
  cdnOrigin,
12165
- setActive: false
12238
+ setActive: false,
12239
+ protectVersions: opts.protectVersions
12166
12240
  });
12167
12241
  return {
12168
12242
  checked: true,
@@ -12304,13 +12378,14 @@ function extractTarball(tarPath, destDir) {
12304
12378
  });
12305
12379
  });
12306
12380
  }
12307
- var import_node_child_process2, import_node_crypto, import_node_fs4, import_node_path4, import_node_stream, import_promises2, DEFAULT_LOCAL_RUNTIME_RETENTION_COUNT;
12381
+ var import_node_child_process2, import_node_crypto, import_node_fs4, import_node_os2, import_node_path4, import_node_stream, import_promises2, DEFAULT_LOCAL_RUNTIME_RETENTION_COUNT;
12308
12382
  var init_src = __esm({
12309
12383
  "../contrast-runtime-delivery/src/index.ts"() {
12310
12384
  "use strict";
12311
12385
  import_node_child_process2 = require("node:child_process");
12312
12386
  import_node_crypto = __toESM(require("node:crypto"), 1);
12313
12387
  import_node_fs4 = __toESM(require("node:fs"), 1);
12388
+ import_node_os2 = __toESM(require("node:os"), 1);
12314
12389
  import_node_path4 = __toESM(require("node:path"), 1);
12315
12390
  import_node_stream = require("node:stream");
12316
12391
  import_promises2 = require("node:stream/promises");
@@ -12320,6 +12395,63 @@ var init_src = __esm({
12320
12395
  }
12321
12396
  });
12322
12397
 
12398
+ // src/runtime-assets.ts
12399
+ function findMonorepoRoot() {
12400
+ if (_monorepoRootCache !== void 0) return _monorepoRootCache;
12401
+ let dir = import_path.default.dirname((0, import_url.fileURLToPath)(__sootsim_import_meta_url));
12402
+ for (let i = 0; i < 12 && dir !== import_path.default.dirname(dir); i++) {
12403
+ if (import_fs.default.existsSync(import_path.default.join(dir, "packages/sootsim-engine/package.json")) && import_fs.default.existsSync(import_path.default.join(dir, "public/sootsim"))) {
12404
+ _monorepoRootCache = dir;
12405
+ return dir;
12406
+ }
12407
+ dir = import_path.default.dirname(dir);
12408
+ }
12409
+ _monorepoRootCache = null;
12410
+ return null;
12411
+ }
12412
+ function resolveDevCheckoutRuntimeRoot() {
12413
+ if (process.env.SOOTSIM_HOME && readActiveRuntime()) return null;
12414
+ const root = findMonorepoRoot();
12415
+ if (!root) return null;
12416
+ const devBuild = import_path.default.join(root, "public/sootsim");
12417
+ if (import_fs.default.existsSync(import_path.default.join(devBuild, "index.html")) && import_fs.default.existsSync(import_path.default.join(devBuild, "engine/index.html")) && import_fs.default.existsSync(import_path.default.join(devBuild, "engine-tenant/worker.js"))) {
12418
+ return devBuild;
12419
+ }
12420
+ return null;
12421
+ }
12422
+ function runtimeVersionFromHostname(hostname) {
12423
+ const suffix = ".runtime.localhost";
12424
+ if (!hostname.toLowerCase().endsWith(suffix)) return null;
12425
+ const labels = hostname.slice(0, -suffix.length).split(".");
12426
+ if (labels.length === 0 || labels.some((label) => !/^[0-9a-f]{1,60}$/i.test(label))) {
12427
+ return null;
12428
+ }
12429
+ const encoded = labels.join("");
12430
+ if (encoded.length % 2 !== 0) return null;
12431
+ try {
12432
+ const version = Buffer.from(encoded, "hex").toString("utf8");
12433
+ return isValidRuntimeVersion(version) ? version : null;
12434
+ } catch {
12435
+ return null;
12436
+ }
12437
+ }
12438
+ function resolveInstalledRuntimeRoot(version) {
12439
+ const dir = runtimeDir(version);
12440
+ if (!import_fs.default.existsSync(import_path.default.join(dir, "index.html"))) return null;
12441
+ return dir;
12442
+ }
12443
+ var import_fs, import_path, import_url, _monorepoRootCache;
12444
+ var init_runtime_assets = __esm({
12445
+ "src/runtime-assets.ts"() {
12446
+ "use strict";
12447
+ import_fs = __toESM(require("fs"), 1);
12448
+ import_path = __toESM(require("path"), 1);
12449
+ import_url = require("url");
12450
+ init_src();
12451
+ init_home_paths();
12452
+ }
12453
+ });
12454
+
12323
12455
  // src/runtime-delivery.ts
12324
12456
  var DEFAULT_RUNTIME_CDN_ORIGIN, SOOTSIM_RUNTIME_PRODUCT, sootsimRuntime;
12325
12457
  var init_runtime_delivery = __esm({
@@ -12339,8 +12471,7 @@ var init_runtime_delivery = __esm({
12339
12471
  installedMarker: "index.html",
12340
12472
  env: {
12341
12473
  cdnOrigin: "SOOTSIM_CDN_ORIGIN",
12342
- channel: "SOOTSIM_RUNTIME_CHANNEL",
12343
- pin: "SOOTSIM_RUNTIME_VERSION"
12474
+ channel: "SOOTSIM_RUNTIME_CHANNEL"
12344
12475
  },
12345
12476
  ensureHome: ensureSootsimHome,
12346
12477
  configFile: configFilePath,
@@ -14733,6 +14864,7 @@ var init_bridge_host = __esm({
14733
14864
  recentActions: []
14734
14865
  };
14735
14866
  this.sims.set(sim.id, sim);
14867
+ this.writeConnectedRuntimeSnapshot();
14736
14868
  if (this.shouldPromoteSim(sim)) {
14737
14869
  this.primarySimId = sim.id;
14738
14870
  }
@@ -14824,6 +14956,7 @@ var init_bridge_host = __esm({
14824
14956
  sim = null;
14825
14957
  role = "cli";
14826
14958
  this.cliIdentityKeyBySocket.set(ws, `ws-${this.nextCliFallbackId++}`);
14959
+ this.writeConnectedRuntimeSnapshot();
14827
14960
  this.broadcastSimAssignments();
14828
14961
  this.broadcastSimClientStates();
14829
14962
  }
@@ -14837,6 +14970,7 @@ var init_bridge_host = __esm({
14837
14970
  sim.url = registration.url;
14838
14971
  sim.title = registration.title;
14839
14972
  sim.userAgent = registration.userAgent;
14973
+ this.writeConnectedRuntimeSnapshot();
14840
14974
  if (typeof registration.kind === "string" && registration.kind.trim()) {
14841
14975
  sim.kind = registration.kind.trim();
14842
14976
  }
@@ -14861,11 +14995,14 @@ var init_bridge_host = __esm({
14861
14995
  return;
14862
14996
  }
14863
14997
  if (msg.type === "bridge:write-shared-config") {
14864
- const patch = msg.patch && typeof msg.patch === "object" ? msg.patch : null;
14865
- if (!patch) return;
14866
- let next;
14998
+ if (!msg.patch || typeof msg.patch !== "object" || Array.isArray(msg.patch)) {
14999
+ return;
15000
+ }
15001
+ const patch = Object.fromEntries(
15002
+ Object.entries(msg.patch)
15003
+ );
14867
15004
  try {
14868
- next = writeSharedConfig(patch);
15005
+ this.writeAndBroadcastSharedConfig(patch);
14869
15006
  } catch (err) {
14870
15007
  process.stderr.write(
14871
15008
  `sootsim: bridge:write-shared-config failed: ${err instanceof Error ? err.message : String(err)}
@@ -14873,17 +15010,6 @@ var init_bridge_host = __esm({
14873
15010
  );
14874
15011
  return;
14875
15012
  }
14876
- const payload = JSON.stringify({
14877
- type: "bridge:shared-config-changed",
14878
- config: next
14879
- });
14880
- for (const peer of this.sims.values()) {
14881
- if (peer.ws.readyState !== import_ws2.WebSocket.OPEN) continue;
14882
- try {
14883
- peer.ws.send(payload);
14884
- } catch {
14885
- }
14886
- }
14887
15013
  return;
14888
15014
  }
14889
15015
  if (msg.type === "bridge:open-path") {
@@ -15198,6 +15324,18 @@ var init_bridge_host = __esm({
15198
15324
  void this.agentHost.seedOnBoot();
15199
15325
  }
15200
15326
  bootstrapping = true;
15327
+ connectedRuntimeVersions() {
15328
+ const versions = /* @__PURE__ */ new Set();
15329
+ for (const sim of this.sims.values()) {
15330
+ try {
15331
+ const url = new URL(sim.url || sim.origin || "http://localhost");
15332
+ const version = runtimeVersionFromHostname(url.hostname);
15333
+ if (version) versions.add(version);
15334
+ } catch {
15335
+ }
15336
+ }
15337
+ return [...versions].sort();
15338
+ }
15201
15339
  buildLockfileSnapshot() {
15202
15340
  return {
15203
15341
  schema: 1,
@@ -15207,6 +15345,7 @@ var init_bridge_host = __esm({
15207
15345
  runtimePort: this.effectivePort,
15208
15346
  activeRuntime: this.activeRuntimeVersion,
15209
15347
  activeRuntimeDir: this.activeRuntimeDirPath,
15348
+ servedRuntimes: this.connectedRuntimeVersions(),
15210
15349
  startedAt: this.startedAt,
15211
15350
  heartbeatAt: Date.now(),
15212
15351
  bootstrapping: this.bootstrapping
@@ -15224,7 +15363,8 @@ var init_bridge_host = __esm({
15224
15363
  cwd: process.cwd(),
15225
15364
  startedAt: this.startedAt,
15226
15365
  heartbeatAt: Date.now(),
15227
- source: "vite-dev"
15366
+ source: "vite-dev",
15367
+ servedRuntimes: this.connectedRuntimeVersions()
15228
15368
  };
15229
15369
  }
15230
15370
  writeLockfileSnapshot() {
@@ -15233,6 +15373,20 @@ var init_bridge_host = __esm({
15233
15373
  writeDevLockfileSnapshot() {
15234
15374
  writeDevBridgeLockfile(this.buildDevLockfileSnapshot());
15235
15375
  }
15376
+ writeConnectedRuntimeSnapshot() {
15377
+ if (this.shouldWriteLockfile && this.httpServer) {
15378
+ try {
15379
+ this.writeLockfileSnapshot();
15380
+ } catch {
15381
+ }
15382
+ }
15383
+ if (this.shouldWriteDevLockfile && this.httpServer) {
15384
+ try {
15385
+ this.writeDevLockfileSnapshot();
15386
+ } catch {
15387
+ }
15388
+ }
15389
+ }
15236
15390
  refreshActiveRuntime() {
15237
15391
  this.activeRuntimeVersion = readActiveRuntime();
15238
15392
  this.activeRuntimeDirPath = resolveDevCheckoutRuntimeRoot() ?? activeRuntimeDir();
@@ -15284,6 +15438,7 @@ var init_bridge_host = __esm({
15284
15438
  }
15285
15439
  startRuntimeUpdater() {
15286
15440
  if (!this.shouldWriteLockfile || this.runtimeUpdateTimer) return;
15441
+ if (import_fs3.default.existsSync(import_path3.default.join(sootsimHomeDir(), "runtime-update-disabled"))) return;
15287
15442
  if (process.env.SOOTSIM_HOME && isSootsimDevCheckout() && !readActiveRuntime()) {
15288
15443
  return;
15289
15444
  }
@@ -15301,7 +15456,9 @@ var init_bridge_host = __esm({
15301
15456
  if (reason === "startup") {
15302
15457
  process.stderr.write("sootsim: checking for runtime updates\u2026\n");
15303
15458
  }
15304
- const result = await sootsimRuntime.updateToLatest();
15459
+ const result = await sootsimRuntime.updateToLatest({
15460
+ protectVersions: this.connectedRuntimeVersions()
15461
+ });
15305
15462
  if (!result.updated || !result.latestVersion) {
15306
15463
  if (reason === "startup") {
15307
15464
  process.stderr.write(
@@ -15362,6 +15519,11 @@ var init_bridge_host = __esm({
15362
15519
  runtimeDir: this.activeRuntimeDirPath
15363
15520
  });
15364
15521
  for (const sim of this.sims.values()) {
15522
+ try {
15523
+ const simUrl = new URL(sim.url || sim.origin || "http://localhost");
15524
+ if (runtimeVersionFromHostname(simUrl.hostname)) continue;
15525
+ } catch {
15526
+ }
15365
15527
  if (sim.ws.readyState === import_ws2.WebSocket.OPEN) {
15366
15528
  try {
15367
15529
  sim.ws.send(payload);
@@ -15405,12 +15567,77 @@ var init_bridge_host = __esm({
15405
15567
  return;
15406
15568
  }
15407
15569
  const method = (req.method || "GET").toUpperCase();
15570
+ let url;
15571
+ try {
15572
+ url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
15573
+ } catch {
15574
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
15575
+ res.end("invalid request URL or host");
15576
+ return;
15577
+ }
15578
+ if (url.pathname === "/__sootsim/shared-config") {
15579
+ res.setHeader("Access-Control-Allow-Origin", "*");
15580
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
15581
+ res.setHeader("Cache-Control", "no-store");
15582
+ if (method === "OPTIONS") {
15583
+ res.writeHead(204, { Allow: "GET, HEAD, POST, OPTIONS" });
15584
+ res.end();
15585
+ return;
15586
+ }
15587
+ if (method === "GET" || method === "HEAD") {
15588
+ let body = "{}";
15589
+ try {
15590
+ body = JSON.stringify(readSharedConfig());
15591
+ } catch {
15592
+ }
15593
+ res.writeHead(200, { "Content-Type": "application/json" });
15594
+ if (method === "HEAD") res.end();
15595
+ else res.end(body);
15596
+ return;
15597
+ }
15598
+ if (method === "POST") {
15599
+ void (async () => {
15600
+ try {
15601
+ const chunks = [];
15602
+ let size = 0;
15603
+ for await (const chunk of req) {
15604
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
15605
+ size += buffer.byteLength;
15606
+ if (size > 8 * 1024 * 1024) {
15607
+ res.writeHead(413, { "Content-Type": "text/plain; charset=utf-8" });
15608
+ res.end("shared config patch is too large");
15609
+ return;
15610
+ }
15611
+ chunks.push(buffer);
15612
+ }
15613
+ const parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
15614
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15615
+ throw new Error("shared config patch must be an object");
15616
+ }
15617
+ const patch = Object.fromEntries(
15618
+ Object.entries(parsed)
15619
+ );
15620
+ const next = this.writeAndBroadcastSharedConfig(patch);
15621
+ res.writeHead(200, { "Content-Type": "application/json" });
15622
+ res.end(JSON.stringify(next));
15623
+ } catch (error) {
15624
+ res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
15625
+ res.end(
15626
+ `invalid shared config patch: ${error instanceof Error ? error.message : String(error)}`
15627
+ );
15628
+ }
15629
+ })();
15630
+ return;
15631
+ }
15632
+ res.writeHead(405, { Allow: "GET, HEAD, POST, OPTIONS" });
15633
+ res.end("method not allowed");
15634
+ return;
15635
+ }
15408
15636
  if (method !== "GET" && method !== "HEAD") {
15409
15637
  res.writeHead(405, { Allow: "GET, HEAD" });
15410
15638
  res.end("method not allowed");
15411
15639
  return;
15412
15640
  }
15413
- const url = new URL(req.url || "/", "http://localhost");
15414
15641
  if (url.pathname === "/__bundle-proxy") {
15415
15642
  const target = url.searchParams.get("url");
15416
15643
  if (!target) {
@@ -15487,30 +15714,13 @@ var init_bridge_host = __esm({
15487
15714
  );
15488
15715
  return;
15489
15716
  }
15490
- if (url.pathname === "/__sootsim/shared-config") {
15491
- res.setHeader("Access-Control-Allow-Origin", "*");
15492
- res.setHeader("Cache-Control", "no-store");
15493
- if (method === "GET" || method === "HEAD") {
15494
- let body = "{}";
15495
- try {
15496
- body = JSON.stringify(readSharedConfig());
15497
- } catch {
15498
- }
15499
- res.writeHead(200, { "Content-Type": "application/json" });
15500
- if (method === "HEAD") res.end();
15501
- else res.end(body);
15502
- return;
15503
- }
15504
- res.writeHead(405, { Allow: "GET, HEAD" });
15505
- res.end("method not allowed (use the bridge over WS for writes)");
15506
- return;
15507
- }
15508
- this.refreshActiveRuntime();
15509
- const baseDir = this.activeRuntimeDirPath;
15717
+ const requestedRuntime = runtimeVersionFromHostname(url.hostname);
15718
+ if (!requestedRuntime) this.refreshActiveRuntime();
15719
+ const baseDir = requestedRuntime ? resolveInstalledRuntimeRoot(requestedRuntime) : this.activeRuntimeDirPath;
15510
15720
  if (!baseDir) {
15511
15721
  res.writeHead(503, { "Content-Type": "text/plain; charset=utf-8" });
15512
15722
  res.end(
15513
- "sootsim: no active runtime installed. run `sootsim runtime install` to fetch one."
15723
+ requestedRuntime ? `sootsim: runtime ${requestedRuntime} is not installed. run \`sootsim open\` from that app again.` : "sootsim: no active runtime installed. run `sootsim runtime install` to fetch one."
15514
15724
  );
15515
15725
  return;
15516
15726
  }
@@ -16117,6 +16327,21 @@ var init_bridge_host = __esm({
16117
16327
  );
16118
16328
  }
16119
16329
  }
16330
+ writeAndBroadcastSharedConfig(patch) {
16331
+ const next = writeSharedConfig(patch);
16332
+ const payload = JSON.stringify({
16333
+ type: "bridge:shared-config-changed",
16334
+ config: next
16335
+ });
16336
+ for (const peer of this.sims.values()) {
16337
+ if (peer.ws.readyState !== import_ws2.WebSocket.OPEN) continue;
16338
+ try {
16339
+ peer.ws.send(payload);
16340
+ } catch {
16341
+ }
16342
+ }
16343
+ return next;
16344
+ }
16120
16345
  broadcastSimClientStates() {
16121
16346
  for (const sim of this.sims.values()) {
16122
16347
  if (sim.ws.readyState !== import_ws2.WebSocket.OPEN) continue;
@@ -16255,6 +16480,7 @@ var init_bridge_host = __esm({
16255
16480
  expiresAt: Date.now() + _SootSimBridgeHost.SIM_RECONNECT_TTL_MS
16256
16481
  });
16257
16482
  this.sims.delete(sim.id);
16483
+ this.writeConnectedRuntimeSnapshot();
16258
16484
  }
16259
16485
  getRestorableSimState(simId) {
16260
16486
  const snapshot = this.restorableSims.get(simId);
@@ -18381,6 +18607,13 @@ var init_auth = __esm({
18381
18607
  }
18382
18608
  });
18383
18609
 
18610
+ // cli/app-project.ts
18611
+ var init_app_project = __esm({
18612
+ "cli/app-project.ts"() {
18613
+ "use strict";
18614
+ }
18615
+ });
18616
+
18384
18617
  // cli/app-fonts.ts
18385
18618
  var import_node_module, import_config_plugin_fonts, require2;
18386
18619
  var init_app_fonts = __esm({
@@ -18388,6 +18621,7 @@ var init_app_fonts = __esm({
18388
18621
  "use strict";
18389
18622
  import_node_module = require("node:module");
18390
18623
  import_config_plugin_fonts = require("sootsim-engine/engine/config-plugin-fonts");
18624
+ init_app_project();
18391
18625
  require2 = (0, import_node_module.createRequire)(__sootsim_import_meta_url);
18392
18626
  }
18393
18627
  });
@@ -18399,6 +18633,7 @@ var init_app_splash = __esm({
18399
18633
  "use strict";
18400
18634
  import_node_module2 = require("node:module");
18401
18635
  import_config_plugin_splash = require("sootsim-engine/engine/config-plugin-splash");
18636
+ init_app_project();
18402
18637
  require3 = (0, import_node_module2.createRequire)(__sootsim_import_meta_url);
18403
18638
  }
18404
18639
  });
@@ -20591,11 +20826,6 @@ var skill = {
20591
20826
  };
20592
20827
 
20593
20828
  // src/skills/builtin/compat-check.ts
20594
- function getCompatStatus(entry) {
20595
- if (entry.stubType === "works") return "full";
20596
- if (entry.stubType === "build-only") return "full";
20597
- return entry.versions.some((version) => version.coverage >= 1) ? "full" : "auto-stub";
20598
- }
20599
20829
  var skill2 = {
20600
20830
  name: "compat-check",
20601
20831
  description: "Check package compatibility with sootsim",
@@ -20613,62 +20843,46 @@ var skill2 = {
20613
20843
  }
20614
20844
  },
20615
20845
  async execute(params, context) {
20616
- const fs11 = await import("fs");
20617
- const path11 = await import("path");
20618
- const { POLYFILL_REGISTRY: POLYFILL_REGISTRY2 } = await Promise.resolve().then(() => (init_web(), web_exports));
20619
- if (params.packageName) {
20620
- const entry = POLYFILL_REGISTRY2[params.packageName];
20621
- if (!entry) {
20622
- return {
20623
- success: true,
20624
- message: `${params.packageName}: not in registry (may be auto-stubbed if native)`,
20625
- data: { package: params.packageName, status: "unknown" }
20626
- };
20627
- }
20628
- const status = getCompatStatus(entry);
20629
- return {
20630
- success: true,
20631
- message: `${params.packageName}: ${status}`,
20632
- data: { package: params.packageName, status, ...entry }
20633
- };
20634
- }
20635
- const pkgPath = path11.join(context.projectDir, "package.json");
20636
- if (!fs11.existsSync(pkgPath)) {
20846
+ const fs11 = await import("node:fs");
20847
+ const path11 = await import("node:path");
20848
+ const { COMPAT_RESULT_CAVEAT: COMPAT_RESULT_CAVEAT2, formatPackageStatus: formatPackageStatus2, formatScanCounts: formatScanCounts2, scanDeps: scanDeps2 } = await Promise.resolve().then(() => (init_web(), web_exports));
20849
+ const packagePath = path11.join(context.projectDir, "package.json");
20850
+ if (!fs11.existsSync(packagePath)) {
20637
20851
  return { success: false, message: "No package.json found" };
20638
20852
  }
20639
- const pkg = JSON.parse(fs11.readFileSync(pkgPath, "utf8"));
20640
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
20641
- const results = {
20642
- full: [],
20643
- partial: [],
20644
- "auto-stub": [],
20645
- unsupported: [],
20646
- unknown: []
20647
- };
20648
- for (const dep of Object.keys(allDeps)) {
20649
- const entry = POLYFILL_REGISTRY2[dep];
20650
- if (entry) {
20651
- const status = getCompatStatus(entry);
20652
- if (results[status]) results[status].push(dep);
20653
- } else if (isNative(dep)) {
20654
- results.unknown.push(dep);
20655
- }
20853
+ const parsed = JSON.parse(fs11.readFileSync(packagePath, "utf8"));
20854
+ if (!parsed || typeof parsed !== "object") {
20855
+ return { success: false, message: "package.json is not an object" };
20656
20856
  }
20857
+ const dependencies = readDependencies(parsed);
20858
+ const scan = params.packageName ? scanDeps2({
20859
+ dependencies: {
20860
+ [params.packageName]: dependencies[params.packageName] ?? "*"
20861
+ }
20862
+ }) : scanDeps2(parsed);
20863
+ const packageResult = params.packageName ? scan.packages[0] : void 0;
20864
+ const message = packageResult ? `${params.packageName}: ${formatPackageStatus2(packageResult)}. ${COMPAT_RESULT_CAVEAT2}` : `${formatScanCounts2(scan)}. ${COMPAT_RESULT_CAVEAT2}`;
20657
20865
  const reportPath = path11.join(context.outputDir, "compat-report.json");
20658
20866
  fs11.mkdirSync(path11.dirname(reportPath), { recursive: true });
20659
- fs11.writeFileSync(reportPath, JSON.stringify(results, null, 2));
20867
+ fs11.writeFileSync(reportPath, `${JSON.stringify(scan, null, 2)}
20868
+ `);
20660
20869
  return {
20661
- success: results.unsupported.length === 0,
20662
- message: `${results.full.length} full, ${results.partial.length} partial, ${results.unsupported.length} unsupported, ${results.unknown.length} unknown`,
20870
+ success: true,
20871
+ message,
20663
20872
  artifacts: [{ type: "report", name: "compat-report", path: reportPath }],
20664
- data: results
20873
+ data: scan
20665
20874
  };
20666
20875
  }
20667
20876
  }
20668
20877
  ]
20669
20878
  };
20670
- function isNative(name) {
20671
- return name.startsWith("expo-") || name.startsWith("react-native-") || name.startsWith("@react-native/") || name.startsWith("@react-native-community/") || name.startsWith("@expo/") || name === "expo";
20879
+ function readDependencies(value) {
20880
+ const dependencies = Reflect.get(value, "dependencies");
20881
+ const devDependencies = Reflect.get(value, "devDependencies");
20882
+ return {
20883
+ ...dependencies && typeof dependencies === "object" ? dependencies : {},
20884
+ ...devDependencies && typeof devDependencies === "object" ? devDependencies : {}
20885
+ };
20672
20886
  }
20673
20887
 
20674
20888
  // src/skills/builtin/maestro-test.ts