sootsim 0.1.40 → 0.1.42

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 (146) hide show
  1. package/README.md +15 -15
  2. package/dist-cli/bin.js +4 -4
  3. package/dist-cli/chunks/{agent-3F5PO4NL.js → agent-4BBZUWD2.js} +2 -2
  4. package/dist-cli/chunks/{agent-wrapper-LVUUZRWL.js → agent-wrapper-TA5GG3MV.js} +2 -2
  5. package/dist-cli/chunks/{assert-4WMVS3WU.js → assert-7GHUOGLI.js} +2 -2
  6. package/dist-cli/chunks/auto-bootstrap-7BJDOYUU.js +2 -0
  7. package/dist-cli/chunks/beta-2LIEPFEJ.js +2 -0
  8. package/dist-cli/chunks/{chunk-QUULF2II.js → chunk-2GKZS2ZZ.js} +2 -2
  9. package/dist-cli/chunks/chunk-2XCCV7J3.js +2 -0
  10. package/dist-cli/chunks/{chunk-CCZHRBXJ.js → chunk-3PN6HIAM.js} +1 -1
  11. package/dist-cli/chunks/{chunk-W2XRHDQQ.js → chunk-4574AJAK.js} +2 -2
  12. package/dist-cli/chunks/chunk-543BSGQG.js +4 -0
  13. package/dist-cli/chunks/{chunk-FNIL6BYS.js → chunk-5EBERDK7.js} +1 -1
  14. package/dist-cli/chunks/{chunk-KGVH3YAG.js → chunk-5PZPKGUJ.js} +2 -2
  15. package/dist-cli/chunks/{chunk-QMBYRPRK.js → chunk-5TRJK5SO.js} +1 -1
  16. package/dist-cli/chunks/chunk-64AKKWVB.js +1 -0
  17. package/dist-cli/chunks/{chunk-YKEBL6GE.js → chunk-6MYXKMLG.js} +1 -1
  18. package/dist-cli/chunks/{chunk-WZE6T3GT.js → chunk-6R3CVNXC.js} +1 -1
  19. package/dist-cli/chunks/chunk-76WF7UIG.js +17 -0
  20. package/dist-cli/chunks/chunk-A2R42ONP.js +27 -0
  21. package/dist-cli/chunks/{chunk-LXR5EI74.js → chunk-BBRAYGDX.js} +2 -2
  22. package/dist-cli/chunks/{chunk-MLCBIX7O.js → chunk-BVCZIJ2X.js} +2 -2
  23. package/dist-cli/chunks/chunk-BZFKV7CT.js +56 -0
  24. package/dist-cli/chunks/{chunk-YJMXGTP4.js → chunk-CZZDYQBA.js} +1 -1
  25. package/dist-cli/chunks/{chunk-Y66CDFAT.js → chunk-D5TKAFJ3.js} +1 -1
  26. package/dist-cli/chunks/{chunk-OV5TY7M3.js → chunk-DNSKIJSB.js} +2 -2
  27. package/dist-cli/chunks/{chunk-EOWN4ZFJ.js → chunk-DUVJOJTP.js} +2 -2
  28. package/dist-cli/chunks/{chunk-6XI4VHIL.js → chunk-DXQKMDX2.js} +2 -2
  29. package/dist-cli/chunks/{chunk-Z3I2I4IO.js → chunk-EDXBJUHH.js} +3 -3
  30. package/dist-cli/chunks/chunk-EZGUKWD5.js +1 -0
  31. package/dist-cli/chunks/{chunk-DSTHAISO.js → chunk-FDB3P2GV.js} +1 -1
  32. package/dist-cli/chunks/{chunk-LCES5ZJI.js → chunk-I4LBW6ED.js} +2 -2
  33. package/dist-cli/chunks/chunk-IFY75LQ3.js +1 -0
  34. package/dist-cli/chunks/{chunk-553OZX4H.js → chunk-ITQGNQE3.js} +2 -2
  35. package/dist-cli/chunks/{chunk-5GK4YX7O.js → chunk-J6HT2ZJW.js} +1 -1
  36. package/dist-cli/chunks/{chunk-TK2IPNHL.js → chunk-KQN7OUQ6.js} +2 -2
  37. package/dist-cli/chunks/{chunk-Y5CFIRLN.js → chunk-L53QRCQZ.js} +1 -1
  38. package/dist-cli/chunks/{chunk-KVOMVYG6.js → chunk-LAOTVMTO.js} +2 -2
  39. package/dist-cli/chunks/chunk-LJLRHMLK.js +1 -0
  40. package/dist-cli/chunks/{chunk-WQXG4I5N.js → chunk-LXZK2TIS.js} +2 -2
  41. package/dist-cli/chunks/{chunk-6X5NQJT7.js → chunk-MQOJIYEY.js} +20 -20
  42. package/dist-cli/chunks/chunk-NLYIKZIF.js +62 -0
  43. package/dist-cli/chunks/{chunk-WPS3TIOB.js → chunk-Q26NNL4G.js} +1 -1
  44. package/dist-cli/chunks/chunk-QLGR4M5H.js +11 -0
  45. package/dist-cli/chunks/{chunk-U3JD6X75.js → chunk-QWGMNIWG.js} +2 -2
  46. package/dist-cli/chunks/chunk-R3KU3IBN.js +117 -0
  47. package/dist-cli/chunks/{chunk-5IAIIX7B.js → chunk-SGBD3EAX.js} +3 -3
  48. package/dist-cli/chunks/{chunk-DEBXVPIE.js → chunk-UY6HKNEV.js} +1 -1
  49. package/dist-cli/chunks/{chunk-4L45Q3YX.js → chunk-WSISSPE3.js} +2 -2
  50. package/dist-cli/chunks/{chunk-PKB6IEGM.js → chunk-WW4G4D4A.js} +2 -2
  51. package/dist-cli/chunks/{chunk-BZED27B2.js → chunk-Y4ENWAOJ.js} +2 -2
  52. package/dist-cli/chunks/{compat-NBFWHK5S.js → compat-GWIATWPP.js} +5 -5
  53. package/dist-cli/chunks/{config-JEDQ3NHA.js → config-OXHJXY6N.js} +2 -2
  54. package/dist-cli/chunks/control-HJ2RPHYO.js +2 -0
  55. package/dist-cli/chunks/cpu-profile-MBYPGUJH.js +2 -0
  56. package/dist-cli/chunks/{daemon-HOL7J3BI.js → daemon-3N3QWMFU.js} +2 -2
  57. package/dist-cli/chunks/{debug-LNVMIWD6.js → debug-POCRM56I.js} +3 -3
  58. package/dist-cli/chunks/demo-app-registry-XABEINTD.js +2 -0
  59. package/dist-cli/chunks/{detox-EEZPH3DZ.js → detox-4NXKQ2CS.js} +2 -2
  60. package/dist-cli/chunks/{device-GVLYQI7X.js → device-DURJHKK5.js} +2 -2
  61. package/dist-cli/chunks/diagnose-IMXDGRVA.js +41 -0
  62. package/dist-cli/chunks/drivers-VXSNI7K2.js +2 -0
  63. package/dist-cli/chunks/{electron-UDV6K3IH.js → electron-NGWA52N6.js} +3 -3
  64. package/dist-cli/chunks/flow-TCG5XPDK.js +2 -0
  65. package/dist-cli/chunks/{hints-46PJLATZ.js → hints-5UNZ7BR2.js} +2 -2
  66. package/dist-cli/chunks/{home-paths-XD7AOYU7.js → home-paths-TENEFM4L.js} +2 -2
  67. package/dist-cli/chunks/{inspect-EDIKZ6O2.js → inspect-IWMH6MUT.js} +94 -92
  68. package/dist-cli/chunks/install-IKBAY4PQ.js +2 -0
  69. package/dist-cli/chunks/{install-desktop-CX6ATQTR.js → install-desktop-ABAWCLFJ.js} +3 -3
  70. package/dist-cli/chunks/{keys-CXQIYEVW.js → keys-NET26VLH.js} +2 -2
  71. package/dist-cli/chunks/{launch-DCFRKVD3.js → launch-TKIA4RC4.js} +3 -3
  72. package/dist-cli/chunks/{login-HCIZL5GT.js → login-AWT3GU5M.js} +4 -4
  73. package/dist-cli/chunks/{logout-7X2YHJY3.js → logout-R2RUNXHO.js} +2 -2
  74. package/dist-cli/chunks/{maestro-TY622MIW.js → maestro-552237HC.js} +2 -2
  75. package/dist-cli/chunks/{preview-DYI6ESOK.js → preview-GKIGPZUO.js} +2 -2
  76. package/dist-cli/chunks/{profile-7SXJEJTG.js → profile-U64JPCPD.js} +2 -2
  77. package/dist-cli/chunks/{react-VGSDY766.js → react-FWSV5JH2.js} +2 -2
  78. package/dist-cli/chunks/record-FLWDPJIP.js +7 -0
  79. package/dist-cli/chunks/runtime-AGNC2TK6.js +2 -0
  80. package/dist-cli/chunks/{runtime-delivery-PWLODFCY.js → runtime-delivery-22PV7UED.js} +2 -2
  81. package/dist-cli/chunks/{screenshot-PTKY4UU4.js → screenshot-4P3TL7CN.js} +2 -2
  82. package/dist-cli/chunks/{screenshot-mode-HSV7VY4G.js → screenshot-mode-GX3Q3IZ6.js} +2 -2
  83. package/dist-cli/chunks/{screenshots-VY7VAGSV.js → screenshots-5HSLPDWZ.js} +2 -2
  84. package/dist-cli/chunks/server-Q3ZD4BEF.js +35 -0
  85. package/dist-cli/chunks/setup-repo-NQVN5PK7.js +2 -0
  86. package/dist-cli/chunks/{skills-KEPQLCMR.js → skills-M2CWX5ZC.js} +2 -2
  87. package/dist-cli/chunks/{start-5CJTBNRM.js → start-24EAF2KS.js} +4 -4
  88. package/dist-cli/chunks/store-GI3SJOKH.js +2 -0
  89. package/dist-cli/chunks/telemetry-73Z4FAUH.js +2 -0
  90. package/dist-cli/chunks/{test-REKHGKFE.js → test-AJBULIVR.js} +3 -3
  91. package/dist-cli/chunks/{three-mode-FZYHB4ZQ.js → three-mode-JBZSQQIQ.js} +2 -2
  92. package/dist-cli/chunks/{timeline-CWZAY52K.js → timeline-VVGWQSVU.js} +2 -2
  93. package/dist-cli/chunks/{upgrade-AIUJEF5F.js → upgrade-IOGHQT7J.js} +2 -2
  94. package/dist-cli/chunks/upload-Z4PPGOX6.js +2 -0
  95. package/dist-cli/chunks/web-XIMVN6KZ.js +2 -0
  96. package/dist-cli/chunks/{what-happened-JSQQVQGE.js → what-happened-STVD3KZ2.js} +2 -2
  97. package/dist-cli/chunks/whoami-L62IGS4U.js +2 -0
  98. package/dist-lib/agent-daemon-client.cjs +1 -1
  99. package/dist-lib/agent-events.cjs +1 -1
  100. package/dist-lib/agent-sessions.cjs +1 -1
  101. package/dist-lib/attached-projects.cjs +1 -1
  102. package/dist-lib/auth/shared-session.cjs +1 -1
  103. package/dist-lib/backend-origin.cjs +1 -1
  104. package/dist-lib/bridge-constants.cjs +1 -1
  105. package/dist-lib/cli-constants.cjs +1 -1
  106. package/dist-lib/config.cjs +1 -1
  107. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  108. package/dist-lib/home-paths.cjs +1 -1
  109. package/dist-lib/host/bridge-host.cjs +14 -8
  110. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  111. package/dist-lib/index.cjs +1 -1
  112. package/dist-lib/metro.cjs +1 -1
  113. package/dist-lib/profiles.cjs +1 -1
  114. package/dist-lib/render-mode.cjs +1 -1
  115. package/dist-lib/vite-base.cjs +14 -8
  116. package/dist-lib/vite.cjs +1 -1
  117. package/package.json +1 -1
  118. package/dist-cli/chunks/auto-bootstrap-Q4A3PTF5.js +0 -2
  119. package/dist-cli/chunks/beta-CLLKUB5X.js +0 -2
  120. package/dist-cli/chunks/chunk-4IAKB3C4.js +0 -117
  121. package/dist-cli/chunks/chunk-CAJ247SC.js +0 -2
  122. package/dist-cli/chunks/chunk-D2ZMP7G4.js +0 -61
  123. package/dist-cli/chunks/chunk-D4Z7MWQY.js +0 -11
  124. package/dist-cli/chunks/chunk-DHJIXXWG.js +0 -27
  125. package/dist-cli/chunks/chunk-DPZDTJVQ.js +0 -1
  126. package/dist-cli/chunks/chunk-FFR7EA4U.js +0 -1
  127. package/dist-cli/chunks/chunk-I7KXFJDK.js +0 -1
  128. package/dist-cli/chunks/chunk-J6BPROH4.js +0 -4
  129. package/dist-cli/chunks/chunk-PNGBWMQH.js +0 -17
  130. package/dist-cli/chunks/chunk-S4PJMUC7.js +0 -2
  131. package/dist-cli/chunks/chunk-XWXFUFB2.js +0 -1
  132. package/dist-cli/chunks/control-IMWZVYC3.js +0 -2
  133. package/dist-cli/chunks/cpu-profile-MQPUSRHG.js +0 -22
  134. package/dist-cli/chunks/demo-app-registry-4RFMJ4FM.js +0 -2
  135. package/dist-cli/chunks/diagnose-JQ7DPTSL.js +0 -41
  136. package/dist-cli/chunks/drivers-JUW6JBWH.js +0 -2
  137. package/dist-cli/chunks/flow-JOW23WNH.js +0 -2
  138. package/dist-cli/chunks/install-CCC3IF5S.js +0 -2
  139. package/dist-cli/chunks/record-7JX2SMVP.js +0 -37
  140. package/dist-cli/chunks/runtime-FTOQD7QK.js +0 -2
  141. package/dist-cli/chunks/server-EDB3EK4K.js +0 -35
  142. package/dist-cli/chunks/setup-repo-S2GFZR7F.js +0 -2
  143. package/dist-cli/chunks/store-SCRULNVS.js +0 -2
  144. package/dist-cli/chunks/telemetry-RC3OT67I.js +0 -2
  145. package/dist-cli/chunks/upload-UDA5ITTE.js +0 -2
  146. package/dist-cli/chunks/whoami-5WUUPIRN.js +0 -2
@@ -1,117 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- var w={bridgePort:7668,defaultShellUrl:"http://localhost:5173/"};function t(o,i){return{flag:o,description:i}}function e(o,i){return{command:o,description:i}}var k=[{id:"preview",slug:"preview",navTitle:"preview",title:"sootsim preview",description:"production-like preview build",summary:"Build the sootsim frontend in preview mode, then either serve it locally or export a static artifact path for later packaging.",order:20,usage:["sootsim preview [options]"],options:[t("--export <path>","export a standalone preview artifact instead of serving"),t("--port <number>","preview server port (defaults to 4173)")],examples:[e("sootsim preview"),e("sootsim preview --export ./preview.html")],related:["electron"]},{id:"test",slug:"test",navTitle:"test",title:"sootsim test",description:"run tests against sootsim",summary:"Run package-local smoke and debugging tests, YAML flows, or the shared Detox-style conformance suites against a running sootsim target.",order:30,usage:["sootsim test [options]"],options:[t("--flows","run YAML flows through the sootsim flow runner"),t("--detox","run the shared Detox-style conformance suites against sootsim"),t("--parallel","run discovered flows in parallel where supported"),t("--watch","re-run interactively when files change"),t("--reporter <type>","select console, json, or junit reporting"),t("--grep <pattern>","filter the test set by name")],examples:[e("sootsim test","run package-local Playwright smoke/debug tests"),e("sootsim test --flows"),e("sootsim test --detox","run the shared conformance suite against sootsim"),e("sootsim test --detox --watch")],related:["flow","inspect","record"]},{id:"flow",slug:"flow",navTitle:"flow",title:"sootsim flow",description:"run flows through the current sim or manage a draft from live inspect actions",summary:"Use flow files when you already have them, or start a draft so inspect-driven actions can be kept one by one and exported into YAML once the path is proven. Flow playback, recording, and profiling now stay on the shared bridge runner.",order:40,usage:["sootsim flow <flow.yaml> [options]","sootsim flow start","sootsim flow keep","sootsim flow end [--output <path>] [--validate] [--video]"],aliases:["good"],options:[t("--record","record a video while the flow runs"),t("--profile","capture perf stats while the flow runs"),t("--out <dir>","directory for recordings"),t("--slow <ms>","delay between steps for easier debugging"),t("--url <url>","load this target before running the flow"),t("--new","open a fresh sim before running the flow"),t("--driver <id>","when paired with --new, launch through a specific driver from the registry (e.g. playwright); see `sootsim list --drivers`"),t("--headless","pass headless=true to the launch driver (useful with --driver playwright)"),t("--sim <sim>","target a specific current sim"),t("--electron","prefer the desktop companion for this flow run"),t("--output <path>","write the drafted flow when using `flow end`"),t("--validate","replay the drafted flow before clearing it"),t("--video","replay and record the drafted flow before clearing it"),t("--preview","record + upload the run as a shareable /preview/<id> link (implies --record)"),t("--preview-origin <url>","override the upload target for --preview (defaults to auto)"),t("--preview-public-origin <url>","override the public link origin for --preview (defaults to sootsim.com for prod uploads)"),t("--preview-open","open the resulting /preview/<id> url after upload")],examples:[e("sootsim flow flows/login.yaml"),e("sootsim flow flows/login.yaml --profile"),e("sootsim flow flows/demo.yaml --record --slow 500"),e("sootsim flow flows/demo.yaml --preview","record + upload a shareable link in one step"),e("sootsim flow start"),e("sootsim flow keep"),e("sootsim flow end --output ./flows/draft.yaml"),e("sootsim flow end --output ./flows/draft.yaml --validate"),e("sootsim flow end --output ./flows/draft.yaml --video")],related:["inspect","record","upload","electron","test"]},{id:"record",slug:"record",navTitle:"record",title:"sootsim record",description:"capture the live sim as webm/mp4/gif, or sample N png frames (--frames)",summary:"Drives the engine's built-in canvas recorder over the WS bridge \u2014 no second browser, no ffmpeg. Encoding happens in the running page (MediaRecorder for webm, WebCodecs for mp4, gifenc for gif). Two modes: atomic (duration-bounded) or stateful (start \u2192 interact \u2192 stop). Stateful mode is webm/mp4 only; for gif/png use atomic mode. Use --frames to sample N evenly-spaced png snapshots instead of a video.",order:60,usage:["sootsim record [options]","sootsim record start [options]","sootsim record stop [--output <path>]","sootsim record status","sootsim record cancel"],options:[t("--format <type>","webm | mp4 | gif | png (inferred from --output extension; stateful mode is webm/mp4 only)"),t("--mode <kind>","video | live | combined (default: video). live captures the event stream; combined captures both and uploads automatically"),t("--duration <seconds>","recording duration (default: 10, atomic mode only)"),t("--fps <number>","target frame rate for video/gif (default: 30)"),t("--output <path>","output file, or directory when --frames is set"),t("--frames <n>","sample N evenly-spaced png frames instead of a video"),t("--max-width <px>","downscale gif frames to this width"),t("--sim <sim>","target a specific sim")],examples:[e("sootsim record --duration 5"),e("sootsim record --format mp4 --output demo.mp4 --duration 8"),e("sootsim record --output demo.gif --duration 3"),e("sootsim record --frames 10 --output ./frames/"),e("sootsim record --mode combined --duration 8 --open","capture video + events and upload a shareable preview link"),e("sootsim record start --format mp4","begin; interact freely across any number of CLI calls"),e("sootsim record start --mode combined","start a stateful preview-share recording"),e("sootsim record stop --output flow.mp4","finalize + save"),e("sootsim record cancel","discard an in-progress recording")],related:["screenshot","flow","upload"]},{id:"profile",slug:"profile",navTitle:"profile",title:"sootsim profile",description:"manage isolated storage profiles",summary:"Create, list, clear, and delete storage profiles. Profiles isolate cookies, localStorage, IndexedDB, cache, permissions, and service workers for Electron and Playwright launches. Browser-tab launches cannot honor profiles and fail clearly if one is requested.",order:65,usage:["sootsim profile ls","sootsim profile create <id>","sootsim profile clear <id>","sootsim profile delete <id>"],options:[t("<id>","profile id: letters, numbers, dot, dash, or underscore")],examples:[e("sootsim profile ls"),e("sootsim profile create qa"),e("sootsim open 8081 --profile qa"),e("sootsim open 8081 --profile qa --driver playwright"),e("sootsim profile clear qa"),e("sootsim profile delete qa")],related:["open","electron"]},{id:"cpu-profile",slug:"cpu-profile",navTitle:"cpu-profile",title:"sootsim cpu-profile",description:"capture a sampled CPU trace from the tenant worker",summary:"Record a sampled CPU profile of the running sootsim tenant worker (where the guest app's JS executes) via the W3C JS Self-Profiler API, and write a standard `.cpuprofile` that loads directly in Chrome DevTools (Performance panel \u2192 Load profile). No CDP, no env flag \u2014 sootsim already serves `Document-Policy: js-profiling` in vite dev and in the electron companion. Ideal for A/B comparisons across commits: `cpu-profile` before, make a change, `cpu-profile` after, then diff in DevTools.",order:66,usage:["sootsim cpu-profile [options]"],options:[t("--duration <seconds>","recording duration (default: 5)"),t("--output <path>","output file (default: /tmp/sootsim.cpuprofile). if the path ends in .gz, the file is gzipped"),t("--sample-interval <ms>","requested sample interval in ms (default: 10 \u2014 chrome may clamp upward)"),t("--max-buffer <n>","max samples buffered (default: 100000)"),t("--sim <sim>","target a specific sim")],examples:[e("sootsim cpu-profile --duration 3","save to /tmp/sootsim.cpuprofile"),e("sootsim cpu-profile --duration 10 --output /tmp/after.cpuprofile.gz"),e("sootsim cpu-profile --duration 3 -o /tmp/before.cpuprofile","capture a baseline before a code change")],related:["record","flow"]},{id:"react",slug:"react",navTitle:"react",title:"sootsim react",description:"inspect React component render cost in the tenant worker",summary:"Start a tenant-worker React render collector, interact with the app, then print slow component and rerender tables from the same bridge-backed sim. This is component-level React profiling; use `sootsim cpu-profile` for sampled JS CPU flame charts.",order:67,usage:["sootsim react profile start [--include-props] [--max-commits N]","sootsim react profile stop","sootsim react summary [--limit 10] [--json]","sootsim react slow [--limit 5] [--json]","sootsim react rerenders [--limit 5] [--json]","sootsim react tree [--depth 3] [--find <name>] [--json]","sootsim react why <fiber-id> [--json]"],options:[t("--include-props","record shallow prop value previews for `why` output"),t("--max-commits <n>","bounded commit ring size (default: 2000)"),t("--limit <n>","rows to print for summary/slow/rerenders (default: 10)"),t("--depth <n>","tree depth for `react tree` (default: 3)"),t("--find <name>","filter `react tree` by component name"),t("--json","print structured output"),t("--sim <sim>","target a specific sim")],examples:[e("sootsim react profile start"),e("sootsim react profile stop"),e("sootsim react summary --limit 10","one-shot table of slowest renders + most rerenders, with the duration-zero warning surfaced"),e("sootsim react slow --limit 10"),e("sootsim react rerenders --json"),e("sootsim react tree --find Avatar")],related:["cpu-profile","what-happened","timeline"]},{id:"screenshot",slug:"screenshot",navTitle:"screenshot",title:"sootsim screenshot",description:"capture the canvas (full or cropped) as PNG",summary:"Capture the canvas from the currently connected sim \u2014 full-screen by default, or cropped to a logical rect via --area / --id / --text. Falls back to a fresh headless chromium when no bridge is running.",order:70,usage:["sootsim screenshot [options]"],options:[t("--output <path>","output file path (default: /tmp/sootsim-inspect.png)"),t("--area <x,y,w,h>","crop to a logical sootsim rect"),t("--id <testID>","crop to the bounding box of a node by testID"),t("--text <text>","crop to the bounding box of a node by text content"),t("--gallery","crawl and capture multiple screens into an HTML gallery"),t("--themes","capture light and dark variants where possible")],examples:[e("sootsim screenshot --output app.png"),e("sootsim screenshot --area 0,200,393,400"),e("sootsim screenshot --id loginButton --output button.png"),e("sootsim screenshot --gallery --themes")],related:["sample-color","record","inspect"]},{id:"screenshots",slug:"screenshots",navTitle:"screenshots",title:"sootsim screenshots",description:"capture + compose app-store screenshot sets from a single plan",summary:"Run a YAML plan that can capture raw screenshots from a flow, frame them with the real device chrome, and export branded marketing canvases for App Store submission.",order:71,usage:["sootsim screenshots --plan <plan.yaml> [options]"],options:[t("--plan <path>","screenshot plan yaml"),t("--sim <sim>","reuse a live sim for capture"),t("--app <target>","override the plan app target for this run"),t("--device <model>","override the plan capture device for this run"),t("--capture-only","stop after raw/framed intermediates"),t("--compose-only","skip flow capture and reuse existing raw screenshots")],examples:[e("sootsim screenshots --plan .sootsim/app-store.yaml"),e("sootsim screenshots --plan .sootsim/app-store.yaml --sim a9","reuse an already-open visible sim"),e("sootsim screenshots --plan .sootsim/app-store.yaml --compose-only","rerender marketing canvases from existing raw PNGs")],related:["flow","screenshot","record"]},{id:"screenshot-mode",slug:"screenshot-mode",navTitle:"screenshot-mode",title:"sootsim screenshot-mode",description:"toggle the screenshot-mode chrome overlay",summary:'Flip the live sim into (or out of) "screenshot mode" \u2014 the same state toggled from the MacMenuBar / rail. Hides the DOM browser chrome around the canvas so a plain `sootsim screenshot` can grab clean marketing-ready frames. Orthogonal to `screenshot --no-shell` (which excludes the iOS shell surfaces from inside the canvas).',order:72,usage:["sootsim screenshot-mode [on|off|toggle]"],options:[],examples:[e("sootsim screenshot-mode","toggle the current state"),e("sootsim screenshot-mode on","force screenshot-mode on"),e("sootsim screenshot-mode off","force screenshot-mode off")],related:["screenshot"]},{id:"three-mode",slug:"three-mode",navTitle:"three-mode",title:"sootsim three-mode",description:"toggle the live browser 3d device stage",summary:"Flip the live sim into (or out of) 3d mode using the same plugin-owned action path as the rail and Mac menu. 3d mode can stay enabled while screenshot mode is enabled, so the screenshot overlay contributes copy while the 3d stage owns the device pose and background.",order:73,usage:["sootsim three-mode [on|off|toggle]","sootsim 3d-mode [on|off|toggle]"],options:[],examples:[e("sootsim three-mode","toggle the current state"),e("sootsim three-mode on","force 3d mode on"),e("sootsim 3d-mode off","force 3d mode off")],related:["screenshot-mode","screenshot"]},{id:"assert",slug:"assert",navTitle:"assert",title:"sootsim assert",description:"run any read verb and convert its output into an exit code",summary:"Thin wrapper that spawns a read verb (describe, find, get errors, \u2026) with --json forced on, parses stdout, and evaluates assertion flags over the payload. Exit codes are bisect-friendly: 0 pass, 1 fail, 125 skip (app not loaded / verb crashed), 2 misuse. Flag-level composition (--count / --contains / --jq / --has-path / --within) means the same predicate surface works across every read verb.",order:69,usage:["sootsim assert <verb> <verb-args...> <assertion-flags...>"],options:[t("--count <n>","result array has exactly n items"),t("--count-at-least <n>","length >= n"),t("--count-at-most <n>","length <= n"),t("--exists","non-null, non-empty (default when no flag given)"),t("--empty","null or zero length"),t("--contains <str>","stringified result contains str (repeatable)"),t("--not-contains <str>","stringified result does not contain str"),t("--matches <regex>","stringified result matches regex"),t("--not-matches <regex>","stringified result does not match regex"),t("--equals <literal>","scalar equality"),t("--has-path <dotted.path>","nested path exists"),t("--path-equals <path> <value>","nested path equals value"),t("--jq <expr>","jq expression is truthy (requires jq on PATH)"),t("--within <ms>","retry verb + predicates until pass or deadline"),t("--allow-timeout","treat verb auto-wait timeout as fail, not skip"),t("--negate, !","flip pass/fail (does not flip skip)"),t("--quiet","suppress the one-line pass/fail summary")],examples:[e("sootsim assert find --testid set-max-input --count 4","exactly four matching nodes"),e("sootsim assert describe --contains swap-form-header","substring anywhere in the tree dump"),e("sootsim assert get errors --count 0","fail the commit if any console error"),e("sootsim assert find --testid submit --exists --within 3000","poll for 3s before failing"),e(`sootsim assert describe --jq '.shell.state == "app"'`,"jq predicate over the payload"),e("git bisect run sootsim assert find --testid set-max-input --count 4","git-bisect integration")],related:["find","describe","get","list"]},{id:"get",slug:"get",navTitle:"get",title:"sootsim get",description:"read runtime state (tree, a11y, url, count, errors, \u2026)",summary:"Grouping verb for pure reads. `sootsim get <noun>` covers the read side of the bridge \u2014 tree, a11y, url, count, console errors/warnings/requests, layout, globals, and single-node lookups.",order:70,usage:["sootsim get <noun> [args]"],examples:[e("sootsim get tree"),e("sootsim get a11y 3"),e("sootsim get url"),e("sootsim get count"),e("sootsim get errors 5"),e("sootsim get requests 5"),e("sootsim get node loginButton"),e("sootsim get layout loginButton"),e("sootsim get globals")],related:["do","find","describe","inspect"]},{id:"do",slug:"do",navTitle:"do",title:"sootsim do",description:"drive the app (tap, type, scroll, swipe, key, reload, \u2026)",summary:"Grouping verb for user-interaction actions. `sootsim do <action>` covers the write side of the bridge \u2014 taps, typing, key presses, touch/scroll/drag/swipe/pinch gestures, reload, and local timing helpers.",order:71,usage:["sootsim do <action> [args]"],examples:[e("sootsim do tap 196 727"),e("sootsim do double-tap 196 400"),e('sootsim do tap-text "Sign in"'),e("sootsim do tap-id loginNextButton"),e('sootsim do type "natew.bsky.social"'),e('sootsim do type-into email "me@example.com"'),e("sootsim do key return"),e("sootsim do key-sequence shift h i"),e("sootsim do dismiss"),e("sootsim do scroll feed 0 400"),e("sootsim do drag 200 720 200 160"),e("sootsim do swipe 200 720 200 160"),e("sootsim do gesture swipe-from-bottom-edge"),e("sootsim do reload")],related:["shell","get","find","describe","inspect","wait"]},{id:"wait",slug:"wait",navTitle:"wait",title:"sootsim wait",description:"block until the runtime reaches a known state",summary:"Grouping verb for explicit synchronization. Use instead of guessing with `sleep`. Every subverb has a bounded timeout and exits non-zero when it times out, so automations fail loudly rather than silently racing animations or missing nodes.",order:72,usage:["sootsim wait <kind> [args]"],examples:[e("sootsim wait ready","block until the app bundle is loaded and painted"),e("sootsim wait idle","block until animations + rAF queue are settled"),e("sootsim wait selector loginButton","block until a node with this testID is present"),e("sootsim wait selector feedList --max-ms 3000","cap how long to wait for a node")],related:["do","find","describe","get"]},{id:"shell",slug:"shell",navTitle:"shell",title:"sootsim shell",description:"drive shell state and simulator chrome",summary:"Top-level shell controls for app launch, home/switcher transitions, and simulator-owned chrome such as appearance and lock state.",order:72,usage:["sootsim shell launch <appId> [waitMs] [--clear-state]","sootsim shell home [waitMs]","sootsim shell switcher [waitMs]","sootsim shell open-card <appId> [waitMs]","sootsim shell appearance <light|dark|auto|toggle>","sootsim shell lock","sootsim shell shake"],examples:[e("sootsim shell launch photos"),e("sootsim shell home"),e("sootsim shell switcher 800"),e("sootsim shell appearance dark"),e("sootsim shell lock")],related:["do","inspect","debug"]},{id:"inspect",slug:"inspect",navTitle:"inspect",title:"sootsim inspect",description:"query, describe, and drive the running sootsim instance",summary:"Use the bridge-backed inspection surface for full access to the real node tree. `sootsim inspect <sub>` works for every subcommand in the surface; day-to-day, prefer the grouped verbs (`describe`, `find`, `get`, `do`, `shell`, `debug`) \u2014 run `sootsim --help` to see the full set.",order:80,usage:["sootsim inspect <subcommand> [args]"],options:[t("--sim <sim>","target a specific connected sim"),t("--port <number>","bridge port (defaults to {{bridgePort}})"),t("--timeout <ms>","command timeout for bridge requests"),t("--base-url <url>","base shell URL used by open when wrapping bundle targets")],examples:[e("sootsim describe"),e("sootsim get errors 5"),e("sootsim get requests 5"),e('sootsim do tap-text "Sign in"'),e("sootsim list"),e("sootsim debug state shell --sim a2")],related:["debug","get","do","shell","find","list","open","use"]},{id:"network",slug:"network",navTitle:"network",title:"sootsim network",description:"inspect live network traffic from the running sootsim worker",summary:"Read the shared observability store (fetch + XHR captured in the render worker) from the terminal. List recent requests, tail in real time, filter by URL or status, inspect one entry in detail, or clear the buffer. Shares its data source with the in-shell devtools dock so both views stay in lock-step.",order:85,usage:["sootsim network [limit]","sootsim network --failed","sootsim network --slow [--threshold <ms>]","sootsim network --filter <substring>","sootsim network --tail","sootsim network get <id>","sootsim network clear"],options:[t("--failed","only show errored or non-2xx responses"),t("--slow","only show requests slower than --threshold (default 1000ms), sorted by duration desc"),t("--threshold <ms>","slow threshold in milliseconds for --slow (default 1000)"),t("--filter <substring>","URL substring filter (case-insensitive)"),t("--limit <n>","max entries to print (default 20)"),t("--tail, -f","follow mode: stream new entries as they land"),t("--json","emit structured JSON instead of formatted rows"),t("--sim <sim>","target a specific connected sim"),t("--port <number>","bridge port (defaults to {{bridgePort}})")],examples:[e("sootsim network","list the last 20 requests"),e("sootsim network 100","list the last 100"),e("sootsim network --failed","only errors and 4xx/5xx"),e("sootsim network --slow","requests slower than 1000ms, sorted by duration desc"),e("sootsim network --slow --threshold 200","tighten the slow threshold to 200ms"),e("sootsim network --filter api.example.com"),e("sootsim network --tail","live follow mode, ctrl-c to stop"),e("sootsim network --tail --failed"),e("sootsim network get req-42-l7k9"),e("sootsim network clear")],related:["inspect","debug","get","logs"]},{id:"logs",slug:"logs",navTitle:"logs",title:"sootsim logs",description:"inspect live console output from the running sootsim worker",summary:"Read the shared observability log store (console.log/info/warn/error/debug plus uncaught errors and unhandled rejections, captured in the render worker) from the terminal. List recent logs, tail in real time, filter by level or substring, or clear the buffer. Shares its data source with the devtools dock so both views stay in lock-step.",order:86,usage:["sootsim logs [limit]","sootsim logs --level <csv>","sootsim logs --filter <substring>","sootsim logs --tail","sootsim logs clear"],options:[t("--level <csv>","comma-separated level filter (log,info,warn,error,debug)"),t("--filter <substring>","message substring filter (case-insensitive)"),t("--limit <n>","max entries to print (default 50)"),t("--tail, -f","follow mode: stream new log entries as they land"),t("--json","emit structured JSON instead of formatted rows"),t("--sim <sim>","target a specific connected sim"),t("--port <number>","bridge port (defaults to {{bridgePort}})")],examples:[e("sootsim logs","last 50 log entries"),e("sootsim logs 200","last 200"),e("sootsim logs --level error,warn"),e('sootsim logs --filter "hydrate"'),e("sootsim logs --tail","live follow mode, ctrl-c to stop"),e("sootsim logs --tail --level error"),e("sootsim logs clear")],related:["network","inspect","get"]},{id:"debug",slug:"debug",navTitle:"debug",title:"sootsim debug",description:"drive __sootsimDebug: channels, snapshots, and inspectors",summary:"Toggle engine debug channels, capture snapshots, and diff internal state from the terminal while targeting any connected sim.",order:90,usage:["sootsim debug <subcommand> [args]"],options:[t("--sim <sim>","target a specific connected sim"),t("--port <number>","bridge port (defaults to {{bridgePort}})"),t("--json","emit compact JSON for machine consumption"),t("--pretty","emit formatted JSON for human inspection")],examples:[e("sootsim debug enable sheets,portals"),e("sootsim debug snapshot before"),e("sootsim debug diff before after"),e("sootsim debug recent portals 20")],related:["inspect","open","what-happened"]},{id:"what-happened",slug:"what-happened",navTitle:"what-happened",title:"sootsim what-happened",description:"show recent semantic events: toasts, keyboards, screens, alerts, fetches, errors",summary:"Read the unified semantic timeline across host, shell worker, and tenant worker. Defaults to events since the previous CLI call (per-agent cursor), so 'what-happened' reliably shows what's new instead of replaying state you already saw. Catches ephemeral UI like toasts that disappear before describe/find can find them.",order:91,usage:["sootsim what-happened","sootsim what-happened --summary","sootsim what-happened --since 5s --kinds toast,fetch"],options:[t("--summary","one-line counts by kind, no event list"),t("--flow","section the event stream by screen/route boundaries (demo-friendly recap)"),t("--all","full ring (ignore the per-identity cursor)"),t("--since <duration>","absolute window; accepts e.g. 5s, 500ms, 2m"),t("--kinds <csv>","filter to specific kinds: toast, keyboard, screen, alert, fetch, console, ..."),t("--limit <n>","cap returned events (default 200)"),t("--json","emit structured JSON"),t("--no-advance","do not advance the read cursor after rendering"),t("--sim <sim>","target a specific connected sim"),t("--port <number>","bridge port (defaults to {{bridgePort}})")],examples:[e("sootsim what-happened","since the previous CLI call"),e("sootsim what-happened --summary","5 errors \xB7 2 warnings \xB7 1 toast"),e("sootsim what-happened --kinds toast,fetch --since 10s","trace a toast back to its trigger"),e("sootsim what-happened --flow --since 5m","section the last 5 minutes by screen/route \u2014 demo-friendly recap"),e("sootsim what-happened --all --json","dump full ring as JSON")],related:["timeline","debug","logs","network","diagnose"]},{id:"diagnose",slug:"diagnose",navTitle:"diagnose",title:"sootsim diagnose",description:'composed read commands for "what just went wrong?"',summary:"One command, one round of bridge calls, one paste-able output. `sootsim diagnose recent` runs timeline + network (failed/slow) + react summary + console errors in parallel against the same sim and returns a grouped report. Beats the agent-device sed/grep workflow on both speed and information density.",order:91.5,usage:["sootsim diagnose recent [--since <duration>] [--include <csv>] [--json]"],options:[t("--since <duration>","absolute window for timeline + network (e.g. 90s, 2m; default 90s)"),t("--include <csv>","dimensions to include: timeline, network, react, console, screen (default: all)"),t("--slow-threshold <n>","network slow-request threshold in milliseconds (default 1000)"),t("--limit <n>","rows to print per react table (default 8)"),t("--json","emit a single structured JSON document"),t("--sim <sim>","target a specific connected sim"),t("--port <number>","bridge port (defaults to {{bridgePort}})")],examples:[e("sootsim diagnose recent","last 90s, all dimensions"),e("sootsim diagnose recent --since 30s --include network,react"),e("sootsim diagnose recent --json > /tmp/diagnose.json","structured snapshot for agent / CI consumption")],related:["what-happened","network","react","logs","timeline"]},{id:"timeline",slug:"timeline",navTitle:"timeline",title:"sootsim timeline",description:"control the semantic event timeline (enable opt-in kinds, clear, dump)",summary:"Distinct from `sootsim record` (video) and `sootsim debug record` (engine channels): manages the semantic agent-facing event stream that backs `sootsim what-happened`. Cheap kinds record at all times; opt-in kinds (scroll, gesture, text-input, reanimated, animation) need to be enabled.",order:92,usage:["sootsim timeline <subcommand> [args]"],options:[t("--sim <sim>","target a specific connected sim"),t("--port <number>","bridge port (defaults to {{bridgePort}})"),t("--json","emit compact JSON for machine consumption")],examples:[e("sootsim timeline status"),e("sootsim timeline start scroll,gesture","enable opt-in kinds"),e("sootsim timeline stop all","silence everything"),e("sootsim timeline dump /tmp/events.json")],related:["what-happened","debug","record"]},{id:"open",slug:"open",navTitle:"open",title:"sootsim open",description:"load a target into the current sim or open a new one",summary:"Resolve a port, dev-server URL, bundle URL, or full shell URL, then load it into the current sim by default. Fresh sims open in a separate window. Named profiles force a fresh Electron or Playwright sim because storage isolation is fixed at window/context construction time.",order:100,usage:["sootsim open <port|target|bundle-url|sootsim-url> [--new]"],options:[t("--new","open a fresh sim instead of reusing the current sim"),t("--profile <id>","open with an isolated persistent storage profile"),t("--ephemeral","open with a temporary isolated storage profile"),t("--driver <id>","launch through a specific driver (electron and playwright support profiles)"),t("--base-url <url>","base shell URL to wrap around bundle targets"),t("--port <number>","bridge port (defaults to {{bridgePort}})")],examples:[e("sootsim open 8081"),e("sootsim open --new 8085"),e("sootsim open 8081 --profile qa"),e("sootsim open 8081 --profile qa --driver playwright"),e("sootsim open http://localhost:8082")],related:["list","use","close","electron"]},{id:"list",slug:"list",navTitle:"list",title:"sootsim list",description:"list connected sims or available launch drivers",summary:"Show every connected sim, or \u2014 with `--drivers` \u2014 enumerate the launch drivers (chromium, electron, playwright, system) along with their availability on the current machine.",order:110,usage:["sootsim list [--sim <sim>] [--json]","sootsim list --drivers"],options:[t("--port <number>","bridge port (defaults to {{bridgePort}})"),t("--sim <sim>","highlight a specific sim in the output"),t("--drivers, -D","list available launch drivers instead of connected sims"),t("--json","emit array of sim objects for scripts")],examples:[e("sootsim list"),e("sootsim list --sim a6"),e("sootsim list --drivers","show every installable launch driver"),e('sootsim list --json | jq ".[] | select(.active)"')],related:["open","use","close","inspect"]},{id:"use",slug:"use",navTitle:"use",title:"sootsim use",description:"select a connected sim",summary:"Make one connected sim current by focusing it and saving it as the default target for later bridge-backed commands.",order:120,usage:["sootsim use [sim]"],aliases:["focus"],options:[t("--port <number>","bridge port (defaults to {{bridgePort}})"),t("--sim <sim>","target a specific connected sim")],examples:[e("sootsim use"),e("sootsim use a6")],related:["list","open","close"]},{id:"claim",slug:"claim",navTitle:"claim",title:"sootsim claim",description:"take an exclusive lease on a sim",summary:"Reserve a connected sim so other agents cannot send commands to it. Leases auto-expire after 60s of inactivity and refresh on every command from the owner. Use --force to steal the lease from another agent.",order:125,usage:["sootsim claim [sim] [--force]"],options:[t("--port <number>","bridge port (defaults to {{bridgePort}})"),t("--sim <sim>","target a specific connected sim"),t("--force","take the lease even if another agent currently holds it")],examples:[e("sootsim claim"),e("sootsim claim a6"),e("sootsim claim a6 --force")],related:["list","use","open","close"]},{id:"close",slug:"close",navTitle:"close",title:"sootsim close",description:"close a connected sim",summary:"Request that a sim close itself over the bridge and wait for it to disappear from the sim registry.",order:130,usage:["sootsim close [sim]"],options:[t("--port <number>","bridge port (defaults to {{bridgePort}})"),t("--sim <sim>","target a specific connected sim")],examples:[e("sootsim close"),e("sootsim close a6")],related:["list","use"]},{id:"config",slug:"config",navTitle:"config",title:"sootsim config",description:"manage sootsim.config.ts",summary:"Create, validate, and inspect project-local configuration so sootsim setup becomes repeatable instead of a pile of ad hoc edits.",order:140,usage:["sootsim config <init|validate|show>"],examples:[e("sootsim config init"),e("sootsim config validate"),e("sootsim config show")],related:["setup-repo","compat"]},{id:"compat",slug:"compat",navTitle:"compat",title:"sootsim compat",description:"check package compatibility (alias: scan)",summary:"Scan a project or specific dependency against the compat registry and summarize where native packages are fully supported, partially shimmed, or still risky.",order:150,usage:["sootsim compat [package-name]"],aliases:["scan"],options:[t("--json","emit machine-readable JSON"),t("--brief","print a compact one-line summary"),t("--app <dir>","scan a target app directory instead of cwd")],examples:[e("sootsim compat"),e("sootsim compat react-native-reanimated"),e("sootsim scan --brief")],related:["setup-repo"]},{id:"electron",slug:"electron",navTitle:"electron",title:"sootsim electron",description:"launch the desktop companion",summary:"Launch the installed desktop companion directly, optionally opening a local Metro/dev-server port through the same shell URL resolver as `sootsim open`. Profiles map to Electron partitions, so cookies and app storage stay isolated per profile.",order:160,usage:["sootsim electron [--port <number>] [--profile <id>] [--driver <id>]"],options:[t("--port <number>","open a local app server through the sootsim shell, for example Metro on 8081"),t("--profile <id>","launch with an isolated persistent storage profile"),t("--ephemeral","launch with a temporary isolated storage profile"),t("--driver <id>","override the launch driver (electron|chromium|playwright|system)")],examples:[e("sootsim electron"),e("sootsim electron --port 8081"),e("sootsim electron --profile qa"),e("sootsim electron --driver system","fall back to the OS default browser")],related:["open","list","debug"]},{id:"setup-repo",slug:"setup-repo",navTitle:"setup-repo",title:"sootsim setup-repo",description:"set up sootsim in your project",summary:"Detect the target app, add the right dependency, update the bundler config, and install a predictable dev script for new projects or monorepos.",order:180,usage:["sootsim setup-repo [options]"],options:[t("-y, --yes","skip confirmations and apply the plan immediately"),t("--dry-run","show the planned edits without changing files")],examples:[e("sootsim setup-repo"),e("sootsim setup-repo --yes"),e("sootsim setup-repo --dry-run")],related:["config","compat","install-desktop"]},{id:"daemon",slug:"daemon",navTitle:"daemon",title:"sootsim daemon",description:"manage the sootsim bridge as a login agent",summary:"Inspect, restart, start, stop, or remove the persistent sootsim bridge daemon. The daemon is registered automatically on first CLI use, so most users never need to run any of these subcommands directly. Use `sootsim daemon uninstall` to remove the launchd/systemd agent if you want sootsim off your machine.",order:173,usage:["sootsim daemon uninstall","sootsim daemon status","sootsim daemon restart","sootsim daemon start","sootsim daemon stop"],examples:[e("sootsim daemon status"),e("sootsim daemon restart"),e("sootsim daemon uninstall")],related:["server","open","install-desktop"]},{id:"server",slug:"server",navTitle:"server",title:"sootsim server",description:"run the sootsim bridge daemon in the foreground",summary:"Host the WS bridge so CLI commands work without needing vite or electron to start one. Any sim (browser, electron, headless playwright) that connects becomes drivable from the top-level inspect commands.",order:175,usage:["sootsim server [options]"],options:[t("--port <number>","bridge port (defaults to {{bridgePort}})"),t("-q, --quiet","suppress per-connection logging")],examples:[e("sootsim server"),e("sootsim server --quiet"),e("sootsim server --port 7668")],related:["electron","install-desktop","open"]},{id:"runtime",slug:"runtime",navTitle:"runtime",title:"sootsim runtime",description:"manage engine runtimes under ~/.sootsim/runtimes/",summary:"Install, list, switch, and remove versioned sootsim engine runtimes. The CLI downloads a tarball from the configured CDN (default sootbean.com), verifies its sha256, and extracts it. The daemon serves the active runtime to Electron and the browser shell.",order:176,usage:["sootsim runtime install [version]","sootsim runtime list","sootsim runtime use <version>","sootsim runtime remove <version>","sootsim runtime which"],options:[t("--channel <name>","channel for 'latest' (default: stable)"),t("--force","reinstall even if already on disk"),t("--no-set-active","don't switch active runtime after install")],examples:[e("sootsim runtime install"),e("sootsim runtime install 1.2.3"),e("sootsim runtime use 1.2.3"),e("sootsim runtime list")],related:["launch","server","daemon"]},{id:"upgrade",slug:"upgrade",navTitle:"upgrade",title:"sootsim upgrade",description:"update the sootsim CLI and engine runtime to the latest",summary:"One command to get current. Checks the npm registry for a newer `sootsim` CLI and installs it globally if your version is behind, then checks the runtime channel and installs + activates the latest engine runtime. Safe to run anytime \u2014 each half is skipped when already up to date.",order:174,usage:["sootsim upgrade"],options:[t("--channel <name>","runtime channel for 'latest' (default: stable)")],examples:[e("sootsim upgrade")],related:["runtime","start","launch"]},{id:"start",slug:"start",navTitle:"start",title:"sootsim start",description:"install + run sootsim, open it in your browser",summary:"The zero-config onboarding entrypoint \u2014 bare `sootsim` (or `npx sootsim`) defaults to this. Installs the engine runtime if missing, ensures the bridge daemon is running (or falls back to a detached server when a daemon can't be registered), waits for the bridge, then opens http://localhost:7668/ in the system browser.",order:175,usage:["sootsim start [options]","sootsim"],options:[t("--port <n>","bridge port (default: 7668)"),t("--no-open","don't open the browser at the end")],examples:[e("sootsim"),e("sootsim start"),e("npx sootsim")],related:["launch","runtime","server","daemon"]},{id:"launch",slug:"launch",navTitle:"launch",title:"sootsim launch",description:"one-shot: install runtime if needed, start daemon, open the app",summary:"The fast-path entrypoint. Ensures a runtime is installed + active (auto-installs the latest on first run), ensures the sootsim daemon is up, then launches the Electron desktop companion. Subsequent runs are a fast lockfile check + spawn.",order:177,usage:["sootsim launch [options]"],options:[t("--no-runtime-install","fail instead of auto-installing a runtime"),t("--channel <name>","runtime channel for auto-install (default: stable)"),t("--version <version>","specific runtime version to auto-install")],examples:[e("sootsim launch"),e("sootsim launch --version 1.2.3")],related:["runtime","server","daemon","install-desktop"]},{id:"install-desktop",slug:"install-desktop",navTitle:"install-desktop",title:"sootsim install-desktop",description:"download and install the optional desktop GUI",summary:"Fetch the right platform artifact (dmg / AppImage / exe) from the public release feed and install it into the OS-native location. The CLI + daemon are the canonical sootsim surface; the desktop GUI is optional. The installed app self-updates via electron-updater afterward.",order:185,usage:["sootsim install-desktop [options]"],options:[t("-y, --yes","skip confirmation and install immediately"),t("--force","reinstall even if the companion is already present")],examples:[e("sootsim install-desktop"),e("sootsim install-desktop --yes")],related:["electron","setup-repo","open"]},{id:"maestro",slug:"maestro",navTitle:"maestro",title:"sootsim maestro",description:"run existing Maestro YAML flows against sootsim",summary:"Drop-in replacement for the Maestro CLI. Runs real `.maestro/*.yaml` flows against a sim \u2014 supports all three Maestro shapes (plain array, frontmatter, multi-doc). Flag surface mirrors the Maestro CLI for easy migration.",order:41,usage:["sootsim maestro test <flow.yaml> [options]","sootsim maestro --list-compat"],options:[t("--env KEY=VALUE","set env vars for ${KEY} interpolation (repeatable)"),t("--continuous","re-run the flow on file changes (alias for --watch)"),t("--format <fmt>","accepted for maestro cli compat (not used)"),t("--new","force a fresh sim for this run"),t("--record","record a webm while the flow runs"),t("--preview","record events+video, upload, and print /preview/<id>"),t("--preview-open","open the uploaded preview link after a successful run"),t("--preview-origin <url>","upload target for --preview"),t("--preview-public-origin <url>","public link origin for --preview"),t("--slow <ms>","delay between steps for natural pacing"),t("--list-compat","print supported and unsupported Maestro verbs")],examples:[e("sootsim maestro test .maestro/login.yaml"),e("sootsim maestro --env USERNAME=alice test .maestro/login.yaml"),e("sootsim maestro test .maestro/flow.yaml --record"),e("sootsim maestro --list-compat")],related:["flow","detox","test","record"]},{id:"detox",slug:"detox",navTitle:"detox",title:"sootsim detox",description:"run existing Detox suites against sootsim on headless Chromium",summary:"Drop-in Detox runner. Existing `import from 'detox'` suites run unchanged via a Jest `moduleNameMapper` rewrite \u2014 no code edits, no config churn. Auto-launches a sootsim shell if none is running.",order:42,usage:["sootsim detox [options] [-- <jest args>]"],options:[t("--config <path>","use a specific jest config instead of the default"),t("--watch","jest watch mode"),t("--headed","keep the sootsim shell window visible"),t("-t <pattern>","pass a Jest --testNamePattern"),t("--no-launch","skip auto-launching a sootsim shell (manage it yourself)")],examples:[e("sootsim detox"),e("sootsim detox --watch"),e('sootsim detox -t "login flow"'),e("sootsim detox --headed","keep the shell visible while iterating on a failing suite")],related:["maestro","flow","test"]},{id:"upload",slug:"upload",navTitle:"upload",title:"sootsim upload",description:"publish the current bundle as a shareable /preview/<id> link",summary:"Capture the currently-loaded bundle + device spec from a running sim and publish it to a shareable `/preview/<id>` URL. Unlisted-public (unguessable id), requires signed-in desktop auth. Attach a gzipped event stream with `--events` or a webm/mp4/gif recording with `--video` \u2014 the recording embeds inline in the preview page and in any PR sticky comment.",order:73,usage:["sootsim upload [options]"],options:[t("--origin <url>","upload target (default: auto \u2014 prefers localhost, falls back to sootbean.com). Override with SOOTSIM_UPLOAD_ORIGIN env var"),t("--public-origin <url>","public preview link origin (default: sootsim.com for prod uploads). Override with SOOTSIM_PREVIEW_ORIGIN env var"),t("--events <path>","path to a gzipped events .jsonl.gz file to attach"),t("--video <path>","path to a webm/mp4/gif flow recording \u2014 embedded inline in the preview page"),t("--sim <sim>","target a specific sim (see: sootsim list)"),t("--open","open the resulting /preview/<id> url in the browser"),t("--assets-only","drop API/JSON/HTML records before upload; keep only images, fonts, css, js, and binary blobs. live-data demos hit the real network at replay time instead of serving recorded API snapshots")],examples:[e("sootsim upload"),e("sootsim upload --open"),e("sootsim upload --origin http://localhost:3000 --open"),e("sootsim upload --events ./my-run.jsonl.gz"),e("sootsim upload --video /tmp/soot-flow.webm"),e("sootsim upload --assets-only","capture assets only, let API calls hit live at replay (for /download demo apps)")],related:["flow","record","login"]},{id:"skills",slug:"skills",navTitle:"skills",title:"sootsim skills",description:"install bundled sootsim skills into a project",summary:"Copy the bundled sootsim skill markdown files (a11y review, debug, perf, visual-diff, maestro, detox, test-flow) into a target project so Claude Code, Cursor, and other agent tools can discover them.",order:122,usage:["sootsim skills --list","sootsim skills <target-dir> [skill-name...]"],options:[t("-l, --list","list available bundled skills")],examples:[e("sootsim skills --list"),e("sootsim skills .claude/skills"),e("sootsim skills .claude/skills sootsim-debug sootsim-a11y")],related:["inspect","debug"]},{id:"login",slug:"login",navTitle:"login",title:"sootsim login",description:"sign in so preview uploads are associated with your account",summary:"Authenticate the local desktop app against the upload origin. Required before `sootsim upload` (and `sootsim flow --preview`) so published preview links are associated with your account.",order:190,usage:["sootsim login [--origin <url>]"],options:[t("--origin <url>","auth host (default: auto). Override with SOOTSIM_UPLOAD_ORIGIN")],examples:[e("sootsim login"),e("sootsim login --origin http://localhost:3000")],related:["logout","whoami","upload"]},{id:"logout",slug:"logout",navTitle:"logout",title:"sootsim logout",description:"clear the local desktop auth state",summary:"Remove the stored authentication credentials for the current machine. Next `sootsim upload` will prompt for login again.",order:191,usage:["sootsim logout"],examples:[e("sootsim logout")],related:["login","whoami"]},{id:"whoami",slug:"whoami",navTitle:"whoami",title:"sootsim whoami",description:"print the currently signed-in account",summary:"Show which account the desktop app is signed in as (or report that no account is active).",order:192,usage:["sootsim whoami"],examples:[e("sootsim whoami")],related:["login","logout"]}],_=new Map(k.map(o=>[o.id,o])),D=new Map;for(let o of k){D.set(o.id,o);for(let i of o.aliases||[])D.set(i,o)}function A(o){return D.get(o)}function s(o,i,n,a,r,l){return{verb:o,description:i,usage:n,examples:a,notes:r,...l}}var S=[s("describe","show visible UI elements with type, text, position, size, and style. filters structural noise by default \u2014 use --all to include everything.",["sootsim describe [filter] [--verbose] [--watch] [--all] [--json]"],[e("sootsim describe"),e("sootsim describe button"),e("sootsim describe --verbose"),e("sootsim describe --all"),e("sootsim describe --json"),e("sootsim describe --watch")],"buttons show [button], tappable views show [tap], inputs show [input]. structural noise (icon wrappers, layout containers, duplicate text inside buttons) is pruned by default. use --all for the unfiltered view.",{shortDescription:"curated human-readable UI summary (default inspection tool)",options:[t("--verbose","include ids, labels, and extra style info"),t("--all","do not prune structural view nodes"),t("--watch","redraw as the UI changes (ctrl-c to stop)"),t("--json","emit structured JSON")],seeAlso:["tree","a11y","find"]}),s("find","unified finder (text, testid, role, type, pressable, visible, interactive-targets)",["sootsim find <text>","sootsim find --text <text>","sootsim find --testid <id>","sootsim find --role <role>","sootsim find --type <component-type>","sootsim find --pressable","sootsim find --visible","sootsim find --interactive-targets"],[e('sootsim find "Sign in"'),e("sootsim find --testid loginButton"),e("sootsim find --role button"),e("sootsim find --pressable"),e("sootsim find --visible --json"),e("sootsim find --testid loginButton --verbose"),e("sootsim find --interactive-targets","ranked list of visible+pressable nodes with copy-paste tap commands"),e("sootsim find --actions","alias for --interactive-targets")],"bare text matches findByText. flag-based forms replace the old `find-id` and `query <predicate>` subcommands. use --verbose to dump the full node JSON for each result instead of the one-liner summary. `--interactive-targets` (alias `--actions`) ranks visible+pressable nodes and emits a `sootsim do tap-...` command per row so the agent's next tap is one copy away.",{shortDescription:"locate nodes by text / testID / role / type / predicate",options:[t("--text <text>","match node text content (same as bare positional)"),t("--testid <id>","match node testID or id exactly"),t("--role <role>","match accessibilityRole"),t("--type <type>","match React component type"),t("--pressable","list all tappable nodes"),t("--visible","list all visible (non-zero) nodes"),t("--interactive-targets, --actions",'ranked tappable nodes with per-row tap command (best demo "next tap" picker)'),t("--verbose, --dump","emit full node JSON for each result, not a one-liner"),t("--json","emit structured JSON")],seeAlso:["describe","node","selector"]}),s("count","show total node count",["sootsim get count [--json]"],[e("sootsim get count"),e("sootsim get count --json")],void 0,{group:"get",subgroup:"tree",shortDescription:"total node count",options:[t("--json","emit { nodes: n } for scripts")]}),s("layout","get layout info for a node by id",["sootsim get layout <id>"],void 0,void 0,{group:"get",subgroup:"tree",shortDescription:"layout {x, y, width, height} by id/testID"}),s("tree","dump the node tree",["sootsim get tree [depth] [--json]"],[e("sootsim get tree"),e("sootsim get tree 3"),e("sootsim get tree --json")],"raw, depth-limited tree dump. good for machine consumption and debugging when describe has filtered something out. for day-to-day inspection prefer describe.",{group:"get",subgroup:"tree",shortDescription:"raw node tree dump (default depth 5)",options:[t("--json","emit { depth, tree } for scripts")],seeAlso:["describe","a11y","node"]}),s("a11y","dump the accessibility tree with roles and labels",["sootsim get a11y [depth]"],[e("sootsim get a11y")],"only accessibility-relevant fields (role, label, hint). for general inspection use describe; for raw tree use get tree.",{group:"get",subgroup:"tree",shortDescription:"accessibility tree (roles, labels, hints)",seeAlso:["describe","tree"]}),s("node","resolve testID/id/text to a full node JSON",["sootsim get node <matcher>"],[e("sootsim get node loginButton"),e('sootsim get node "Sign in"')],void 0,{group:"get",subgroup:"tree",shortDescription:"resolve testID \u2192 id \u2192 text, full node JSON",seeAlso:["find","layout"]}),s("tap","tap at absolute coordinates, or resolve --testid / --text to a node center",["sootsim do tap <x> <y>","sootsim do tap --testid <id>","sootsim do tap --text <text>"],[e("sootsim do tap 196 400"),e("sootsim do tap --testid loginButton"),e('sootsim do tap --text "Sign in"')],"uses the internal interaction bridge (fireTap), not real PointerEvent dispatch. for gesture-handler debugging, use drag/swipe which dispatch real pointer events. --testid and --text also work on double-tap and long-press.",{group:"do",subgroup:"targeting",shortDescription:"tap absolute coords, or a node by --testid / --text",seeAlso:["tap-id","tap-text","double-tap","long-press"],order:10}),s("double-tap","tap the same spot twice with a short gap",["sootsim do double-tap <x> <y> [gapMs]","sootsim do double-tap --testid <id> [gapMs]"],[e("sootsim do double-tap 196 400"),e("sootsim do double-tap --testid myButton 80")],"uses the shared interact bridge so text selection and other double-tap gestures run through the same path as live taps.",{group:"do",subgroup:"targeting",shortDescription:"tap the same coordinates twice quickly",seeAlso:["tap"],order:20}),s("tap-text","find a node by text content and tap its center",["sootsim do tap-text <text> [--nth <n>] [--within <testID>]"," [--min-y <px>] [--max-y <px>] [--min-x <px>] [--max-x <px>]"," [--near <x> <y>] [--exact] [--role <role>] [--first]"],[e('sootsim do tap-text "Sign in"'),e("sootsim do tap-text Swap --within sheet-container"),e("sootsim do tap-text Swap --min-y 700"),e("sootsim do tap-text Cancel --nth 2"),e("sootsim do tap-text OK --near 283 784")],"errors with a ranked candidate list (testID + abs position + ancestor chain) when more than one visible match remains after filters, instead of silently picking one. pass --first to keep the old behavior.",{group:"do",subgroup:"targeting",shortDescription:"findByText then tap (rich disambiguation flags)",options:[t("--nth <n>","pick the nth visible match (supports -1)"),t("--within <testID>","descendants of a testID ancestor only"),t("--min-y <px>","absolute y-position floor"),t("--max-y <px>","absolute y-position ceiling"),t("--min-x <px>","absolute x-position floor"),t("--max-x <px>","absolute x-position ceiling"),t("--near <x> <y>","pick the closest match to a coordinate"),t("--exact","exact text match (default: substring)"),t("--role <role>","narrow to accessibilityRole"),t("--first","keep legacy pick-first-silently behavior")],seeAlso:["tap","tap-id","find"],order:30}),s("tap-id","find a node by testID and tap its center",["sootsim do tap-id <id>"],[e("sootsim do tap-id loginButton")],void 0,{group:"do",subgroup:"targeting",shortDescription:"findByTestId then tap center",seeAlso:["tap","tap-text","tap-best"],order:40}),s("tap-best","tap by query: try testID first, then visible text \u2014 one command, two strategies",["sootsim do tap-best <query>"],[e('sootsim do tap-best "Create expense"',`agent doesn't need to know whether "Create expense" is a testID or text`),e("sootsim do tap-best floating-action-button")],"designed for agent ergonomics: when you have a label and don't know whether it's the testID or the visible text, tap-best tries findByTestId first (strongest signal), then findByText. prints which strategy matched. for ambiguity (multiple text matches) use tap-text with --nth / --within instead \u2014 tap-best fails fast and points you there.",{group:"do",subgroup:"targeting",shortDescription:"testID-first, text-fallback robust tap",seeAlso:["tap-id","tap-text","find"],order:41}),s("type","type text through the iOS keyboard (requires a focused input)",["sootsim do type <text>"],[e('sootsim do type "hello world"')],"a text input must be focused first via tap-id or tap-text. if no input is focused, this silently does nothing.",{group:"do",subgroup:"text input",shortDescription:"type through the iOS visual keyboard",seeAlso:["type-into","key","dismiss"],order:10}),s("type-into","find an input by testID, tap to focus, then type text",["sootsim do type-into <id> <text>"],[e('sootsim do type-into kav-input "hello world"')],"combines tap-id + type in one command. warns if the target is not a text input or if the keyboard did not open.",{group:"do",subgroup:"text input",shortDescription:"focus input by testID then type",seeAlso:["type","tap-id"],order:20}),s("key","press a special keyboard key",["sootsim do key <name>"],[e("sootsim do key return"),e("sootsim do key delete")],"valid keys: return, delete, space, shift, 123, ABC, #+=, num-back",{group:"do",subgroup:"text input",shortDescription:"special key (return, delete, space, shift, ...)",seeAlso:["key-sequence","keycode"],order:30}),s("key-sequence","press multiple visual keyboard keys in order",["sootsim do key-sequence <key> [<key> ...]"],[e("sootsim do key-sequence shift h i")],void 0,{group:"do",subgroup:"text input",shortDescription:"press multiple visual keyboard keys",seeAlso:["key","keycode"],order:40}),s("keycode","press DOM-style key codes through the visual keyboard",["sootsim do keycode <code> [<code> ...]"],[e("sootsim do keycode ShiftLeft KeyH KeyI")],void 0,{group:"do",subgroup:"text input",shortDescription:"press DOM-style key codes via visual keyboard",seeAlso:["key","key-sequence"],order:50}),s("dismiss","dismiss the keyboard",["sootsim do dismiss"],void 0,void 0,{group:"do",subgroup:"text input",shortDescription:"dismiss the iOS keyboard",order:60}),s("scroll","scroll a scrollview by testID",["sootsim do scroll <id> <dx> <dy>","sootsim do scroll --testid <id> <dx> <dy>"],[e("sootsim do scroll feed-list 0 500"),e("sootsim do scroll --testid feed-list 0 500")],void 0,{group:"do",subgroup:"gestures",shortDescription:"scroll a ScrollView by testID",seeAlso:["gesture","swipe"],order:10}),s("drag","drag from one point to another using real PointerEvent dispatch",["sootsim do drag <x1> <y1> <x2> <y2> [steps] [stepMs]"],[e("sootsim do drag 200 400 200 200 20 16")],"dispatches real PointerEvents to the canvas, unlike tap which uses the internal bridge. use this for gesture-handler and responder chain debugging.",{group:"do",subgroup:"gestures",shortDescription:"drag via real PointerEvents",seeAlso:["swipe","touch","pinch"],order:20}),s("swipe","swipe gesture (drag with fewer steps)",["sootsim do swipe <x1> <y1> <x2> <y2> [steps] [stepMs]"],[e("sootsim do swipe 350 400 50 400 10 16")],void 0,{group:"do",subgroup:"gestures",shortDescription:"drag with faster defaults",seeAlso:["drag","gesture"],order:30}),s("long-press","hold a press at coordinates",["sootsim do long-press <x> <y> [durationMs]","sootsim do long-press --testid <id> [durationMs]"],[e("sootsim do long-press 196 400 600"),e("sootsim do long-press --testid myButton 600")],void 0,{group:"do",subgroup:"gestures",shortDescription:"hold a press at coordinates",seeAlso:["tap","gesture"],order:40}),s("touch","low-level touch primitives",["sootsim do touch <down|move|up|cancel> <x> <y> [pointerId]"],[e("sootsim do touch down 200 720 1"),e("sootsim do touch up 200 720 1")],void 0,{group:"do",subgroup:"gestures",shortDescription:"low-level touch primitives",seeAlso:["drag","pinch"],order:50}),s("gesture","preset gestures such as scrolls and edge swipes",["sootsim do gesture <preset> [durationMs]"],[e("sootsim do gesture swipe-from-bottom-edge"),e("sootsim do gesture scroll-up 250")],void 0,{group:"do",subgroup:"gestures",shortDescription:"preset gestures (scroll / edge swipes)",seeAlso:["swipe","scroll"],order:60}),s("pinch","two-pointer pinch helper",["sootsim do pinch <x1> <y1> <x2> <y2> <x1'> <y1'> <x2'> <y2'> [steps] [stepMs]"],[e("sootsim do pinch 120 320 280 320 90 320 310 320")],void 0,{group:"do",subgroup:"gestures",shortDescription:"two-pointer pinch helper",seeAlso:["touch","gesture"],order:70}),s("state","dump raw runtime state (shell, worker, keyboard, node, scroll, hit, gesture)",["sootsim debug state <kind> [args]","sootsim debug state shell","sootsim debug state keyboard","sootsim debug state node <id>","sootsim debug state hit <x> <y>","sootsim debug state gesture <x> <y>"],[e("sootsim debug state shell"),e("sootsim debug state keyboard"),e("sootsim debug state node photos"),e("sootsim debug state hit 200 720")],void 0,{group:"debug",subgroup:"state",shortDescription:"runtime dumps (shell, worker, keyboard, hit, gesture, ...)",seeAlso:["js"]}),s("js","evaluate javascript in the browser context",["sootsim debug js <expression>"],[e("sootsim debug js SootSim.bridges.test.getNodeCount()"),e("sootsim debug js SootSim.bridges.keyboard.isVisible()"),e("sootsim debug js SootSim.state.root.children.length")],"reach into the SootSim global: bridges (test, debug, interact, keyboard, a11y, perf, ...), state (root, simId, ...), chrome (shell, headless, engineBase, ...).",{group:"debug",subgroup:"eval",shortDescription:"evaluate JS in the engine realm (reach into SootSim.*)",seeAlso:["eval","state"]}),s("eval","evaluate javascript (same as js)",["sootsim debug eval <expression>"],[e('sootsim debug eval "document.title"')],void 0,{group:"debug",subgroup:"eval",shortDescription:"alias for js",seeAlso:["js"]}),s("errors","show recent console errors",["sootsim get errors [n|clear] [--json]"],[e("sootsim get errors 5"),e("sootsim get errors clear"),e("sootsim get errors --json | jq length")],void 0,{group:"get",subgroup:"diagnostics",shortDescription:"console errors (default 20)",options:[t("--json","emit array of { timestamp, args, stack } for scripts")],seeAlso:["warnings","requests","logs"]}),s("warnings","show recent console warnings",["sootsim get warnings [n] [--json]"],[e("sootsim get warnings 5"),e("sootsim get warnings --json")],void 0,{group:"get",subgroup:"diagnostics",shortDescription:"console warnings (default 20)",options:[t("--json","emit array of { timestamp, args } for scripts")],seeAlso:["errors","logs"]}),s("requests","show recent failed network requests",["sootsim get requests [n|all n|clear] [--json]"],[e("sootsim get requests 5"),e("sootsim get requests all 10"),e("sootsim get requests --json")],void 0,{group:"get",subgroup:"diagnostics",shortDescription:"failed / all / clear request buffer",options:[t("--json","emit array of request entries for scripts")],seeAlso:["errors","network"]}),s("animations","list every active animation with id, kind, progress, and current value",["sootsim get animations","sootsim get animations --json"],[e("sootsim get animations","one line per running animation"),e("sootsim get animations --json | jq","structured for scripts")],'answers "what is moving right now?" without resorting to screenshots or debug eval. pair with `sootsim get animation <id>` for full spec, `sootsim do stop-animation <id>` to halt one, or `sootsim debug trace anim on <id>` for per-tick values.',{group:"get",subgroup:"animations",shortDescription:"list active animations",seeAlso:["animation","stop-animation","state"]}),s("animation","full detail for a single active animation by id",["sootsim get animation <id>"],[e("sootsim get animation 3","JSON spec + current value + progress")],"ids come from `sootsim get animations`. poll this to watch a single animation settle without enabling a trace.",{group:"get",subgroup:"animations",shortDescription:"single animation detail",seeAlso:["animations","stop-animation"]}),s("state","runtime dashboard: url, active animation counts, diagnostics, shell scene",["sootsim get state"],[e("sootsim get state","what is this sim doing right now")],'one-shot JSON snapshot answering "what is happening in this sim right now" \u2014 intended as the first call of a debugging pass. separate from `sootsim debug state <kind>`, which dumps raw runtime state for one subsystem.',{group:"get",subgroup:"diagnostics",shortDescription:"runtime dashboard snapshot",seeAlso:["animations","errors","keyboard"]}),s("memory","show engine + worker memory usage (image cache + WASM + JS heap)",["sootsim get memory","sootsim get memory --json"],[e("sootsim get memory","image cache pressure + worker/host heap"),e("sootsim get memory --json | jq","structured for monitoring")],"pulls the engine queryStats memory block. use this to verify the image cache LRU is doing its job, watch WASM-pixel pressure during a long sim run, and catch regressions in worker heap usage. workerHeap and hostHeap are chrome-only (Safari omits performance.memory).",{group:"get",subgroup:"diagnostics",shortDescription:"engine + worker memory snapshot",options:[t("--json","emit { imageLoader, workerHeap, hostHeap } for scripts")],seeAlso:["state","animations"]}),s("stop-animation","stop a running animation by id, or stop all active animations",["sootsim do stop-animation <id|all>"],[e("sootsim do stop-animation 3"),e("sootsim do stop-animation all","freeze everything for inspection")],"the id comes from `sootsim get animations`. stopping fires the animation's finished callback with finished=false, matching react-native Animated.stop() semantics.",{group:"do",subgroup:"animations",shortDescription:"stop a running animation",seeAlso:["animations","animation"]}),s("perf","performance profiling (stats, start, stop, frames, transition)",["sootsim debug perf <stats|start|stop|frames|transition> [args]"],[e("sootsim debug perf stats"),e("sootsim debug perf start"),e("sootsim debug perf stop"),e("sootsim debug perf transition goHome")],void 0,{group:"debug",subgroup:"profiling",shortDescription:"frame-timing profiling"}),s("trace","opt-in trace recorders: shell animation frames or per-tick animation values",["sootsim debug trace shell [on [limit]|off|status|clear|<recentLimit>]","sootsim debug trace anim <on <id|all> [limit]|off [id|all]|status|clear|<id> [limit]>"],[e("sootsim debug trace shell on 240","arm the shell frame recorder"),e("sootsim debug trace shell 120","dump the last 120 shell samples"),e("sootsim debug trace anim on 3 240","record up to 240 ticks for anim #3"),e("sootsim debug trace anim 3 60","read last 60 ticks for anim #3"),e("sootsim debug trace anim on all","record every active animation")],"for a focused perf trace: `get animations` to find the id, `trace anim on <id>` to arm, reproduce the issue, then `trace anim <id>` to pull per-tick value + velocity samples. `trace anim off` disables when done.",{group:"debug",subgroup:"profiling",shortDescription:"per-tick animation + shell frame trace",seeAlso:["animations","animation","stop-animation"]}),s("sample-color","average the canvas color over a pixel or rect; returns hex + rgba",["sootsim debug sample-color <x> <y> [w] [h]","sootsim debug sample-color --area x,y,w,h","sootsim debug sample-color --id <testID>","sootsim debug sample-color --text <text>"],[e("sootsim debug sample-color 196 400"),e("sootsim debug sample-color 100 200 50 80"),e("sootsim debug sample-color --id loginButton"),e('sootsim debug sample-color --text "Sign in" --json')],"coords are logical sootsim units. pixels are averaged across the rect. --json emits { r, g, b, a, hex, samples, rect } for scripts.",{group:"debug",subgroup:"profiling",shortDescription:"average canvas color over a rect",options:[t("--area x,y,w,h","rect to sample (coords are logical sootsim units)"),t("--id <testID>","sample over a node's layout rect"),t("--text <text>","sample over the node matching text"),t("--json","emit { r, g, b, a, hex, samples, rect }")],seeAlso:["screenshot"]}),s("screenshot","capture the canvas (full or a cropped area) as PNG",["sootsim screenshot [--output <path>]","sootsim screenshot --area x,y,w,h [--output <path>]","sootsim screenshot --id <testID> [--output <path>]","sootsim screenshot --text <text> [--output <path>]"],[e("sootsim screenshot"),e("sootsim screenshot --output /tmp/app.png"),e("sootsim screenshot --area 0,200,393,400"),e("sootsim screenshot --id loginButton --output button.png")],"area args use logical sootsim coords; default output is /tmp/sootsim-inspect.png.",{options:[t("--output <path>","write PNG here (default /tmp/sootsim-inspect.png)"),t("--area x,y,w,h","crop to rect (logical sootsim coords)"),t("--id <testID>","crop to a node's layout rect"),t("--text <text>","crop to the node matching text")],seeAlso:["sample-color","record"]}),s("url","show the current page URL",["sootsim get url [--json]"],[e("sootsim get url"),e("sootsim get url --json")],void 0,{group:"get",subgroup:"info",shortDescription:"current page URL",options:[t("--json","emit { url } for scripts")]}),s("reload","reload the page and wait for reconnection",["sootsim do reload"],void 0,void 0,{group:"do",subgroup:"lifecycle",shortDescription:"reload the page + wait for reconnect",seeAlso:["wait","ready"],order:10}),s("globals","list all __sootsim* globals available in the browser",["sootsim get globals"],void 0,void 0,{group:"get",subgroup:"info",shortDescription:"list available __sootsim* globals"}),s("sleep","simple local pause between commands",["sootsim do sleep [seconds]"],[e("sootsim do sleep 0.5")],void 0,{group:"do",subgroup:"lifecycle",shortDescription:"local pause between commands",seeAlso:["settle","idle"],order:40}),s("wait","wait for the current sim to reconnect",["sootsim do wait [seconds]"],[e("sootsim do wait 30")],void 0,{group:"do",subgroup:"lifecycle",shortDescription:"wait for sim reconnect",seeAlso:["ready","idle","settle"],order:20}),s("settle","wait until the visible layout is stable (default max 3s)",["sootsim do settle [seconds] [--strict]"],[e("sootsim do settle 3"),e("sootsim do settle 2 --strict")],"default requires layout hash to be stable for 3 consecutive polls \u2014 this is the right signal for slide transitions, spring-driven position changes, and scroll momentum. --strict additionally requires the worker animation flags (hasActiveAnims, hasActiveNativeAnimations, hasPendingAnimationFrames) to all be false. use --strict only when the target app has no perpetual background animation, otherwise it will time out.",{group:"do",subgroup:"lifecycle",shortDescription:"wait for animations to settle",options:[t("--strict","also require all worker animation flags to be false (use only when no perpetual bg animation)")],seeAlso:["idle","ready","selector"],order:30}),s("ready","block until the guest app bundle is loaded and painted (20s default)",["sootsim wait ready [--max-ms <ms>]"],[e("sootsim wait ready"),e("sootsim wait ready --max-ms 30000","cold uniswap/expensify reload"),e("sootsim wait ready --max-ms 5000")],"authoritative signal: the persistent __sootsimExternalAppReady flag (set by the sootsim:externalAppReady event handler, cleared on reload). polls CLI-side so --max-ms can exceed the bridge per-command timeout. prefer this over `do wait`, which is a node-count heuristic that false-positives on ConnectRN skeletons. idempotent: calling it after the app is already ready returns in ~200ms. exits non-zero on timeout.",{group:"wait",shortDescription:"block until the guest app bundle is loaded and painted",options:[t("--max-ms <ms>","override the 20s default deadline")],seeAlso:["idle","selector","settle"],order:10}),s("idle","block until animations, rAF queue, and layout are all settled",["sootsim wait idle [--max-ms <ms>] [--strict]"],[e("sootsim wait idle"),e("sootsim wait idle --max-ms 2000 --strict")],"same signal as `do settle` but under the discoverable `wait` verb. exits non-zero on timeout.",{group:"wait",shortDescription:"block until animations + rAF + layout are settled",options:[t("--max-ms <ms>","override the idle deadline"),t("--strict","also require worker animation flags to all be false")],seeAlso:["ready","selector","settle"],order:20}),s("selector","block until a node with <testid> is present with non-zero layout",["sootsim wait selector <testid> [--max-ms <ms>]"],[e("sootsim wait selector loginButton"),e("sootsim wait selector feedList --max-ms 3000")],"playwright-style. when `find --testid <id>` returns no result, the CLI now prints this command as a hint. exits non-zero on timeout.",{group:"wait",shortDescription:"block until a testID appears with non-zero layout",options:[t("--max-ms <ms>","override the deadline")],seeAlso:["ready","idle","find"],order:30}),s("event","block until a semantic timeline event of <kind> appears",["sootsim wait event <kind> [--max-ms <ms>]","sootsim wait event <kind> --filter <substring>","sootsim wait event <kind> --equals <exact>"],[e("sootsim wait event fetch --filter BeginSignIn","wait for the next BeginSignIn fetch after a tap"),e("sootsim wait event screen --equals Inbox","wait until a screen named Inbox appears"),e("sootsim wait event alert","wait for the next iOS alert/sheet"),e("sootsim wait event react-commit --max-ms 2000","block until the next React commit fires (or 2s)")],"timeline-backed companion to wait selector / wait idle. polls SootSim.bridges.timeline.recent() at 200ms; matches against url, message, name, path, title, and phase fields. defaults: --max-ms 5000, --since now (only counts events that arrive after this command starts; pass --since cursor to include prior events). exits non-zero on timeout.",{group:"wait",shortDescription:"block until the next timeline event of a given kind",options:[t("--max-ms <ms>","wait deadline in milliseconds (default 5000)"),t("--filter <substring>","substring match against url/message/name/path/title (case-insensitive)"),t("--equals <exact>","exact match against any of the same fields"),t("--since <mode>",'"now" (default \u2014 only events after this command starts) or "cursor"'),t("--json","emit { found, elapsedMs, event } JSON")],seeAlso:["ready","idle","selector"],order:40}),s("dispatch","dispatch a raw keyboard key (bypasses visual shift state)",["sootsim do dispatch <char>"],[e("sootsim do dispatch A")],void 0,{group:"do",subgroup:"text input",shortDescription:"raw key (bypasses visual shift state)",seeAlso:["key","type"],order:70}),s("keyboard","on-screen keyboard state (type, returnKey, autoCap, mode, shift, accessory)",["sootsim get keyboard [--json]"],[e("sootsim get keyboard"),e("sootsim get keyboard --json")],void 0,{group:"get",subgroup:"info",shortDescription:"on-screen keyboard state",options:[t("--json","emit structured JSON")],seeAlso:["type","dismiss","state"]}),s("screens","list the current navigation stack: active app, route stack, modal stack, keyboard",["sootsim get screens [--json]"],[e("sootsim get screens"),e("sootsim get screens --json")],'pulls from the shell (active app, switcher phase), the navigation bridge (route stack for react-navigation / one-router if present), and the top presented modal. use this when a screen id or nav-state is the fastest orientation signal \u2014 e.g. "am I on the onboarding landing, or did the login sheet open over it?". Exits quietly when no navigation bridge is available.',{group:"get",subgroup:"info",shortDescription:"current screen + modal + route stack",options:[t("--json","emit structured JSON")],seeAlso:["describe","state","keyboard"]})],L=new Map(S.map(o=>[o.verb,o]));function x(o){return L.get(o)}function f(o,i=w){return o.replaceAll("{{bridgePort}}",String(i.bridgePort)).replaceAll("{{defaultShellUrl}}",i.defaultShellUrl)}function R(){return k.slice().sort((o,i)=>o.order-i.order)}function y(o,i){let n=Math.max(...i.map(r=>r.name.length),0)+2,a=i.map(r=>` ${r.name.padEnd(n)}${r.desc}`);return` ${o}
3
- ${a.join(`
4
- `)}`}function j(o,i){let n=o.usage[0]??`sootsim ${o.verb}`,a=[i?`sootsim ${i} `:"","sootsim inspect ","sootsim "].filter(Boolean),r=n;for(let l of a)if(r.startsWith(l)){r=r.slice(l.length);break}return r}function C(o){return o.shortDescription??o.description}function I(o){return S.filter(i=>i.group===o)}function q(o){let i=I(o);if(!i.length)return"";let n=new Map;for(let l of i){let d=l.subgroup??"",m=n.get(d)??[];m.push(l),n.set(d,m)}let a=[];for(let l of i){let d=l.subgroup??"";a.includes(d)||a.push(d)}let r=[];for(let l of a){let d=n.get(l)??[];d.sort((g,v)=>{let c=g.order??Number.POSITIVE_INFINITY,b=v.order??Number.POSITIVE_INFINITY;return c!==b?c-b:g.verb.localeCompare(v.verb)});let m=d.map(g=>({name:j(g,o),desc:C(g)})),h=Math.max(...m.map(g=>g.name.length),0)+2,p=l?` ${l}:`:"",u=m.map(g=>` ${g.name.padEnd(h)}${g.desc}`);r.push([p,...u].filter(Boolean).join(`
5
- `))}return r.join(`
6
-
7
- `)}function H(o,i=w,n=""){let r={do:{title:"interact with the running sootsim instance",noun:"<action>"},get:{title:"read state from the running sootsim instance",noun:"<noun>"},debug:{title:"drive __sootsimDebug: channels, snapshots, inspectors",noun:"<tool>"},wait:{title:"block until a runtime condition is met",noun:"<condition>"}}[o];if(!r)return null;let l=q(o);if(!l)return null;let d=I(o).flatMap(u=>u.examples??[]).slice(0,8).map(u=>` ${f(u.command,i)}`).join(`
8
- `),m=`
9
- options:
10
- --port <port> WS bridge port (default: ${i.bridgePort})
11
- --sim <sim> target a specific connected sim
12
- --timeout <ms> per-command timeout (default: 15000)${n?`
13
- `+n:""}`,p=`see also: ${["do","get","debug","wait"].filter(u=>u!==o).map(u=>`sootsim ${u} --help`).join(", ")}, sootsim inspect --help`;return`
14
- sootsim ${o} ${r.noun} \u2014 ${r.title}
15
-
16
- ${l}
17
- ${d?`
18
- examples:
19
- ${d}
20
- `:""}${m}
21
-
22
- ${p}
23
- `.trim()}function K(o=w,i=""){let n=new Set(["get","do","debug","wait","inspect"]),a=R().filter(c=>!n.has(c.id)).map(c=>` ${c.id.padEnd(16)}${f(c.description,o)}`).join(`
24
- `),r=I("find"),l=x("find"),d=r.length?r.map(c=>({name:j(c,null),desc:C(c)})):[{name:"find <text>",desc:"findByText (shorthand \u2014 bare text)"},...(l?.options??[]).map(c=>({name:`find ${c.flag.split(",")[0]?.trim()??""}`.trim(),desc:c.description}))],m=C(x("describe")??{verb:"describe",description:"",usage:[]}),h=c=>I(c).slice().sort((b,T)=>{let M=b.order??Number.POSITIVE_INFINITY,O=T.order??Number.POSITIVE_INFINITY;return M!==O?M-O:b.verb.localeCompare(T.verb)}).map(b=>({name:j(b,c),desc:C(b)})),p=h("get"),u=h("do"),g=h("debug"),v=h("wait");return`
25
- sootsim \u2014 iOS simulator for React Native
26
-
27
- usage:
28
- sootsim show getting-started + this help
29
- sootsim [command] [options]
30
- sootsim [-- <bundler-command>]
31
-
32
- commands:
33
- ${a}
34
-
35
- runtime verbs:
36
- sootsim describe [filter] [--verbose] [--json]
37
- ${m}
38
-
39
- ${y("sootsim find <...> unified finder",d)}
40
-
41
- ${y("sootsim get <noun> read runtime state",p)}
42
-
43
- ${y("sootsim do <action> drive the app",u)}
44
-
45
- sootsim shell <action> simulator shell + chrome
46
- launch <appId> launch an app
47
- home go home
48
- switcher open app switcher
49
- appearance <mode> set light/dark/auto/toggle
50
- lock toggle lock state
51
- shake trigger shake
52
-
53
- ${y("sootsim debug <tool> instrumentation",g)}
54
-
55
- ${y("sootsim wait <condition> block on runtime state",v)}
56
-
57
- sim targeting:
58
- sootsim list show every connected sim and its id
59
- sootsim use <sim> make one sim current (saved default)
60
- sootsim --sim <sim> <cmd> override the saved default for one call
61
- sootsim claim <sim> take an exclusive lease on a sim
62
-
63
- global flags:
64
- -h, --help show help
65
- -V, --version show version
66
- -v, --verbose verbose output
67
- -p, --port <number> server port
68
- ${i}
69
-
70
- examples:
71
- sootsim open start in browser
72
- sootsim open 8081 start in browser, opening React Native app on 8081
73
- sootsim electron open electron
74
- sootsim describe inspect the current UI tree
75
- sootsim find --testid loginButton look up a node by testID
76
- sootsim get errors 5 show the latest console failures
77
- sootsim get screens current route / modal / keyboard state
78
- sootsim do tap-text "Sign in" tap a node by its text
79
- sootsim debug state shell inspect shell runtime state
80
- sootsim shell appearance dark set simulator appearance
81
- `.trimStart()}function $(o,i){if(!o?.length)return"";let n=Math.max(...o.map(r=>r.flag.length),0)+2;return`
82
- options:
83
- ${o.map(r=>` ${f(r.flag,i).padEnd(n)}${f(r.description,i)}`).join(`
84
- `)}
85
- `}function N(o,i){let n=$(o.options,i),a=o.examples?.length?`
86
- examples:
87
- ${o.examples.map(p=>` ${f(p.command,i)}`).join(`
88
- `)}
89
- `:"",r=o.notes?`
90
- note:
91
- ${o.notes}
92
- `:"",l=o.seeAlso?.length?`
93
- see also: ${o.seeAlso.map(p=>`sootsim ${p}`).join(", ")}
94
- `:"",d=A(o.verb)!=null,m=o.group?`sootsim ${o.group} --help`:null,h=m?`
95
- full list: ${m}`:d?"":`
96
- full list: sootsim inspect --help`;return`
97
- sootsim ${o.verb} \u2014 ${o.description}
98
-
99
- usage:
100
- ${o.usage.map(p=>f(p,i)).join(`
101
- `)}${n}${a}${r}${l}${h}`.trimEnd()}function X(o,i=w){let n=x(o);if(n)return N(n,i);let a=A(o);if(!a)return null;let r=a.aliases?.length?`
102
- aliases:
103
- ${a.aliases.join(", ")}
104
- `:"",l=a.options?.length?`
105
- options:
106
- ${a.options.map(m=>` ${f(m.flag,i).padEnd(24)}${f(m.description,i)}`).join(`
107
- `)}
108
- `:"",d=a.examples?.length?`
109
- examples:
110
- ${a.examples.map(m=>` ${f(m.command,i)}`).join(`
111
- `)}
112
- `:"";return`
113
- sootsim ${a.id} \u2014 ${f(a.description,i)}
114
-
115
- usage:
116
- ${a.usage.map(m=>f(m,i)).join(`
117
- `)}${r}${l}${d}`.trimEnd()}var E={"--help":{type:"boolean",short:"-h"},"--version":{type:"boolean",short:"-V"},"--verbose":{type:"boolean",short:"-v"},"--port":{type:"number",short:"-p"},"--device":{type:"string",short:"-d"},"--theme":{type:"string",short:"-t"},"--headless":{type:"boolean"},"--driver":{type:"string"},"--sim":{type:"string"}},P=new Set(["list","describe","find","get","do","wait","network","logs","shell"]),U={tap:"sootsim do tap","double-tap":"sootsim do double-tap","tap-text":"sootsim do tap-text","tap-id":"sootsim do tap-id",type:"sootsim do type","type-into":"sootsim do type-into",key:"sootsim do key",dispatch:"sootsim do dispatch",dismiss:"sootsim do dismiss",scroll:"sootsim do scroll",drag:"sootsim do drag",swipe:"sootsim do swipe",pinch:"sootsim do pinch",reload:"sootsim do reload",sleep:"sootsim do sleep",settle:"sootsim do settle",tree:"sootsim get tree",a11y:"sootsim get a11y",count:"sootsim get count",layout:"sootsim get layout",url:"sootsim get url",globals:"sootsim get globals",errors:"sootsim get errors",warnings:"sootsim get warnings",requests:"sootsim get requests",node:"sootsim get node",state:"sootsim debug state",js:"sootsim debug js",eval:"sootsim debug eval",perf:"sootsim debug perf","sample-color":"sootsim debug sample-color","find-id":"sootsim find --testid",query:"sootsim find --text|--role|--type|--pressable|--visible",capture:"sootsim screenshot"},F=new Set(["preview","test","detox","maestro","flow","record","profile","cpu-profile","react","screenshot","screenshots","screenshot-mode","three-mode","3d-mode","inspect","debug","timeline","what-happened","diagnose","shell","assert","device","open","list","use","focus","claim","close","config","compat","scan","electron","login","logout","whoami","install","setup-repo","install-desktop","server","daemon","runtime","upgrade","launch","start","skills","upload","keys","hints","agent-wrapper","agent",...P,...Object.keys(U)]);function Z(o){let i=o.slice(2),n={command:null,commandArgs:[],globalFlags:{},help:!1,version:!1,verbose:!1},a=0;for(;a<i.length;){let r=i[a];if(r==="--"){n.commandArgs.push(...i.slice(a+1));break}let l=Object.entries(E).find(([d,m])=>d===r||m.short===r);if(l){let[d,m]=l,h=d.replace(/^--/,"");if(m.type==="boolean")n.globalFlags[h]=!0,a++;else{let p=i[a+1];if(p===void 0||p.startsWith("-")){console.error(` warning: ${r} requires a value`),a++;continue}if(m.type==="number"){let u=Number(p);if(Number.isNaN(u)){console.error(` warning: ${r} requires a number, got "${p}"`),a+=2;continue}n.globalFlags[h]=u}else n.globalFlags[h]=p;a+=2}continue}if(!n.command&&!r.startsWith("-")){if(F.has(r)){n.command=r;let d=i.slice(a+1);n.commandArgs=d[0]==="--"?d.slice(1):d;break}n.commandArgs=i.slice(a);break}n.commandArgs.push(r),a++}return n.help=!!n.globalFlags.help,n.version=!!n.globalFlags.version,n.verbose=!!n.globalFlags.verbose,n}export{H as a,K as b,X as c,P as d,U as e,Z as f};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as l,b as d}from"./chunk-WZE6T3GT.js";import{a as c,c as m}from"./chunk-TK2IPNHL.js";import{a as n,b as s,c as u}from"./chunk-EOWN4ZFJ.js";function g(){let r=n();return r?{available:!0,reason:null,detail:r}:{available:!1,reason:"no chromium-family browser found (chrome, chromium, edge, brave, arc)"}}async function D(r){let e=n();if(!e)return{launched:!1,message:"chromium not found \u2014 install chrome or run `sootsim list --drivers`"};if(!r.url)return{launched:!1,message:"chromium driver requires a target url"};if(r.profileId||r.ephemeralProfile)return{launched:!1,message:"profiles require electron or playwright; browser tabs use your current Chrome profile"};try{let i=await u(r.url,{headless:r.headless,detached:r.detached??!0});return{launched:!0,message:`chromium launched \u2192 ${r.url}`,pid:i.pid,target:i.target??e,attachUrl:r.url}}catch(i){return{launched:!1,message:`chromium spawn failed: ${i instanceof Error?i.message:String(i)}`}}}var h={id:"chromium",name:"chromium",description:"system chrome / chromium / edge \u2014 supports --headless",kind:"browser",availability:g,launch:D};function w(){let r=l();return r?{available:!0,reason:null,detail:`${r.kind} @ ${r.path}`}:{available:!1,reason:"sootsim desktop app not installed (run `sootsim install-desktop`)"}}async function b(r){let e=l();if(!e)return{launched:!1,message:"sootsim desktop app not installed"};try{let i=await d(r.url,e,{device:r.device,profileId:r.profileId,ephemeralProfile:r.ephemeralProfile});return i.launched?{launched:!0,message:r.url?`electron launched via ${i.via} \u2192 ${r.url}`:`electron launched via ${i.via}`,target:i.target,attachUrl:r.url}:{launched:!1,message:"desktop companion failed to start"}}catch(i){return{launched:!1,message:`electron launch failed: ${i instanceof Error?i.message:String(i)}`}}}var p={id:"electron",name:"electron",description:"sootsim desktop companion app (native window, menu bar)",kind:"native",availability:w,launch:b};import{mkdtempSync as L}from"fs";import{createRequire as k}from"module";import{tmpdir as x}from"os";import{join as P}from"path";function f(){try{let r=k(process.cwd()+"/");for(let e of["playwright","playwright-chromium","playwright-core"])try{return r.resolve(e),e}catch{}}catch{}return null}function $(){let r=f();return r?{available:!0,reason:null,detail:`resolved via ${r}`}:{available:!1,reason:"playwright not installed in the current workspace"}}async function R(r){let e=f();if(!e)return{launched:!1,message:"playwright not installed \u2014 run `bun add -D playwright` first"};if(!r.url)return{launched:!1,message:"playwright driver requires a target url"};try{let i=await import(e);return await(await(r.profileId?await i.chromium.launchPersistentContext(c(m(r.profileId).id),{headless:r.headless??!0}):r.ephemeralProfile?await i.chromium.launchPersistentContext(L(P(x(),"sootsim-playwright-profile-")),{headless:r.headless??!0}):await(async()=>(await i.chromium.launch({headless:r.headless??!0})).newContext())()).newPage()).goto(r.url),{launched:!0,message:`playwright chromium launched \u2192 ${r.url}`,target:e,attachUrl:r.url}}catch(i){return{launched:!1,message:`playwright launch failed: ${i instanceof Error?i.message:String(i)}`}}}var v={id:"playwright",name:"playwright",description:"programmatic chromium via the playwright package \u2014 headless default",kind:"automation",availability:$,launch:R};function A(){return process.platform!=="darwin"&&process.platform!=="linux"&&process.platform!=="win32"?{available:!1,reason:`unsupported platform: ${process.platform}`}:{available:!0,reason:null,detail:process.platform==="darwin"?"open(1)":process.platform==="win32"?"cmd /c start":"xdg-open(1)"}}async function I(r){if(r.headless)return{launched:!1,message:"system driver does not support --headless; use chromium or playwright"};if(!r.url)return{launched:!1,message:"system driver requires a target url"};if(r.profileId||r.ephemeralProfile)return{launched:!1,message:"profiles require electron or playwright; browser tabs use your current browser profile"};try{let e=await s(r.url,{detached:r.detached??!0,newWindow:r.newWindow});return{launched:!0,message:`${e.via} launch \u2192 ${r.url}`,pid:e.pid,target:e.target,attachUrl:r.url}}catch(e){return{launched:!1,message:`system launch failed: ${e instanceof Error?e.message:String(e)}`}}}var y={id:"system",name:"system",description:"shared browser launcher \u2014 system default or chromium window",kind:"delegate",availability:A,launch:I};var t=[h,p,v,y];function M(){return t}function N(r){return t.find(e=>e.id===r)??null}function Q(r,e){if(r)return t.find(a=>a.id===r)??null;for(let i of e){let a=t.find(o=>o.id===i);if(a&&a.availability().available)return a}return null}function T(){return t.map(r=>{let e=r.availability();return{id:r.id,name:r.name,kind:r.kind,description:r.description,available:e.available,reason:e.reason,detail:e.detail??null}})}export{h as a,p as b,v as c,y as d,t as e,M as f,N as g,Q as h,T as i};
@@ -1,61 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as P,b as v}from"./chunk-J6BPROH4.js";import{d as j}from"./chunk-Z3I2I4IO.js";import{a as b}from"./chunk-W2XRHDQQ.js";import{execSync as A}from"child_process";import{existsSync as w,readFileSync as _,writeFileSync as x}from"fs";import{relative as B,resolve as m}from"path";import{existsSync as a,readFileSync as k,readdirSync as O,statSync as R}from"fs";import{basename as L,dirname as T,join as c}from"path";function D(e){let t=e;for(;;){if(a(c(t,"turbo.json"))||a(c(t,"nx.json"))||a(c(t,"lerna.json"))||a(c(t,"pnpm-workspace.yaml")))return t;let s=c(t,"package.json");if(a(s))try{if(JSON.parse(k(s,"utf8")).workspaces)return t}catch{}let i=T(t);if(i===t)break;t=i}return e}function W(e){return a(c(e,"bun.lock"))||a(c(e,"bun.lockb"))?"bun":a(c(e,"pnpm-lock.yaml"))?"pnpm":a(c(e,"yarn.lock"))?"yarn":"npm"}function J(e){if(a(c(e,"turbo.json")))return"turbo";if(a(c(e,"nx.json")))return"nx";if(a(c(e,"pnpm-workspace.yaml")))return"pnpm";let t=c(e,"package.json");if(a(t))try{if(JSON.parse(k(t,"utf8")).workspaces)return"npm-workspaces"}catch{}return"single"}function N(e){return e.one?"one":e.expo?"expo":e["react-native"]?"bare":"unknown"}function y(e){let t=c(e,"package.json");if(!a(t))return null;let s;try{s=JSON.parse(k(t,"utf8"))}catch{return null}let i={...s.dependencies,...s.devDependencies},r=N(i);return r==="unknown"?null:{dir:e,name:s.name||L(e),framework:r,hasViteConfig:a(c(e,"vite.config.ts"))||a(c(e,"vite.config.js")),hasMetroConfig:a(c(e,"metro.config.js"))||a(c(e,"metro.config.ts")),hasSootsimDependency:!!i.sootsim,devCommand:s.scripts?.dev||null}}function V(e){let t=[],s=c(e,"pnpm-workspace.yaml");if(a(s)){let o=k(s,"utf8").match(/packages:\s*\n((?:\s+-\s+.+\n?)+)/);if(o){let n=o[1].split(`
3
- `).filter(Boolean);for(let g of n){let p=g.replace(/^\s*-\s*['"]?/,"").replace(/['"]?\s*$/,"");p&&t.push(...S(e,p))}}return t}let i=c(e,"package.json");if(a(i))try{let r=JSON.parse(k(i,"utf8")),o=Array.isArray(r.workspaces)?r.workspaces:r.workspaces?.packages||[];for(let n of o)t.push(...S(e,n))}catch{}return t}function S(e,t){let s=t.replace(/\/\*\*?$/,"").replace(/\*$/,""),i=c(e,s);if(!a(i))return[];try{return O(i).map(o=>c(i,o)).filter(o=>{try{return R(o).isDirectory()&&a(c(o,"package.json"))}catch{return!1}})}catch{return[]}}function M(e){let t=J(e),s=W(e);if(t==="single"){let n=y(e);return{root:e,type:t,packageManager:s,apps:n?[n]:[]}}let i=V(e),r=[],o=y(e);o&&r.push(o);for(let n of i){if(n===e)continue;let g=y(n);g&&r.push(g)}return{root:e,type:t,packageManager:s,apps:r}}function $(e){switch(e){case"bun":return"bun add -d sootsim";case"pnpm":return"pnpm add -D sootsim";case"yarn":return"yarn add -D sootsim";case"npm":return"npm install -D sootsim"}}async function ce(e){(e.includes("--help")||e.includes("-h"))&&(console.log(`
4
- sootsim setup-repo \u2014 set up sootsim in your project
5
-
6
- detects your bundler (one, expo, metro), adds sootsim as a
7
- dependency, configures the bundler plugin, and adds dev scripts.
8
-
9
- usage:
10
- sootsim setup-repo interactive setup
11
- sootsim setup-repo --yes skip confirmations
12
- sootsim setup-repo --dry-run show what would be done
13
-
14
- works with monorepos (turbo, pnpm, nx) \u2014 scans for app packages.
15
- `),process.exit(0));let t=e.includes("--yes")||e.includes("-y"),s=e.includes("--dry-run"),i=process.cwd(),r=D(i),o=M(r);if(console.log(`
16
- sootsim \u2014 project setup
17
- `),!s)try{await j()}catch(l){console.error(` bootstrap failed: ${l instanceof Error?l.message:String(l)}`),console.error(" continuing with project setup; rerun later if needed.")}o.apps.length===0&&(console.log(" no react native apps found in this project."),console.log(` sootsim works with one, expo, and react-native projects.
18
- `),process.exit(1));let n;if(o.type!=="single"&&o.apps.length>1){console.log(` monorepo detected (${o.type} + ${o.packageManager})
19
- `);let l=await v("which app?",o.apps.map(f=>`${(B(r,f.dir)||".").padEnd(30)} (${f.framework})`));n=o.apps[l]}else n=o.apps[0];let g=n.framework==="one"?"metro (via one)":n.framework==="expo"?"metro (via expo)":"metro";console.log(" detected:"),console.log(` framework: ${n.framework}`),console.log(` bundler: ${g}`),console.log(` package: ${n.name}`),console.log(` manager: ${o.packageManager}`),console.log();let p=[];if(!n.hasSootsimDependency){let l=$(o.packageManager);p.push({label:"add sootsim as devDependency",run:()=>{A(l,{cwd:n.dir,stdio:"pipe"})}})}n.framework==="one"&&n.hasViteConfig?p.push({label:"add sootsimPlugin() to vite.config.ts",run:()=>C(n.dir)}):(n.framework==="expo"||n.framework==="bare")&&n.hasMetroConfig?p.push({label:"add withSootsim() to metro.config.js",run:()=>G(n.dir)}):n.framework==="one"?p.push({label:"add sootsimPlugin() to vite.config.ts",run:()=>C(n.dir)}):p.push({label:"create metro.config.js with withSootsim()",run:()=>z(n.dir,n.framework)}),console.log(" will:"),p.forEach((l,f)=>console.log(` ${f+1}. ${l.label}`)),console.log(),s&&(console.log(` (dry run \u2014 no changes made)
20
- `),process.exit(0)),t||(await P("continue?")||(console.log(`
21
- cancelled.
22
- `),process.exit(0)),console.log());let d=0;for(let l=0;l<p.length;l++){let f=p[l];process.stdout.write(` [${l+1}/${p.length}] ${f.label}...`);try{f.run(),console.log(" done")}catch(h){d++,console.log(` failed: ${h.message}`),(f.label.includes("vite.config")||f.label.includes("metro.config"))&&H(n)}}b({event:"cli_setup_repo",properties:{framework:n.framework,package_manager:o.packageManager,workspace_type:o.type,step_count:p.length,step_failures:d}}),console.log(`
23
- done! run your app normally and hit:
24
-
25
- then open:
26
- http://localhost:8081/__soot/
27
- `)}function C(e){let t=m(e,"vite.config.ts")||m(e,"vite.config.js"),s=m(e,"vite.config.ts"),i=m(e,"vite.config.js"),r=w(s)?s:i;if(!w(r))throw new Error(`no vite config found at ${e}`);let o=_(r,"utf8");if(o.includes("sootsimPlugin")){console.log(" (already configured)");return}let n="import { sootsimPlugin } from 'sootsim/vite'",g=/^(import\s+.+)$/gm,p=0,d;for(;(d=g.exec(o))!==null;)p=d.index+d[0].length;p>0?o=o.slice(0,p)+`
28
- `+n+o.slice(p):o=n+`
29
- `+o;let l=o.match(/plugins\s*:\s*\[/);if(l&&l.index!==void 0){let f=l.index+l[0].length,h=1,u=f;for(;u<o.length&&h>0;)o[u]==="["&&h++,o[u]==="]"&&h--,h>0&&u++;let E=o.slice(0,u),q=o.slice(u),F=o.slice(f).match(/\n(\s+)\S/)?.[1]||" ",I=o.slice(o.lastIndexOf(`
30
- `,u),u+1).match(/\n(\s*)/)?.[1]||" ";o=E.trimEnd()+`
31
- `+F+`sootsimPlugin(),
32
- `+I+q}else throw new Error("could not find plugins array in vite config");x(r,o)}function G(e){let t=m(e,"metro.config.js"),s=m(e,"metro.config.ts"),i=w(t)?t:s;if(!w(i))throw new Error(`no metro config found at ${e}`);let r=_(i,"utf8");if(r.includes("withSootsim")){console.log(" (already configured)");return}r="const { withSootsim } = require('sootsim/metro')"+`
33
- `+r,r=r.replace(/(module\.exports\s*=\s*)([^;]+)/,(n,g,p)=>`${g}withSootsim(${p.trim()})`),x(i,r)}function z(e,t){let s=m(e,"metro.config.js"),i;t==="expo"?i=`const { getDefaultConfig } = require('expo/metro-config')
34
- const { withSootsim } = require('sootsim/metro')
35
-
36
- const config = getDefaultConfig(__dirname)
37
-
38
- module.exports = withSootsim(config)
39
- `:i=`const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config')
40
- const { withSootsim } = require('sootsim/metro')
41
-
42
- const config = {}
43
-
44
- module.exports = withSootsim(mergeConfig(getDefaultConfig(__dirname), config))
45
- `,x(s,i)}function H(e){e.framework==="one"?console.log(`
46
- manual setup \u2014 add to your vite.config.ts:
47
-
48
- import { sootsimPlugin } from 'sootsim/vite'
49
-
50
- export default {
51
- plugins: [
52
- one({ /* ... */ }),
53
- sootsimPlugin(),
54
- ],
55
- }
56
- `):console.log(`
57
- manual setup \u2014 add to your metro.config.js:
58
-
59
- const { withSootsim } = require('sootsim/metro')
60
- module.exports = withSootsim(config)
61
- `)}export{ce as a};
@@ -1,11 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as B,b as H,c as ne}from"./chunk-CCZHRBXJ.js";import{b as T}from"./chunk-QMBYRPRK.js";import{g as se}from"./chunk-CAJ247SC.js";import{a as x,b as z}from"./chunk-WZE6T3GT.js";import{c as re}from"./chunk-TK2IPNHL.js";import{d as te}from"./chunk-EOWN4ZFJ.js";import{a as Y}from"./chunk-S4PJMUC7.js";import{b as oe,c as S,d as w,e as $}from"./chunk-PNGBWMQH.js";import{c as N,d as g,e as F}from"./chunk-Y66CDFAT.js";import{E as Z,F as ee}from"./chunk-YKEBL6GE.js";import ve from"node:fs";import xe from"node:os";import j from"node:path";function ge(e){return e.replace(/^\[|\]$/g,"").toLowerCase()}function _(e){let o=ge(e);return o==="localhost"||o.endsWith(".localhost")||o==="0.0.0.0"||o==="::1"||/^127(?:\.\d{1,3}){3}$/.test(o)}function ie(e){return new Error(`could not resolve a native bundle for ${e}. pass an explicit bundle URL or open Connect and choose the app there.`)}function we(e,o){return e?.includes("/one/metro-entry.bundle")?"one":typeof o=="string"&&o?"expo":"unknown"}function ce(e,o,t){return`${e}//${o}:${t}`}function be(e){if(typeof window>"u")return!1;try{let o=new URL(e);return _(o.hostname)&&_(window.location.hostname)&&o.origin!==window.location.origin}catch{return!1}}async function ae(e,o){let t={...o,cache:o?.cache??"no-store"};return be(e)?fetch(`/__fetch-proxy?url=${encodeURIComponent(e)}`,t):fetch(e,t)}function ye(e){return e==="https:"?443:80}function Se(e){let o=e.pathname||"/";return(o==="/"||o==="")&&!e.search&&!e.hash}async function le(e,o){let t=e.replace(/\/+$/,"");try{let n=await ae(`${t}/`,{headers:{"expo-platform":"ios"}});if(n.ok){let r=await n.json(),i=r?.extra?.expoClient||r?.extra||{},s=typeof r?.launchAsset?.url=="string"?r.launchAsset.url:void 0;if(s||i.name)return{bundleUrl:ne(s||`${t}${H}`),port:o,framework:we(s,i.sdkVersion),projectName:i.name}}}catch{}try{let n=await ae(`${t}/status`);if(n.ok&&(await n.text()).includes("packager-status:running"))return{bundleUrl:`${t}${H}`,port:o,framework:"metro"}}catch{}return null}async function $e(e){return le(ce("http:","localhost",e),e)}async function q(e){let o=e.trim();if(/^\d+$/.test(o)){let a=parseInt(o,10),l=await $e(a);if(l)return l;throw ie(`localhost:${a}`)}let t=o.startsWith("http")?o:`http://${o}`,n;try{n=new URL(t)}catch{throw new Error(`could not parse "${e}". pass a dev-server port, a dev-server base URL, or a full bundle URL.`)}let r=n.protocol||"http:",i=n.port?parseInt(n.port,10):ye(r),s=ce(r,n.hostname,i);if(_(n.hostname)&&Se(n)){let a=await le(s,i);if(a)return a;throw ie(s)}return{bundleUrl:n.toString(),port:i,framework:"unknown"}}function qe(){console.error(" no sootsim desktop companion found."),console.error(""),console.error(" install the desktop app:"),console.error(" sootsim install-desktop"),console.error(""),console.error(" or wire sootsim into your own project so your dev server hosts the bridge:"),console.error(" sootsim setup-repo"),console.error("")}function me(e){let o=x();if(console.log(` note: no sootsim bridge detected on port ${e}`),o){console.log(" launch the installed companion with:"),console.log(" sootsim electron");return}console.log(""),console.log(" to get a bridge running, either:"),console.log(" sootsim install-desktop # download the electron app"),console.log(" sootsim setup-repo # wire sootsim into your own project")}function je(e){console.error(""),console.error(` no sim is connected to the sootsim bridge on port ${e}.`),console.error(""),console.error(" start your app dev server, then open it in sootsim:"),console.error(" npx expo start --localhost --port 8081"),console.error(" sootsim open 8081"),console.error(""),console.error(" then inspect the live app:"),console.error(" sootsim describe")}var Ce={rn:"/rn",connectrn:"/rn","connect-rn":"/rn",clock:"/app/clock","native-ui":"/app/native-ui",tamagui:"/app/tamagui",settings:"/app/settings",photos:"/app/photos",camera:"/app/camera"};function I(e){return new Promise(o=>setTimeout(o,e))}function G(e){return e.trim()}function W(e){try{let o=new URL(e),t=o.pathname.replace(/\/+$/,"")||"/";return o.searchParams.has("open")||o.searchParams.has("port")||o.searchParams.has("bundle")||o.searchParams.has("demo")||o.pathname.includes("/sootsim/index.html")||t==="/sootsim"||o.pathname==="/__soot"||o.pathname==="/__soot/"||t==="/rn"||/^\/rn\/[^/]+$/i.test(t)||/^\/app\/[^/]+$/i.test(t)||t==="/__soot/rn"||/^\/__soot\/rn\/[^/]+$/i.test(t)||/^\/__soot\/app\/[^/]+$/i.test(t)}catch{return!1}}async function at(e){let o=G(e);return(await q(o)).bundleUrl}function Q(e){let o=e.replace(/\/+$/,"")||"/";return o==="/__soot"||o.startsWith("/__soot/")?"/__soot":""}function Pe(e,o){let t=G(e),n=new URL(o),r=Q(n.pathname);return n.pathname=`${r}/rn`,n.searchParams.delete("bundle"),n.searchParams.delete("demo"),n.searchParams.delete("app"),n.searchParams.delete("open"),n.searchParams.delete("port"),/^\d+$/.test(t)?n.pathname=`${r}/rn/${t}`:(n.pathname=`${r}/rn`,n.searchParams.set("open",t)),n.toString()}function Ie(e){let o=G(e);return/^\d+$/.test(o)||/^https?:\/\//i.test(o)?!0:/^(localhost|127\.0\.0\.1|\[::1\]|[^/]+\.localhost):\d+(?:\/.*)?$/i.test(o)}async function Ue(e,o){let t=await q(e),n=new URL(o),r=Q(n.pathname);return n.pathname=`${r}/rn`,n.searchParams.delete("open"),n.searchParams.delete("port"),n.searchParams.delete("demo"),n.searchParams.delete("app"),n.searchParams.set("bundle",t.bundleUrl),n.toString()}function ke(e){return e.startsWith("~/")?j.join(xe.homedir(),e.slice(2)):j.isAbsolute(e)?e:j.resolve(process.cwd(),e)}function Me(e){let o={};for(let t=0;t<e.length;t++){if(e[t]!=="--replace")continue;let n=e[t+1];n||(console.error(" sootsim open: --replace expects <module>=<file>"),process.exit(1));let r=n.indexOf("=");(r<=0||r===n.length-1)&&(console.error(" sootsim open: --replace expects <module>=<file>"),process.exit(1));let i=n.slice(0,r).trim(),s=ke(n.slice(r+1).trim());ve.existsSync(s)||(console.error(` sootsim open: replacement file not found: ${s}`),process.exit(1)),o[i]={file:s},t++}return Object.keys(o).length>0?{modules:o}:void 0}function ue(){let e=Z();return ee(e)&&e.runtimePort>0?`http://localhost:${e.runtimePort}/`:Y}async function R(e,o=ue()){if(!e)return new URL(o).toString();if(W(e))return new URL(e).toString();let t=Ce[e.toLowerCase()];if(t){let n=new URL(o),r=Q(n.pathname);return n.pathname=`${r}${t}`,n.toString()}return Ie(e)?Ue(e,o):Pe(e,o)}function de(e,o){let t=e?.url||e?.origin||o;try{let n=new URL(t);return n.searchParams.delete("bundle"),n.searchParams.delete("demo"),n.searchParams.delete("app"),n.searchParams.delete("open"),n.searchParams.delete("port"),n.searchParams.delete("inspectOpen"),n.toString()}catch{return o}}async function Te(e,o,t){let n=new URL(await R(e,o));return n.searchParams.set("inspectOpen",t),n.toString()}async function Be(e,o,t,n={}){let r=n.attempts??30,i=n.intervalMs??500,s=n.minNodeCount??10;for(let a=0;a<r;a++){let l=w(e,{commandTimeoutMs:o,simId:t,simIdSource:"flag"});try{let m=await l.send({type:"evaluate",code:"(async () => (await window.__sootsimTest?.getNodeCount()) || 0)()"});if(typeof m=="number"&&m>s)return{bridge:l,count:m}}catch{}l.close(),await I(i)}return null}function _e(e){if(!e)return null;try{let o=new URL(e);if(o.searchParams.has("bundle")){let t=o.searchParams.get("bundle")||"";try{let n=new URL(t),r=n.pathname.length>36?`...${n.pathname.slice(-36)}`:n.pathname;return`bundle ${n.host}${r}`}catch{return"bundle"}}return o.searchParams.has("port")?`connect :${o.searchParams.get("port")||""}`:o.searchParams.has("open")?`connect ${o.searchParams.get("open")||""}`:o.searchParams.has("demo")?`demo ${o.searchParams.get("demo")||"default"}`:o.pathname.includes("/sootsim/index.html")||o.pathname==="/sootsim/"||o.pathname==="/sootsim"?"embedded sootsim":null}catch{return null}}function Oe(e,o){if(e.length===0){console.log(" no sims connected");return}console.log(` connected sims (${e.length}):
3
- `);for(let t of e){let n=t.lockedBy&&t.lockExpiresAt?`locked by ${t.lockedBy} (${Math.max(0,Math.round((t.lockExpiresAt-Date.now())/1e3))}s)`:"",r=[t.isPrimary?"primary":"",t.id===o?"selected":"",t.readyState,t.attachedCliCount&&t.attachedCliCount>0?"in use":"",t.userFocused?"focused":"",n].filter(Boolean);console.log(` ${t.id}${r.length?` [${r.join(", ")}]`:""}`);let i=_e(t.url);if(i&&console.log(` loaded: ${i}`),t.url?console.log(` url: ${t.url}`):t.origin&&console.log(` origin: ${t.origin}`),t.title&&console.log(` title: ${t.title}`),console.log(` connected: ${new Date(t.connectedAt).toISOString()}`),t.lastActiveAt&&t.lastActiveAt>0){let s=Math.round((Date.now()-t.lastActiveAt)/1e3),a=s<60?`${s}s ago`:s<3600?`${Math.round(s/60)}m ago`:`${Math.round(s/3600)}h ago`;console.log(` last active: ${a}`)}}}async function O(e,o,t,n={}){let r=n.attempts??30,i=n.intervalMs??500;for(let s=0;s<r;s++){let a=w(e,{commandTimeoutMs:o});try{let m=(await a.listSims()).find(t);if(m)return m}catch{}finally{a.close()}await I(i)}return null}async function Re(e,o,t,n={}){let r=n.attempts??20,i=n.intervalMs??250;for(let s=0;s<r;s++){let a=w(e,{commandTimeoutMs:o});try{let m=(await a.listSims()).find(b=>b.id===t);if(!m||m.readyState!=="open")return!0}catch{return!0}finally{a.close()}await I(i)}return!1}function L(e,o){if(o){let r=o.trim(),i=e.find(s=>s.id===r);if(!i)throw new Error(`no sim connected with id ${r}`);return i}let t=e.find(r=>r.isPrimary&&r.readyState==="open");if(t)return t;let n=e.find(r=>r.readyState==="open");if(n)return n;throw new Error("no sim connected")}function C(e,o,t){console.log(` ${t==="current sim"?"loaded":"opened"}: ${e} [${t}]`),console.log(` current sim: ${o.id}`),console.log(JSON.stringify({simId:o.id,url:o.url},null,2))}async function P(e,o,t){if(t.includes("--no-describe"))return;let n=process.env.SOOTSIM_QUIET_TARGET_NOTICE;process.env.SOOTSIM_QUIET_TARGET_NOTICE="1";try{await Le(e,o,{stableMs:150,maxMs:400});let r=w(e,{commandTimeoutMs:3e3,simId:o,cliLabel:"open --describe",simIdSource:"flag"});try{let s=`(async () => {
4
- const t = window.__sootsimTest
5
- const ms = window.SootSim?.bridges?.mainShell
6
- if (!t) return null
7
- let shell = null
8
- try { shell = ms?.getState ? await ms.getState() : null } catch {}
9
- const tree = await t.dumpTree(12, ${JSON.stringify({describe:!0,verbose:!1,filter:""})})
10
- return { tree, shell }
11
- })()`,a=await r.send({type:"evaluate",code:s});if(!a?.tree)return;if(console.log(""),a.shell?.state){let l=[`state=${a.shell.state}`,a.shell.activeApp?`app=${a.shell.activeApp}`:null].filter(Boolean);console.log(` shell: ${l.join(" ")}`)}console.log(a.tree)}finally{r.close()}}catch{}finally{n===void 0?delete process.env.SOOTSIM_QUIET_TARGET_NOTICE:process.env.SOOTSIM_QUIET_TARGET_NOTICE=n}}async function Le(e,o,t){let n=Date.now()+t.maxMs,r="(async () => (await window.__sootsimTest?.getNodeCount?.()) || 0)()",i=w(e,{commandTimeoutMs:2e3,simId:o,simIdSource:"flag"});try{let s=-1,a=0;for(;Date.now()<n;){let l=-1;try{let m=await i.send({type:"evaluate",code:r});typeof m=="number"&&(l=m)}catch{}if(l>=0&&l===s){if(Date.now()-a>=t.stableMs)return}else s=l,a=Date.now();await I(50)}}finally{i.close()}}async function ct(e,o={}){let t=S(e,{port:o.port,commandTimeoutMs:o.timeoutMs}),n=$(t);try{let r=await n.listSims();Oe(r,t.simId)}finally{n.close()}}async function lt(e,o={}){let t=S(e,{port:o.port,commandTimeoutMs:o.timeoutMs,stripBooleanFlags:["--new","--headless","--ephemeral"],stripValueFlags:["--base-url","--replace","--driver","--profile"]}),n=e.find((c,d)=>e[d-1]==="--profile"),r=e.includes("--ephemeral");n&&r&&(console.error(" sootsim open: --profile cannot be combined with --ephemeral"),process.exit(1));let i=n?re(n).id:void 0,s=!!i||r,a=e.includes("--new")||s,l=Me(e);a&&t.simIdSource==="flag"&&(console.error(" sootsim open: --new, --profile, and --ephemeral cannot be combined with --sim"),process.exit(1));let m=t.positional[0]||"",b=e.find((c,d)=>e[d-1]==="--driver")||"",fe=e.includes("--headless"),v=e.find((c,d)=>e[d-1]==="--base-url")||ue(),J=e.includes("--base-url"),K=N();if(!a&&(t.simIdSource==="flag"||t.simIdSource==="saved"&&!!K)){let c=$(t),d=t.simId?` --sim ${t.simId}`:"",u=!1;try{let p=null;try{let h=await c.listSims();if(t.simIdSource==="saved"?(p=h.find(y=>y.id===K&&y.readyState==="open")??null,p||F()):p=L(h,t.simId),!p)if(t.simIdSource==="saved")u=!0;else throw new Error("no sim connected");if(!u&&p){let y=J||W(m)?v:de(p,v),M=await R(m,B(y,l));c.send({type:"evaluate",simId:p.id,code:`window.location.href = ${JSON.stringify(M)}`}).catch(()=>{})}}catch(h){console.error(` open failed: ${h instanceof Error?h.message:String(h)}`),await T(c,{errorsCommand:`sootsim get errors 5${d}`,warningsCommand:`sootsim get warnings 5${d}`,requestsCommand:`sootsim get requests 5${d}`}),process.exit(1)}if(!u&&p){await I(1500);let h=await Be(t.wsPort,t.commandTimeoutMs,p.id);h||(console.error(" timed out waiting for current sim to load target"),process.exit(1)),h.bridge.close(),g(p.id);let y=J||W(m)?v:de(p,v),M=await R(m,B(y,l));C(M,{...p,url:M},"current sim"),await P(t.wsPort,p.id,e);return}}finally{c.close()}}let V=B(v,l),he=await R(m,V),X=`cli-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`,f=await Te(m,V,X),E={newWindow:!0},U=c=>c.url?.includes(`inspectOpen=${X}`)??!1;if(b){let c=se(b);c||(console.error(` unknown driver "${b}" \u2014 run \`sootsim list --drivers\``),process.exit(1));let d=await c.launch({url:f,headless:fe,newWindow:E.newWindow,profileId:i,ephemeralProfile:r});d.launched||(console.error(` ${c.name} driver: ${d.message}`),process.exit(1));let u=await O(t.wsPort,t.commandTimeoutMs,U,{attempts:60,intervalMs:500});u||(console.error(" timed out waiting for opened sim to connect"),process.exit(1)),g(u.id),C(f,u,`${c.name} driver`),await P(t.wsPort,u.id,e);return}if(s){let c=x();c||(console.error(" profiles require electron or playwright; install the desktop companion or use `--driver playwright`"),process.exit(1));try{(await z(f,c,{profileId:i,ephemeralProfile:r})).launched||(console.error(" desktop companion failed to start"),process.exit(1))}catch(u){console.error(` desktop companion failed: ${u instanceof Error?u.message:String(u)}`),process.exit(1)}let d=await O(t.wsPort,t.commandTimeoutMs,U,{attempts:40,intervalMs:500});d||(console.error(" timed out waiting for profiled sim to connect"),process.exit(1)),g(d.id),C(f,d,"desktop companion"),await P(t.wsPort,d.id,e);return}let D=!1,A=!1;try{let c=w(t.wsPort,{commandTimeoutMs:2e3});try{await c.listSims(),D=!0}finally{c.close()}}catch{}if(D)try{let c=w(t.wsPort,{commandTimeoutMs:3e3});try{await c.openUrl(f,E),A=!0}finally{c.close()}}catch{}if(!A){let c=x();if(c)try{if((await z(f,c)).launched){let u=await O(t.wsPort,t.commandTimeoutMs,U,{attempts:20,intervalMs:500});if(u){g(u.id),C(f,u,"desktop companion"),await P(t.wsPort,u.id,e);return}}}catch{}if(await te(f,E),!D){console.log(` opened: ${he}`),me(t.wsPort);return}}let k=await O(t.wsPort,t.commandTimeoutMs,U,{attempts:30,intervalMs:500});k||(console.error(" timed out waiting for opened sim to connect"),process.exit(1)),g(k.id),C(f,k,A?"bridge":"direct shell open"),await P(t.wsPort,k.id,e)}async function pe(e,o={}){let t=S(e,{port:o.port,commandTimeoutMs:o.timeoutMs}),n=$(t),r=t.simId?` --sim ${t.simId}`:"";try{try{let i=await n.listSims(),s=L(i,t.positional[0]||t.simId);await n.focusSim(s.id),g(s.id),console.log(` using: ${s.id}`)}catch(i){console.error(` use failed: ${i instanceof Error?i.message:String(i)}`),await T(n,{errorsCommand:`sootsim get errors 5${r}`,warningsCommand:`sootsim get warnings 5${r}`,requestsCommand:`sootsim get requests 5${r}`}),process.exit(1)}}finally{n.close()}}async function mt(e,o={}){await pe(e,o)}async function dt(e,o={}){await pe(e,o)}async function ut(e,o={}){let t=S(e,{port:o.port,commandTimeoutMs:o.timeoutMs,stripBooleanFlags:["--force"]}),n=e.includes("--force"),r=$(t);try{try{let i=await r.listSims(),s=L(i,t.positional[0]||t.simId),a=n&&s.lockedBy&&s.lockedByKind!=="user-active"?s.lockedBy:null,l=await r.claim(s.id,{force:n});g(s.id);let m=Math.max(0,Math.round((l.lockExpiresAt-Date.now())/1e3)),b=l.bootedCount>0?` (booted ${l.bootedCount})`:"";console.log(` claimed: ${l.simId} [${m}s]${b}`),a&&console.log(` took over from: ${a}`)}catch(i){if(i instanceof oe){let s=Math.max(0,Math.round(i.lock.expiresInMs/1e3));console.error(` claim failed: locked by ${i.lock.by} for ${s}s more`),console.error(" use --force to take it, or `sootsim open --new` for a fresh sim"),process.exit(1)}console.error(` claim failed: ${i instanceof Error?i.message:String(i)}`),process.exit(1)}}finally{r.close()}}async function pt(e,o={}){let t=S(e,{port:o.port,commandTimeoutMs:o.timeoutMs}),n=$(t),r=t.simId?` --sim ${t.simId}`:"";try{try{let i=await n.listSims(),s=L(i,t.positional[0]||t.simId),a=i.find(m=>m.id!==s.id&&m.readyState==="open");if(await n.closeSim(s.id),!await Re(t.wsPort,t.commandTimeoutMs,s.id)){console.log(` close requested: ${s.id} (still connected)`);return}N()===s.id&&(a?g(a.id):F()),console.log(` closed: ${s.id}`)}catch(i){console.error(` close failed: ${i instanceof Error?i.message:String(i)}`),await T(n,{errorsCommand:`sootsim get errors 5${r}`,warningsCommand:`sootsim get warnings 5${r}`,requestsCommand:`sootsim get requests 5${r}`}),process.exit(1)}}finally{n.close()}}export{qe as a,je as b,at as c,ue as d,R as e,Te as f,_e as g,Oe as h,O as i,ct as j,lt as k,mt as l,dt as m,ut as n,pt as o};
@@ -1,27 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{d as g,e as u}from"./chunk-QUULF2II.js";import{a as l}from"./chunk-553OZX4H.js";var c="https://sootbean.com";function d(e){let n=e.replace(/^\[|\]$/g,"").toLowerCase();return n==="localhost"||n.endsWith(".localhost")||n==="0.0.0.0"||n==="::1"||/^127(?:\.\d{1,3}){3}$/.test(n)}function h(e){let{currentOrigin:n,search:r="",explicitOrigin:s}=e;if(s)return s;let a=new URLSearchParams(r).get("sootbean");if(a)return a;try{let i=new URL(n);if(d(i.hostname)){if(i.port==="3000")return i.origin;let t=new URL(i.origin);return t.port="3000",t.origin}if(i.hostname==="sootbean.com"||i.hostname.endsWith(".sootbean.com"))return i.origin}catch{return c}return c}function f(){return typeof window>"u"?c:h({currentOrigin:window.location.origin,search:window.location.search,explicitOrigin:window.__sootsimSootbeanOrigin})}var p=f();function m(e,n){if(n)return n.replace(/\/$/,"");if(e.kind==="session"&&e.origin)return e.origin.replace(/\/$/,"");let r=l();return r?.origin?r.origin.replace(/\/$/,""):p.replace(/\/$/,"")}async function $(e,n={}){let r=g();if(r||(process.stderr.write(`
3
- sootsim ${e} needs auth before it can check recording access.
4
-
5
- pick one:
6
- \u2022 run \`sootsim login\`
7
- \u2022 set SOOTSIM_API_KEY=sk_sootsim_... (recommended for CI)
8
- \u2022 save a personal key with \`sootsim keys use sk_sootsim_...\`
9
-
10
- `),process.exit(1)),r.kind==="github"&&n.allowGitHubAuth)return;let s=m(r,n.originOverride),o;try{o=await fetch(`${s}/api/sootsim/billing/subscription`,{headers:{authorization:u(r)}})}catch(t){process.stderr.write(`
11
- sootsim ${e} couldn't verify recording access.
12
- ${t instanceof Error?t.message:String(t)}
13
-
14
- `),process.exit(1)}if(o.status===401&&(process.stderr.write(`
15
- sootsim ${e} couldn't verify recording access with the current auth against ${s}.
16
- sign in again for that origin, or provide a valid SOOTSIM_API_KEY.
17
-
18
- `),process.exit(1)),!o.ok){let t=await o.text().catch(()=>"");process.stderr.write(`
19
- sootsim ${e} couldn't verify recording access (${o.status}).
20
- ${t||"unexpected billing response"}
21
-
22
- `),process.exit(1)}let a=await o.json();if(a.entitlements?.desktopRecording===!0)return;let i=a.plan??"free";process.stderr.write(`
23
- sootsim ${e} recording requires Personal, Team, or an active trial.
24
- current plan: ${i}.
25
- free keeps basic 3d mode; the trial unlocks the full Personal surface.
26
-
27
- `),process.exit(1)}export{$ as a};
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1,4 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{createInterface as i}from"readline";var c=()=>i({input:process.stdin,output:process.stdout});function s(r){return new Promise(n=>{let e=c();e.question(r,t=>{e.close(),n(t.trim())})})}async function a(r,n=!0){let t=await s(` ${r} ${n?"[Y/n]":"[y/N]"} `);return t===""?n:t.toLowerCase().startsWith("y")}async function l(r,n){console.log(`
3
- ${r}
4
- `);for(let o=0;o<n.length;o++)console.log(` ${o+1}. ${n[o]}`);console.log();let e=await s(` choose [1-${n.length}]: `),t=parseInt(e,10);return t>=1&&t<=n.length?t-1:0}export{a,l as b};
@@ -1,17 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as T,c as P,e as v}from"./chunk-Y66CDFAT.js";import{E as B,F as k}from"./chunk-YKEBL6GE.js";import{WebSocket as I}from"ws";function C(){let e=B();return e&&k(e)?e.bridgePort:7668}var w=class extends Error{lock;constructor(t,s){super(t),this.name="BridgeSimLockedError",this.lock=s}};function x(e){return e==="flag"?"via --sim":e==="saved"?"saved via `sootsim use`":"primary fallback \u2014 no sim pinned"}function $(e,t={}){let s=new Set,c=t.port??C(),l=t.commandTimeoutMs??15e3,d,u="none",p=new Set(t.stripBooleanFlags??[]),g=new Set(t.stripValueFlags??[]);for(let n=0;n<e.length;n++){let a=e[n];if(a==="--port"||a==="-p"){s.add(n),n+1<e.length&&(s.add(n+1),c=Number(e[n+1])),n++;continue}if(a.startsWith("--port=")){s.add(n),c=Number(a.slice(7));continue}if(a==="--timeout"){s.add(n),n+1<e.length&&(s.add(n+1),l=Number(e[n+1])),n++;continue}if(a==="--sim"){s.add(n),n+1<e.length&&(s.add(n+1),d=e[n+1]?.trim()||void 0,u="flag"),n++;continue}if(p.has(a)){s.add(n);continue}g.has(a)&&(s.add(n),n+1<e.length&&s.add(n+1),n++)}if(!d){let n=P();n&&(d=n.trim()||void 0,u="saved")}return{positional:e.filter((n,a)=>!s.has(a)),wsPort:c,simId:d,simIdSource:u,commandTimeoutMs:l}}function O(e,t={}){let s=1,c=t.commandTimeoutMs??15e3,l=new Map,d=new I(`ws://localhost:${e}`),u=t.simId?{key:`sim:${t.simId}`,source:"explicit-sim-id",stable:!0}:T(),p=new Promise((r,i)=>{d.on("open",()=>{try{d.send(JSON.stringify({type:"bridge:hello",id:0,cliIdentityKey:u.key,cliIdentitySource:u.source,cliLabel:t.cliLabel}))}catch{}r()}),d.on("error",o=>i(new Error(`could not connect to ws://localhost:${e}: ${o.message}`)))}),g=!1,n=!1;function a(r,i){if(n||process.env.SOOTSIM_QUIET_TARGET_NOTICE==="1"||r.startsWith("bridge:")||r==="focus"||r==="close")return;n=!0;let o=i??"primary",m=x(i?t.simIdSource:"none");process.stderr.write(` \u2192 ${o} (${m})
3
- `)}return d.on("message",r=>{let i;try{i=JSON.parse(r.toString())}catch{return}if(i.id===0)return;let o=l.get(i.id);o&&(l.delete(i.id),i.i>0&&!g&&(g=!0,process.stderr.write(`
4
- \u26A0 ${i.i} other CLI identity/identities are driving this sim
5
- taps, scrolls, and keyboard input from multiple agents will collide on
6
- the same screen state (this is not a bridge throughput limit).
7
- use \`sootsim open --new\` for an isolated sim per agent.
8
-
9
- `)),i.error?i.o?o.reject(new w(i.error,i.o)):o.reject(new Error(i.error)):o.resolve(i.result))}),d.on("close",(r,i)=>{let o=i?.toString()||"connection closed";for(let[m,y]of l)l.delete(m),y.reject(new Error(`sim disconnected: ${o} (code ${r})`))}),{async send(r){let i=async o=>{await p;let m=s++;return new Promise((y,h)=>{let S=setTimeout(()=>{l.delete(m),h(new Error(`command timed out after ${Math.round(c/1e3)}s`))},c);l.set(m,{resolve:b=>{clearTimeout(S),y(b)},reject:b=>{clearTimeout(S),h(b)}});let f={...r,id:m};f.simId===void 0&&o&&(f.simId=o),a(r.type??"",f.simId),d.send(JSON.stringify(f))})};try{return await i(t.simId)}catch(o){let m=o instanceof Error?o.message:String(o);if(t.fallbackOnMissingSimId&&t.simId&&r.simId===void 0&&m===`no sim connected with id ${t.simId}`)return v(),process.stderr.write(` \u26A0 saved sim ${t.simId} is gone \u2014 falling back to primary
10
- pin a sim with: sootsim use <sim>
11
- `),n=!1,i(void 0);throw o}},async listSims(){let r=await this.send({type:"bridge:list-sims"});return Array.isArray(r)?r:[]},async openUrl(r,i={}){return this.send({type:"bridge:open",url:r,newWindow:i.newWindow===!0})},async focusSim(r){return this.send({type:"focus",simId:r})},async closeSim(r){return this.send({type:"close",simId:r})},async claim(r,i={}){return await this.send({type:"bridge:claim",simId:r,force:i.force===!0})},close(){try{d.readyState===I.OPEN&&d.send(JSON.stringify({type:"bridge:bye",id:0}))}catch{}d.close();let r=setTimeout(()=>{d.readyState!==I.CLOSED&&d.terminate()},250)}}}function F(e){return O(e.wsPort,{commandTimeoutMs:e.commandTimeoutMs,simId:e.simId,fallbackOnMissingSimId:e.simIdSource==="saved",simIdSource:e.simIdSource})}async function N(e){try{return await e.send({type:"evaluate",code:"document.hidden"})===!0?(process.stderr.write(`
12
- \u26A0 target sim is hidden (document.hidden = true)
13
- animations and rAF callbacks are throttled \u2014 coordinates may be wrong
14
- and launch/transition animations will not complete.
15
- bring the sim to the foreground or use: sootsim debug eval "window.focus()"
16
-
17
- `),{hidden:!0,warned:!0}):{hidden:!1,warned:!1}}catch{return{hidden:!1,warned:!1}}}async function R(e,t,s={}){let c={type:"evaluate",code:t};return s.acquireLock&&(c.acquireLock=!0),e.send(c)}async function _(e,t,...s){return e.send({type:"call",path:t,args:s})}async function D(e,t,...s){return e.send({type:"call",path:t,args:s,acquireLock:!0})}export{C as a,w as b,$ as c,O as d,F as e,N as f,R as g,_ as h,D as i};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- var t="http://localhost:5173/";export{t as a};
@@ -1 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{c as a,d as b,e as c,f as d,g as e,h as f,i as g,j as h,k as i,l as j,m as k,n as l,o as m}from"./chunk-D4Z7MWQY.js";import"./chunk-CCZHRBXJ.js";import"./chunk-QMBYRPRK.js";import"./chunk-CAJ247SC.js";import"./chunk-WZE6T3GT.js";import"./chunk-TK2IPNHL.js";import"./chunk-I7KXFJDK.js";import"./chunk-EOWN4ZFJ.js";import"./chunk-S4PJMUC7.js";import"./chunk-PNGBWMQH.js";import"./chunk-Y66CDFAT.js";import"./chunk-XWXFUFB2.js";import"./chunk-YKEBL6GE.js";import"./chunk-DEBXVPIE.js";export{d as buildOpenUrl,c as buildShellUrl,f as printConnectedSims,a as resolveBundleTarget,b as resolveDefaultShellBaseUrl,l as runClaimCommand,m as runCloseCommand,k as runFocusCommand,h as runListCommand,i as runOpenCommand,j as runUseCommand,e as summarizeSimUrl,g as waitForSimMatch};
@@ -1,22 +0,0 @@
1
- /*! sootsim v0.1.40 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{c as I,e as N,i as S}from"./chunk-PNGBWMQH.js";import"./chunk-Y66CDFAT.js";import"./chunk-XWXFUFB2.js";import"./chunk-YKEBL6GE.js";import"./chunk-DEBXVPIE.js";import{createWriteStream as v,mkdirSync as k,writeFileSync as w}from"fs";import{dirname as C,resolve as T}from"path";import{Readable as x}from"stream";import{pipeline as y}from"stream/promises";import{createGzip as F}from"zlib";async function E(r,l){if(r.includes("--help")||r.includes("-h"))return console.log(`
3
- sootsim cpu-profile \u2014 capture a sampled CPU trace from the tenant worker
4
-
5
- usage:
6
- sootsim cpu-profile [options]
7
-
8
- options:
9
- --duration <seconds> recording duration (default: 5)
10
- --output <path> output file (default: /tmp/sootsim.cpuprofile)
11
- if the path ends in .gz, the output is gzipped
12
- --sample-interval <ms> requested sample interval in ms (default: 10 \u2014
13
- chrome may clamp upward)
14
- --max-buffer <n> max samples buffered (default: 100000)
15
- --sim <sim> target a specific sim
16
- --port <number> ws bridge port (default: 7668)
17
-
18
- examples:
19
- sootsim cpu-profile --duration 3
20
- sootsim cpu-profile --duration 10 --output /tmp/after.cpuprofile.gz
21
- sootsim cpu-profile --duration 3 -o /tmp/before.cpuprofile
22
- `),0;let n=Number(b(r,"--duration")??"5");if(!Number.isFinite(n)||n<=0)return console.error(" --duration must be a positive number (seconds)"),1;let m=Number(b(r,"--sample-interval")??"10");if(!Number.isFinite(m)||m<=0)return console.error(" --sample-interval must be a positive number (milliseconds)"),1;let s=Number(b(r,"--max-buffer")??"100000");if(!Number.isFinite(s)||s<=0)return console.error(" --max-buffer must be a positive number"),1;let p=b(r,"--output")??b(r,"-o"),u=T(process.cwd(),p??"/tmp/sootsim.cpuprofile"),g=u.endsWith(".gz"),h=I(r,{port:l.port,stripValueFlags:["--duration","--output","-o","--sample-interval","--max-buffer"]}),c=N({...h,commandTimeoutMs:Math.max(3e4,(n+20)*1e3)});try{let a=await S(c,"SootSim.bridges.workerCpuProfileStart",{sampleInterval:m,maxBufferSize:s});if(!a?.started)return console.error(" could not start tenant-worker profiler."),console.error(" is sootsim running? (try `bun sootsim list`)"),1;l.verbose&&console.error(` started: sampleInterval=${a.sampleInterval}ms buffer=${a.maxBufferSize}`),console.log(` recording for ${n}s\u2026`),await new Promise(o=>setTimeout(o,n*1e3));let t=await S(c,"SootSim.bridges.workerCpuProfileStop");if(!t||t.error||!t.trace)return console.error(` profile capture failed: ${t?.error??"no trace returned"}`),t?.error?.includes("not available")&&(console.error(" your browser or page is missing the JS Self-Profiler API."),console.error(" requires chromium-based browser and Document-Policy: js-profiling.")),1;let P=z(t.trace),e=JSON.stringify(P);k(C(u),{recursive:!0}),g?await y(x.from([e]),F(),v(u)):w(u,e);let i=(Buffer.byteLength(e)/1024/1024).toFixed(2);return console.log(` samples: ${P.samples.length} (${i} MB uncompressed)`),console.log(` saved: ${u}`),console.log(" open in chrome devtools \u2192 Performance \u2192 Load profile to inspect."),0}catch(a){let t=a?.message??String(a);return console.error(` profile failed: ${t}`),/could not connect|ECONNREFUSED/i.test(t)&&console.error(" is sootsim running? (try `bun sootsim list`)"),1}finally{c.close()}}function z(r){let l=r.frames??[],n=r.resources??[],m=r.stacks??[],s=r.samples??[],p=1,u=[{id:p,callFrame:{functionName:"(root)",scriptId:"0",url:"",lineNumber:-1,columnNumber:-1},hitCount:0,children:[]}];for(let e=0;e<m.length;e++){let i=m[e],o=l[i.frameId]??{name:"(unknown)"},f=o.resource!==void 0?n[o.resource]??"":"";u.push({id:e+2,callFrame:{functionName:o.name||"(anonymous)",scriptId:o.resource!==void 0?String(o.resource):"0",url:f,lineNumber:typeof o.line=="number"?o.line-1:-1,columnNumber:typeof o.column=="number"?o.column-1:-1},hitCount:0,children:[]})}for(let e=0;e<m.length;e++){let i=m[e],o=e+2,f=i.parentId!==void 0?i.parentId+2:p,d=u[f-1];d&&!d.children.includes(o)&&d.children.push(o)}let g=[],h=[],c=s.length>0?s[0].timestamp:0,a=c;for(let e=0;e<s.length;e++){let i=s[e],o=i.stackId!==void 0?i.stackId+2:p;g.push(o);let f=u[o-1];f&&f.hitCount++;let d=e===0?0:i.timestamp-a;h.push(Math.max(0,Math.round(d*1e3))),a=i.timestamp}let t=Math.round(c*1e3),P=s.length>0?Math.round(s[s.length-1].timestamp*1e3):t;return{nodes:u,startTime:t,endTime:P,samples:g,timeDeltas:h}}function b(r,l){let n=r.indexOf(l);if(!(n<0||n===r.length-1))return r[n+1]}export{E as runCpuProfile};