sootsim 0.1.201 → 0.1.203

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-SHGFOKZN.js → agent-PWM4XT5H.js} +2 -2
  3. package/dist-cli/chunks/{agent-wrapper-3O4ZW24C.js → agent-wrapper-V2BSTANB.js} +2 -2
  4. package/dist-cli/chunks/{app-fonts-KZOBGGZQ.js → app-fonts-7M4PTF6W.js} +2 -2
  5. package/dist-cli/chunks/{assert-RZCBKONA.js → assert-SEW5OJL3.js} +2 -2
  6. package/dist-cli/chunks/{auth-PBD7GLXL.js → auth-3YY55NBL.js} +2 -2
  7. package/dist-cli/chunks/beta-Y4ZJUKAM.js +2 -0
  8. package/dist-cli/chunks/{camera-5OOLGGLN.js → camera-CYXCASB3.js} +2 -2
  9. package/dist-cli/chunks/{chunk-HVSDT56M.js → chunk-4NHPXU3X.js} +1 -1
  10. package/dist-cli/chunks/{chunk-423YVBZK.js → chunk-62AKBZTO.js} +1 -1
  11. package/dist-cli/chunks/{chunk-WJ7YQAKC.js → chunk-6DN4BD7O.js} +38 -38
  12. package/dist-cli/chunks/{chunk-FITJFYBO.js → chunk-6M56UHEY.js} +2 -2
  13. package/dist-cli/chunks/{chunk-T6WXCSWP.js → chunk-A3FIOG7U.js} +2 -2
  14. package/dist-cli/chunks/{chunk-7672K2JN.js → chunk-B7VBDZ3U.js} +1 -1
  15. package/dist-cli/chunks/{chunk-63EDUOKZ.js → chunk-BBOFH45Q.js} +2 -2
  16. package/dist-cli/chunks/{chunk-SKGV3OSH.js → chunk-BJJXNQHO.js} +2 -2
  17. package/dist-cli/chunks/{chunk-7GG4TZ4L.js → chunk-BL67OSVN.js} +2 -2
  18. package/dist-cli/chunks/{chunk-EVCOHQPK.js → chunk-EBH65V62.js} +3 -3
  19. package/dist-cli/chunks/{chunk-NYNAI5PO.js → chunk-FF3C57T5.js} +2 -2
  20. package/dist-cli/chunks/{chunk-JEJV3UHZ.js → chunk-FUC7C5HS.js} +2 -2
  21. package/dist-cli/chunks/{chunk-XAFLM5JS.js → chunk-G5GRLVYA.js} +2 -2
  22. package/dist-cli/chunks/chunk-G5VOIXM7.js +1 -0
  23. package/dist-cli/chunks/{chunk-D6CTB275.js → chunk-GMVAXDHH.js} +1 -1
  24. package/dist-cli/chunks/{chunk-S2AQ4WGX.js → chunk-GTBF3M2H.js} +1 -1
  25. package/dist-cli/chunks/{chunk-RN662UDP.js → chunk-GYTATFQA.js} +2 -2
  26. package/dist-cli/chunks/{chunk-AHJ2WJST.js → chunk-JLOAWSX6.js} +1 -1
  27. package/dist-cli/chunks/{chunk-ENY46QHT.js → chunk-K3CRSIGS.js} +2 -2
  28. package/dist-cli/chunks/{chunk-SVM25PDV.js → chunk-LKJ5LA6K.js} +6 -6
  29. package/dist-cli/chunks/{chunk-SZVYOFMB.js → chunk-LTRNOX6N.js} +2 -2
  30. package/dist-cli/chunks/{chunk-3RAGDMGM.js → chunk-MQHSWXDS.js} +1 -1
  31. package/dist-cli/chunks/{chunk-SQXH3XGO.js → chunk-NHSO4473.js} +2 -2
  32. package/dist-cli/chunks/chunk-ODW7RHSD.js +97 -0
  33. package/dist-cli/chunks/{chunk-FPRZHFV2.js → chunk-OK7QEDHD.js} +2 -2
  34. package/dist-cli/chunks/{chunk-CHVGDYQJ.js → chunk-OXQOPTTB.js} +1 -1
  35. package/dist-cli/chunks/{chunk-F5TOTCUW.js → chunk-P4GJL7OH.js} +2 -2
  36. package/dist-cli/chunks/chunk-PELEQURX.js +4 -0
  37. package/dist-cli/chunks/chunk-QKDII5UB.js +1 -0
  38. package/dist-cli/chunks/chunk-R7GIVBTY.js +2 -0
  39. package/dist-cli/chunks/{chunk-GBUUYMPP.js → chunk-RVOF6RIE.js} +1 -1
  40. package/dist-cli/chunks/chunk-RZU5TDPJ.js +29 -0
  41. package/dist-cli/chunks/{chunk-HIWE4YYN.js → chunk-U6OCMLMU.js} +2 -2
  42. package/dist-cli/chunks/{chunk-MQEEYPOK.js → chunk-UJ64GJSZ.js} +1 -1
  43. package/dist-cli/chunks/{chunk-YOPEO6SA.js → chunk-UZGLGCLM.js} +1 -1
  44. package/dist-cli/chunks/{chunk-QTI33DV5.js → chunk-WSOZGPIX.js} +2 -2
  45. package/dist-cli/chunks/{chunk-CPVTNQAS.js → chunk-WVF2EDZF.js} +1 -1
  46. package/dist-cli/chunks/{chunk-ZSWQLDDQ.js → chunk-XYMOH3HP.js} +1 -1
  47. package/dist-cli/chunks/{chunk-WHNPGZX4.js → chunk-YJNJ5GVC.js} +3 -3
  48. package/dist-cli/chunks/{chunk-USGQNFAM.js → chunk-YP3NIRTK.js} +1 -1
  49. package/dist-cli/chunks/chunk-ZGM7N2AV.js +7 -0
  50. package/dist-cli/chunks/{chunk-NIDV74L2.js → chunk-ZRWVLKFL.js} +1 -1
  51. package/dist-cli/chunks/cli-version-N2RKZ2CU.js +2 -0
  52. package/dist-cli/chunks/{compat-NNM7DTTO.js → compat-FK5MP535.js} +3 -3
  53. package/dist-cli/chunks/{config-LBGQRC2P.js → config-T3W2HI67.js} +2 -2
  54. package/dist-cli/chunks/{control-5DHHQZ26.js → control-ONFLGDEC.js} +2 -2
  55. package/dist-cli/chunks/daemon-FDI5YYZN.js +2 -0
  56. package/dist-cli/chunks/{debug-PILWFGN3.js → debug-Z6NG4KY2.js} +3 -3
  57. package/dist-cli/chunks/{desktop-CZ43IZFV.js → desktop-BIWB2UYC.js} +3 -3
  58. package/dist-cli/chunks/detox-6K7WFLQO.js +50 -0
  59. package/dist-cli/chunks/{device-M26JCUHY.js → device-TPD5WAP2.js} +2 -2
  60. package/dist-cli/chunks/{diagnose-HZZMVPQZ.js → diagnose-NACPOTYX.js} +2 -2
  61. package/dist-cli/chunks/drivers-G2YZR6G6.js +2 -0
  62. package/dist-cli/chunks/flow-OAW2MZCD.js +2 -0
  63. package/dist-cli/chunks/help-MY2CBECX.js +2 -0
  64. package/dist-cli/chunks/home-paths-WWFDX6YU.js +2 -0
  65. package/dist-cli/chunks/inspect-ZPVPUSAK.js +2 -0
  66. package/dist-cli/chunks/install-desktop-QZWSSGJV.js +2 -0
  67. package/dist-cli/chunks/{login-VBCYZKJ6.js → login-WGCO2V4S.js} +4 -4
  68. package/dist-cli/chunks/{logout-J22GLKHX.js → logout-VWSSNYF5.js} +2 -2
  69. package/dist-cli/chunks/maestro-YED424Y6.js +96 -0
  70. package/dist-cli/chunks/{mode-XN46XIMK.js → mode-QCOM4UM4.js} +2 -2
  71. package/dist-cli/chunks/optional-demo-registry-3KZFUZZD.js +2 -0
  72. package/dist-cli/chunks/{perf-ZIYESQRA.js → perf-FZHCFAID.js} +2 -2
  73. package/dist-cli/chunks/{record-L74E3HCL.js → record-NF3PKBSW.js} +3 -3
  74. package/dist-cli/chunks/runtime-ETELTKFT.js +2 -0
  75. package/dist-cli/chunks/screenshot-command-KLPOA5TW.js +151 -0
  76. package/dist-cli/chunks/screenshots-capture-MCTZSXLE.js +41 -0
  77. package/dist-cli/chunks/{serve-CNDRR7XN.js → serve-XBWYWHMC.js} +18 -18
  78. package/dist-cli/chunks/{setup-XZDN7XMI.js → setup-W7EROACZ.js} +3 -3
  79. package/dist-cli/chunks/{skills-KJK67CVB.js → skills-RBGNF7GS.js} +2 -2
  80. package/dist-cli/chunks/{storage-UOLMRHLQ.js → storage-3SFE2DUE.js} +2 -2
  81. package/dist-cli/chunks/store-Z3CHVRR3.js +2 -0
  82. package/dist-cli/chunks/telemetry-SWFOAVWP.js +2 -0
  83. package/dist-cli/chunks/test-UP6VTFKE.js +69 -0
  84. package/dist-cli/chunks/{timeline-UJFIJ2GT.js → timeline-A54JB74A.js} +2 -2
  85. package/dist-cli/chunks/upgrade-H7XE3N76.js +4 -0
  86. package/dist-cli/chunks/upload-X2BRKPBJ.js +2 -0
  87. package/dist-cli/chunks/version-4JV7HX23.js +4 -0
  88. package/dist-cli/chunks/{web-QP4KDHR6.js → web-5OLZXKVF.js} +2 -2
  89. package/dist-cli/chunks/{what-happened-VW274SXG.js → what-happened-Q4L6SI4I.js} +2 -2
  90. package/dist-lib/agent-daemon-client.cjs +1 -1
  91. package/dist-lib/agent-events.cjs +1 -1
  92. package/dist-lib/agent-identity.cjs +1 -1
  93. package/dist-lib/agent-sessions.cjs +1 -1
  94. package/dist-lib/attached-projects.cjs +1 -1
  95. package/dist-lib/auth/shared-session.cjs +1 -1
  96. package/dist-lib/backend-origin.cjs +1 -1
  97. package/dist-lib/beta.cjs +1 -1
  98. package/dist-lib/beta.mjs +1 -1
  99. package/dist-lib/bridge-constants.cjs +1 -1
  100. package/dist-lib/cli-constants.cjs +1 -1
  101. package/dist-lib/config.cjs +1 -1
  102. package/dist-lib/detox/index.cjs +1 -1
  103. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  104. package/dist-lib/home-paths.cjs +32 -1
  105. package/dist-lib/host/bridge-host.cjs +51 -2
  106. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  107. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  108. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  109. package/dist-lib/host/websocket-proxy.cjs +1 -1
  110. package/dist-lib/index.cjs +1 -1
  111. package/dist-lib/jump-to-source-babel.cjs +1 -1
  112. package/dist-lib/menu.cjs +1 -1
  113. package/dist-lib/menu.mjs +1 -1
  114. package/dist-lib/metro.cjs +1 -1
  115. package/dist-lib/profiles.cjs +1 -1
  116. package/dist-lib/render-mode.cjs +1 -1
  117. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  118. package/dist-lib/sdk.cjs +1 -1
  119. package/dist-lib/sdk.mjs +1 -1
  120. package/dist-lib/skills.cjs +104 -19
  121. package/dist-lib/vite.cjs +1 -1
  122. package/package.json +1 -1
  123. package/src/home-paths.ts +48 -1
  124. package/src/host/bridge-host.ts +12 -0
  125. package/src/runtime-delivery.ts +59 -9
  126. package/dist-cli/chunks/beta-EJBRLRKG.js +0 -2
  127. package/dist-cli/chunks/chunk-3W273A5J.js +0 -4
  128. package/dist-cli/chunks/chunk-5GICTRK5.js +0 -1
  129. package/dist-cli/chunks/chunk-AGSCFR4P.js +0 -27
  130. package/dist-cli/chunks/chunk-O56GZV3H.js +0 -1
  131. package/dist-cli/chunks/chunk-ORIISY22.js +0 -6
  132. package/dist-cli/chunks/chunk-PMPQ57UY.js +0 -92
  133. package/dist-cli/chunks/cli-version-WTA2LUDV.js +0 -2
  134. package/dist-cli/chunks/daemon-3KGXTPXJ.js +0 -2
  135. package/dist-cli/chunks/detox-D6XW7Y4V.js +0 -49
  136. package/dist-cli/chunks/drivers-7ZM3KW22.js +0 -2
  137. package/dist-cli/chunks/flow-DDDWJ4KD.js +0 -2
  138. package/dist-cli/chunks/help-23GXSWP3.js +0 -2
  139. package/dist-cli/chunks/home-paths-746ZDH2I.js +0 -2
  140. package/dist-cli/chunks/inspect-T37ALZRL.js +0 -2
  141. package/dist-cli/chunks/install-desktop-FSY2ND5S.js +0 -2
  142. package/dist-cli/chunks/maestro-5LWJ2S6K.js +0 -96
  143. package/dist-cli/chunks/optional-demo-registry-YF4W5LPU.js +0 -2
  144. package/dist-cli/chunks/runtime-7WSR27U6.js +0 -2
  145. package/dist-cli/chunks/screenshot-command-ZCI5QE4T.js +0 -190
  146. package/dist-cli/chunks/store-FGCNPJBA.js +0 -2
  147. package/dist-cli/chunks/telemetry-QEO7INJC.js +0 -2
  148. package/dist-cli/chunks/test-5DK2XCRU.js +0 -69
  149. package/dist-cli/chunks/upgrade-CXZFJIH7.js +0 -4
  150. package/dist-cli/chunks/upload-ZFINR6K3.js +0 -2
  151. package/dist-cli/chunks/version-XMSYOSAX.js +0 -4
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,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b,c,d}from"./chunk-MQEEYPOK.js";import"./chunk-NIDV74L2.js";export{d as BETA_ASK_HEADLINE,b as BETA_LABEL,c as BETA_TAGLINE,a as IS_BETA};
@@ -1,4 +0,0 @@
1
- /*! sootsim v0.1.201 | (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-ORIISY22.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};
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1,27 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as p,b as $,c as w,e as y,f as R}from"./chunk-3W273A5J.js";import{d}from"./chunk-HIWE4YYN.js";import{D as m,E as a,F as h,G as f,l as v}from"./chunk-ORIISY22.js";import b from"fs";import{WebSocket as A}from"ws";async function P(n,e={}){let[o,...t]=n;if(!o||o==="--help"||o==="-h"){O();return}switch(o){case"install":return D(t,e);case"list":case"ls":return N(t);case"use":return T(t);case"remove":case"rm":return L(t);case"which":case"active":return _();case"notes":case"changelog":return C(t);default:console.error(` unknown runtime subcommand: ${o}`),O(),process.exit(1)}}function O(){console.log(`
3
- sootsim runtime \u2014 manage engine runtimes under ~/.sootsim/runtimes/
4
-
5
- usage:
6
- sootsim runtime install [version] install a version (default: channel latest)
7
- sootsim runtime list show installed + available versions
8
- sootsim runtime use <version> switch active runtime
9
- sootsim runtime remove <version> delete an installed runtime
10
- sootsim runtime which print active runtime version
11
- sootsim runtime notes print hosted release notes URL
12
-
13
- flags:
14
- --channel <name> channel to resolve 'latest' from (default: stable)
15
- --force reinstall even if the version is already on disk
16
- --set-active=false do not switch active runtime after install
17
-
18
- environment:
19
- SOOTSIM_CDN_ORIGIN override the CDN base URL (default: ${p})
20
-
21
- examples:
22
- sootsim runtime install
23
- sootsim runtime install 1.2.3
24
- sootsim runtime install --channel beta
25
- sootsim runtime use 1.2.3
26
- sootsim runtime notes
27
- `)}async function D(n,e){let{version:o,flags:t}=E(n),s=t.channel??e.channel??"stable",l=t.force===!0,c=t.setActive!==!1;m(),console.log("sootsim runtime install"),console.log(` cdn: ${w()}`);try{let i=await R({version:o,channel:s,force:l,setActive:c});console.log(` version: ${i.version} (channel: ${i.channel})`),i.installed?console.log(` installed ${i.version}`):console.log(` already installed at ${i.runtimeDir}`),c&&await I(i.version)}catch(i){console.error(` ${k(i)}`),process.exit(1)}}async function I(n){h(n),console.log(` active: ${n}`),await S(n)||console.log(` (no daemon running \u2014 next sootsim/electron launch will pick up ${n})`)}async function S(n){let{isDaemonLockfileFresh:e,readDaemonLockfile:o}=await import("./home-paths-746ZDH2I.js"),t=o();if(!e(t))return!1;let s=d();return new Promise(l=>{let c=!1,i=!1,u=x=>{c||(c=!0,l(x))},r=new A(`ws://127.0.0.1:${s}`,{handshakeTimeout:800}),g=setTimeout(()=>{try{r.close()}catch{}u(i)},1500);r.on("open",()=>{try{r.send(JSON.stringify({type:"runtime:use",version:n,id:0})),i=!0}catch{}setTimeout(()=>{try{r.close()}catch{}},100)}),r.on("close",()=>{clearTimeout(g),u(i)}),r.on("error",()=>{clearTimeout(g),u(!1)})})}async function N(n){m();let e=f(),o=a();if(console.log("installed:"),e.length===0)console.log(" (none)");else for(let t of e)console.log(` ${t===o?"*":" "} ${t}`);try{let t=await y();console.log("available (latest per channel):");for(let[s,l]of Object.entries(t.channels))console.log(` ${s.padEnd(8)} ${l.latest}`)}catch(t){console.log(`available: (could not fetch manifest: ${k(t)})`)}}async function T(n){let e=n[0];e||(console.error(" usage: sootsim runtime use <version>"),process.exit(1));let o=f();o.includes(e)||(console.error(` version ${e} is not installed`),console.error(` installed: ${o.join(", ")||"(none)"}`),console.error(` run \`sootsim runtime install ${e}\` first`),process.exit(1)),await I(e)}async function L(n){let e=n[0];e||(console.error(" usage: sootsim runtime remove <version>"),process.exit(1)),a()===e&&(console.error(` cannot remove active runtime ${e}`),console.error(" switch with `sootsim runtime use <other>` first, or install another version"),process.exit(1));let t=v(e);if(!b.existsSync(t)){console.error(` ${e} is not installed`);return}b.rmSync(t,{recursive:!0,force:!0}),console.log(` removed ${e}`)}async function _(){let n=a();if(!n){console.log(" no active runtime");return}console.log(n)}async function C(n){n.length>0&&(console.error(" usage: sootsim runtime notes"),process.exit(1));let e=a();e&&console.log(` active runtime: v${e}`),console.log(` release notes: ${$}`)}function E(n){let e={},o=[];for(let t=0;t<n.length;t++){let s=n[t];if(s==="--channel"&&t+1<n.length){e.channel=n[t+1],t++;continue}if(s.startsWith("--channel=")){e.channel=s.slice(10);continue}if(s==="--force"){e.force=!0;continue}if(s==="--set-active=false"||s==="--no-set-active"){e.setActive=!1;continue}o.push(s)}return{version:o[0]??null,flags:e}}function k(n){return n instanceof Error?n.message:String(n)}export{P as a};
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1,6 +0,0 @@
1
- /*! sootsim v0.1.201 | (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,92 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as ke,b as Ee,c as Re}from"./chunk-FPRZHFV2.js";import{c as Te}from"./chunk-D6CTB275.js";import{d as we}from"./chunk-S2AQ4WGX.js";import{g as Se,i as ve,k as x}from"./chunk-HIWE4YYN.js";import{d as xe}from"./chunk-YOPEO6SA.js";import{a as Me,b as v}from"./chunk-QTI33DV5.js";import{a as be}from"./chunk-FITJFYBO.js";import{createHash as ut}from"crypto";import{readFileSync as L}from"fs";import{gzipSync as Pe,gunzipSync as ct}from"zlib";function Ie(e){let t=(e||"").toLowerCase().split(";")[0].trim();return t?!(t.startsWith("image/")||t.startsWith("video/")||t.startsWith("audio/")||t==="font/woff2"||t==="application/font-woff2"||t==="application/zip"||t==="application/gzip"):!0}import{brotliCompressSync as Xe,constants as Ze}from"node:zlib";var et=9;function Be(e){return Xe(e,{params:{[Ze.BROTLI_PARAM_QUALITY]:et}})}function te(e,t){let r=new AbortController,o=setTimeout(()=>r.abort(new Error(`${t} timed out after ${e/1e3}s`)),e);return{signal:r.signal,cancel:()=>clearTimeout(o)}}async function tt(e){let t=te(12e4,`PUT ${e.url}`),r;try{r=await fetch(e.url,{method:"PUT",headers:{"content-type":e.contentType},body:e.bytes,signal:t.signal})}finally{t.cancel()}if(!r.ok){let o=await r.text().catch(()=>"");throw new Error(`PUT ${e.url} failed ${r.status}: ${o.slice(0,200)}`)}}async function nt(e,t=8,r){let o=0,i=0,a=e.length;await Promise.all(Array.from({length:Math.min(t,a)},async()=>{for(;o<a;){let u=e[o++];await tt(u),i++,r?.(i,a)}}))}function rt(e,t,r,o){let i=[];i.push({url:e.bundle.putUrl,contentType:e.bundle.contentType,bytes:t});for(let a of e.files){let u=r.get(a.urlhash);u&&i.push({url:a.putUrl,contentType:a.contentType,bytes:u})}e.events&&o?.eventsBytes&&i.push({url:e.events.putUrl,contentType:e.events.contentType,bytes:o.eventsBytes}),e.timeline&&o?.timelineBytes&&i.push({url:e.timeline.putUrl,contentType:e.timeline.contentType,bytes:o.timelineBytes}),e.flowVideo&&o?.videoBytes&&i.push({url:e.flowVideo.putUrl,contentType:e.flowVideo.contentType,bytes:o.videoBytes}),e.storage&&o?.storageBytes&&i.push({url:e.storage.putUrl,contentType:e.storage.contentType,bytes:o.storageBytes});for(let a of e.snapshots??[]){let u=o?.snapshotBytes?.get(a.id);u&&i.push({url:a.putUrl,contentType:a.contentType,bytes:u})}return i}function st(e){return e.replace(/\/+$/,"")}var z=class extends Error{constructor(r,o,i,a=""){super(r);this.status=o;this.phase=i;this.responseText=a;this.name="PresignedUploadError"}};async function Ae(e){let t=st(e.originBase),r={"content-type":"application/json"};e.authHeader&&(r.authorization=e.authHeader),e.adminBypassToken&&(r["x-soot-admin-upload-bypass"]=e.adminBypassToken);let o=te(3e4,"init"),i;try{i=await fetch(`${t}/api/preview/upload/init`,{method:"POST",headers:r,body:JSON.stringify(e.initBody),signal:o.signal})}finally{o.cancel()}if(!i.ok){let f=await i.text().catch(()=>"");throw new z(`init failed: ${i.status} ${f.slice(0,200)}`,i.status,"init",f)}let a=await i.json(),u=rt(a,e.bundleBytes,e.filesByHash,e.extras);await nt(u,e.concurrency??8,e.onPutProgress);let y=te(3e4,"finalize"),m;try{m=await fetch(`${t}/api/preview/upload/finalize`,{method:"POST",headers:r,body:JSON.stringify({token:a.token}),signal:y.signal})}finally{y.cancel()}if(!m.ok){let f=await m.text().catch(()=>"");throw new z(`finalize failed: ${m.status} ${f.slice(0,200)}`,m.status,"finalize",f)}let F=await m.json();return{init:a,finalize:F}}function ot(e){return e!==null&&typeof e=="object"?e:null}function Ue(e){return typeof e=="number"&&Number.isFinite(e)?e:void 0}function ne(e){let t=ot(e);return t?t.schemaVersion===1&&Ue(t.t)!==void 0&&Ue(t.seq)!==void 0&&typeof t.context=="string"&&typeof t.kind=="string"&&"data"in t:!1}function it(e,t){let r=[];for(let o of e.split(`
3
- `)){let i=o.trim();if(i)try{let a=JSON.parse(i);t(a)&&r.push(a)}catch{}}return r}function _e(e){return it(e,ne)}function dt(e){if(!e)return null;try{let t=JSON.parse(e);return typeof t.message=="string"&&t.message?t.message:null}catch{return null}}var Ce=process.env.SOOTSIM_UPLOAD_ORIGIN||"https://sootbean.com",Ne=process.env.SOOTSIM_PREVIEW_ORIGIN||"https://sootsim.com",se="http://localhost:3000",pt=2e3;async function mt(e){if(e)return e;if(process.env.SOOTSIM_UPLOAD_ORIGIN)return process.env.SOOTSIM_UPLOAD_ORIGIN;try{let t=await fetch(`${se}/api/preview/upload/init`,{method:"OPTIONS",signal:AbortSignal.timeout(pt)});if(t.ok||t.status===204||t.status===405)return se}catch{}return Ce}function U(e){return e.replace(/\/$/,"")}function ft(e){try{let r=new URL(e).hostname.replace(/^\[|\]$/g,"").toLowerCase();return r==="localhost"||r.endsWith(".localhost")||r==="0.0.0.0"||r==="::1"||/^127(?:\.\d{1,3}){3}$/.test(r)}catch{return!1}}function gt(e){try{let t=new URL(e).hostname.toLowerCase();return t==="sootbean.com"||t.endsWith(".sootbean.com")}catch{return!1}}function ht(e,t){return t?U(t):process.env.SOOTSIM_PREVIEW_ORIGIN?U(process.env.SOOTSIM_PREVIEW_ORIGIN):ft(e)?U(e):gt(e)?U(Ne):U(e)}function yt(){console.log(`
4
- sootsim record upload \u2014 publish the current recorded bundle as a /preview/<id> link
5
-
6
- usage:
7
- sootsim record upload [--origin <url>] [--public-origin <url>] (--events <path> | --video <path>) [--screenshot <path>] [--owner <org> --repo <repo>] [--sim <sim>] [--open] [--assets-only]
8
-
9
- options:
10
- --origin <url> upload target (default: auto)
11
- prefers ${se} when available, otherwise
12
- falls back to ${Ce}
13
- override with SOOTSIM_UPLOAD_ORIGIN env var
14
- --public-origin <url>
15
- public /preview link origin (default: ${Ne}
16
- for prod uploads, upload origin for localhost/custom origins).
17
- override with SOOTSIM_PREVIEW_ORIGIN env var
18
- --events <path> path to a gzipped events .jsonl.gz file to attach
19
- required unless --video is present; previews must
20
- have replay or recording playback data
21
- --screenshot <path>
22
- path to a PNG thumbnail to attach to the share
23
- --video <path> path to a mp4/gif flow recording. embedded inline
24
- in the pr sticky comment served at
25
- /api/preview/flow-video?id=<share-id>
26
- --video-duration-ms <ms>
27
- duration hint for attached flow video
28
- --timeline-events <path>
29
- extra trace timeline rows, JSONL or JSONL gz
30
- --trace-frame-stats <path>
31
- frame stats JSON returned by the recorder bridge
32
- --owner <org> associate a session upload with a linked org repo
33
- --repo <repo> repo name for --owner; the signed-in user must belong
34
- to the local/org team that owns the link
35
- --billing-kind test_run
36
- meter this upload as a natural-language test run
37
- --sim <sim> target a specific sim (see: sootsim list)
38
- --open open the resulting /preview/<id> url in the browser
39
- --assets-only drop API/JSON/HTML records before upload; keep images,
40
- fonts, css, js, and binary blobs. live-data demos hit
41
- the real network at replay time instead of serving
42
- recorded API snapshots
43
- -h, --help
44
-
45
- examples:
46
- sootsim record upload --events ./my-session.jsonl.gz
47
- sootsim record upload --origin http://localhost:3000 --events ./my-session.jsonl.gz --open
48
- sootsim record upload --video /tmp/soot-flow.mp4 --video-duration-ms 12000
49
- `)}function d(e,t){let r=e.findIndex(i=>i===t);if(r<0)return;let o=e[r+1];return e.splice(r,2),o}function Le(e,t){let r=e.findIndex(o=>o===t);return r<0?!1:(e.splice(r,1),!0)}function Fe(e,t){if(!e)return;let r=Number(e);return(!Number.isFinite(r)||r<=0)&&(console.error(` invalid ${t}: ${e}`),process.exit(1)),Math.round(r)}function $e(e){let t=(e||"").toLowerCase().split(";")[0].trim();return t?!!(t.startsWith("image/")||t.startsWith("font/")||t.startsWith("video/")||t.startsWith("audio/")||t.startsWith("model/")||t==="text/css"||t==="application/javascript"||t==="text/javascript"||t==="application/wasm"||t==="application/font-woff"||t==="application/font-woff2"||t==="application/octet-stream"):!1}async function Oe(e){let t=await fetch(e);if(!t.ok)throw new Error(`fetch ${e} -> ${t.status} ${t.statusText}`);return new Uint8Array(await t.arrayBuffer())}function bt(e){return/\.bundle($|\?)/.test(e)||/\.js($|\?)/.test(e)?"application/javascript":/\.zip($|\?)/.test(e)?"application/zip":"application/javascript"}function Mt(e){try{return new URL(e).searchParams.get("appFonts")||""}catch{return""}}function wt(e){try{return new URL(e).searchParams.get("appSplash")||""}catch{return""}}function Tt(e){try{let t=new URL(e);return t.searchParams.get("lazy")==="true"?(t.searchParams.set("lazy","false"),t.toString()):e}catch{return e}}async function St(e,t){let o=[];for(let i=0;i<t;i+=15e5){let a=Math.min(i+15e5,t),u=await x(e,`(window.__sootsimLastTransformedBundle?.text || "").slice(${i}, ${a})`);if(typeof u!="string")return null;o.push(u)}return o.join("")}function vt(e){return e.recordedDurationMs&&e.recordingStartedAtMs?{since:e.recordingStartedAtMs-1e4,until:e.recordingStartedAtMs+e.recordedDurationMs+5e3,limit:2e3}:{limit:2e3}}function xt(e){let t=L(e),o=(t[0]===31&&t[1]===139?ct(t):t).toString("utf8");return _e(o)}function kt(e){return JSON.parse(L(e,"utf8"))}function Et(e){if(e===null||typeof e!="object")return[];let t=e,r=typeof t.startedAtEpochMs=="number"&&t.startedAtEpochMs>0?t.startedAtEpochMs:null;if(r===null||!Array.isArray(t.samples))return[];let o=[];for(let i of t.samples){if(!Array.isArray(i)||typeof i[0]!="number")continue;let a=u=>typeof u=="number"&&Number.isFinite(u)?Math.round(u*100)/100:0;o.push({schemaVersion:1,t:r+Math.round(i[0]),seq:o.length+1,context:"tenant",kind:"frame",data:{totalMs:a(i[1]),layoutMs:a(i[2]),renderMs:a(i[3]),copyMs:a(i[4])}})}return o}function Rt(e){if(e===null||typeof e!="object")return e;let{samples:t,...r}=e;return r}function re(e){let t=new Map;for(let r of e)t.set(r.urlhash||r.url,r);return Array.from(t.values())}async function Wt(e,t){(e.includes("--help")||e.includes("-h"))&&(yt(),process.exit(0));let r=[...e],o=await mt(d(r,"--origin")),i=ht(o,d(r,"--public-origin")),a=d(r,"--events"),u=d(r,"--screenshot"),y=d(r,"--video"),m=d(r,"--timeline-events"),F=d(r,"--trace-frame-stats"),f=d(r,"--video-duration-ms"),oe=Fe(d(r,"--recorded-duration-ms"),"--recorded-duration-ms"),ie=Fe(d(r,"--recording-started-at-ms"),"--recording-started-at-ms"),H=d(r,"--owner"),j=d(r,"--repo"),$=d(r,"--billing-kind"),De=Le(r,"--open");(H&&!j||!H&&j)&&(console.error(" --owner and --repo must be provided together"),process.exit(1)),$&&$!=="test_run"&&(console.error(` invalid --billing-kind: ${$}`),process.exit(1)),!a&&!y&&(console.error(" preview uploads require playback data: pass --events or --video.\n for a normal local preview, use `sootsim record --mode combined --open`\n or `sootsim flow --preview` instead of uploading a bundle-only snapshot."),process.exit(1));let ae=Le(r,"--assets-only"),ze=Se(r,{stripBooleanFlags:[],stripValueFlags:[]}),b=ve(ze),c,le=[],M=[],w=m?xt(m):[],ue=F?kt(F):null;w.push(...Et(ue));let ce=w.length,J=null;try{if(c=await x(b,'(typeof window.__sootsimCaptureBundle === "function") ? window.__sootsimCaptureBundle() : null'),c?.bundleUrl){let l=new URL(c.bundleUrl).origin,[B,X]=await Promise.all([x(b,`(window.__sootsimPreviewRecorder?.list?.(${JSON.stringify(l)}) || [])`),x(b,`(async () => {
50
- const list = window.__sootsimListWorkerFetchRecorder
51
- return typeof list === 'function'
52
- ? await list(${JSON.stringify(l)})
53
- : []
54
- })()`)]);le=re([...B,...X]).filter(p=>{try{return!new URL(p.url).pathname.startsWith("/api/")}catch{return!0}});let A=await x(b,`(async () => {
55
- const rec = window.__sootsimPreviewRecorder
56
- const workerList = window.__sootsimListWorkerFetchRecorder
57
- return {
58
- main: rec?.list ? rec.list() : [],
59
- worker: typeof workerList === 'function' ? await workerList() : [],
60
- }
61
- })()`),P=(A?re([...A.main,...A.worker]):[]).filter(p=>{try{return new URL(p.url).origin!==l}catch{return!1}}),S=P.length>0?{count:P.length,totalBytes:P.reduce((p,Z)=>p+(Z.size||0),0)}:null;if(S&&S.count>0){console.log(` ${S.count} recorded cross-origin responses (${(S.totalBytes/1024).toFixed(1)} KiB)`);let p=await x(b,`(async () => {
62
- const rec = window.__sootsimPreviewRecorder
63
- const workerDump = window.__sootsimDumpWorkerFetchRecorder
64
- const bundleUrl = ${JSON.stringify(c.bundleUrl)}
65
- const keep = (r) => {
66
- try {
67
- const u = new URL(r.url)
68
- if (u.origin !== ${JSON.stringify(l)}) return true
69
- // bundle-origin api responses came from the rewritten
70
- // /__app-api proxy at record time (re-keyed by the
71
- // recorder's unwrap step). they need bodies attached the
72
- // same way as cross-origin records \u2014 direct re-fetch from
73
- // the dev server lacks the bundle's session cookies.
74
- if (r.url !== bundleUrl && u.pathname.startsWith('/api/')) return true
75
- return false
76
- } catch { return false }
77
- }
78
- return {
79
- main: rec?.dump ? rec.dump().filter(keep) : [],
80
- worker: typeof workerDump === 'function'
81
- ? (await workerDump()).filter(keep)
82
- : [],
83
- }
84
- })()`);if(M=re([...p.main,...p.worker]),ae){let Z=M.length;M=M.filter(ee=>$e(ee.contentType));let Ye=S.totalBytes-M.reduce((ee,Qe)=>ee+(Qe.size||0),0);console.log(` --assets-only: kept ${M.length}/${Z} records (dropped ${(Ye/1024).toFixed(1)} KiB of API responses)`)}}}c?.transformedBundle&&(J=await St(b,c.transformedBundle.byteLength));let n=await x(b,`(() => {
85
- const bridge = window.SootSim?.bridges?.timeline ?? window.__sootsimTimeline
86
- if (!bridge || typeof bridge.recent !== 'function') return null
87
- return bridge.recent(${JSON.stringify(vt({recordedDurationMs:oe,recordingStartedAtMs:ie}))})
88
- })()`).catch(l=>(console.warn(` warning: failed to capture trace timeline: ${l instanceof Error?l.message:String(l)}`),null)),s=Array.isArray(n?.events)?n.events:[];w=[...w,...s.filter(ne)].sort((l,B)=>l.t-B.t||l.seq-B.seq)}finally{b.close()}c||(console.error(" could not read bundle snapshot \u2014 is sootsim running and is the bundle loaded?"),process.exit(2)),c.bundleUrl||(console.error(` no ?bundle= URL on the current sootsim tab.
89
- open the app you want to share first (e.g. sootsim open 8082), then run upload.`),process.exit(2));let g,K=!1;if(J!==null)g=new TextEncoder().encode(J),K=!0,console.log(` using post-transform bundle: ${(g.byteLength/1024).toFixed(1)} KiB`);else{let n=Tt(c.bundleUrl);n!==c.bundleUrl&&console.log(" forcing lazy=false for self-contained bundle"),console.log(` capturing: ${n}`),g=await Oe(n),console.log(` main bundle: ${(g.byteLength/1024).toFixed(1)} KiB`)}let O=le.filter(n=>n.url!==c.bundleUrl);ae&&(O=O.filter(n=>$e(n.contentType))),console.log(` fetching ${O.length} extra files\u2026`);let C=(await Promise.all(O.map(async n=>{try{let s=await Oe(n.url);return{...n,bytes:s}}catch(s){return console.error(` warning: failed to re-fetch ${n.url}: ${s instanceof Error?s.message:s}`),null}}))).filter(n=>!!n),He=C.reduce((n,s)=>n+s.bytes.byteLength,0);console.log(` ${C.length} extra files: ${(He/1024).toFixed(1)} KiB`);let k;a&&(k=L(a)),w.sort((n,s)=>n.t-s.t||n.seq-s.seq);let de=w.map(n=>JSON.stringify(n)).join(`
90
- `),V=Pe(de?`${de}
91
- `:"");console.log(` trace timeline: ${w.length} events${ce?` (${ce} from runner sidecars)`:""}`);let h;u&&(h=L(u),console.log(` attaching screenshot: ${(h.byteLength/1024).toFixed(1)} KiB`));let T,_,pe;if(y){if(T=L(y),_=y.endsWith(".mp4")?"video/mp4":y.endsWith(".gif")?"image/gif":void 0,_||(console.error(" preview flow videos must be mp4 or gif"),await v(),process.exit(1)),f){let n=Number(f);(!Number.isFinite(n)||n<=0)&&(console.error(` invalid --video-duration-ms: ${f}`),await v(),process.exit(1)),pe=Math.round(n)}console.log(` attaching flow video: ${(T.byteLength/1024).toFixed(1)} KiB (${_})`)}let E=ke(),R=Re(E),je=be();Me({event:"preview_upload_started",identity:{userId:E?.kind==="session"?je?.user?.id??null:null,repoId:R?.repoId??null,installationId:R?.installationId??null},properties:{origin:o,hasAuth:!!E,authMode:E?.kind??"none",bundleBytes:g.byteLength,isTransformed:K,extraFiles:C.length,recordedFetches:M.length,hasEvents:!!k,traceTimelineEvents:w.length,hasScreenshot:!!h,hasVideo:!!T}}),E||(console.error(" preview uploads need auth."),console.error(" set SOOTSIM_API_KEY=sk_sootsim_..., run `sootsim login`, or use the soot github runner."),await v(),process.exit(1));let Je=Ee(E),Ke=(()=>{try{return new URL(c.bundleUrl).origin}catch{return null}})(),N=(n,s,l,B,X,A,ye,P)=>{let S=Ie(A),p=S?Pe(n):n;return{url:s,urlhash:l,method:B,bodyHash:X,requestVaryHash:P,contentType:A,responseHeaders:ye,bytes:p,encoding:S?"gzip":void 0}},me=[],q=Mt(c.pageHref)||"";if(q){let n=await we(q,{onStaged:({url:s,byteLength:l})=>console.log(` staged app font: ${s} (${(l/1024).toFixed(1)} KiB)`),onError:(s,l)=>console.error(` warning: failed to fetch app font ${s}: ${l instanceof Error?l.message:l}`)});for(let s of n)me.push(N(Buffer.from(s.bytes),s.url,s.urlhash,"GET","-",s.contentType))}let fe=[],W=wt(c.pageHref)||"";if(W){let n=await Te(W,{onStaged:({url:s,byteLength:l})=>console.log(` staged splash image: ${s} (${(l/1024).toFixed(1)} KiB)`),onError:(s,l)=>console.error(` warning: failed to fetch splash image ${s}: ${l instanceof Error?l.message:l}`)});for(let s of n)fe.push(N(Buffer.from(s.bytes),s.url,s.urlhash,"GET","-",s.contentType))}let G=[...C.map(n=>N(n.bytes,n.url,n.urlhash,"GET","-",n.contentType,n.responseHeaders,n.requestVaryHash)),...M.map(n=>N(Buffer.from(n.bodyBase64,"base64"),n.url,n.urlhash,n.method||"GET",n.bodyHash||"-",n.contentType,n.responseHeaders,n.requestVaryHash)),...me,...fe],Y=Be(g),Ve=ut("sha256").update(g).digest("hex");console.log(` bundle raw=${(g.byteLength/1024).toFixed(1)} KiB br=${(Y.byteLength/1024).toFixed(1)} KiB`);let qe={contentHash:Ve,bundleSizeBytes:g.byteLength,bundleContentType:bt(c.bundleUrl),bundleEncoding:"br",bundleOrigin:Ke,runtimeVersion:c.runtimeVersion??void 0,appFonts:q||void 0,appSplash:W||void 0,entry:c.entry,isTransformed:K,deviceSpec:c.deviceSpec,installationId:R?.installationId??void 0,repoId:R?.repoId,owner:H??R?.owner,repo:j??R?.repo,billingKind:$==="test_run"?"test_run":void 0,files:G.map(n=>({url:n.url,urlhash:n.urlhash,method:n.method,bodyHash:n.bodyHash,contentType:n.contentType,responseHeaders:n.responseHeaders,requestVaryHash:n.requestVaryHash,encoding:n.encoding,sizeBytes:n.bytes.byteLength})),events:k?{sizeBytes:k.byteLength}:void 0,timeline:{sizeBytes:V.byteLength,encoding:"gzip"},traceFrameStats:Rt(ue),recordedDurationMs:oe,recordingStartedAtMs:ie,snapshots:h?[{id:"landing",label:"Landing",kind:"landing",t:0,contentType:"image/png",sizeBytes:h.byteLength}]:void 0,flowVideo:T&&_?{sizeBytes:T.byteLength,contentType:_,durationMs:pe}:void 0},ge=new Map;for(let n of G)ge.set(n.urlhash,n.bytes);let he=new Map;h&&he.set("landing",h);let We=Date.now(),I=Y.byteLength;for(let n of G)I+=n.bytes.byteLength;k&&(I+=k.byteLength),I+=V.byteLength,h&&(I+=h.byteLength),T&&(I+=T.byteLength);let Ge=`${U(o)}/api/preview/upload/init`;console.log(` init: ${Ge}`);let D;try{let n=await Ae({originBase:o,initBody:qe,bundleBytes:Y,filesByHash:ge,extras:{eventsBytes:k,timelineBytes:V,videoBytes:T,snapshotBytes:he},authHeader:Je,concurrency:16});D=n.finalize;let s=1+n.init.files.length+(n.init.events?1:0)+(n.init.timeline?1:0)+n.init.snapshots.length+(n.init.flowVideo?1:0);console.log(` PUT ${s} objects in ${Date.now()-We}ms (${(I/1024).toFixed(1)} KiB)`)}catch(n){let s=n;s.phase==="init"&&s.status===401&&(console.error(" preview upload requires a valid login."),console.error(" run `sootsim login` and retry."),await v(),process.exit(1)),s.status===402&&(console.error(` ${dt(s.responseText)??"preview uploads require Personal, Team, or an active trial \u2014 upgrade from the billing dialog."}`),await v(),process.exit(1)),console.error(` ${s.message??`${s.phase??"upload"} failed: ${String(n)}`}`),await v(),process.exit(1)}let Q=`${i}${D.url}`;return console.log(`
92
- stored ${D.filesStored??0} extra files`),console.log(` preview: ${Q}`),De&&await xe(Q),await v(),{previewUrl:Q,shareId:D.id}}export{mt as a,ht as b,Wt as c};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a}from"./chunk-3RAGDMGM.js";import"./chunk-NIDV74L2.js";export{a as getCliVersion};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b,c,d,e}from"./chunk-SQXH3XGO.js";import"./chunk-QTI33DV5.js";import"./chunk-FITJFYBO.js";import"./chunk-SKGV3OSH.js";import"./chunk-RN662UDP.js";import"./chunk-SVM25PDV.js";import"./chunk-T6WXCSWP.js";import"./chunk-AHJ2WJST.js";import"./chunk-CHVGDYQJ.js";import"./chunk-ORIISY22.js";import"./chunk-3RAGDMGM.js";import"./chunk-MQEEYPOK.js";import"./chunk-423YVBZK.js";import"./chunk-NIDV74L2.js";export{c as daemonInstall,b as getDaemonServiceStatus,a as runDaemon,d as teardownDaemonService,e as teardownSootsimHome};
@@ -1,49 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{d as S}from"./chunk-WJ7YQAKC.js";import"./chunk-WHNPGZX4.js";import"./chunk-XAFLM5JS.js";import"./chunk-SZVYOFMB.js";import"./chunk-PMPQ57UY.js";import"./chunk-FPRZHFV2.js";import"./chunk-ENY46QHT.js";import"./chunk-63EDUOKZ.js";import"./chunk-O56GZV3H.js";import"./chunk-D6CTB275.js";import"./chunk-S2AQ4WGX.js";import"./chunk-HVSDT56M.js";import"./chunk-F5TOTCUW.js";import"./chunk-ZSWQLDDQ.js";import"./chunk-HIWE4YYN.js";import"./chunk-7GG4TZ4L.js";import"./chunk-CPVTNQAS.js";import"./chunk-YOPEO6SA.js";import"./chunk-GBUUYMPP.js";import"./chunk-NYNAI5PO.js";import"./chunk-QTI33DV5.js";import"./chunk-FITJFYBO.js";import"./chunk-SVM25PDV.js";import"./chunk-T6WXCSWP.js";import"./chunk-AHJ2WJST.js";import"./chunk-CHVGDYQJ.js";import"./chunk-ORIISY22.js";import"./chunk-423YVBZK.js";import"./chunk-NIDV74L2.js";import{spawn as D}from"child_process";import{existsSync as u,mkdirSync as _,writeFileSync as f,unlinkSync as I}from"fs";import{tmpdir as P}from"os";import{dirname as $,resolve as n,join as M}from"path";function E(){try{let e=import.meta.resolve("sootsim/detox");return $(e.startsWith("file://")?e.slice(7):e)}catch{return n(import.meta.dirname,"..","..","detox")}}var F=`
3
- sootsim detox \u2014 run detox-style tests against a sootsim shell
4
-
5
- usage:
6
- sootsim detox [testFiles...] run all tests under e2e/, test/e2e/, or detox/
7
- sootsim detox --config <path> use a specific jest config
8
- sootsim detox init scaffold sootsim-detox.config.cjs + sample test
9
- sootsim detox --watch jest watch mode
10
- sootsim detox --headed keep the sootsim shell window visible
11
- sootsim detox -t <pattern> pass a jest --testNamePattern
12
- sootsim detox --no-launch skip auto-launching a sootsim shell
13
-
14
- sootsim detox auto-launches a sootsim shell if none is running on the
15
- expected port. disable with --no-launch if you're managing the shell yourself.
16
- `;async function B(e,s={}){if((e.includes("--help")||e.includes("-h"))&&(console.log(F),process.exit(0)),e[0]==="init")return U();let c=t=>{let o=e.indexOf(t);return o>=0?e[o+1]:void 0},r=t=>e.includes(t),w=r("--watch"),h=r("--headed"),O=r("--no-launch"),y=c("--config"),d=s.port||Number(process.env.SOOTSIM_PORT)||5173,j=e.filter((t,o)=>{if(t.startsWith("--")||t==="-t"||t==="-h")return!1;let m=e[o-1];return!(m==="--config"||m==="-t"||m==="--testNamePattern"||m==="--grep")}),T=e.filter(t=>t==="--runInBand"||t.startsWith("--maxWorkers=")),x=["e2e","test/e2e","detox"],l=process.env.SOOTSIM_TEST_DIR?n(process.cwd(),process.env.SOOTSIM_TEST_DIR):null;if(!l)for(let t of x){let o=n(process.cwd(),t);if(u(o)){l=o;break}}l||(console.error(` error: no detox tests found. expected one of: ${x.join(", ")}
17
- run 'sootsim detox init' to scaffold a starter suite.`),process.exit(1)),O||await k(d);let i=["jest"],p=y||R(["sootsim-detox.config.cjs","jest.config.cjs","jest.config.js"]),a=null;if(p)i.push("--config",n(process.cwd(),p));else{let t=n(E(),"jest-preset.cjs");a=M(P(),`sootsim-detox-${process.pid}.config.cjs`);let o={rootDir:l,roots:["<rootDir>"],testMatch:["<rootDir>/**/*.test.ts","<rootDir>/**/*.test.js"]};f(a,`module.exports = { ...require(${JSON.stringify(t)}), ...${JSON.stringify(o)} }
18
- `),i.push("--config",a)}w&&i.push("--watch");let g=c("-t")||c("--testNamePattern")||c("--grep");g&&i.push("-t",g),process.env.SOOTSIM_JEST_JSON==="1"&&i.push("--json"),process.env.SOOTSIM_JEST_OUTPUT_FILE&&i.push("--outputFile",process.env.SOOTSIM_JEST_OUTPUT_FILE),i.push(...T),i.push(...j),console.log(" sootsim detox"),console.log(` test dir: ${l}`),console.log(` port: ${d}${h?" (headed)":""}`),p&&console.log(` config: ${p}`);let v=D("npx",i,{cwd:process.cwd(),stdio:"inherit",env:{...process.env,SOOTSIM_TEST_DIR:l,SOOTSIM_PORT:String(d),SOOTSIM_HEADED:h?"1":"",SOOTSIM_URL:process.env.SOOTSIM_URL||`http://localhost:${d}`}}),b=await new Promise(t=>v.on("exit",o=>t(o||0)));if(a)try{I(a)}catch{}process.exit(b)}function R(e){for(let s of e)if(u(n(process.cwd(),s)))return s;return null}async function k(e){await S()||console.warn(` warn: no sootsim shell reachable on :${e}. start one with \`bun run dev:sootsim:shell\`
19
- or pass --no-launch if you're managing the shell yourself.`)}function U(){let e=process.cwd(),s=n(e,"sootsim-detox.config.cjs");u(s)?console.log(` skip: ${s} already exists`):(f(s,`// sootsim detox jest config.
20
- // extends the sootsim preset, which rewrites \`import ... from 'detox'\`
21
- // to the sootsim driver and keeps your jest transforms intact.
22
-
23
- /** @type {import('@jest/types').Config.InitialOptions} */
24
- module.exports = {
25
- preset: 'sootsim/detox/jest-preset',
26
- rootDir: __dirname,
27
- testMatch: ['<rootDir>/e2e/**/*.test.ts', '<rootDir>/e2e/**/*.test.js'],
28
- }
29
- `),console.log(` created ${s}`));let c=n(e,"e2e"),r=n(c,"example.test.ts");u(r)?console.log(` skip: ${r} already exists`):(_(c,{recursive:!0}),f(r,`// sample sootsim detox test. resolve \`detox\` via the jest preset in
30
- // sootsim-detox.config.cjs \u2014 no extra imports required.
31
-
32
- import { by, device, element, expect, waitFor } from 'detox'
33
-
34
- describe('example', () => {
35
- beforeAll(async () => {
36
- await device.launchApp()
37
- })
38
-
39
- it('shows the welcome text', async () => {
40
- await waitFor(element(by.text('Welcome'))).toBeVisible().withTimeout(5000)
41
- })
42
-
43
- it('taps a button by id', async () => {
44
- await element(by.id('start-button')).tap()
45
- await expect(element(by.id('home-screen'))).toBeVisible()
46
- })
47
- })
48
- `),console.log(` created ${r}`)),console.log(`
49
- next:`),console.log(" sootsim detox # run the sample"),console.log(" sootsim detox --watch"),process.exit(0)}export{B as runDetox};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import"./chunk-5GICTRK5.js";import{f as a,g as b,h as c,i as d,j as e,k as f,l as g,m as h,n as i}from"./chunk-7GG4TZ4L.js";import"./chunk-CPVTNQAS.js";import"./chunk-YOPEO6SA.js";import"./chunk-CHVGDYQJ.js";import"./chunk-ORIISY22.js";import"./chunk-NIDV74L2.js";export{e as ALL_DRIVERS,i as buildDriverListRows,a as chromiumDriver,b as electronDriver,f as getAllDrivers,g as getDriver,c as playwrightDriver,h as resolveDriver,d as systemDriver};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{c,d,e,f,g}from"./chunk-WJ7YQAKC.js";import{c as a,d as b}from"./chunk-WHNPGZX4.js";import"./chunk-XAFLM5JS.js";import"./chunk-SZVYOFMB.js";import"./chunk-PMPQ57UY.js";import"./chunk-FPRZHFV2.js";import"./chunk-ENY46QHT.js";import"./chunk-63EDUOKZ.js";import"./chunk-O56GZV3H.js";import"./chunk-D6CTB275.js";import"./chunk-S2AQ4WGX.js";import"./chunk-HVSDT56M.js";import"./chunk-F5TOTCUW.js";import"./chunk-ZSWQLDDQ.js";import"./chunk-HIWE4YYN.js";import"./chunk-7GG4TZ4L.js";import"./chunk-CPVTNQAS.js";import"./chunk-YOPEO6SA.js";import"./chunk-GBUUYMPP.js";import"./chunk-NYNAI5PO.js";import"./chunk-QTI33DV5.js";import"./chunk-FITJFYBO.js";import"./chunk-SVM25PDV.js";import"./chunk-T6WXCSWP.js";import"./chunk-AHJ2WJST.js";import"./chunk-CHVGDYQJ.js";import"./chunk-ORIISY22.js";import"./chunk-423YVBZK.js";import"./chunk-NIDV74L2.js";export{d as discoverSootsimUrl,c as getLastFlowPreviewUploadResult,f as hoistLeadingSimFlag,a as parseFlowFile,g as runFlow,e as runFlowPlayback,b as validateFlowFile};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b,c}from"./chunk-SKGV3OSH.js";import"./chunk-RN662UDP.js";import"./chunk-SVM25PDV.js";import"./chunk-T6WXCSWP.js";import"./chunk-AHJ2WJST.js";import"./chunk-CHVGDYQJ.js";import"./chunk-ORIISY22.js";import"./chunk-3RAGDMGM.js";import"./chunk-MQEEYPOK.js";import"./chunk-423YVBZK.js";import"./chunk-NIDV74L2.js";export{c as printCommandHelp,b as printGroupHelp,a as printHelp};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z}from"./chunk-ORIISY22.js";import"./chunk-NIDV74L2.js";export{b as ACTIVE_RUNTIME_FILE,e as CONFIG_FILE,f as DAEMON_HEARTBEAT_STALE_MS,c as DAEMON_LOCKFILE,d as DEV_BRIDGE_LOCKFILE,a as SOOTSIM_HOME_ENV,I as activeRuntimeDir,m as activeRuntimeFile,u as cacheDir,P as claimDaemonLockfile,H as compareSemver,x as configFilePath,s as daemonAppBundlePath,r as daemonAppDir,t as daemonAppLauncherPath,v as daemonLockfilePath,w as devBridgeLockfilePath,n as electronDir,o as electronUserDataDir,p as electronVersionDir,D as ensureSootsimHome,L as isDaemonLockfileFresh,M as isDevBridgeLockfileFresh,i as isDevWorkstation,h as isSootsimDevCheckout,G as listInstalledRuntimes,q as profilesDir,E as readActiveRuntime,J as readDaemonLockfile,K as readDevBridgeLockfile,B as readOrCreateAnonymousId,y as readSharedConfig,A as readTelemetryEnabled,Q as removeDaemonLockfile,R as removeDevBridgeLockfile,l as runtimeDir,k as runtimesDir,j as shouldSkipPersistentDaemon,g as sootsimHomeDir,F as writeActiveRuntime,N as writeDaemonLockfile,O as writeDevBridgeLockfile,z as writeSharedConfig,C as writeTelemetryEnabled};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{j as a,k as b}from"./chunk-WHNPGZX4.js";import"./chunk-XAFLM5JS.js";import"./chunk-ENY46QHT.js";import"./chunk-63EDUOKZ.js";import"./chunk-O56GZV3H.js";import"./chunk-D6CTB275.js";import"./chunk-S2AQ4WGX.js";import"./chunk-HVSDT56M.js";import"./chunk-F5TOTCUW.js";import"./chunk-ZSWQLDDQ.js";import"./chunk-HIWE4YYN.js";import"./chunk-7GG4TZ4L.js";import"./chunk-CPVTNQAS.js";import"./chunk-YOPEO6SA.js";import"./chunk-GBUUYMPP.js";import"./chunk-NYNAI5PO.js";import"./chunk-SVM25PDV.js";import"./chunk-T6WXCSWP.js";import"./chunk-AHJ2WJST.js";import"./chunk-CHVGDYQJ.js";import"./chunk-ORIISY22.js";import"./chunk-423YVBZK.js";import"./chunk-NIDV74L2.js";export{a as printShellPerfReport,b as runInspect};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a}from"./chunk-EVCOHQPK.js";import"./chunk-USGQNFAM.js";import"./chunk-CPVTNQAS.js";import"./chunk-NIDV74L2.js";export{a as runInstallDesktop};
@@ -1,96 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{e as w,f as v}from"./chunk-WJ7YQAKC.js";import{c as y}from"./chunk-WHNPGZX4.js";import{a as h}from"./chunk-XAFLM5JS.js";import"./chunk-SZVYOFMB.js";import"./chunk-PMPQ57UY.js";import"./chunk-FPRZHFV2.js";import"./chunk-ENY46QHT.js";import"./chunk-63EDUOKZ.js";import"./chunk-O56GZV3H.js";import"./chunk-D6CTB275.js";import"./chunk-S2AQ4WGX.js";import"./chunk-HVSDT56M.js";import"./chunk-F5TOTCUW.js";import"./chunk-ZSWQLDDQ.js";import"./chunk-HIWE4YYN.js";import"./chunk-7GG4TZ4L.js";import"./chunk-CPVTNQAS.js";import"./chunk-YOPEO6SA.js";import"./chunk-GBUUYMPP.js";import"./chunk-NYNAI5PO.js";import"./chunk-QTI33DV5.js";import"./chunk-FITJFYBO.js";import"./chunk-SVM25PDV.js";import"./chunk-T6WXCSWP.js";import"./chunk-AHJ2WJST.js";import"./chunk-CHVGDYQJ.js";import"./chunk-ORIISY22.js";import"./chunk-423YVBZK.js";import"./chunk-NIDV74L2.js";import*as n from"fs";import*as a from"path";var S=[".maestro","maestro"];function k(){console.log(`
3
- sootsim maestro \u2014 run maestro YAML flows against sootsim (drop-in)
4
-
5
- usage:
6
- sootsim maestro # discover .maestro/ or maestro/ in cwd, run all
7
- sootsim maestro test <flow-or-dir> # mirrors "maestro test"
8
- sootsim maestro test .maestro/ # every *.yaml / *.yml in a directory
9
- sootsim maestro init # scaffold .maestro/login.yaml
10
- sootsim maestro --list-compat # print supported/unsupported verbs
11
-
12
- options:
13
- --env KEY=VALUE set env vars for \${KEY} interpolation (repeatable)
14
- --continuous re-run the flow on file changes (alias for --watch)
15
- --format <fmt> accepted for maestro cli compat (not used)
16
- --new force a fresh browser window for this run
17
- --headed force a fresh, VISIBLE window (implies --new,
18
- overrides --headless) \u2014 watch the flow run live
19
- --record record a webm while the flow runs
20
- --preview record events+video, upload, and print /preview/<id>
21
- --preview-open open the uploaded preview link after a successful run
22
- --preview-origin <url> upload target for --preview
23
- --preview-public-origin <url>
24
- public link origin for --preview
25
- --slow <ms> delay between steps for natural pacing
26
-
27
- examples:
28
- sootsim maestro test .maestro/login.yaml
29
- sootsim maestro test .maestro/
30
- sootsim maestro --env USERNAME=alice test .maestro/login.yaml
31
- sootsim maestro init
32
- `)}function A(){console.log(`
33
- sootsim maestro \u2014 compatibility matrix
34
-
35
- supported verbs:
36
- launchApp, stopApp, clearState, clearKeychain (warn-only),
37
- tapOn, tapAtCoords, longPressOn,
38
- inputText, pressKey, dispatchKey, hideKeyboard, eraseText,
39
- assertVisible, assertNotVisible, assertTreeContains,
40
- waitFor, extendedWaitUntil, waitForAnimationToEnd,
41
- scroll, scrollUntilVisible, scrollTo, swipe, pinch,
42
- takeScreenshot, dumpTree, back,
43
- repeat (times), runFlow (file / inline commands / env / when),
44
- runScript (maestro JS: http, json(), output, env vars, console.log),
45
- evalScript, copyTextFrom (\${maestro.copiedText}), openLink,
46
- when: (visible / notVisible / platform / true),
47
- optional:, label:, onFlowStart, onFlowComplete,
48
- \${...} JS-expression interpolation at step execution time
49
- (env vars as globals, output.*, maestro.*, \${EXPR || 'default'}).
50
-
51
- workspace mode (directory runs):
52
- config.yaml / config.yml discovery, flows: inclusion globs,
53
- includeTags / excludeTags against flow frontmatter tags.
54
-
55
- partial:
56
- clearKeychain \u2014 warn-only, sootsim has no keychain surface.
57
- when.platform \u2014 sootsim reports iOS.
58
- openLink \u2014 dispatched into the app via the Linking emulation
59
- (react-navigation deep links work); no OS-level Safari fallback.
60
- launchApp.arguments \u2014 ignored; sootsim has no native process.
61
- launchApp.resetRuntime \u2014 sootsim-only extension for recorded benchmarks:
62
- keep tenant storage/auth, but soft-reload the guest app inside launchApp.
63
- takeScreenshot \u2014 maestro string form works unchanged; sootsim also accepts
64
- { path, withFrame } for framed export.
65
- env \u2014 sootsim seeds the JS env from the full shell environment, not just
66
- MAESTRO_* (so \${SOOTSIM_*} credentials resolve); a bare \${NAME} that is
67
- undefined fails the step loudly instead of typing "undefined".
68
-
69
- not yet implemented (will throw "unsupported maestro verb"):
70
- travel, setLocation, setAirplaneMode, killApp, faker,
71
- addMedia, startRecording, stopRecording (use --record flag),
72
- repeat.while (only repeat.times is supported).
73
- `)}function b(o){return o.startsWith("-")}function $(o){let i=[];for(let e=0;e<o.length;e++){if(o[e]==="--env"||o[e]==="-e"){let s=o[e+1];if(!s||!s.includes("="))throw new Error(`--env expects KEY=VALUE (got ${JSON.stringify(s??"")})`);let p=s.indexOf("="),l=s.slice(0,p),f=s.slice(p+1);process.env[l]=f,e+=1;continue}i.push(o[e])}return i}function T(o){let i=[];for(let e=0;e<o.length;e++){let s=o[e];if(s==="--format"||s==="--output"||s==="--device"||s==="-d"){e+=1;continue}if(s==="--continuous"){console.warn(" warn: --continuous is not yet implemented in sootsim \u2014 running once");continue}i.push(s)}return i}function x(o){for(let i of S){let e=a.join(o,i);if(n.existsSync(e)&&n.statSync(e).isDirectory())return e}return null}function F(o){let i=n.statSync(o);if(i.isFile())return[o];if(!i.isDirectory())throw new Error(`not a file or directory: ${o}`);let e={},s=["config.yaml","config.yml"].map(t=>a.join(o,t)).find(t=>n.existsSync(t));if(s){let t=h.parse(n.readFileSync(s,"utf8"));t&&typeof t=="object"&&(e=t)}let p=Array.isArray(e.flows)&&e.flows.length>0?e.flows:["*"],l=t=>{let r=a.basename(t);return!(r.startsWith(".")||!r.endsWith(".yaml")&&!r.endsWith(".yml")||r==="config.yaml"||r==="config.yml")},f=new Set;for(let t of p)for(let r of n.globSync(t,{cwd:o})){if(!l(r))continue;let c=a.join(o,r);n.existsSync(c)&&n.statSync(c).isFile()&&f.add(c)}let m=[...f].sort(),u=Array.isArray(e.includeTags)?e.includeTags:[],d=Array.isArray(e.excludeTags)?e.excludeTags:[];return(u.length>0||d.length>0)&&(m=m.filter(t=>{let{frontmatter:r}=y(n.readFileSync(t,"utf8")),c=Array.isArray(r.tags)?r.tags:[];return!(u.length>0&&!c.some(g=>u.includes(g))||c.some(g=>d.includes(g)))})),m}async function E(o){let i=a.join(o,".maestro");n.mkdirSync(i,{recursive:!0});let e=a.join(i,"login.yaml");return n.existsSync(e)?(console.error(` error: ${e} already exists`),1):(n.writeFileSync(e,`# sootsim maestro starter flow \u2014 drop-in compatible with the maestro cli.
74
- # run: sootsim maestro test .maestro/login.yaml
75
- appId: com.example.app
76
- ---
77
- - launchApp
78
- - tapOn:
79
- id: "email"
80
- - inputText: "user@example.com"
81
- - tapOn:
82
- id: "password"
83
- - inputText: "secret123"
84
- - tapOn: "Sign in"
85
- - assertVisible: "Welcome"
86
- `,"utf8"),console.log(` + wrote ${e}`),console.log(" next: sootsim maestro test .maestro/login.yaml"),0)}async function R(o,i={}){if(o.includes("--help")||o.includes("-h"))return k(),0;if(o.includes("--list-compat"))return A(),0;let e;try{e=$(o)}catch(t){return console.error(` error: ${t.message}`),1}e=T(e),e=v(e);let s=process.cwd(),p=e[0];if(p==="init")return E(s);let l=null,f;if(p==="test"){let t=e.slice(1).find(r=>!b(r));t?(l=a.resolve(s,t),f=e.slice(1).filter(r=>r!==t)):(l=x(s),f=e.slice(1))}else p&&!b(p)?(l=a.resolve(s,p),f=e.slice(1)):(l=x(s),f=e);if(!l)return console.error(`
87
- error: no maestro flows found.
88
- expected one of: ${S.map(t=>`./${t}/`).join(", ")}
89
- or pass a flow explicitly: sootsim maestro test path/to/flow.yaml
90
- or scaffold a starter flow: sootsim maestro init
91
- `),1;if(!n.existsSync(l))return console.error(` error: ${l} not found`),1;let m;try{m=F(l)}catch(t){return console.error(` error: ${t.message}`),1}if(m.length===0)return console.error(` error: no *.yaml or *.yml files found in ${l}`),1;console.log(`
92
- sootsim maestro \u2014 ${m.length} flow${m.length===1?"":"s"}
93
- root: ${a.relative(s,l)||"."}
94
- `);let u=0,d=[];for(let t of m){console.log(`
95
- \u25B6 ${a.relative(s,t)}`);let r=[t,...f];i.port&&!f.includes("--url")&&r.push("--url",String(i.port));let c=0;try{c=await w(r)}catch(g){console.error(` x ${g.message}`),c=1}d.push({file:t,exit:c}),c!==0&&u===0&&(u=c)}if(m.length>1){console.log(`
96
- maestro summary:`);for(let r of d){let c=r.exit===0?"pass":"fail";console.log(` ${c} ${a.relative(s,r.file)}`)}let t=d.filter(r=>r.exit===0).length;console.log(` ${t}/${d.length} passed`)}return u}export{F as collectFlowFiles,R as runMaestro};