sootsim 0.1.295 → 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 (182) hide show
  1. package/README.md +13 -4
  2. package/dist-cli/bin.js +5 -5
  3. package/dist-cli/chunks/{agent-ASAYE5YY.js → agent-3F4K6MQK.js} +2 -2
  4. package/dist-cli/chunks/{agent-wrapper-K2D4Q5AA.js → agent-wrapper-5LLVCQRZ.js} +2 -2
  5. package/dist-cli/chunks/app-fonts-LPTY7A5G.js +4 -0
  6. package/dist-cli/chunks/{assert-4FKHHDY7.js → assert-R2ZUXEMS.js} +2 -2
  7. package/dist-cli/chunks/{auth-BQPOPEFK.js → auth-CA62Q6IS.js} +2 -2
  8. package/dist-cli/chunks/{beta-3LNYONDF.js → beta-4AXSOQLU.js} +2 -2
  9. package/dist-cli/chunks/{camera-Y5MZ5V4L.js → camera-Y4RBIONE.js} +2 -2
  10. package/dist-cli/chunks/{chunk-L7V46673.js → chunk-222Y7NQA.js} +1 -1
  11. package/dist-cli/chunks/{chunk-6VF477QO.js → chunk-2LQOLMRK.js} +1 -1
  12. package/dist-cli/chunks/{chunk-S6BEA6OC.js → chunk-2XUKWIAL.js} +1 -1
  13. package/dist-cli/chunks/{chunk-XQRUWC3F.js → chunk-3PAZJSIG.js} +2 -2
  14. package/dist-cli/chunks/{chunk-UAZ3NVKO.js → chunk-4MDLYOEI.js} +2 -2
  15. package/dist-cli/chunks/{chunk-MEYVMDZR.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-FH7DQ2HB.js → chunk-6C24WCDK.js} +2 -2
  19. package/dist-cli/chunks/{chunk-T2DHHOWE.js → chunk-74FOMZP7.js} +2 -2
  20. package/dist-cli/chunks/{chunk-K7LQB4IF.js → chunk-7GQWF7HE.js} +1 -1
  21. package/dist-cli/chunks/{chunk-RNW36VM5.js → chunk-7V3JNANH.js} +4 -4
  22. package/dist-cli/chunks/{chunk-SU5WQ4SQ.js → chunk-7YQXQSG7.js} +1 -1
  23. package/dist-cli/chunks/{chunk-DKS75M2I.js → chunk-AUBP44CY.js} +2 -2
  24. package/dist-cli/chunks/chunk-B2FB6IYX.js +4 -0
  25. package/dist-cli/chunks/{chunk-E6DZD6JD.js → chunk-B2LDO55K.js} +1 -1
  26. package/dist-cli/chunks/{chunk-XCCDO6I2.js → chunk-BVWIR4CD.js} +1 -1
  27. package/dist-cli/chunks/{chunk-UBIGBHUM.js → chunk-C462URRQ.js} +2 -2
  28. package/dist-cli/chunks/{chunk-CVDC2ER2.js → chunk-CAAADPRH.js} +2 -2
  29. package/dist-cli/chunks/{chunk-ETDZQUGI.js → chunk-CDCVWGNU.js} +3 -3
  30. package/dist-cli/chunks/{chunk-JPPGM6RZ.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-TNKZWHD2.js → chunk-EJ6BAJKX.js} +1 -1
  34. package/dist-cli/chunks/chunk-F2MS365Z.js +4 -0
  35. package/dist-cli/chunks/{chunk-I4QJVACX.js → chunk-FROCFR77.js} +1 -1
  36. package/dist-cli/chunks/{chunk-RV67EC7L.js → chunk-FUPQIX4U.js} +2 -2
  37. package/dist-cli/chunks/{chunk-U2QPKFOY.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-7GZ6XF2B.js → chunk-IR3DUBRF.js} +2 -2
  42. package/dist-cli/chunks/chunk-LHUY46GE.js +4 -0
  43. package/dist-cli/chunks/{chunk-WFYQNZTT.js → chunk-MDBR4XTV.js} +3 -3
  44. package/dist-cli/chunks/{chunk-UWFAWMPE.js → chunk-MQ6QX46T.js} +2 -2
  45. package/dist-cli/chunks/chunk-N3FZRJT7.js +4 -0
  46. package/dist-cli/chunks/{chunk-QPA44BZC.js → chunk-NEARYPSC.js} +11 -11
  47. package/dist-cli/chunks/chunk-P7HBJBHH.js +4 -0
  48. package/dist-cli/chunks/{chunk-7FYIMLU4.js → chunk-PEUWZRXA.js} +17 -17
  49. package/dist-cli/chunks/{chunk-X5FTDIHQ.js → chunk-QIDFGREY.js} +1 -1
  50. package/dist-cli/chunks/{chunk-4NJIXTVJ.js → chunk-R65XMN4D.js} +1 -1
  51. package/dist-cli/chunks/{chunk-Y6FJ5FCY.js → chunk-S5Z4HKWX.js} +1 -1
  52. package/dist-cli/chunks/chunk-SUJSGGSS.js +23 -0
  53. package/dist-cli/chunks/{chunk-BX2NUJGR.js → chunk-TIOPG6QZ.js} +1 -1
  54. package/dist-cli/chunks/chunk-WJVBD2UL.js +9 -0
  55. package/dist-cli/chunks/{chunk-3WQISFAK.js → chunk-X7B5KJ6N.js} +1 -1
  56. package/dist-cli/chunks/{chunk-2RI6RC6I.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-I6BOBK57.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-VG2JOF6H.js → debug-AOYA2CLR.js} +3 -3
  66. package/dist-cli/chunks/{desktop-GH2POW6X.js → desktop-G7XWDCTM.js} +3 -3
  67. package/dist-cli/chunks/{detox-X6T265L5.js → detox-QXJ3L2ZI.js} +2 -2
  68. package/dist-cli/chunks/{device-VDOOSAXO.js → device-NZZ5ZT3Q.js} +2 -2
  69. package/dist-cli/chunks/{diagnose-6D6WNAGL.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-UZWYISAB.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-UGMJ4LJX.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-K43TRKUC.js → login-LB4TMM4T.js} +4 -4
  80. package/dist-cli/chunks/{logout-L43CR6M2.js → logout-33IOBD4X.js} +2 -2
  81. package/dist-cli/chunks/{maestro-7VZFYLNS.js → maestro-EEVXX536.js} +3 -3
  82. package/dist-cli/chunks/{maestro-generate-PMYHUVH4.js → maestro-generate-ICPWVXGA.js} +3 -3
  83. package/dist-cli/chunks/{mode-QQS5XFFX.js → mode-6IXOZDK3.js} +2 -2
  84. package/dist-cli/chunks/{optional-demo-registry-2V327HDG.js → optional-demo-registry-KBMC7SNI.js} +2 -2
  85. package/dist-cli/chunks/{perf-CL5P3JVP.js → perf-CTTJSAIJ.js} +2 -2
  86. package/dist-cli/chunks/{permissions-RGVCEC2B.js → permissions-BFQS4DIY.js} +2 -2
  87. package/dist-cli/chunks/{record-ASW6UFQT.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-FEJGQMQF.js → screenshot-command-MGNASAXR.js} +3 -3
  91. package/dist-cli/chunks/{screenshots-capture-3JPZ5WMX.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-T6EEX5P6.js → skills-TD35LDKK.js} +2 -2
  95. package/dist-cli/chunks/{storage-ZFUK4EQA.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-EIURYQVC.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-L5ZN7S5G.js → what-happened-HRHB6ETL.js} +2 -2
  104. package/dist-lib/agent-daemon-client.cjs +1 -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 +1 -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 +215 -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 +2 -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 +366 -154
  135. package/dist-lib/vite.cjs +2 -2
  136. package/package.json +2 -1
  137. package/scripts/postinstall.cjs +2 -78
  138. package/skills/contrast/SKILL.md +1 -0
  139. package/skills/sootsim-setup/SKILL.md +25 -0
  140. package/src/config.ts +4 -0
  141. package/src/disk-cleanup.ts +5 -26
  142. package/src/home-paths.ts +58 -23
  143. package/src/host/bridge-host.ts +152 -57
  144. package/src/menu.ts +0 -1
  145. package/src/runtime-assets.ts +35 -0
  146. package/src/runtime-delivery.ts +5 -8
  147. package/src/skills/builtin/compat-check.ts +32 -65
  148. package/dist-cli/chunks/app-fonts-36N7AVQ3.js +0 -4
  149. package/dist-cli/chunks/chunk-4ODQVUBH.js +0 -6
  150. package/dist-cli/chunks/chunk-4V2KYK5T.js +0 -4
  151. package/dist-cli/chunks/chunk-7GVBNEQ3.js +0 -9
  152. package/dist-cli/chunks/chunk-COEKUTWV.js +0 -4
  153. package/dist-cli/chunks/chunk-D2DU3ROC.js +0 -6
  154. package/dist-cli/chunks/chunk-DXCQP3VE.js +0 -12
  155. package/dist-cli/chunks/chunk-HT6WYHQL.js +0 -6
  156. package/dist-cli/chunks/chunk-I7AWRAXM.js +0 -125
  157. package/dist-cli/chunks/chunk-OQ7SSRYK.js +0 -4
  158. package/dist-cli/chunks/chunk-QKZVANQG.js +0 -31
  159. package/dist-cli/chunks/chunk-TGYZYSDB.js +0 -15
  160. package/dist-cli/chunks/chunk-TXURXDZN.js +0 -4
  161. package/dist-cli/chunks/chunk-ZJZYGLJ2.js +0 -4
  162. package/dist-cli/chunks/cleanup-SMCA44QC.js +0 -25
  163. package/dist-cli/chunks/compat-FCM2ZQLV.js +0 -35
  164. package/dist-cli/chunks/config-PZSANSAI.js +0 -13
  165. package/dist-cli/chunks/control-PM554MHE.js +0 -4
  166. package/dist-cli/chunks/daemon-5KXEZZLJ.js +0 -4
  167. package/dist-cli/chunks/disk-cleanup-HTOIDDKK.js +0 -4
  168. package/dist-cli/chunks/drivers-CKLBQDXG.js +0 -4
  169. package/dist-cli/chunks/flow-VOS3RKB3.js +0 -4
  170. package/dist-cli/chunks/help-LVMDERFQ.js +0 -4
  171. package/dist-cli/chunks/home-paths-3LYT47SY.js +0 -4
  172. package/dist-cli/chunks/inspect-FHCSOWGQ.js +0 -4
  173. package/dist-cli/chunks/install-desktop-KLMCVDGZ.js +0 -4
  174. package/dist-cli/chunks/runtime-GB3ZE27C.js +0 -4
  175. package/dist-cli/chunks/serve-SYXRHRQO.js +0 -44
  176. package/dist-cli/chunks/setup-QLGL6MC6.js +0 -99
  177. package/dist-cli/chunks/store-WCDTSWJC.js +0 -4
  178. package/dist-cli/chunks/telemetry-D7SL4WW7.js +0 -4
  179. package/dist-cli/chunks/upgrade-Q7ZGWMCJ.js +0 -6
  180. package/dist-cli/chunks/upload-7XEICYJX.js +0 -4
  181. package/dist-cli/chunks/version-PDG4V26R.js +0 -6
  182. package/dist-cli/chunks/web-AMW2TRWB.js +0 -4
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! sootsim v0.1.295 | (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;
@@ -85,7 +85,7 @@ function readDaemonLockfile() {
85
85
  const bytesRead = import_node_fs.default.readSync(fd, buf, 0, DAEMON_LOCKFILE_MAX_BYTES, 0);
86
86
  const raw = buf.subarray(0, bytesRead).toString("utf8");
87
87
  const parsed = JSON.parse(raw);
88
- 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") {
88
+ 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"))) {
89
89
  return parsed;
90
90
  }
91
91
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sootsim",
3
- "version": "0.1.295",
3
+ "version": "0.1.296",
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",
@@ -169,6 +169,7 @@
169
169
  },
170
170
  "dependencies": {
171
171
  "@expo/plist": "^0.5.4",
172
+ "jiti": "2.7.0",
172
173
  "ws": "^8.18.0"
173
174
  },
174
175
  "peerDependencies": {
@@ -1,79 +1,3 @@
1
1
  #!/usr/bin/env node
2
- // postinstall — best-effort runtime install after npm/npx fetches sootsim.
3
- //
4
- // runs as a child of npm. failures here are non-fatal; setup/open/runtime
5
- // commands can install the runtime later.
6
- //
7
- // skipped when running as root via sudo: $HOME would be /root, but the user
8
- // who actually wants the runtime probably isn't root. their first sootsim
9
- // invocation will install into their own ~/.sootsim instead.
10
-
11
- const { spawnSync } = require('child_process')
12
- const fs = require('fs')
13
- const os = require('os')
14
- const path = require('path')
15
-
16
- if (process.env.SOOTSIM_SKIP_POSTINSTALL === '1') {
17
- process.exit(0)
18
- }
19
-
20
- // in dev / workspace installs (i.e. running inside the Contrast monorepo, not
21
- // installed as a dep), the package dir is `<repo>/packages/sootsim/` rather
22
- // than `.../node_modules/sootsim/`. skip the runtime fetch in that case —
23
- // the workspace already has the engine source.
24
- const pkgDir = path.resolve(__dirname, '..')
25
- if (!pkgDir.includes(`${path.sep}node_modules${path.sep}`)) {
26
- process.exit(0)
27
- }
28
-
29
- const isRoot = typeof process.getuid === 'function' && process.getuid() === 0
30
- if (isRoot && process.env.SUDO_USER) {
31
- console.log(
32
- 'sootsim: postinstall skipped under sudo — runtime will install on first run',
33
- )
34
- process.exit(0)
35
- }
36
-
37
- const existingActiveRuntime = readExistingActiveRuntime()
38
- if (existingActiveRuntime) {
39
- console.log(
40
- `sootsim: postinstall skipped — active runtime ${existingActiveRuntime} already installed`,
41
- )
42
- process.exit(0)
43
- }
44
-
45
- const RUNTIME_INSTALL_TIMEOUT_MS = 60_000
46
-
47
- const bin = path.join(__dirname, '..', 'dist-cli', 'bin.js')
48
- const res = spawnSync(process.execPath, [bin, 'runtime', 'install'], {
49
- stdio: 'inherit',
50
- timeout: RUNTIME_INSTALL_TIMEOUT_MS,
51
- })
52
-
53
- if (res.error && res.error.code === 'ETIMEDOUT') {
54
- // the runtime tarball is ~48MB; on a slow link it does not finish inside the
55
- // npm-install budget above. say that plainly instead of implying we chose to
56
- // defer — the user has to run the install themselves for it to complete.
57
- console.log(
58
- `sootsim: runtime download did not finish within ${RUNTIME_INSTALL_TIMEOUT_MS / 1000}s, so npm install could return — run \`sootsim upgrade\` to finish it`,
59
- )
60
- } else if (res.status !== 0) {
61
- console.log('sootsim: runtime install deferred to first run')
62
- }
63
- process.exit(0)
64
-
65
- function readExistingActiveRuntime() {
66
- const home =
67
- process.env.SOOTSIM_HOME && process.env.SOOTSIM_HOME.length > 0
68
- ? path.resolve(process.env.SOOTSIM_HOME)
69
- : path.join(os.homedir(), '.sootsim')
70
- const runtimesDir = path.join(home, 'runtimes')
71
- try {
72
- const active = fs.readFileSync(path.join(runtimesDir, 'active'), 'utf8').trim()
73
- if (!active) return null
74
- const indexPath = path.join(runtimesDir, active, 'index.html')
75
- return fs.existsSync(indexPath) ? active : null
76
- } catch {
77
- return null
78
- }
79
- }
2
+ // npm installs package code only. `sootsim setup` owns runtime and machine
3
+ // changes so installs never download a runtime or register a service implicitly.
@@ -52,6 +52,7 @@ automation, inspecting the render tree, and managing sims.
52
52
  | `sootsim close` | close a connected sim |
53
53
  | `sootsim setup` | check local setup and install the recommended bridge daemon |
54
54
  | `sootsim compat` | check package compatibility |
55
+ | `sootsim report-issue` | send an explicitly approved compatibility report |
55
56
  | `sootsim desktop` | launch or install the desktop companion |
56
57
  | `sootsim daemon` | manage the optional background bridge service |
57
58
  | `sootsim version` | show the CLI + engine runtime versions you are running |
@@ -36,6 +36,7 @@ debugging or `/sootsim-test` for writing tests.
36
36
 
37
37
  ```sh
38
38
  sootsim setup # one-time guided setup; installs daemon
39
+ sootsim compat --json # scan the app's native package compatibility
39
40
  sootsim open 8081 # load a metro/expo dev server
40
41
  sootsim list # confirm exactly one sim is reachable
41
42
  sootsim use <id> # pin it for the rest of the workflow
@@ -59,6 +60,30 @@ sootsim open https://my-app.local # load a hosted bundle
59
60
  every subsequent command
60
61
  - `sootsim get errors 5` and `sootsim get requests 5` are quiet (or the
61
62
  warnings are ones you understand)
63
+ - the compatibility scan's partial, unsupported, unknown, and version-mismatched
64
+ packages have been summarized without treating its aggregate score as a pass/fail gate
65
+
66
+ ## compatibility feedback requires approval
67
+
68
+ run `sootsim compat --json` from the app project root during setup. if the scan
69
+ identifies a plausible missing SootSim seam, or the running app gives concrete
70
+ evidence that behavior is missing or broken specifically in SootSim, summarize
71
+ the package, expected behavior, actual behavior, and runtime evidence for the
72
+ user. then offer to send that finding with `sootsim report-issue`.
73
+
74
+ **never submit a report automatically or behind the user's back.** wait for an
75
+ explicit yes. only then run this from the app project root:
76
+
77
+ ```sh
78
+ sootsim report-issue --yes "<package; expected behavior; actual behavior; runtime evidence>"
79
+ ```
80
+
81
+ the command automatically attaches the local compatibility scan plus bounded
82
+ CLI, runtime, operating-system, and architecture metadata. it does not attach
83
+ source files, environment variables, terminal output, logs, screenshots, git
84
+ data, or app data. `--dry-run` previews the attachment summary without sending.
85
+ do not use `--yes` merely because the agent believes a report would be useful;
86
+ it records the user's approval after the offer.
62
87
 
63
88
  ## recovery — common failure modes
64
89
 
package/src/config.ts CHANGED
@@ -18,6 +18,8 @@ export type NativeModuleResolution = ModuleResolution | Record<string, any> // d
18
18
  export interface SootSimConfig<
19
19
  Settings extends Record<string, any> = Record<string, any>,
20
20
  > {
21
+ // host-only engine selection. omitted means the current stable runtime.
22
+ runtimeVersion?: string
21
23
  modules?: Record<string, ModuleResolution>
22
24
  turboModules?: Record<string, NativeModuleResolution>
23
25
  nativeModules?: Record<string, NativeModuleResolution>
@@ -61,6 +63,7 @@ export function hasSootSimConfig<
61
63
  >(config?: SootSimConfig<Settings> | null): config is SootSimConfig<Settings> {
62
64
  if (!config) return false
63
65
  return (
66
+ (typeof config.runtimeVersion === 'string' && config.runtimeVersion.length > 0) ||
64
67
  hasOwnKeys(config.modules as Record<string, unknown> | undefined) ||
65
68
  hasOwnKeys(config.turboModules as Record<string, unknown> | undefined) ||
66
69
  hasOwnKeys(config.nativeModules as Record<string, unknown> | undefined) ||
@@ -82,6 +85,7 @@ export function mergeSootSimConfig<
82
85
  if (!hasSootSimConfig(override)) return base ? { ...base } : undefined
83
86
 
84
87
  return {
88
+ runtimeVersion: override.runtimeVersion ?? base.runtimeVersion,
85
89
  modules: { ...(base.modules || {}), ...(override.modules || {}) },
86
90
  turboModules: { ...(base.turboModules || {}), ...(override.turboModules || {}) },
87
91
  nativeModules: {
@@ -1,12 +1,7 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { isDisposableBrowserCacheDir } from './browser-cache'
4
- import {
5
- ensureSootsimHome,
6
- isDaemonLockfileFresh,
7
- readDaemonLockfile,
8
- sootsimHomeDir,
9
- } from './home-paths'
4
+ import { ensureSootsimHome, readLiveRuntimeVersions, sootsimHomeDir } from './home-paths'
10
5
  import { sootsimRuntime } from './runtime-delivery'
11
6
 
12
7
  const AUTOMATIC_CLEANUP_GENERATION = 1
@@ -38,7 +33,6 @@ export interface SootsimCleanupOptions {
38
33
  export interface SootsimCleanupPlan {
39
34
  home: string
40
35
  activeRuntime: string | null
41
- pinnedRuntime: string | null
42
36
  entries: SootsimCleanupEntry[]
43
37
  reclaimableBytes: number
44
38
  protectedBytes: number
@@ -183,18 +177,11 @@ function collectRecordingEntries(
183
177
  })
184
178
  }
185
179
 
186
- function inUseRuntimeVersions(): string[] {
187
- const lock = readDaemonLockfile()
188
- if (!isDaemonLockfileFresh(lock) || !lock.activeRuntime) return []
189
- return [lock.activeRuntime]
190
- }
191
-
192
180
  function runtimeEntries(aggressive: boolean): {
193
181
  activeRuntime: string | null
194
- pinnedRuntime: string | null
195
182
  entries: SootsimCleanupEntry[]
196
183
  } {
197
- const inUseVersions = new Set(inUseRuntimeVersions())
184
+ const inUseVersions = new Set(readLiveRuntimeVersions())
198
185
  const protectVersions = [...inUseVersions]
199
186
  const normal = sootsimRuntime.planLocalRetention({
200
187
  retainVersions: 2,
@@ -218,13 +205,9 @@ function runtimeEntries(aggressive: boolean): {
218
205
  const runtimePath = sootsimRuntime.product.runtimeDir(version)
219
206
  let state: SootsimCleanupState = 'retained'
220
207
  let detail = 'retained runtime'
221
- if (
222
- inUseVersions.has(version) &&
223
- version !== normal.activeVersion &&
224
- version !== normal.pinnedVersion
225
- ) {
208
+ if (inUseVersions.has(version) && version !== normal.activeVersion) {
226
209
  state = 'in-use'
227
- detail = 'runtime is served by the running daemon'
210
+ detail = 'runtime is serving a connected simulator'
228
211
  } else if (normallyRemoved.has(version)) {
229
212
  state = 'safe'
230
213
  detail = 'inactive runtime outside the two-version rollback window'
@@ -233,8 +216,6 @@ function runtimeEntries(aggressive: boolean): {
233
216
  detail = 'most recent rollback runtime'
234
217
  } else if (version === normal.activeVersion) {
235
218
  detail = 'active runtime; always protected'
236
- } else if (version === normal.pinnedVersion) {
237
- detail = 'pinned runtime; always protected'
238
219
  }
239
220
  entries.push({
240
221
  path: runtimePath,
@@ -259,7 +240,6 @@ function runtimeEntries(aggressive: boolean): {
259
240
 
260
241
  return {
261
242
  activeRuntime: normal.activeVersion,
262
- pinnedRuntime: normal.pinnedVersion,
263
243
  entries,
264
244
  }
265
245
  }
@@ -279,7 +259,6 @@ export function planSootsimCleanup(
279
259
  return {
280
260
  home,
281
261
  activeRuntime: runtimes.activeRuntime,
282
- pinnedRuntime: runtimes.pinnedRuntime,
283
262
  entries,
284
263
  reclaimableBytes: entries.reduce(
285
264
  (total, entry) => total + (entry.selected ? entry.bytes : 0),
@@ -307,7 +286,7 @@ export function applySootsimCleanup(
307
286
  const retainVersions = options.aggressive === true ? 1 : 2
308
287
  sootsimRuntime.pruneLocalRetention({
309
288
  retainVersions,
310
- protectVersions: inUseRuntimeVersions(),
289
+ protectVersions: readLiveRuntimeVersions(),
311
290
  })
312
291
 
313
292
  for (const entry of plan.entries) {
package/src/home-paths.ts CHANGED
@@ -20,8 +20,8 @@
20
20
  // │ by the next interactive CLI run
21
21
  // ├── automatic-cleanup-v1.json completion marker for the one-time safe
22
22
  // │ cleanup introduced for legacy unbounded data
23
- // └── config.json user prefs: runtime pin, update channel, cdn
24
- // origin override, telemetry, engine settings
23
+ // └── config.json user prefs: update channel, cdn origin override,
24
+ // telemetry, engine settings
25
25
 
26
26
  import { randomUUID } from 'node:crypto'
27
27
  import fs from 'node:fs'
@@ -167,19 +167,9 @@ export function configFilePath(): string {
167
167
  // ~/.sootsim/config.json is the single user-level config file every sootsim
168
168
  // surface (cli, electron renderer, dev browser via electron ipc) agrees on.
169
169
  //
170
- // {
171
- // "telemetry": true, // splash checkbox; cli + electron
172
- // "settings": { // engine settings persisted across
173
- // "onboardingComplete": true, // sessions. shape mirrors the
174
- // "betaConsentVersion": 1, // PERSISTED_KEYS slice of
175
- // "detailedTelemetry": false, // SettingsValues — see
176
- // ... // sootsim-engine/settings/schema.ts
177
- // }
178
- // }
179
- //
180
- // telemetry default is opt-in (missing/corrupt config means "enabled") so a
181
- // fresh install never silently drops error reports. settings default is empty
182
- // (the engine's getDefaults() fills the rest).
170
+ // privacy preferences live in the nested settings object so the cli, desktop
171
+ // main process, renderer, daemon-served browser, and settings UI all read and
172
+ // write one persisted value per choice.
183
173
 
184
174
  export interface SharedConfig {
185
175
  telemetry?: boolean
@@ -218,8 +208,33 @@ export function writeSharedConfig(patch: Partial<SharedConfig>): SharedConfig {
218
208
  return next
219
209
  }
220
210
 
221
- export function readTelemetryEnabled(): boolean {
222
- return readSharedConfig().telemetry !== false
211
+ export interface PrivacyPreferences {
212
+ productAnalytics: boolean
213
+ crashReports: boolean
214
+ configured: boolean
215
+ }
216
+
217
+ export function readPrivacyPreferences(): PrivacyPreferences {
218
+ const settings = readSharedConfig().settings
219
+ const productAnalytics = settings?.productAnalytics
220
+ const crashReports = settings?.crashReports
221
+ return {
222
+ productAnalytics: typeof productAnalytics === 'boolean' ? productAnalytics : true,
223
+ crashReports: typeof crashReports === 'boolean' ? crashReports : true,
224
+ configured:
225
+ typeof productAnalytics === 'boolean' && typeof crashReports === 'boolean',
226
+ }
227
+ }
228
+
229
+ export function writePrivacyPreferences(
230
+ preferences: Pick<PrivacyPreferences, 'productAnalytics' | 'crashReports'>,
231
+ ): void {
232
+ writeSharedConfig({
233
+ settings: {
234
+ productAnalytics: preferences.productAnalytics,
235
+ crashReports: preferences.crashReports,
236
+ },
237
+ })
223
238
  }
224
239
 
225
240
  // stable per-machine anonymous telemetry id. every signed-out cli used to
@@ -234,10 +249,6 @@ export function readOrCreateAnonymousId(): string {
234
249
  return created
235
250
  }
236
251
 
237
- export function writeTelemetryEnabled(enabled: boolean): void {
238
- writeSharedConfig({ telemetry: enabled })
239
- }
240
-
241
252
  // --- background-upgrade notice --------------------------------------------
242
253
  //
243
254
  // the daemon auto-updates the engine runtime in the background (hourly), so
@@ -342,6 +353,8 @@ export interface DaemonLockfile {
342
353
  activeRuntime: string | null
343
354
  /** absolute path to the active runtime's dist directory, or null. */
344
355
  activeRuntimeDir: string | null
356
+ /** repo-selected runtime versions currently served to connected sims. */
357
+ servedRuntimes?: string[]
345
358
  /** epoch-ms of daemon start. */
346
359
  startedAt: number
347
360
  /** epoch-ms of last heartbeat. daemons update this every ~5s; readers
@@ -369,6 +382,8 @@ export interface DevBridgeLockfile {
369
382
  startedAt: number
370
383
  heartbeatAt: number
371
384
  source: 'vite-dev'
385
+ /** repo-selected runtime versions currently served to connected sims. */
386
+ servedRuntimes?: string[]
372
387
  }
373
388
 
374
389
  const DAEMON_LOCKFILE_MAX_BYTES = 16 * 1024
@@ -390,7 +405,10 @@ export function readDaemonLockfile(): DaemonLockfile | null {
390
405
  typeof parsed.bridgePort === 'number' &&
391
406
  typeof parsed.runtimePort === 'number' &&
392
407
  typeof parsed.startedAt === 'number' &&
393
- typeof parsed.heartbeatAt === 'number'
408
+ typeof parsed.heartbeatAt === 'number' &&
409
+ (parsed.servedRuntimes === undefined ||
410
+ (Array.isArray(parsed.servedRuntimes) &&
411
+ parsed.servedRuntimes.every((version) => typeof version === 'string')))
394
412
  ) {
395
413
  return parsed as DaemonLockfile
396
414
  }
@@ -421,7 +439,10 @@ export function readDevBridgeLockfile(): DevBridgeLockfile | null {
421
439
  typeof parsed.cwd === 'string' &&
422
440
  typeof parsed.startedAt === 'number' &&
423
441
  typeof parsed.heartbeatAt === 'number' &&
424
- (parsed.shellPort === undefined || typeof parsed.shellPort === 'number')
442
+ (parsed.shellPort === undefined || typeof parsed.shellPort === 'number') &&
443
+ (parsed.servedRuntimes === undefined ||
444
+ (Array.isArray(parsed.servedRuntimes) &&
445
+ parsed.servedRuntimes.every((version) => typeof version === 'string')))
425
446
  ) {
426
447
  return parsed as DevBridgeLockfile
427
448
  }
@@ -475,6 +496,20 @@ export function isDevBridgeLockfileFresh(
475
496
  }
476
497
  }
477
498
 
499
+ export function readLiveRuntimeVersions(): string[] {
500
+ const versions = new Set<string>()
501
+ const daemon = readDaemonLockfile()
502
+ if (isDaemonLockfileFresh(daemon)) {
503
+ if (daemon.activeRuntime) versions.add(daemon.activeRuntime)
504
+ for (const version of daemon.servedRuntimes ?? []) versions.add(version)
505
+ }
506
+ const devBridge = readDevBridgeLockfile()
507
+ if (isDevBridgeLockfileFresh(devBridge)) {
508
+ for (const version of devBridge.servedRuntimes ?? []) versions.add(version)
509
+ }
510
+ return [...versions].sort()
511
+ }
512
+
478
513
  export function writeDaemonLockfile(data: DaemonLockfile): void {
479
514
  ensureSootsimHome()
480
515
  const tmp = `${daemonLockfilePath()}.tmp`