sootsim 0.1.201 → 0.1.202

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist-cli/bin.js +5 -4
  2. package/dist-cli/chunks/{agent-SHGFOKZN.js → agent-SP6EE5XV.js} +2 -2
  3. package/dist-cli/chunks/{agent-wrapper-3O4ZW24C.js → agent-wrapper-PWZVJ3OK.js} +2 -2
  4. package/dist-cli/chunks/{app-fonts-KZOBGGZQ.js → app-fonts-EMJB6QLM.js} +2 -2
  5. package/dist-cli/chunks/{assert-RZCBKONA.js → assert-H2B6TDXT.js} +2 -2
  6. package/dist-cli/chunks/{auth-PBD7GLXL.js → auth-SOHXBYLQ.js} +2 -2
  7. package/dist-cli/chunks/beta-CCNQQ73A.js +2 -0
  8. package/dist-cli/chunks/{camera-5OOLGGLN.js → camera-ULLCBM5H.js} +2 -2
  9. package/dist-cli/chunks/{chunk-WHNPGZX4.js → chunk-2CID4LNK.js} +3 -3
  10. package/dist-cli/chunks/{chunk-423YVBZK.js → chunk-2V43M57C.js} +1 -1
  11. package/dist-cli/chunks/{chunk-SQXH3XGO.js → chunk-3ZENZOIX.js} +2 -2
  12. package/dist-cli/chunks/{chunk-NIDV74L2.js → chunk-4KT2BGQE.js} +1 -1
  13. package/dist-cli/chunks/{chunk-GBUUYMPP.js → chunk-4KVSVFRF.js} +1 -1
  14. package/dist-cli/chunks/{chunk-HVSDT56M.js → chunk-4NM6JBYH.js} +1 -1
  15. package/dist-cli/chunks/chunk-5J63S2NM.js +1 -0
  16. package/dist-cli/chunks/{chunk-MQEEYPOK.js → chunk-5SKKMM7E.js} +1 -1
  17. package/dist-cli/chunks/chunk-6FZ6SNVP.js +4 -0
  18. package/dist-cli/chunks/{chunk-SZVYOFMB.js → chunk-6QFVYLX6.js} +2 -2
  19. package/dist-cli/chunks/{chunk-XAFLM5JS.js → chunk-AAQVPSIV.js} +2 -2
  20. package/dist-cli/chunks/{chunk-ENY46QHT.js → chunk-B6JB4THC.js} +2 -2
  21. package/dist-cli/chunks/{chunk-PMPQ57UY.js → chunk-BZ7AW7ZB.js} +2 -2
  22. package/dist-cli/chunks/{chunk-EVCOHQPK.js → chunk-CB7LDZKJ.js} +3 -3
  23. package/dist-cli/chunks/{chunk-D6CTB275.js → chunk-CGJ2U6ST.js} +1 -1
  24. package/dist-cli/chunks/chunk-DKBKU55D.js +2 -0
  25. package/dist-cli/chunks/{chunk-YOPEO6SA.js → chunk-DRJ4YWUG.js} +1 -1
  26. package/dist-cli/chunks/{chunk-F5TOTCUW.js → chunk-EMOZJ773.js} +2 -2
  27. package/dist-cli/chunks/{chunk-CPVTNQAS.js → chunk-ENXHZGSL.js} +1 -1
  28. package/dist-cli/chunks/{chunk-T6WXCSWP.js → chunk-F2LLN4U7.js} +2 -2
  29. package/dist-cli/chunks/{chunk-USGQNFAM.js → chunk-GUYKLSTX.js} +1 -1
  30. package/dist-cli/chunks/chunk-GZKT53SA.js +7 -0
  31. package/dist-cli/chunks/{chunk-7GG4TZ4L.js → chunk-HZYHSLNH.js} +2 -2
  32. package/dist-cli/chunks/chunk-I6O7NBPZ.js +1 -0
  33. package/dist-cli/chunks/{chunk-JEJV3UHZ.js → chunk-JP7J325R.js} +2 -2
  34. package/dist-cli/chunks/{chunk-3RAGDMGM.js → chunk-K3UBHPVP.js} +1 -1
  35. package/dist-cli/chunks/{chunk-HIWE4YYN.js → chunk-KRGHRSKY.js} +2 -2
  36. package/dist-cli/chunks/{chunk-QTI33DV5.js → chunk-KYGUR727.js} +2 -2
  37. package/dist-cli/chunks/{chunk-RN662UDP.js → chunk-LNDRRZP3.js} +2 -2
  38. package/dist-cli/chunks/{chunk-NYNAI5PO.js → chunk-OG6TFGYF.js} +2 -2
  39. package/dist-cli/chunks/{chunk-AHJ2WJST.js → chunk-PL4GXELT.js} +1 -1
  40. package/dist-cli/chunks/{chunk-S2AQ4WGX.js → chunk-RJVIUQK4.js} +1 -1
  41. package/dist-cli/chunks/{chunk-ZSWQLDDQ.js → chunk-ROYBEBG2.js} +1 -1
  42. package/dist-cli/chunks/{chunk-SKGV3OSH.js → chunk-RS7QLHEH.js} +2 -2
  43. package/dist-cli/chunks/{chunk-FITJFYBO.js → chunk-RWXCSRNE.js} +2 -2
  44. package/dist-cli/chunks/{chunk-CHVGDYQJ.js → chunk-RZQO4Q7E.js} +1 -1
  45. package/dist-cli/chunks/{chunk-SVM25PDV.js → chunk-SQBX2HS7.js} +6 -6
  46. package/dist-cli/chunks/{chunk-FPRZHFV2.js → chunk-TANTCV45.js} +2 -2
  47. package/dist-cli/chunks/chunk-W4Y3JJX6.js +29 -0
  48. package/dist-cli/chunks/{chunk-WJ7YQAKC.js → chunk-WILIVPUU.js} +38 -38
  49. package/dist-cli/chunks/{chunk-63EDUOKZ.js → chunk-WVDWIXPH.js} +2 -2
  50. package/dist-cli/chunks/{chunk-7672K2JN.js → chunk-Z5VMWFCY.js} +1 -1
  51. package/dist-cli/chunks/cli-version-Z4NGLJLH.js +2 -0
  52. package/dist-cli/chunks/{compat-NNM7DTTO.js → compat-YW3OJ7RX.js} +3 -3
  53. package/dist-cli/chunks/{config-LBGQRC2P.js → config-ZGOXFEX4.js} +2 -2
  54. package/dist-cli/chunks/{control-5DHHQZ26.js → control-75JRFOYV.js} +2 -2
  55. package/dist-cli/chunks/daemon-SHFBQRHD.js +2 -0
  56. package/dist-cli/chunks/{debug-PILWFGN3.js → debug-4APEP64L.js} +3 -3
  57. package/dist-cli/chunks/{desktop-CZ43IZFV.js → desktop-CS7S4CXK.js} +3 -3
  58. package/dist-cli/chunks/detox-7R7DOKQM.js +50 -0
  59. package/dist-cli/chunks/{device-M26JCUHY.js → device-EIDKLBEK.js} +2 -2
  60. package/dist-cli/chunks/{diagnose-HZZMVPQZ.js → diagnose-J2F7MJAJ.js} +2 -2
  61. package/dist-cli/chunks/drivers-55L7BHDF.js +2 -0
  62. package/dist-cli/chunks/flow-AHWVTHJP.js +2 -0
  63. package/dist-cli/chunks/help-C4BTMPEE.js +2 -0
  64. package/dist-cli/chunks/home-paths-WVCREWI7.js +2 -0
  65. package/dist-cli/chunks/inspect-M4Z4SSSB.js +2 -0
  66. package/dist-cli/chunks/install-desktop-PVRUAEMA.js +2 -0
  67. package/dist-cli/chunks/{login-VBCYZKJ6.js → login-EOBJ7CZ4.js} +4 -4
  68. package/dist-cli/chunks/{logout-J22GLKHX.js → logout-RL7D66VQ.js} +2 -2
  69. package/dist-cli/chunks/maestro-F66ZIJKA.js +96 -0
  70. package/dist-cli/chunks/{mode-XN46XIMK.js → mode-R2VIW66U.js} +2 -2
  71. package/dist-cli/chunks/optional-demo-registry-ILX5YV34.js +2 -0
  72. package/dist-cli/chunks/{perf-ZIYESQRA.js → perf-PGYO5BBR.js} +2 -2
  73. package/dist-cli/chunks/{record-L74E3HCL.js → record-IGDVHC67.js} +3 -3
  74. package/dist-cli/chunks/runtime-43RJGUGS.js +2 -0
  75. package/dist-cli/chunks/{screenshot-command-ZCI5QE4T.js → screenshot-command-HX4GVUJM.js} +2 -2
  76. package/dist-cli/chunks/{serve-CNDRR7XN.js → serve-3TFVHYLY.js} +18 -18
  77. package/dist-cli/chunks/{setup-XZDN7XMI.js → setup-JSPPRVET.js} +3 -3
  78. package/dist-cli/chunks/{skills-KJK67CVB.js → skills-LK3B76W7.js} +2 -2
  79. package/dist-cli/chunks/{storage-UOLMRHLQ.js → storage-NC5RNBUA.js} +2 -2
  80. package/dist-cli/chunks/store-3XMEKA6H.js +2 -0
  81. package/dist-cli/chunks/telemetry-CVJ5YZNS.js +2 -0
  82. package/dist-cli/chunks/test-2CXPLH5N.js +69 -0
  83. package/dist-cli/chunks/{timeline-UJFIJ2GT.js → timeline-WDTEVELN.js} +2 -2
  84. package/dist-cli/chunks/upgrade-TT2IJSNJ.js +4 -0
  85. package/dist-cli/chunks/upload-4HMNB4HA.js +2 -0
  86. package/dist-cli/chunks/version-JUNH4FOR.js +4 -0
  87. package/dist-cli/chunks/{web-QP4KDHR6.js → web-BWCW26ZA.js} +2 -2
  88. package/dist-cli/chunks/{what-happened-VW274SXG.js → what-happened-L6EEB6MB.js} +2 -2
  89. package/dist-lib/agent-daemon-client.cjs +1 -1
  90. package/dist-lib/agent-events.cjs +1 -1
  91. package/dist-lib/agent-identity.cjs +1 -1
  92. package/dist-lib/agent-sessions.cjs +1 -1
  93. package/dist-lib/attached-projects.cjs +1 -1
  94. package/dist-lib/auth/shared-session.cjs +1 -1
  95. package/dist-lib/backend-origin.cjs +1 -1
  96. package/dist-lib/beta.cjs +1 -1
  97. package/dist-lib/beta.mjs +1 -1
  98. package/dist-lib/bridge-constants.cjs +1 -1
  99. package/dist-lib/cli-constants.cjs +1 -1
  100. package/dist-lib/config.cjs +1 -1
  101. package/dist-lib/detox/index.cjs +1 -1
  102. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  103. package/dist-lib/home-paths.cjs +32 -1
  104. package/dist-lib/host/bridge-host.cjs +51 -2
  105. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  106. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  107. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  108. package/dist-lib/host/websocket-proxy.cjs +1 -1
  109. package/dist-lib/index.cjs +1 -1
  110. package/dist-lib/jump-to-source-babel.cjs +1 -1
  111. package/dist-lib/menu.cjs +1 -1
  112. package/dist-lib/menu.mjs +1 -1
  113. package/dist-lib/metro.cjs +1 -1
  114. package/dist-lib/profiles.cjs +1 -1
  115. package/dist-lib/render-mode.cjs +1 -1
  116. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  117. package/dist-lib/sdk.cjs +1 -1
  118. package/dist-lib/sdk.mjs +1 -1
  119. package/dist-lib/skills.cjs +104 -19
  120. package/dist-lib/vite.cjs +1 -1
  121. package/package.json +1 -1
  122. package/src/home-paths.ts +48 -1
  123. package/src/host/bridge-host.ts +12 -0
  124. package/src/runtime-delivery.ts +59 -9
  125. package/dist-cli/chunks/beta-EJBRLRKG.js +0 -2
  126. package/dist-cli/chunks/chunk-3W273A5J.js +0 -4
  127. package/dist-cli/chunks/chunk-5GICTRK5.js +0 -1
  128. package/dist-cli/chunks/chunk-AGSCFR4P.js +0 -27
  129. package/dist-cli/chunks/chunk-O56GZV3H.js +0 -1
  130. package/dist-cli/chunks/chunk-ORIISY22.js +0 -6
  131. package/dist-cli/chunks/cli-version-WTA2LUDV.js +0 -2
  132. package/dist-cli/chunks/daemon-3KGXTPXJ.js +0 -2
  133. package/dist-cli/chunks/detox-D6XW7Y4V.js +0 -49
  134. package/dist-cli/chunks/drivers-7ZM3KW22.js +0 -2
  135. package/dist-cli/chunks/flow-DDDWJ4KD.js +0 -2
  136. package/dist-cli/chunks/help-23GXSWP3.js +0 -2
  137. package/dist-cli/chunks/home-paths-746ZDH2I.js +0 -2
  138. package/dist-cli/chunks/inspect-T37ALZRL.js +0 -2
  139. package/dist-cli/chunks/install-desktop-FSY2ND5S.js +0 -2
  140. package/dist-cli/chunks/maestro-5LWJ2S6K.js +0 -96
  141. package/dist-cli/chunks/optional-demo-registry-YF4W5LPU.js +0 -2
  142. package/dist-cli/chunks/runtime-7WSR27U6.js +0 -2
  143. package/dist-cli/chunks/store-FGCNPJBA.js +0 -2
  144. package/dist-cli/chunks/telemetry-QEO7INJC.js +0 -2
  145. package/dist-cli/chunks/test-5DK2XCRU.js +0 -69
  146. package/dist-cli/chunks/upgrade-CXZFJIH7.js +0 -4
  147. package/dist-cli/chunks/upload-ZFINR6K3.js +0 -2
  148. 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,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};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b}from"./chunk-HVSDT56M.js";import"./chunk-NIDV74L2.js";export{a as loadOptionalDemoApps,b as optionalDemoAppsSync};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a}from"./chunk-AGSCFR4P.js";import"./chunk-3W273A5J.js";import"./chunk-HIWE4YYN.js";import"./chunk-AHJ2WJST.js";import"./chunk-CHVGDYQJ.js";import"./chunk-ORIISY22.js";import"./chunk-NIDV74L2.js";export{a as runRuntime};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a}from"./chunk-NYNAI5PO.js";import"./chunk-423YVBZK.js";import"./chunk-NIDV74L2.js";export{a as settingsStore};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b}from"./chunk-QTI33DV5.js";import"./chunk-FITJFYBO.js";import"./chunk-ORIISY22.js";import"./chunk-NIDV74L2.js";export{b as flushCliTelemetry,a as trackCliEvent};
@@ -1,69 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as T}from"./chunk-XAFLM5JS.js";import{a as P}from"./chunk-PMPQ57UY.js";import{a as O,b as k}from"./chunk-FPRZHFV2.js";import"./chunk-D6CTB275.js";import"./chunk-S2AQ4WGX.js";import{f as x}from"./chunk-ZSWQLDDQ.js";import{g as S,i as v}from"./chunk-HIWE4YYN.js";import"./chunk-YOPEO6SA.js";import"./chunk-QTI33DV5.js";import"./chunk-FITJFYBO.js";import"./chunk-AHJ2WJST.js";import"./chunk-CHVGDYQJ.js";import"./chunk-ORIISY22.js";import"./chunk-NIDV74L2.js";import{spawn as B}from"child_process";import{existsSync as g,mkdtempSync as R,readdirSync as $,writeFileSync as C}from"fs";import{tmpdir as J}from"os";import{resolve as d,dirname as Y,join as _}from"path";import{fileURLToPath as V}from"url";var h=Y(V(import.meta.resolve("sootsim-engine/package.json"))),H=process.env.SOOTSIM_TEST_MODEL||"deepseek-v4-flash",A=18e3,W=18e4;async function he(e,t){let n=e.includes("--flows"),c=e.includes("--detox"),a=e.includes("--parallel"),i=e.includes("--watch"),u=e.find((l,o)=>e[o-1]==="--reporter")||"console",r=G(e);if((e.includes("--help")||e.includes("-h"))&&(console.log(`
3
- sootsim test \u2014 run tests against sootsim
4
-
5
- usage:
6
- sootsim test [options]
7
- sootsim test "natural language goal" [--owner <org> --repo <repo>]
8
-
9
- modes:
10
- (default) run package-local playwright tests
11
- "<prompt>" generate, run, upload, and register a replayable flow
12
- --flows run YAML flows
13
- --detox run shared Detox-style conformance suites
14
-
15
- options:
16
- --parallel run tests in parallel
17
- --watch re-run on changes
18
- --reporter output format (console|json|junit)
19
- --origin sootbean upload / LLM origin (default: auto)
20
- --owner org account to attach the uploaded run to
21
- --repo repo name for --owner
22
- -t, --testNamePattern <pat>
23
- filter tests by name
24
-
25
- examples:
26
- sootsim test
27
- sootsim test "tap the login button and verify the welcome screen"
28
- sootsim test --flows
29
- sootsim test --detox --watch
30
- sootsim test -t "login"
31
-
32
- notes:
33
- playwright is best for smoke and package-local debugging
34
- --detox is the preferred parity lane for behavior that should match real RN
35
- `),process.exit(0)),r&&!n&&!c)await oe(r.prompt,r.index,e,t);else if(n)await ie(e,t,a,u);else if(c){let{runDetox:l}=await import("./detox-D6XW7Y4V.js");await l(e.filter(o=>o!=="--detox"),t)}else await se(e,t)}function y(e,t){let n=e.indexOf(t);return n>=0?e[n+1]:void 0}function z(e,t){for(let n of t){let c=y(e,n);if(c)return c}}var F=new Set(["--reporter","-t","--testNamePattern","--grep","--origin","--preview-origin","--public-origin","--preview-public-origin","--owner","--repo","--url","--driver","--sim","--device","--slow","--tail-wait","--remap","--replace"]);function G(e){for(let t=0;t<e.length;t++){let n=e[t];if(n){if(F.has(n)){t+=1;continue}if(!n.startsWith("-"))return g(n)||/\.(?:[cm]?[jt]sx?|ya?ml|json)$/i.test(n)||!/\s/.test(n)&&n.length<16?null:{prompt:n,index:t}}}return null}function K(e,t){return e.filter((n,c)=>c!==t)}async function X(e,t){let n=K(e,t),c=S(n,{stripBooleanFlags:["--parallel","--watch","--flows","--detox","--new","--headless","--headed","--electron","--preview-open"],stripValueFlags:[...F]}),a=v(c);try{let i=await x(a,{describe:!0,verbose:!1,filter:"",compact:!0,hideXy:!0}),r=[i.shell?`shell: ${JSON.stringify(i.shell)}`:"",i.keyboard?`keyboard: ${JSON.stringify(i.keyboard)}`:"",typeof i.nodeCount=="number"?`nodes: ${i.nodeCount}`:"",i.tree||""].filter(Boolean).join(`
36
- `);return r.length>A?r.slice(0,A)+`
37
- [tree truncated]`:r}finally{a.close()}}var q=`
38
- You write SootSim YAML flows from a user's natural-language test goal.
39
- Return only JSON, no markdown, with this exact shape:
40
- {"summary":"short human-readable result goal","steps":[{"waitFor":{"text":"...","timeout":10000}}]}
41
-
42
- Use only these flow commands:
43
- - waitFor: { "text"?: string, "id"?: string, "timeout"?: number }
44
- - assertVisible: string or { "text"?: string, "id"?: string }
45
- - assertNotVisible: string or { "text"?: string, "id"?: string }
46
- - tapOn: string or { "text"?: string, "id"?: string, "index"?: number }
47
- - inputText: string
48
- - pressKey: string
49
- - hideKeyboard: true
50
- - swipe: { "direction": "UP" | "DOWN" | "LEFT" | "RIGHT", "duration"?: number }
51
- - waitForAnimationToEnd: true
52
- - takeScreenshot: "label"
53
-
54
- Prefer ids/testIDs from the tree. Prefer visible text when no id exists.
55
- Do not invent app-specific data that is not visible. Keep the flow short.
56
- End with an assertion or wait that proves the user's goal.
57
- `.trim();function Q(e,t){return`
58
- User goal:
59
- ${e}
60
-
61
- Current SootSim visible tree:
62
- ${t||"[no tree available]"}
63
-
64
- Generate the shortest replayable flow that verifies the goal from this current state.
65
- `.trim()}function M(e){return typeof e=="string"?e:Array.isArray(e)?e.map(t=>t&&typeof t=="object"&&t.type==="text"?String(t.text??""):"").join(""):""}async function Z(e){let t=await fetch(`${e.origin.replace(/\/$/,"")}/api/llm`,{method:"POST",headers:{"content-type":"application/json",authorization:e.authHeader},body:JSON.stringify({modelId:H,context:{systemPrompt:q,messages:[{role:"user",content:Q(e.prompt,e.describe),timestamp:Date.now()}]},options:{maxTokens:1800,temperature:.2},threadId:null}),signal:AbortSignal.timeout(W)});if(!t.ok||!t.body){let o=await t.text().catch(()=>"");throw new Error(`/api/llm ${t.status}${o?`: ${o}`:""}`)}let n=t.body.getReader(),c=new TextDecoder,a=[],i="",u="",r=o=>{let p=o.trim();if(!p)return;let s=JSON.parse(p);if(s.type==="text_delta"&&s.delta)a.push(s.delta);else if(s.type==="text_end"&&s.content)i=s.content;else if(s.type==="done")i=M(s.message?.content);else if(s.type==="error")throw new Error(s.error?.errorMessage||M(s.error?.content)||"llm error")};for(;;){let{value:o,done:p}=await n.read();if(p)break;u+=c.decode(o,{stream:!0});let s;for(;(s=u.indexOf(`
66
- `))>=0;)r(u.slice(0,s)),u=u.slice(s+1)}u+=c.decode(),u.trim()&&r(u);let l=(i||a.join("")).trim();if(!l)throw new Error("llm returned an empty test flow");return l}function ee(e){let t=e.match(/```(?:json)?\s*([\s\S]*?)```/i),n=t?t[1]:e.slice(e.indexOf("{"),e.lastIndexOf("}")+1);return JSON.parse(n)}function E(e){return!!(e&&typeof e=="object"&&!Array.isArray(e))}function te(e){let t=ee(e);if(!E(t))throw new Error("llm did not return a JSON object");let n=typeof t.summary=="string"&&t.summary.trim()?t.summary.trim().slice(0,500):"SootSim generated test",c=t.steps;if(!Array.isArray(c)||c.length===0)throw new Error("llm returned no flow steps");let a=c.map((i,u)=>{if(!E(i))throw new Error(`flow step ${u+1} is not an object`);return i});return{summary:n,steps:a,yamlText:T.stringify(a)}}function ne(e,t,n,c){let a=[n,"--preview","--preview-origin",c,"--billing-kind","test_run"],i=new Set(["--public-origin","--preview-public-origin","--owner","--repo","--url","--driver","--sim","--device","--slow","--tail-wait","--remap","--replace"]),u=new Set(["--new","--headless","--headed","--electron","--preview-open","--no-shell","--shell-only"]);for(let r=0;r<e.length;r++){if(r===t)continue;let l=e[r];if(l==="--origin"||l==="--preview-origin"){r+=1;continue}if(l==="--public-origin"){let o=e[r+1];o&&a.push("--preview-public-origin",o),r+=1;continue}if(i.has(l)){let o=e[r+1];o&&a.push(l,o),r+=1;continue}u.has(l)&&a.push(l)}return a}async function re(e){let t=await fetch(`${e.origin.replace(/\/$/,"")}/api/sootsim/test-runs`,{method:"POST",headers:{"content-type":"application/json",authorization:e.authHeader},body:JSON.stringify({prompt:e.prompt,summary:e.summary,status:e.status,failureMessage:e.failureMessage,previewShareId:e.previewShareId,owner:e.owner,repo:e.repo,durationMs:e.durationMs,flowYamlSizeBytes:e.flowYamlSizeBytes})});if(!t.ok){let a=await t.text().catch(()=>"");throw new Error(`/api/sootsim/test-runs ${t.status}${a?`: ${a}`:""}`)}let n=await t.json(),c=n.run?.id;if(!c)throw new Error("test run registration returned no id");return{id:c,traceUrl:n.run?.traceUrl??null,previewUrl:n.run?.previewUrl??null}}async function oe(e,t,n,c){let a=O();(!a||a.kind==="github")&&(console.error(' sootsim test "<prompt>" needs a login or SOOTSIM_API_KEY.'),console.error(" API keys must include preview_upload and llm scopes."),process.exit(1));let i=k(a),u=await P(z(n,["--origin","--preview-origin"]));console.log(" inspecting current sim\u2026");let r=await X(n,t);console.log(" generating flow\u2026");let l=await Z({prompt:e,describe:r,origin:u,authHeader:i}),o=te(l),p=R(_(J(),"sootsim-test-")),s=_(p,"flow.yaml");C(s,o.yamlText),console.log(` flow: ${s}`);let U=Date.now(),{runFlowPlayback:D,getLastFlowPreviewUploadResult:N}=await import("./flow-DDDWJ4KD.js"),I=ne(n,t,s,u),b=await D(I),f=N(),m=b===0?"passed":"failed",L=y(n,"--owner"),j=y(n,"--repo"),w=await re({origin:u,authHeader:i,prompt:e,summary:o.summary,status:m,failureMessage:m==="failed"?"flow playback failed":void 0,previewShareId:f?.shareId??null,owner:L,repo:j,durationMs:Date.now()-U,flowYamlSizeBytes:Buffer.byteLength(o.yamlText,"utf8")});console.log(`
67
- result: ${m}`),console.log(` run: ${w.id}`),f?.previewUrl&&console.log(` preview: ${f.previewUrl}`),w.traceUrl&&console.log(` replay: ${w.traceUrl}`),process.exit(b)}async function se(e,t){let n=["playwright","test"],c=e.find((r,l)=>e[l-1]==="-t")||e.find((r,l)=>e[l-1]==="--testNamePattern")||e.find((r,l)=>e[l-1]==="--grep");c&&n.push("-g",c),e.includes("--watch")&&n.push("--ui");let a=new Set(["--flows","--detox","--parallel","--watch","--reporter","-t","--testNamePattern","--grep"]);for(let r=0;r<e.length;r++){if(a.has(e[r])){(e[r]==="-t"||e[r]==="--testNamePattern"||e[r]==="--grep"||e[r]==="--reporter")&&r++;continue}n.push(e[r])}console.log(` running: npx ${n.join(" ")}`);let i=B("npx",n,{cwd:h,stdio:"inherit",env:{...process.env}}),u=await new Promise(r=>{i.on("exit",l=>r(l||0))});process.exit(u)}async function ie(e,t,n,c){let a=["flows","test/flows","e2e/flows",".maestro","maestro"],i=[];for(let l of a){let o=d(h,l);if(g(o)){let p=$(o).filter(s=>s.endsWith(".yaml")||s.endsWith(".yml"));for(let s of p)i.push(d(o,s))}}for(let l of a){let o=d(process.cwd(),l);if(!o.startsWith(h)&&g(o)){let p=$(o).filter(s=>s.endsWith(".yaml")||s.endsWith(".yml"));for(let s of p)i.push(d(o,s))}}i.length===0&&(console.log(" no flow files found in flows/, test/flows/, or e2e/flows/"),process.exit(0)),console.log(` found ${i.length} flow(s)`);let u=0,r=0;for(let l of i)try{let{runFlowPlayback:o}=await import("./flow-DDDWJ4KD.js"),p=t.port?`http://localhost:${t.port}`:"http://localhost:5173";if(await o([l,"--url",p,"--new"])!==0){r++;continue}u++}catch{r++}console.log(`
68
- results: ${u} passed, ${r} failed (${i.length} total)
69
- `),process.exit(r>0?1:0)}export{he as runTest};
@@ -1,4 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as i}from"./chunk-AGSCFR4P.js";import"./chunk-3W273A5J.js";import"./chunk-HIWE4YYN.js";import"./chunk-AHJ2WJST.js";import"./chunk-CHVGDYQJ.js";import{H as e}from"./chunk-ORIISY22.js";import{a as s}from"./chunk-3RAGDMGM.js";import"./chunk-NIDV74L2.js";var r="https://registry.npmjs.org/sootsim/latest";async function g(n){let o=n.indexOf("--channel"),t=o>=0&&n[o+1]?n[o+1]:void 0;console.log(`sootsim upgrade
3
- `),await l(),console.log(`
4
- runtime:`),await i(["install",...t?["--channel",t]:[]],{})}async function l(){let n=s(),o=await c();if(!o){console.log(` cli: v${n} \u2014 couldn't reach the npm registry, skipped`);return}if(e(o,n)<=0){console.log(` cli: v${n} (latest)`);return}console.log(` cli: v${n} \u2192 v${o}`),console.log(" cli: use the latest CLI with:"),console.log(" sootsim@latest")}async function c(){try{let n=await fetch(r,{headers:{accept:"application/json"},signal:AbortSignal.timeout(8e3)});if(!n.ok)return null;let o=await n.json();return typeof o.version=="string"&&o.version?o.version:null}catch{return null}}export{g as runUpgrade};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.201 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a,b,c}from"./chunk-PMPQ57UY.js";import"./chunk-FPRZHFV2.js";import"./chunk-D6CTB275.js";import"./chunk-S2AQ4WGX.js";import"./chunk-HIWE4YYN.js";import"./chunk-YOPEO6SA.js";import"./chunk-QTI33DV5.js";import"./chunk-FITJFYBO.js";import"./chunk-AHJ2WJST.js";import"./chunk-CHVGDYQJ.js";import"./chunk-ORIISY22.js";import"./chunk-NIDV74L2.js";export{a as resolveDefaultUploadOrigin,b as resolvePublicPreviewOrigin,c as runUpload};