sootsim 0.1.296 → 0.1.298

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/detox/index.ts +380 -28
  2. package/detox/motion-change.cjs +109 -0
  3. package/detox/motion-change.d.cts +35 -0
  4. package/dist-cli/bin.js +5 -5
  5. package/dist-cli/chunks/{agent-3F4K6MQK.js → agent-U3HXOT6B.js} +2 -2
  6. package/dist-cli/chunks/{agent-wrapper-5LLVCQRZ.js → agent-wrapper-LJOPJUAN.js} +2 -2
  7. package/dist-cli/chunks/{app-fonts-LPTY7A5G.js → app-fonts-4EVLV2BX.js} +2 -2
  8. package/dist-cli/chunks/{assert-R2ZUXEMS.js → assert-NNKBTEO7.js} +2 -2
  9. package/dist-cli/chunks/{auth-CA62Q6IS.js → auth-SF2WR2K2.js} +2 -2
  10. package/dist-cli/chunks/{beta-4AXSOQLU.js → beta-SCLWNOHI.js} +2 -2
  11. package/dist-cli/chunks/{camera-Y4RBIONE.js → camera-TZJ2Z3TZ.js} +2 -2
  12. package/dist-cli/chunks/{chunk-MDBR4XTV.js → chunk-3R4W2SQB.js} +3 -3
  13. package/dist-cli/chunks/{chunk-X7B5KJ6N.js → chunk-4H3Y3TGT.js} +1 -1
  14. package/dist-cli/chunks/{chunk-4MDLYOEI.js → chunk-5VGTEGDT.js} +2 -2
  15. package/dist-cli/chunks/{chunk-F2MS365Z.js → chunk-6YIZZDG2.js} +2 -2
  16. package/dist-cli/chunks/{chunk-7V3JNANH.js → chunk-7ZOI5KJZ.js} +2 -2
  17. package/dist-cli/chunks/{chunk-QIDFGREY.js → chunk-AHMMVYVE.js} +1 -1
  18. package/dist-cli/chunks/{chunk-ZIBKGZJD.js → chunk-ANFOERLP.js} +1 -1
  19. package/dist-cli/chunks/{chunk-GG6XOQ5P.js → chunk-APNG6X2R.js} +2 -2
  20. package/dist-cli/chunks/{chunk-54TQ3ZGI.js → chunk-AT3NTZPP.js} +3 -3
  21. package/dist-cli/chunks/{chunk-CAAADPRH.js → chunk-BUM4RR4G.js} +2 -2
  22. package/dist-cli/chunks/{chunk-PEUWZRXA.js → chunk-CAEQ7AOI.js} +3 -3
  23. package/dist-cli/chunks/{chunk-C462URRQ.js → chunk-CK3MTK2D.js} +2 -2
  24. package/dist-cli/chunks/{chunk-NEARYPSC.js → chunk-CXSMZLI6.js} +3 -3
  25. package/dist-cli/chunks/{chunk-HSGS2U7P.js → chunk-EH4B5ZQ4.js} +2 -2
  26. package/dist-cli/chunks/{chunk-74FOMZP7.js → chunk-EYBWIEDO.js} +2 -2
  27. package/dist-cli/chunks/{chunk-B2FB6IYX.js → chunk-FTMTIUBK.js} +2 -2
  28. package/dist-cli/chunks/{chunk-FUPQIX4U.js → chunk-G3NMMKBL.js} +2 -2
  29. package/dist-cli/chunks/{chunk-MQ6QX46T.js → chunk-GM2TRB5Z.js} +2 -2
  30. package/dist-cli/chunks/{chunk-CS23DQB2.js → chunk-GR7USTCW.js} +2 -2
  31. package/dist-cli/chunks/{chunk-AUBP44CY.js → chunk-GXVG5LJG.js} +2 -2
  32. package/dist-cli/chunks/{chunk-7GQWF7HE.js → chunk-HAJSNFUY.js} +1 -1
  33. package/dist-cli/chunks/{chunk-222Y7NQA.js → chunk-HG63AS7K.js} +1 -1
  34. package/dist-cli/chunks/{chunk-LHUY46GE.js → chunk-HGSYUEQP.js} +2 -2
  35. package/dist-cli/chunks/{chunk-TIOPG6QZ.js → chunk-HOQD6TZN.js} +1 -1
  36. package/dist-cli/chunks/{chunk-CDCVWGNU.js → chunk-IBMZ7A6M.js} +2 -2
  37. package/dist-cli/chunks/{chunk-EISE56VD.js → chunk-IRMKZSWK.js} +2 -2
  38. package/dist-cli/chunks/{chunk-SUJSGGSS.js → chunk-J42Y66IM.js} +2 -2
  39. package/dist-cli/chunks/{chunk-FROCFR77.js → chunk-JM36NHNU.js} +1 -1
  40. package/dist-cli/chunks/{chunk-WJVBD2UL.js → chunk-KVYZYYSY.js} +1 -1
  41. package/dist-cli/chunks/{chunk-3PAZJSIG.js → chunk-MFMFNCBK.js} +2 -2
  42. package/dist-cli/chunks/{chunk-DUYKBCM7.js → chunk-MHCSRMK4.js} +1 -1
  43. package/dist-cli/chunks/{chunk-S5Z4HKWX.js → chunk-MTL4DPUK.js} +1 -1
  44. package/dist-cli/chunks/{chunk-ZPBIHFQW.js → chunk-NRC73NVF.js} +2 -2
  45. package/dist-cli/chunks/{chunk-6C24WCDK.js → chunk-PLEZAZTJ.js} +2 -2
  46. package/dist-cli/chunks/{chunk-IR3DUBRF.js → chunk-PYUI2PHI.js} +2 -2
  47. package/dist-cli/chunks/{chunk-R65XMN4D.js → chunk-QZCVY5BX.js} +1 -1
  48. package/dist-cli/chunks/{chunk-5FNPAMP4.js → chunk-SQR7NH32.js} +1 -1
  49. package/dist-cli/chunks/{chunk-XMVVZCIM.js → chunk-TB77ANGH.js} +2 -2
  50. package/dist-cli/chunks/chunk-THIBWEML.js +4 -0
  51. package/dist-cli/chunks/{chunk-N3FZRJT7.js → chunk-TTFO75GL.js} +1 -1
  52. package/dist-cli/chunks/{chunk-4NFGOGB7.js → chunk-U5TY536V.js} +2 -2
  53. package/dist-cli/chunks/{chunk-7YQXQSG7.js → chunk-UTUBMEFS.js} +1 -1
  54. package/dist-cli/chunks/{chunk-B2LDO55K.js → chunk-VFI23QUE.js} +1 -1
  55. package/dist-cli/chunks/{chunk-BVWIR4CD.js → chunk-WDKLXBQR.js} +1 -1
  56. package/dist-cli/chunks/{chunk-2XUKWIAL.js → chunk-WV6CBQYZ.js} +1 -1
  57. package/dist-cli/chunks/{chunk-2LQOLMRK.js → chunk-X3STT7I3.js} +1 -1
  58. package/dist-cli/chunks/{chunk-GG5PWLGC.js → chunk-XITV33JC.js} +2 -2
  59. package/dist-cli/chunks/{chunk-EJ6BAJKX.js → chunk-ZBETYTSD.js} +1 -1
  60. package/dist-cli/chunks/{chunk-G2SO5FPN.js → chunk-ZCHO67PX.js} +2 -2
  61. package/dist-cli/chunks/{cleanup-4RSKYPQ7.js → cleanup-YZPCNPWE.js} +2 -2
  62. package/dist-cli/chunks/{cli-version-VZFF7GGM.js → cli-version-ECA6G6JQ.js} +2 -2
  63. package/dist-cli/chunks/{compat-FABLN4UI.js → compat-OM7ODBMO.js} +2 -2
  64. package/dist-cli/chunks/{config-74J37LKC.js → config-ZIX6LPFG.js} +2 -2
  65. package/dist-cli/chunks/{control-VBK32Y7F.js → control-BY6UAMKN.js} +2 -2
  66. package/dist-cli/chunks/daemon-BQ2T53ZA.js +4 -0
  67. package/dist-cli/chunks/{debug-AOYA2CLR.js → debug-MVPFRIQA.js} +3 -3
  68. package/dist-cli/chunks/{desktop-G7XWDCTM.js → desktop-ZXH7NRHW.js} +3 -3
  69. package/dist-cli/chunks/{detox-QXJ3L2ZI.js → detox-4RZ5UER3.js} +2 -2
  70. package/dist-cli/chunks/{device-NZZ5ZT3Q.js → device-7R4BSFMS.js} +2 -2
  71. package/dist-cli/chunks/{diagnose-5CAEJ42M.js → diagnose-4JAFIXWD.js} +2 -2
  72. package/dist-cli/chunks/disk-cleanup-3E7GYLGW.js +4 -0
  73. package/dist-cli/chunks/drivers-2OW33GOL.js +4 -0
  74. package/dist-cli/chunks/{film-C25VRUNC.js → film-Q7KZIGBH.js} +3 -3
  75. package/dist-cli/chunks/flow-QTOSAQ5X.js +4 -0
  76. package/dist-cli/chunks/help-UE32FDEY.js +4 -0
  77. package/dist-cli/chunks/{hidden-runtime-alias-NSLIK6XT.js → hidden-runtime-alias-4ZUCLCVJ.js} +2 -2
  78. package/dist-cli/chunks/{home-paths-OC6JZXIM.js → home-paths-PVZPREN7.js} +2 -2
  79. package/dist-cli/chunks/inspect-QWR2U3DK.js +4 -0
  80. package/dist-cli/chunks/install-desktop-R2ZM5ZJH.js +4 -0
  81. package/dist-cli/chunks/{login-LB4TMM4T.js → login-UMP65DJG.js} +4 -4
  82. package/dist-cli/chunks/{logout-33IOBD4X.js → logout-F45WBVJV.js} +2 -2
  83. package/dist-cli/chunks/{maestro-EEVXX536.js → maestro-33RGEXP4.js} +3 -3
  84. package/dist-cli/chunks/{maestro-generate-ICPWVXGA.js → maestro-generate-5SLZMOMB.js} +3 -3
  85. package/dist-cli/chunks/{mode-6IXOZDK3.js → mode-LZORZYWP.js} +2 -2
  86. package/dist-cli/chunks/{optional-demo-registry-KBMC7SNI.js → optional-demo-registry-DIWO7DG5.js} +2 -2
  87. package/dist-cli/chunks/{perf-CTTJSAIJ.js → perf-VW3UTYCK.js} +2 -2
  88. package/dist-cli/chunks/{permissions-BFQS4DIY.js → permissions-7JIR6QPO.js} +2 -2
  89. package/dist-cli/chunks/{record-MJR5ELHE.js → record-A4MRKJ2E.js} +3 -3
  90. package/dist-cli/chunks/{report-issue-FMJRNDKH.js → report-issue-JUWTB66Z.js} +2 -2
  91. package/dist-cli/chunks/runtime-7LSOODXL.js +4 -0
  92. package/dist-cli/chunks/{screenshot-command-MGNASAXR.js → screenshot-command-6LROQJPJ.js} +3 -3
  93. package/dist-cli/chunks/{screenshots-capture-N6PANAVT.js → screenshots-capture-FMYE24EW.js} +2 -2
  94. package/dist-cli/chunks/{serve-RS6TIO7U.js → serve-64SRDCU5.js} +2 -2
  95. package/dist-cli/chunks/{setup-MFYGYR2F.js → setup-2CUXSDQH.js} +2 -2
  96. package/dist-cli/chunks/{skills-TD35LDKK.js → skills-IILJVUMS.js} +2 -2
  97. package/dist-cli/chunks/{storage-VCFXDJNO.js → storage-BUQQJAMD.js} +2 -2
  98. package/dist-cli/chunks/store-3Z2GSFGY.js +4 -0
  99. package/dist-cli/chunks/telemetry-2UMKQXU7.js +4 -0
  100. package/dist-cli/chunks/{timeline-UJCZAWJV.js → timeline-62RWUHSF.js} +2 -2
  101. package/dist-cli/chunks/{upgrade-S24NH4YG.js → upgrade-GHXDTO2A.js} +2 -2
  102. package/dist-cli/chunks/upload-H6RTYSLO.js +4 -0
  103. package/dist-cli/chunks/{version-IVPWT2ZQ.js → version-F4CKBMB6.js} +2 -2
  104. package/dist-cli/chunks/{web-6BMWAWJL.js → web-YM6GIFOU.js} +2 -2
  105. package/dist-cli/chunks/{what-happened-HRHB6ETL.js → what-happened-UKBSKIGA.js} +2 -2
  106. package/dist-lib/agent-daemon-client.cjs +1 -1
  107. package/dist-lib/agent-events.cjs +1 -1
  108. package/dist-lib/agent-identity.cjs +1 -1
  109. package/dist-lib/agent-sessions.cjs +1 -1
  110. package/dist-lib/attached-projects.cjs +1 -1
  111. package/dist-lib/auth/shared-session.cjs +1 -1
  112. package/dist-lib/backend-origin.cjs +1 -1
  113. package/dist-lib/beta.cjs +1 -1
  114. package/dist-lib/beta.mjs +1 -1
  115. package/dist-lib/bridge-constants.cjs +6 -1
  116. package/dist-lib/cli-constants.cjs +1 -1
  117. package/dist-lib/config.cjs +1 -1
  118. package/dist-lib/detox/index.cjs +312 -27
  119. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  120. package/dist-lib/home-paths.cjs +1 -1
  121. package/dist-lib/host/bridge-host.cjs +1 -1
  122. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  123. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  124. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  125. package/dist-lib/host/websocket-proxy.cjs +1 -1
  126. package/dist-lib/index.cjs +1 -1
  127. package/dist-lib/jump-to-source-babel.cjs +1 -1
  128. package/dist-lib/menu.cjs +1 -1
  129. package/dist-lib/menu.mjs +1 -1
  130. package/dist-lib/metro.cjs +1 -1
  131. package/dist-lib/profiles.cjs +1 -1
  132. package/dist-lib/render-mode.cjs +1 -1
  133. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  134. package/dist-lib/sdk.cjs +1 -1
  135. package/dist-lib/sdk.mjs +1 -1
  136. package/dist-lib/skills.cjs +15 -11
  137. package/dist-lib/vite.cjs +1 -1
  138. package/package.json +1 -1
  139. package/src/bridge-constants.ts +9 -0
  140. package/dist-cli/chunks/chunk-P7HBJBHH.js +0 -4
  141. package/dist-cli/chunks/daemon-VFO7LRR4.js +0 -4
  142. package/dist-cli/chunks/disk-cleanup-GZSUZJ6Y.js +0 -4
  143. package/dist-cli/chunks/drivers-OLJCUSPO.js +0 -4
  144. package/dist-cli/chunks/flow-IW3WUF5D.js +0 -4
  145. package/dist-cli/chunks/help-GEVJF2DA.js +0 -4
  146. package/dist-cli/chunks/inspect-KXJNI7CU.js +0 -4
  147. package/dist-cli/chunks/install-desktop-IYWPRFPB.js +0 -4
  148. package/dist-cli/chunks/runtime-PV4HH2OB.js +0 -4
  149. package/dist-cli/chunks/store-VNSTKYBQ.js +0 -4
  150. package/dist-cli/chunks/telemetry-GPNHSYFF.js +0 -4
  151. package/dist-cli/chunks/upload-CCKAO2M5.js +0 -4
package/detox/index.ts CHANGED
@@ -15,6 +15,12 @@ import {
15
15
  waitForSootsimGestureHandler,
16
16
  } from './gestures'
17
17
  import { by, type Matcher } from './matchers'
18
+ import {
19
+ MOTION_CHANGE_DEFINITION,
20
+ measureMotionChange,
21
+ type MotionChangeDefinition,
22
+ type MotionChangeMeasurement,
23
+ } from './motion-change.cjs'
18
24
  import type { SootSimElement } from './element-types'
19
25
  import type { Browser, BrowserContext, Page } from 'playwright'
20
26
  import type { SootsimEventMap, SootsimEventName } from 'sootsim-engine/sootsim-event'
@@ -554,6 +560,32 @@ function decodePngDataUrl(dataUrl: string): Buffer {
554
560
  return Buffer.from(match[1], 'base64')
555
561
  }
556
562
 
563
+ type FastScreenshotData = {
564
+ label: string
565
+ offsetMs: number
566
+ actualMs: number
567
+ dataUrl: string
568
+ frameIndex?: number
569
+ virtualTimestampMs?: number
570
+ }
571
+
572
+ function writeFastScreenshotFrames<T extends FastScreenshotData>(
573
+ namePrefix: string,
574
+ frames: T[],
575
+ ): Array<Omit<T, 'dataUrl'> & { path: string }> {
576
+ ensureScreenshotDir()
577
+ return frames.map((frame) => {
578
+ const safeName = `${namePrefix}-${frame.label}`.replace(/[^a-zA-Z0-9._-]+/g, '-')
579
+ const filePath = path.join(SCREENSHOT_DIR, `${safeName}.png`)
580
+ fs.writeFileSync(filePath, decodePngDataUrl(frame.dataUrl))
581
+ const { dataUrl: _dataUrl, ...metadata } = frame
582
+ return {
583
+ ...metadata,
584
+ path: filePath,
585
+ }
586
+ })
587
+ }
588
+
557
589
  // single-shot canvas capture — no frame-stability poll. used for multi-stage
558
590
  // animation captures where we explicitly want to sample mid-transition. the
559
591
  // usual captureSootsimPng polls until two consecutive frames are byte-equal
@@ -1347,10 +1379,352 @@ export const device = {
1347
1379
  await dispatchSootsimEventInPage('sootsim:menuAnimOverride', { value: progress })
1348
1380
  },
1349
1381
 
1350
- async setBottomTabAnimOverride(
1351
- value: { elapsedMs: number; fromIndex: number; toIndex: number } | null,
1352
- ): Promise<void> {
1353
- await dispatchSootsimEventInPage('sootsim:bottomTabAnimOverride', { value })
1382
+ // park every render clock before the interaction, detect the first changed
1383
+ // stepped frame inside the named component, then advance exact 60fps frames
1384
+ // to each requested offset. native extraction also chooses the first frame
1385
+ // at or after an offset, so both sides use the same rounding convention.
1386
+ async takeScreenshotStagesFromVisibleChangeFast(
1387
+ namePrefix: string,
1388
+ stages: Array<{ label: string; offsetMs: number }>,
1389
+ options: {
1390
+ detectionTimeoutMs?: number
1391
+ frame?: { height: number; width: number; x: number; y: number } | null
1392
+ rootFrame?: { height: number; width: number; x: number; y: number } | null
1393
+ trigger: () => unknown
1394
+ },
1395
+ ): Promise<{
1396
+ frames: Array<{
1397
+ label: string
1398
+ offsetMs: number
1399
+ actualMs: number
1400
+ path: string
1401
+ frameIndex: number
1402
+ virtualTimestampMs: number
1403
+ }>
1404
+ triggerCompletedMs: number
1405
+ triggerStartMs: number
1406
+ visibleChangeDetection: {
1407
+ actualMs: number
1408
+ changedPixelPercent: number
1409
+ changedPixels: number
1410
+ channelThreshold: number
1411
+ frame: { height: number; width: number; x: number; y: number } | null
1412
+ minimumChangePercent: number
1413
+ rootFrame: { height: number; width: number; x: number; y: number } | null
1414
+ totalPixels: number
1415
+ frameIndex: number
1416
+ virtualTimestampMs: number
1417
+ }
1418
+ }> {
1419
+ const page = getPage()
1420
+ const token = `motion-${process.pid}-${Date.now()}-${Math.random()}`
1421
+ await page.evaluate(measureMotionChange, {
1422
+ definition: MOTION_CHANGE_DEFINITION,
1423
+ installGlobal: true,
1424
+ })
1425
+ const capturePromise: Promise<{
1426
+ frames: Array<
1427
+ FastScreenshotData & {
1428
+ frameIndex: number
1429
+ virtualTimestampMs: number
1430
+ }
1431
+ >
1432
+ visibleChangeDetection: {
1433
+ actualMs: number
1434
+ changedPixelPercent: number
1435
+ changedPixels: number
1436
+ channelThreshold: number
1437
+ frameIndex: number
1438
+ minimumChangePercent: number
1439
+ totalPixels: number
1440
+ virtualTimestampMs: number
1441
+ }
1442
+ }> = page.evaluate(
1443
+ async (input) => {
1444
+ type FrameReceipt = { frameIndex: number; virtualTimestampMs: number }
1445
+ type CaptureState = {
1446
+ cancelled: boolean
1447
+ ready: boolean
1448
+ triggerCompleted: boolean
1449
+ }
1450
+ type CaptureWindow = Window & {
1451
+ __sootsimMeasureMotionChange?: (input: {
1452
+ baseline: Uint8ClampedArray
1453
+ candidate: Uint8ClampedArray
1454
+ definition: MotionChangeDefinition
1455
+ height: number
1456
+ label: string
1457
+ width: number
1458
+ }) => MotionChangeMeasurement
1459
+ __sootsimVisibleChangeCaptures?: Record<string, CaptureState>
1460
+ SootSim?: {
1461
+ bridges?: {
1462
+ liveComposite?: {
1463
+ captureFresh: () => Promise<{
1464
+ canvas: HTMLCanvasElement | null
1465
+ frameToken: number
1466
+ }>
1467
+ }
1468
+ lockstepCapture?: {
1469
+ begin: (fps: number) => Promise<{
1470
+ stepFrame: () => Promise<FrameReceipt>
1471
+ stepToOffset: (
1472
+ anchor: FrameReceipt,
1473
+ offsetMs: number,
1474
+ ) => Promise<FrameReceipt>
1475
+ end: () => void
1476
+ }>
1477
+ }
1478
+ }
1479
+ }
1480
+ }
1481
+ type Snapshot = {
1482
+ actualMs: number
1483
+ bitmap: ImageBitmap
1484
+ label: string
1485
+ offsetMs: number
1486
+ frameIndex: number
1487
+ virtualTimestampMs: number
1488
+ }
1489
+
1490
+ const captureWindow = window as unknown as CaptureWindow
1491
+ const live = captureWindow.SootSim?.bridges?.liveComposite
1492
+ const lockstep = captureWindow.SootSim?.bridges?.lockstepCapture
1493
+ if (!live || typeof live.captureFresh !== 'function') {
1494
+ throw new Error('SootSim live composite bridge is not installed')
1495
+ }
1496
+ if (!lockstep || typeof lockstep.begin !== 'function') {
1497
+ throw new Error('SootSim lockstep capture bridge is not installed')
1498
+ }
1499
+ const registry =
1500
+ captureWindow.__sootsimVisibleChangeCaptures ??
1501
+ (captureWindow.__sootsimVisibleChangeCaptures = {})
1502
+ const state: CaptureState = {
1503
+ cancelled: false,
1504
+ ready: false,
1505
+ triggerCompleted: false,
1506
+ }
1507
+ registry[input.token] = state
1508
+ const measureChange = captureWindow.__sootsimMeasureMotionChange
1509
+ if (!measureChange) {
1510
+ throw new Error('SootSim motion change definition is not installed')
1511
+ }
1512
+
1513
+ const captureBitmap = async () => {
1514
+ const { canvas } = await live.captureFresh()
1515
+ if (!canvas) throw new Error('SootSim live composite produced no canvas')
1516
+ return createImageBitmap(canvas)
1517
+ }
1518
+ const boundsFor = (bitmap: ImageBitmap) => {
1519
+ const scale =
1520
+ input.rootFrame && input.rootFrame.width > 0
1521
+ ? bitmap.width / input.rootFrame.width
1522
+ : 1
1523
+ return {
1524
+ height: input.frame
1525
+ ? Math.max(
1526
+ 1,
1527
+ Math.min(
1528
+ bitmap.height,
1529
+ Math.ceil((input.frame.y + input.frame.height) * scale),
1530
+ ) - Math.max(0, Math.floor(input.frame.y * scale)),
1531
+ )
1532
+ : bitmap.height,
1533
+ left: input.frame ? Math.max(0, Math.floor(input.frame.x * scale)) : 0,
1534
+ top: input.frame ? Math.max(0, Math.floor(input.frame.y * scale)) : 0,
1535
+ width: input.frame
1536
+ ? Math.max(
1537
+ 1,
1538
+ Math.min(
1539
+ bitmap.width,
1540
+ Math.ceil((input.frame.x + input.frame.width) * scale),
1541
+ ) - Math.max(0, Math.floor(input.frame.x * scale)),
1542
+ )
1543
+ : bitmap.width,
1544
+ }
1545
+ }
1546
+ const regionPixels = (bitmap: ImageBitmap) => {
1547
+ const bounds = boundsFor(bitmap)
1548
+ const canvas = document.createElement('canvas')
1549
+ canvas.width = bounds.width
1550
+ canvas.height = bounds.height
1551
+ const context = canvas.getContext('2d', { willReadFrequently: true })
1552
+ if (!context) throw new Error('SootSim motion detector needs a 2d canvas')
1553
+ context.drawImage(
1554
+ bitmap,
1555
+ bounds.left,
1556
+ bounds.top,
1557
+ bounds.width,
1558
+ bounds.height,
1559
+ 0,
1560
+ 0,
1561
+ bounds.width,
1562
+ bounds.height,
1563
+ )
1564
+ return context.getImageData(0, 0, bounds.width, bounds.height).data
1565
+ }
1566
+ const encode = (snapshot: Snapshot) => {
1567
+ const canvas = document.createElement('canvas')
1568
+ canvas.width = snapshot.bitmap.width
1569
+ canvas.height = snapshot.bitmap.height
1570
+ const context = canvas.getContext('2d')
1571
+ if (!context) throw new Error('SootSim motion capture needs a 2d canvas')
1572
+ context.drawImage(snapshot.bitmap, 0, 0)
1573
+ snapshot.bitmap.close()
1574
+ return {
1575
+ actualMs: snapshot.actualMs,
1576
+ dataUrl: canvas.toDataURL('image/png'),
1577
+ label: snapshot.label,
1578
+ offsetMs: snapshot.offsetMs,
1579
+ frameIndex: snapshot.frameIndex,
1580
+ virtualTimestampMs: snapshot.virtualTimestampMs,
1581
+ }
1582
+ }
1583
+
1584
+ const controller = await lockstep.begin(60)
1585
+ try {
1586
+ const baselineBitmap = await captureBitmap()
1587
+ const baseline = regionPixels(baselineBitmap)
1588
+ baselineBitmap.close()
1589
+ state.ready = true
1590
+ while (!state.triggerCompleted) {
1591
+ if (state.cancelled) throw new Error('SootSim motion capture was cancelled')
1592
+ await new Promise((resolve) => setTimeout(resolve, 0))
1593
+ }
1594
+
1595
+ let visibleStart: FrameReceipt | null = null
1596
+ let detectedMeasurement: MotionChangeMeasurement | null = null
1597
+ const detectionFrameLimit = Math.ceil((input.detectionTimeoutMs * 60) / 1000)
1598
+ for (let frame = 0; frame < detectionFrameLimit; frame++) {
1599
+ if (state.cancelled) throw new Error('SootSim motion capture was cancelled')
1600
+ const receipt = await controller.stepFrame()
1601
+ const candidateBitmap = await captureBitmap()
1602
+ const bounds = boundsFor(candidateBitmap)
1603
+ const measurement = measureChange({
1604
+ baseline,
1605
+ candidate: regionPixels(candidateBitmap),
1606
+ definition: input.motionChangeDefinition,
1607
+ height: bounds.height,
1608
+ label: 'SootSim visible-change detector',
1609
+ width: bounds.width,
1610
+ })
1611
+ candidateBitmap.close()
1612
+ if (measurement.passedMinimum) {
1613
+ visibleStart = receipt
1614
+ detectedMeasurement = measurement
1615
+ break
1616
+ }
1617
+ }
1618
+ if (!visibleStart || !detectedMeasurement) {
1619
+ throw new Error(
1620
+ `SootSim motion had no visible change before ${input.detectionTimeoutMs}ms`,
1621
+ )
1622
+ }
1623
+
1624
+ const snapshots: Snapshot[] = []
1625
+ for (const stage of input.stages) {
1626
+ if (state.cancelled) throw new Error('SootSim motion capture was cancelled')
1627
+ const receipt = await controller.stepToOffset(visibleStart, stage.offsetMs)
1628
+ snapshots.push({
1629
+ actualMs: receipt.virtualTimestampMs - visibleStart.virtualTimestampMs,
1630
+ bitmap: await captureBitmap(),
1631
+ frameIndex: receipt.frameIndex,
1632
+ label: stage.label,
1633
+ offsetMs: stage.offsetMs,
1634
+ virtualTimestampMs: receipt.virtualTimestampMs,
1635
+ })
1636
+ }
1637
+ return {
1638
+ frames: snapshots.map(encode),
1639
+ visibleChangeDetection: {
1640
+ actualMs: (visibleStart.frameIndex * 1000) / 60,
1641
+ changedPixelPercent: detectedMeasurement.changePercent,
1642
+ changedPixels: detectedMeasurement.changePixels,
1643
+ channelThreshold: input.motionChangeDefinition.channelThreshold,
1644
+ minimumChangePercent: input.motionChangeDefinition.minimumChangePercent,
1645
+ totalPixels: detectedMeasurement.totalPixels,
1646
+ frameIndex: visibleStart.frameIndex,
1647
+ virtualTimestampMs: visibleStart.virtualTimestampMs,
1648
+ },
1649
+ }
1650
+ } finally {
1651
+ controller.end()
1652
+ delete registry[input.token]
1653
+ }
1654
+ },
1655
+ {
1656
+ detectionTimeoutMs: options.detectionTimeoutMs ?? 3000,
1657
+ frame: options.frame ?? null,
1658
+ motionChangeDefinition: MOTION_CHANGE_DEFINITION,
1659
+ rootFrame: options.rootFrame ?? null,
1660
+ stages,
1661
+ token,
1662
+ },
1663
+ )
1664
+
1665
+ await page.waitForFunction(
1666
+ (captureToken) => {
1667
+ const registry = (
1668
+ window as Window & {
1669
+ __sootsimVisibleChangeCaptures?: Record<
1670
+ string,
1671
+ { cancelled: boolean; ready: boolean; triggerCompleted: boolean }
1672
+ >
1673
+ }
1674
+ ).__sootsimVisibleChangeCaptures
1675
+ return registry?.[captureToken]?.ready === true
1676
+ },
1677
+ token,
1678
+ { timeout: 5000 },
1679
+ )
1680
+
1681
+ const triggerStartMs = Date.now()
1682
+ let triggerCompletedMs = 0
1683
+ try {
1684
+ await Promise.resolve(options.trigger())
1685
+ triggerCompletedMs = Date.now()
1686
+ await page.evaluate((captureToken) => {
1687
+ const registry = (
1688
+ window as Window & {
1689
+ __sootsimVisibleChangeCaptures?: Record<
1690
+ string,
1691
+ { cancelled: boolean; ready: boolean; triggerCompleted: boolean }
1692
+ >
1693
+ }
1694
+ ).__sootsimVisibleChangeCaptures
1695
+ const capture = registry?.[captureToken]
1696
+ if (!capture) throw new Error('SootSim motion capture disarmed before trigger')
1697
+ capture.triggerCompleted = true
1698
+ }, token)
1699
+ const capture = await capturePromise
1700
+ return {
1701
+ frames: writeFastScreenshotFrames(namePrefix, capture.frames),
1702
+ triggerCompletedMs,
1703
+ triggerStartMs,
1704
+ visibleChangeDetection: {
1705
+ ...capture.visibleChangeDetection,
1706
+ frame: options.frame ?? null,
1707
+ rootFrame: options.rootFrame ?? null,
1708
+ },
1709
+ }
1710
+ } catch (error) {
1711
+ await page
1712
+ .evaluate((captureToken) => {
1713
+ const registry = (
1714
+ window as Window & {
1715
+ __sootsimVisibleChangeCaptures?: Record<
1716
+ string,
1717
+ { cancelled: boolean; ready: boolean; triggerCompleted: boolean }
1718
+ >
1719
+ }
1720
+ ).__sootsimVisibleChangeCaptures
1721
+ const capture = registry?.[captureToken]
1722
+ if (capture) capture.cancelled = true
1723
+ }, token)
1724
+ .catch(() => {})
1725
+ await capturePromise.catch(() => {})
1726
+ throw error
1727
+ }
1354
1728
  },
1355
1729
 
1356
1730
  // FAST multi-stage capture for sub-second animation diffs. each stage
@@ -1490,18 +1864,7 @@ export const device = {
1490
1864
  },
1491
1865
  { stages, skipWarmup: options?.skipWarmup === true },
1492
1866
  )
1493
- ensureScreenshotDir()
1494
- return frames.map((f) => {
1495
- const safeName = `${namePrefix}-${f.label}`.replace(/[^a-zA-Z0-9._-]+/g, '-')
1496
- const filePath = path.join(SCREENSHOT_DIR, `${safeName}.png`)
1497
- fs.writeFileSync(filePath, decodePngDataUrl(f.dataUrl))
1498
- return {
1499
- label: f.label,
1500
- offsetMs: f.offsetMs,
1501
- actualMs: f.actualMs,
1502
- path: filePath,
1503
- }
1504
- })
1867
+ return writeFastScreenshotFrames(namePrefix, frames)
1505
1868
  },
1506
1869
 
1507
1870
  // batched multi-stage capture for animation diffs. all N captures happen
@@ -1549,18 +1912,7 @@ export const device = {
1549
1912
  },
1550
1913
  { stages },
1551
1914
  )
1552
- ensureScreenshotDir()
1553
- return frames.map((f) => {
1554
- const safeName = `${namePrefix}-${f.label}`.replace(/[^a-zA-Z0-9._-]+/g, '-')
1555
- const filePath = path.join(SCREENSHOT_DIR, `${safeName}.png`)
1556
- fs.writeFileSync(filePath, decodePngDataUrl(f.dataUrl))
1557
- return {
1558
- label: f.label,
1559
- offsetMs: f.offsetMs,
1560
- actualMs: f.actualMs,
1561
- path: filePath,
1562
- }
1563
- })
1915
+ return writeFastScreenshotFrames(namePrefix, frames)
1564
1916
  },
1565
1917
 
1566
1918
  async shake() {
@@ -0,0 +1,109 @@
1
+ // native recordings and SootSim live frames must use this exact definition.
2
+ // retained static Android crops measured as high as 0.0331% after the channel
3
+ // filter. the smallest corrected component transition with visible motion was
4
+ // 0.1748%, so a 0.1% floor separates every measured static and moving frame.
5
+ const MOTION_CHANGE_DEFINITION = Object.freeze({
6
+ channelThreshold: 24,
7
+ minimumChangePercent: 0.1,
8
+ })
9
+
10
+ function measureMotionChange(input) {
11
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
12
+ throw new Error('motion change measurement requires an input object')
13
+ }
14
+ if (input.installGlobal === true) {
15
+ globalThis.__sootsimMeasureMotionChange = measureMotionChange
16
+ return null
17
+ }
18
+
19
+ const definition = input.definition
20
+ if (
21
+ !definition ||
22
+ typeof definition !== 'object' ||
23
+ !Number.isInteger(definition.channelThreshold) ||
24
+ definition.channelThreshold < 0 ||
25
+ definition.channelThreshold > 255 ||
26
+ !Number.isFinite(definition.minimumChangePercent) ||
27
+ definition.minimumChangePercent <= 0 ||
28
+ definition.minimumChangePercent >= 100
29
+ ) {
30
+ throw new Error(
31
+ 'motion change definition needs a channel threshold from 0 to 255 and a minimum greater than 0% and less than 100%',
32
+ )
33
+ }
34
+
35
+ const label =
36
+ typeof input.label === 'string' && input.label.length > 0
37
+ ? input.label
38
+ : 'motion change'
39
+ let changePixels = input.changePixels
40
+ let totalPixels = input.totalPixels
41
+ if (input.baseline !== undefined || input.candidate !== undefined) {
42
+ const baseline = input.baseline
43
+ const candidate = input.candidate
44
+ const width = input.width
45
+ const height = input.height
46
+ if (
47
+ !baseline ||
48
+ !candidate ||
49
+ typeof baseline.length !== 'number' ||
50
+ typeof candidate.length !== 'number' ||
51
+ !Number.isInteger(width) ||
52
+ !Number.isInteger(height) ||
53
+ width <= 0 ||
54
+ height <= 0
55
+ ) {
56
+ throw new Error(`${label} needs two RGBA pixel arrays and positive dimensions`)
57
+ }
58
+ totalPixels = width * height
59
+ const expectedBytes = totalPixels * 4
60
+ if (baseline.length !== expectedBytes || candidate.length !== expectedBytes) {
61
+ throw new Error(
62
+ `${label} pixel data has ${baseline.length}/${candidate.length} bytes; expected ${expectedBytes}`,
63
+ )
64
+ }
65
+ changePixels = 0
66
+ for (let offset = 0; offset < expectedBytes; offset += 4) {
67
+ if (
68
+ Math.abs(baseline[offset] - candidate[offset]) > definition.channelThreshold ||
69
+ Math.abs(baseline[offset + 1] - candidate[offset + 1]) >
70
+ definition.channelThreshold ||
71
+ Math.abs(baseline[offset + 2] - candidate[offset + 2]) >
72
+ definition.channelThreshold ||
73
+ Math.abs(baseline[offset + 3] - candidate[offset + 3]) >
74
+ definition.channelThreshold
75
+ ) {
76
+ changePixels++
77
+ }
78
+ }
79
+ }
80
+
81
+ if (!Number.isInteger(totalPixels) || totalPixels <= 0) {
82
+ throw new Error(`${label} has no positive measured region area`)
83
+ }
84
+ if (!Number.isInteger(changePixels) || changePixels < 0 || changePixels > totalPixels) {
85
+ throw new Error(
86
+ `${label} changed pixel count ${changePixels} is outside its ${totalPixels}px measured region`,
87
+ )
88
+ }
89
+ const changePercent = (changePixels / totalPixels) * 100
90
+ return {
91
+ changePercent,
92
+ changePixels,
93
+ passedMinimum: changePercent >= definition.minimumChangePercent,
94
+ totalPixels,
95
+ }
96
+ }
97
+
98
+ function measureProofMotionChange(input) {
99
+ return measureMotionChange({
100
+ ...input,
101
+ definition: MOTION_CHANGE_DEFINITION,
102
+ })
103
+ }
104
+
105
+ module.exports = {
106
+ MOTION_CHANGE_DEFINITION,
107
+ measureMotionChange,
108
+ measureProofMotionChange,
109
+ }
@@ -0,0 +1,35 @@
1
+ export type MotionChangeDefinition = {
2
+ channelThreshold: number
3
+ minimumChangePercent: number
4
+ }
5
+
6
+ type MotionChangePixels = ArrayLike<number>
7
+
8
+ export type MotionChangeInput = {
9
+ baseline?: MotionChangePixels
10
+ candidate?: MotionChangePixels
11
+ changePixels?: number
12
+ definition: MotionChangeDefinition
13
+ height?: number
14
+ installGlobal?: boolean
15
+ label?: string
16
+ totalPixels?: number
17
+ width?: number
18
+ }
19
+
20
+ export type MotionChangeMeasurement = {
21
+ changePercent: number
22
+ changePixels: number
23
+ passedMinimum: boolean
24
+ totalPixels: number
25
+ }
26
+
27
+ export const MOTION_CHANGE_DEFINITION: Readonly<MotionChangeDefinition>
28
+
29
+ export function measureMotionChange(
30
+ input: MotionChangeInput,
31
+ ): MotionChangeMeasurement | null
32
+
33
+ export function measureProofMotionChange(
34
+ input: Omit<MotionChangeInput, 'definition' | 'installGlobal'>,
35
+ ): MotionChangeMeasurement
package/dist-cli/bin.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- /*! sootsim v0.1.296 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
+ /*! sootsim v0.1.298 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
3
3
  import { createRequire as __sootsimCreateRequire } from 'node:module'
4
4
  var require = __sootsimCreateRequire(import.meta.url)
5
- import{a as w,b as g}from"./chunks/chunk-DUYKBCM7.js";import"./chunks/chunk-X7B5KJ6N.js";function b(t){let a=t instanceof Error?t.message:String(t);(/^command timed out after \d+s$/.test(a)||a.startsWith("sim disconnected:")||a.startsWith("bridge never reconnected")||a.startsWith("multiple sims are connected:")||a.startsWith("saved sim ")||a.startsWith("no sim connected with id ")||a.startsWith("could not connect to ws://")||a.startsWith("sootsim bridge daemon is not running")||a.startsWith("sootsim bridge lockfile is fresh")||a.startsWith("sootsim bridge exited before becoming ready")||a.startsWith("background daemon setup is unavailable")||a.startsWith("sootsim bridge did not start within"))&&!process.env.SOOTSIM_VERBOSE&&(process.stderr.write(` ${a}
5
+ import{a as w,b as g}from"./chunks/chunk-MHCSRMK4.js";import"./chunks/chunk-4H3Y3TGT.js";function b(t){let a=t instanceof Error?t.message:String(t);(/^command timed out after \d+s$/.test(a)||a.startsWith("sim disconnected:")||a.startsWith("bridge never reconnected")||a.startsWith("multiple sims are connected:")||a.startsWith("saved sim ")||a.startsWith("no sim connected with id ")||a.startsWith("could not connect to ws://")||a.startsWith("sootsim bridge daemon is not running")||a.startsWith("sootsim bridge lockfile is fresh")||a.startsWith("sootsim bridge exited before becoming ready")||a.startsWith("background daemon setup is unavailable")||a.startsWith("sootsim bridge did not start within"))&&!process.env.SOOTSIM_VERBOSE&&(process.stderr.write(` ${a}
6
6
  `),process.exit(1)),t instanceof Error&&t.stack?process.stderr.write(`${t.stack}
7
7
  `):process.stderr.write(`${a}
8
8
  `),process.exit(1)}process.on("unhandledRejection",b);process.on("uncaughtException",b);var A=typeof __SOOTSIM_STANDALONE__<"u"&&__SOOTSIM_STANDALONE__,k=new Set(["desktop"]);function S(t){!A||!k.has(t)||(process.stderr.write(` sootsim ${t} isn't available in the standalone binary \u2014
9
9
  it needs vite / electron / playwright from a project's node_modules.
10
10
  run the npm package instead:
11
11
  sootsim ${t}
12
- `),process.exit(1))}var e=g(process.argv);if(!e.command&&e.commandArgs.length>0){let{resolveHiddenRuntimeAlias:t}=await import("./chunks/hidden-runtime-alias-NSLIK6XT.js");e=t(e)}var _=e.command==="cleanup"&&e.commandArgs.length===1&&e.commandArgs[0]==="--automatic-worker";if(!_){let{trackCliEvent:t}=await import("./chunks/telemetry-GPNHSYFF.js");t({event:"cli_command_invoked",properties:{command:e.command||(e.version?"version":e.help?"help":"none"),arg_count:e.commandArgs.length,platform:process.platform,arch:process.arch,node_version:process.versions.node}})}if(e.command!=="serve"&&e.command!=="daemon"){let{consumeRuntimeUpgradeNotice:t}=await import("./chunks/home-paths-OC6JZXIM.js"),a=t();a&&process.stderr.write(` SootSim engine upgraded to v${a.to}`+(a.from?` (from v${a.from})`:"")+` \xB7 what's new: https://sootsim.com/changelog
13
- `)}var f=!1;async function v(){if(!f&&(f=!0,!(e.command==="cleanup"||e.command==="serve"||e.command==="agent-wrapper"||e.command==="daemon"&&e.commandArgs[0]==="uninstall")))try{let{isAutomaticSootsimCleanupComplete:t}=await import("./chunks/disk-cleanup-GZSUZJ6Y.js");if(t())return;let{spawn:a}=await import("node:child_process"),s=["cleanup","--automatic-worker"],m=process.argv[1],d=A?a(process.execPath,s,{detached:!0,stdio:["ignore","ignore","inherit"],env:process.env}):m?a(process.execPath,[m,...s],{detached:!0,stdio:["ignore","ignore","inherit"],env:process.env}):a("sootsim",s,{detached:!0,stdio:["ignore","ignore","inherit"],env:process.env});d.once("error",p=>{process.stderr.write(` SootSim automatic cleanup will retry on the next run: ${p.message}
12
+ `),process.exit(1))}var e=g(process.argv);if(!e.command&&e.commandArgs.length>0){let{resolveHiddenRuntimeAlias:t}=await import("./chunks/hidden-runtime-alias-4ZUCLCVJ.js");e=t(e)}var _=e.command==="cleanup"&&e.commandArgs.length===1&&e.commandArgs[0]==="--automatic-worker";if(!_){let{trackCliEvent:t}=await import("./chunks/telemetry-2UMKQXU7.js");t({event:"cli_command_invoked",properties:{command:e.command||(e.version?"version":e.help?"help":"none"),arg_count:e.commandArgs.length,platform:process.platform,arch:process.arch,node_version:process.versions.node}})}if(e.command!=="serve"&&e.command!=="daemon"){let{consumeRuntimeUpgradeNotice:t}=await import("./chunks/home-paths-PVZPREN7.js"),a=t();a&&process.stderr.write(` SootSim engine upgraded to v${a.to}`+(a.from?` (from v${a.from})`:"")+` \xB7 what's new: https://sootsim.com/changelog
13
+ `)}var f=!1;async function v(){if(!f&&(f=!0,!(e.command==="cleanup"||e.command==="serve"||e.command==="agent-wrapper"||e.command==="daemon"&&e.commandArgs[0]==="uninstall")))try{let{isAutomaticSootsimCleanupComplete:t}=await import("./chunks/disk-cleanup-3E7GYLGW.js");if(t())return;let{spawn:a}=await import("node:child_process"),s=["cleanup","--automatic-worker"],m=process.argv[1],d=A?a(process.execPath,s,{detached:!0,stdio:["ignore","ignore","inherit"],env:process.env}):m?a(process.execPath,[m,...s],{detached:!0,stdio:["ignore","ignore","inherit"],env:process.env}):a("sootsim",s,{detached:!0,stdio:["ignore","ignore","inherit"],env:process.env});d.once("error",p=>{process.stderr.write(` SootSim automatic cleanup will retry on the next run: ${p.message}
14
14
  `)}),d.unref()}catch(t){process.stderr.write(` SootSim automatic cleanup will retry on the next run: ${t instanceof Error?t.message:String(t)}
15
- `)}}async function n(t){let{flushCliTelemetry:a}=await import("./chunks/telemetry-GPNHSYFF.js");await a(),await v(),process.exit(t)}if(e.version){let{getCliVersion:t}=await import("./chunks/cli-version-VZFF7GGM.js"),{IS_BETA:a,BETA_LABEL:s}=await import("./chunks/beta-4AXSOQLU.js"),m=a?` \xB7 ${s}`:"";console.log(`sootsim v${t()}${m}`);let{readActiveRuntime:d}=await import("./chunks/home-paths-OC6JZXIM.js"),p=d();console.log(p?`runtime v${p}`:"runtime not installed"),await n(0)}if(e.help&&!e.command){let{printHelp:t}=await import("./chunks/help-GEVJF2DA.js");t(),await n(0)}var o=e.globalFlags.port,c=e.verbose,u=e.globalFlags.device,l=e.globalFlags.theme,C=e.globalFlags.driver,O=e.globalFlags.headless===!0,h=e.globalFlags.sim??e.globalFlags.session??e.globalFlags.tab,i=h?["--sim",h,...e.commandArgs]:e.commandArgs;if(u||l){let{settingsStore:t}=await import("./chunks/store-VNSTKYBQ.js"),a={};u&&(a.deviceModel=u),l&&(a.colorScheme=l),t.apply(a)}if(!e.command&&e.commandArgs.length===0){let{runSetup:t,shouldRunSetupForBareCommand:a}=await import("./chunks/setup-MFYGYR2F.js");a()&&(await t(["--from-welcome"]),await n(0));let{printHelp:s}=await import("./chunks/help-GEVJF2DA.js");s(),await n(0)}var r=e.command??"";if(!r){if(e.commandArgs.length>0){let a=e.commandArgs.find(s=>!s.startsWith("-"))??e.commandArgs[0];console.error(` unknown command: ${a}`),console.error(" run `sootsim --help` to see the full surface."),await n(1)}let{printHelp:t}=await import("./chunks/help-GEVJF2DA.js");t(),await n(0)}if(e.help||e.commandArgs.includes("--help")||e.commandArgs.includes("-h")){if(r==="skill"){let{runSkill:s}=await import("./chunks/skills-TD35LDKK.js");await s(e.commandArgs),await n(0)}let t=r==="do"||r==="get"||r==="debug"||r==="shell"||r==="perf"||r==="wait",a=e.commandArgs.find(s=>!s.startsWith("-"));if(!(r==="shell"||r==="perf"))if(t&&!a){let{printGroupHelp:s}=await import("./chunks/help-GEVJF2DA.js");s(r)&&await n(0)}else{let{printCommandHelp:s}=await import("./chunks/help-GEVJF2DA.js");s(t&&a?a:r,{prefer:t&&a?"verb":"command",group:t&&a?r:void 0}),await n(0)}}S(r);if(w.has(r)){let{runInspect:t}=await import("./chunks/inspect-KXJNI7CU.js");await t([r,...i],{port:o,verbose:c})}else switch(r){case"assert":{let{runAssert:t}=await import("./chunks/assert-R2ZUXEMS.js");await t(e.commandArgs);break}case"detox":{let{runDetox:t}=await import("./chunks/detox-QXJ3L2ZI.js");await t(e.commandArgs,{port:o,verbose:c});break}case"maestro":{let{runMaestro:t}=await import("./chunks/maestro-EEVXX536.js"),a=await t(i,{port:o,verbose:c});await n(typeof a=="number"?a:0)}case"record":{let{runRecord:t}=await import("./chunks/record-MJR5ELHE.js");await t(i,{port:o,verbose:c});break}case"film":{let{runFilm:t}=await import("./chunks/film-C25VRUNC.js"),a=await t(i,{port:o,verbose:c});await n(typeof a=="number"?a:0)}case"storage":{let{runStorage:t}=await import("./chunks/storage-VCFXDJNO.js"),a=await t(i,{port:o,verbose:c});await n(typeof a=="number"?a:0)}case"perf":{let{runPerf:t}=await import("./chunks/perf-CTTJSAIJ.js"),a=await t(i,{port:o,verbose:c});await n(typeof a=="number"?a:0)}case"screenshot":{let{runScreenshotCommand:t}=await import("./chunks/screenshot-command-MGNASAXR.js"),a=await t(i,{port:o,verbose:c});await n(typeof a=="number"?a:0)}case"mode":{let{runMode:t}=await import("./chunks/mode-6IXOZDK3.js");await t(i,{port:o,verbose:c});break}case"permissions":{let{runPermissions:t}=await import("./chunks/permissions-BFQS4DIY.js"),a=await t(i,{port:o,verbose:c});await n(a)}case"inspect":{let{runInspect:t}=await import("./chunks/inspect-KXJNI7CU.js");await t(i,{port:o,verbose:c});break}case"debug":{let{runDebug:t}=await import("./chunks/debug-AOYA2CLR.js");await t(i,{port:o,verbose:c});break}case"timeline":{let{runTimeline:t}=await import("./chunks/timeline-UJCZAWJV.js");await t(i,{port:o,verbose:c});break}case"what-happened":{let{runWhatHappened:t}=await import("./chunks/what-happened-HRHB6ETL.js");await t(i,{port:o,verbose:c});break}case"open":{let{runOpenCommand:t}=await import("./chunks/control-VBK32Y7F.js");await t(i,{port:o});break}case"use":{let{runUseCommand:t}=await import("./chunks/control-VBK32Y7F.js");await t(i,{port:o});break}case"claim":{let{runClaimCommand:t}=await import("./chunks/control-VBK32Y7F.js");await t(i,{port:o});break}case"close":{let{runCloseCommand:t}=await import("./chunks/control-VBK32Y7F.js");await t(i,{port:o});break}case"device":{let{runDeviceCommand:t}=await import("./chunks/device-NZZ5ZT3Q.js");await t(i,{port:o});break}case"compat":{let{runCompat:t}=await import("./chunks/compat-FABLN4UI.js");await t(e.commandArgs);break}case"report-issue":{let{runReportIssue:t}=await import("./chunks/report-issue-FMJRNDKH.js"),a=await t(e.commandArgs);await n(a)}case"desktop":{let{runDesktop:t}=await import("./chunks/desktop-G7XWDCTM.js");await t(e.commandArgs,{port:o,device:u,driver:C});break}case"login":{let{runLogin:t}=await import("./chunks/login-LB4TMM4T.js");await t(e.commandArgs);break}case"logout":{let{runLogout:t}=await import("./chunks/logout-33IOBD4X.js");await t();break}case"auth":{let{runAuth:t}=await import("./chunks/auth-CA62Q6IS.js");await t(e.commandArgs);break}case"setup":{let{runSetup:t}=await import("./chunks/setup-MFYGYR2F.js");await t(e.commandArgs);break}case"serve":{let{runServe:t}=await import("./chunks/serve-RS6TIO7U.js");await t(e.commandArgs,{port:o});break}case"daemon":{let{runDaemon:t}=await import("./chunks/daemon-VFO7LRR4.js");await t(e.commandArgs,{port:o});break}case"runtime":{let{runRuntime:t}=await import("./chunks/runtime-PV4HH2OB.js");await t(e.commandArgs);break}case"upgrade":case"update":{let{runUpgrade:t}=await import("./chunks/upgrade-S24NH4YG.js");await t(e.commandArgs);break}case"version":{let{runVersion:t}=await import("./chunks/version-IVPWT2ZQ.js");await t(e.commandArgs);break}case"agent":{let{runAgentCommand:t}=await import("./chunks/agent-3F4K6MQK.js"),a=await t(e.commandArgs);await n(a)}case"agent-wrapper":{let{runAgentWrapper:t}=await import("./chunks/agent-wrapper-5LLVCQRZ.js"),a=await t(e.commandArgs);await n(a)}case"skill":{let{runSkill:t}=await import("./chunks/skills-TD35LDKK.js");await t(e.commandArgs);break}case"app-fonts":{let{runAppFonts:t}=await import("./chunks/app-fonts-LPTY7A5G.js");await t(e.commandArgs);break}case"config":{let{runConfig:t}=await import("./chunks/config-74J37LKC.js");await t(e.commandArgs);break}case"cleanup":{let{runCleanup:t}=await import("./chunks/cleanup-4RSKYPQ7.js"),a=await t(e.commandArgs);await n(a)}default:console.error(` unknown command: ${r}`),console.error(" run `sootsim --help` to see the full surface."),await n(1)}await v();
15
+ `)}}async function n(t){let{flushCliTelemetry:a}=await import("./chunks/telemetry-2UMKQXU7.js");await a(),await v(),process.exit(t)}if(e.version){let{getCliVersion:t}=await import("./chunks/cli-version-ECA6G6JQ.js"),{IS_BETA:a,BETA_LABEL:s}=await import("./chunks/beta-SCLWNOHI.js"),m=a?` \xB7 ${s}`:"";console.log(`sootsim v${t()}${m}`);let{readActiveRuntime:d}=await import("./chunks/home-paths-PVZPREN7.js"),p=d();console.log(p?`runtime v${p}`:"runtime not installed"),await n(0)}if(e.help&&!e.command){let{printHelp:t}=await import("./chunks/help-UE32FDEY.js");t(),await n(0)}var o=e.globalFlags.port,c=e.verbose,u=e.globalFlags.device,l=e.globalFlags.theme,C=e.globalFlags.driver,O=e.globalFlags.headless===!0,h=e.globalFlags.sim??e.globalFlags.session??e.globalFlags.tab,i=h?["--sim",h,...e.commandArgs]:e.commandArgs;if(u||l){let{settingsStore:t}=await import("./chunks/store-3Z2GSFGY.js"),a={};u&&(a.deviceModel=u),l&&(a.colorScheme=l),t.apply(a)}if(!e.command&&e.commandArgs.length===0){let{runSetup:t,shouldRunSetupForBareCommand:a}=await import("./chunks/setup-2CUXSDQH.js");a()&&(await t(["--from-welcome"]),await n(0));let{printHelp:s}=await import("./chunks/help-UE32FDEY.js");s(),await n(0)}var r=e.command??"";if(!r){if(e.commandArgs.length>0){let a=e.commandArgs.find(s=>!s.startsWith("-"))??e.commandArgs[0];console.error(` unknown command: ${a}`),console.error(" run `sootsim --help` to see the full surface."),await n(1)}let{printHelp:t}=await import("./chunks/help-UE32FDEY.js");t(),await n(0)}if(e.help||e.commandArgs.includes("--help")||e.commandArgs.includes("-h")){if(r==="skill"){let{runSkill:s}=await import("./chunks/skills-IILJVUMS.js");await s(e.commandArgs),await n(0)}let t=r==="do"||r==="get"||r==="debug"||r==="shell"||r==="perf"||r==="wait",a=e.commandArgs.find(s=>!s.startsWith("-"));if(!(r==="shell"||r==="perf"))if(t&&!a){let{printGroupHelp:s}=await import("./chunks/help-UE32FDEY.js");s(r)&&await n(0)}else{let{printCommandHelp:s}=await import("./chunks/help-UE32FDEY.js");s(t&&a?a:r,{prefer:t&&a?"verb":"command",group:t&&a?r:void 0}),await n(0)}}S(r);if(w.has(r)){let{runInspect:t}=await import("./chunks/inspect-QWR2U3DK.js");await t([r,...i],{port:o,verbose:c})}else switch(r){case"assert":{let{runAssert:t}=await import("./chunks/assert-NNKBTEO7.js");await t(e.commandArgs);break}case"detox":{let{runDetox:t}=await import("./chunks/detox-4RZ5UER3.js");await t(e.commandArgs,{port:o,verbose:c});break}case"maestro":{let{runMaestro:t}=await import("./chunks/maestro-33RGEXP4.js"),a=await t(i,{port:o,verbose:c});await n(typeof a=="number"?a:0)}case"record":{let{runRecord:t}=await import("./chunks/record-A4MRKJ2E.js");await t(i,{port:o,verbose:c});break}case"film":{let{runFilm:t}=await import("./chunks/film-Q7KZIGBH.js"),a=await t(i,{port:o,verbose:c});await n(typeof a=="number"?a:0)}case"storage":{let{runStorage:t}=await import("./chunks/storage-BUQQJAMD.js"),a=await t(i,{port:o,verbose:c});await n(typeof a=="number"?a:0)}case"perf":{let{runPerf:t}=await import("./chunks/perf-VW3UTYCK.js"),a=await t(i,{port:o,verbose:c});await n(typeof a=="number"?a:0)}case"screenshot":{let{runScreenshotCommand:t}=await import("./chunks/screenshot-command-6LROQJPJ.js"),a=await t(i,{port:o,verbose:c});await n(typeof a=="number"?a:0)}case"mode":{let{runMode:t}=await import("./chunks/mode-LZORZYWP.js");await t(i,{port:o,verbose:c});break}case"permissions":{let{runPermissions:t}=await import("./chunks/permissions-7JIR6QPO.js"),a=await t(i,{port:o,verbose:c});await n(a)}case"inspect":{let{runInspect:t}=await import("./chunks/inspect-QWR2U3DK.js");await t(i,{port:o,verbose:c});break}case"debug":{let{runDebug:t}=await import("./chunks/debug-MVPFRIQA.js");await t(i,{port:o,verbose:c});break}case"timeline":{let{runTimeline:t}=await import("./chunks/timeline-62RWUHSF.js");await t(i,{port:o,verbose:c});break}case"what-happened":{let{runWhatHappened:t}=await import("./chunks/what-happened-UKBSKIGA.js");await t(i,{port:o,verbose:c});break}case"open":{let{runOpenCommand:t}=await import("./chunks/control-BY6UAMKN.js");await t(i,{port:o});break}case"use":{let{runUseCommand:t}=await import("./chunks/control-BY6UAMKN.js");await t(i,{port:o});break}case"claim":{let{runClaimCommand:t}=await import("./chunks/control-BY6UAMKN.js");await t(i,{port:o});break}case"close":{let{runCloseCommand:t}=await import("./chunks/control-BY6UAMKN.js");await t(i,{port:o});break}case"device":{let{runDeviceCommand:t}=await import("./chunks/device-7R4BSFMS.js");await t(i,{port:o});break}case"compat":{let{runCompat:t}=await import("./chunks/compat-OM7ODBMO.js");await t(e.commandArgs);break}case"report-issue":{let{runReportIssue:t}=await import("./chunks/report-issue-JUWTB66Z.js"),a=await t(e.commandArgs);await n(a)}case"desktop":{let{runDesktop:t}=await import("./chunks/desktop-ZXH7NRHW.js");await t(e.commandArgs,{port:o,device:u,driver:C});break}case"login":{let{runLogin:t}=await import("./chunks/login-UMP65DJG.js");await t(e.commandArgs);break}case"logout":{let{runLogout:t}=await import("./chunks/logout-F45WBVJV.js");await t();break}case"auth":{let{runAuth:t}=await import("./chunks/auth-SF2WR2K2.js");await t(e.commandArgs);break}case"setup":{let{runSetup:t}=await import("./chunks/setup-2CUXSDQH.js");await t(e.commandArgs);break}case"serve":{let{runServe:t}=await import("./chunks/serve-64SRDCU5.js");await t(e.commandArgs,{port:o});break}case"daemon":{let{runDaemon:t}=await import("./chunks/daemon-BQ2T53ZA.js");await t(e.commandArgs,{port:o});break}case"runtime":{let{runRuntime:t}=await import("./chunks/runtime-7LSOODXL.js");await t(e.commandArgs);break}case"upgrade":case"update":{let{runUpgrade:t}=await import("./chunks/upgrade-GHXDTO2A.js");await t(e.commandArgs);break}case"version":{let{runVersion:t}=await import("./chunks/version-F4CKBMB6.js");await t(e.commandArgs);break}case"agent":{let{runAgentCommand:t}=await import("./chunks/agent-U3HXOT6B.js"),a=await t(e.commandArgs);await n(a)}case"agent-wrapper":{let{runAgentWrapper:t}=await import("./chunks/agent-wrapper-LJOPJUAN.js"),a=await t(e.commandArgs);await n(a)}case"skill":{let{runSkill:t}=await import("./chunks/skills-IILJVUMS.js");await t(e.commandArgs);break}case"app-fonts":{let{runAppFonts:t}=await import("./chunks/app-fonts-4EVLV2BX.js");await t(e.commandArgs);break}case"config":{let{runConfig:t}=await import("./chunks/config-ZIX6LPFG.js");await t(e.commandArgs);break}case"cleanup":{let{runCleanup:t}=await import("./chunks/cleanup-YZPCNPWE.js"),a=await t(e.commandArgs);await n(a)}default:console.error(` unknown command: ${r}`),console.error(" run `sootsim --help` to see the full surface."),await n(1)}await v();