sootsim 0.0.1 → 0.0.3

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 (122) hide show
  1. package/README.md +12 -0
  2. package/dist-cli/bin.js +16 -10
  3. package/dist-cli/chunks/agent-D5NBV32O.js +61 -0
  4. package/dist-cli/chunks/agent-wrapper-Y7I5QGHM.js +15 -0
  5. package/dist-cli/chunks/assert-EJ7DQS2H.js +47 -0
  6. package/dist-cli/chunks/auto-bootstrap-Q7GNLISM.js +2 -0
  7. package/dist-cli/chunks/{chunk-7X6OPSRD.js → chunk-2FPPPJE5.js} +2 -2
  8. package/dist-cli/chunks/{chunk-G5MR66EB.js → chunk-3K6VDPVD.js} +2 -2
  9. package/dist-cli/chunks/{chunk-PWXPA745.js → chunk-3SLEIN6B.js} +1 -1
  10. package/dist-cli/chunks/chunk-3WPAEUOO.js +1 -0
  11. package/dist-cli/chunks/chunk-44CBTM22.js +2 -0
  12. package/dist-cli/chunks/chunk-46LRF7PH.js +5 -0
  13. package/dist-cli/chunks/chunk-4RYT6AQV.js +16 -0
  14. package/dist-cli/chunks/chunk-5AG24UFX.js +119 -0
  15. package/dist-cli/chunks/chunk-5IPP4HAW.js +5 -0
  16. package/dist-cli/chunks/{chunk-J2S3OCWA.js → chunk-AFTHIY3L.js} +1 -1
  17. package/dist-cli/chunks/chunk-BU3TZP4Y.js +11 -0
  18. package/dist-cli/chunks/chunk-BYLX2DO4.js +27 -0
  19. package/dist-cli/chunks/chunk-CPMW2QLM.js +1 -0
  20. package/dist-cli/chunks/{chunk-YCETS3B3.js → chunk-CQ6PX2EU.js} +2 -2
  21. package/dist-cli/chunks/chunk-D4JFMCXD.js +2 -0
  22. package/dist-cli/chunks/chunk-EEBR5YP5.js +62 -0
  23. package/dist-cli/chunks/chunk-EQ7G3UHS.js +4 -0
  24. package/dist-cli/chunks/{chunk-64TOMNZX.js → chunk-FTRI7SVV.js} +2 -2
  25. package/dist-cli/chunks/{chunk-GPVPHE2B.js → chunk-H3JVJXOC.js} +2 -2
  26. package/dist-cli/chunks/chunk-LV5U7TI4.js +1 -0
  27. package/dist-cli/chunks/chunk-NKJLTISU.js +4 -0
  28. package/dist-cli/chunks/chunk-O2HBPZW5.js +22 -0
  29. package/dist-cli/chunks/{chunk-KSACMDXK.js → chunk-OG5CKIPC.js} +2 -2
  30. package/dist-cli/chunks/{chunk-E522F5JW.js → chunk-P5C3UASK.js} +1 -1
  31. package/dist-cli/chunks/chunk-REYWQVAH.js +2 -0
  32. package/dist-cli/chunks/chunk-RLS6PHBW.js +4 -0
  33. package/dist-cli/chunks/chunk-SUZR2SZZ.js +34 -0
  34. package/dist-cli/chunks/{chunk-OROM7DZI.js → chunk-USRNDVQ3.js} +1 -1
  35. package/dist-cli/chunks/{chunk-JSF5LPNT.js → chunk-UZL5ZZ4E.js} +5 -5
  36. package/dist-cli/chunks/{chunk-QOBRRY5X.js → chunk-VI3VW5BL.js} +1 -1
  37. package/dist-cli/chunks/chunk-WUYJFYOW.js +2 -0
  38. package/dist-cli/chunks/chunk-X2W4IRXK.js +3 -0
  39. package/dist-cli/chunks/chunk-XJBPH4JR.js +308 -0
  40. package/dist-cli/chunks/chunk-ZSRMXBGK.js +2 -0
  41. package/dist-cli/chunks/{compat-MRN2ORY5.js → compat-5KSMOWLB.js} +4 -4
  42. package/dist-cli/chunks/{config-CO5IYWUY.js → config-NJB6PQHU.js} +5 -5
  43. package/dist-cli/chunks/control-2F3AGZAO.js +2 -0
  44. package/dist-cli/chunks/{daemon-G4XVRFHM.js → daemon-MLG65V4S.js} +2 -2
  45. package/dist-cli/chunks/{debug-ZNSZTWT6.js → debug-QVOBTTLP.js} +4 -4
  46. package/dist-cli/chunks/demo-app-registry-XRYNJ4GC.js +2 -0
  47. package/dist-cli/chunks/{detox-JEGYNTYV.js → detox-ZZSNZL4T.js} +2 -2
  48. package/dist-cli/chunks/{device-BS34FAFM.js → device-PQB3YGHN.js} +2 -2
  49. package/dist-cli/chunks/drivers-GWDQEGWD.js +2 -0
  50. package/dist-cli/chunks/electron-JB26VHOO.js +15 -0
  51. package/dist-cli/chunks/flow-7JRQXMFV.js +2 -0
  52. package/dist-cli/chunks/{hints-7Z656W4H.js → hints-IGYDXXDS.js} +2 -2
  53. package/dist-cli/chunks/home-paths-CEGSGQTD.js +2 -0
  54. package/dist-cli/chunks/{inspect-NAHXP2M5.js → inspect-DSU6ELRM.js} +153 -165
  55. package/dist-cli/chunks/install-K6IJKADG.js +65 -0
  56. package/dist-cli/chunks/{install-desktop-PYIZIH67.js → install-desktop-SC3LNFFF.js} +8 -4
  57. package/dist-cli/chunks/install-dev-desktop-4DP3UY2X.js +100 -0
  58. package/dist-cli/chunks/keys-R5LAPAAL.js +19 -0
  59. package/dist-cli/chunks/launch-K3WJV4QA.js +16 -0
  60. package/dist-cli/chunks/{login-Z5Z54HUJ.js → login-A23PYJAW.js} +5 -5
  61. package/dist-cli/chunks/{logout-T2QDYGCB.js → logout-AJ24PH5O.js} +2 -2
  62. package/dist-cli/chunks/{maestro-4AXTS7OE.js → maestro-YALWKKGU.js} +2 -2
  63. package/dist-cli/chunks/{preview-NMGWHWMX.js → preview-D35EEONY.js} +2 -2
  64. package/dist-cli/chunks/{profile-6RGJA4FR.js → profile-MAF7NM5Q.js} +3 -3
  65. package/dist-cli/chunks/record-ZCPQNGFW.js +37 -0
  66. package/dist-cli/chunks/runtime-Z2WIXYUN.js +25 -0
  67. package/dist-cli/chunks/{screenshot-R3GCCSCI.js → screenshot-NQVZYC3C.js} +3 -3
  68. package/dist-cli/chunks/screenshot-mode-E45D2ZFH.js +17 -0
  69. package/dist-cli/chunks/{screenshots-4UQJE4NC.js → screenshots-I4SQI4DA.js} +2 -2
  70. package/dist-cli/chunks/server-ZUXKJRR5.js +29 -0
  71. package/dist-cli/chunks/{skills-2PPKPL4B.js → skills-N4U63E5W.js} +2 -2
  72. package/dist-cli/chunks/store-4A6X4GBJ.js +2 -0
  73. package/dist-cli/chunks/{test-5LFKOQ4M.js → test-VBD6N3AR.js} +3 -3
  74. package/dist-cli/chunks/upload-Y6FZ5XF2.js +2 -0
  75. package/dist-cli/chunks/{whoami-H6FW34JS.js → whoami-4K6JGMWH.js} +2 -2
  76. package/dist-lib/agent-daemon-client.cjs +414 -0
  77. package/dist-lib/agent-events.cjs +48 -0
  78. package/dist-lib/agent-sessions.cjs +692 -0
  79. package/dist-lib/attached-projects.cjs +448 -0
  80. package/dist-lib/auth/shared-session.cjs +174 -0
  81. package/dist-lib/backend-origin.cjs +70 -0
  82. package/dist-lib/bridge-constants.cjs +32 -0
  83. package/dist-lib/cli-constants.cjs +32 -0
  84. package/dist-lib/config.cjs +88 -0
  85. package/dist-lib/dev-bundle-resolution.cjs +236 -0
  86. package/dist-lib/home-paths.cjs +234 -0
  87. package/dist-lib/host/bridge-host.cjs +3458 -0
  88. package/dist-lib/index.cjs +361 -0
  89. package/dist-lib/metro.cjs +215 -0
  90. package/dist-lib/render-mode.cjs +54 -0
  91. package/dist-lib/vite-base.cjs +4217 -0
  92. package/dist-lib/vite.cjs +178 -0
  93. package/package.json +80 -13
  94. package/scripts/postinstall.cjs +70 -0
  95. package/dist-cli/chunks/bridge-host-2EY7Z4AO.js +0 -2
  96. package/dist-cli/chunks/chunk-3C3ZH7PP.js +0 -4
  97. package/dist-cli/chunks/chunk-3R4ZZESY.js +0 -119
  98. package/dist-cli/chunks/chunk-74XPLOV4.js +0 -2
  99. package/dist-cli/chunks/chunk-7LMDCMSI.js +0 -8
  100. package/dist-cli/chunks/chunk-A2CZQIWO.js +0 -1
  101. package/dist-cli/chunks/chunk-CKZ376AY.js +0 -322
  102. package/dist-cli/chunks/chunk-E5UBZEYR.js +0 -2
  103. package/dist-cli/chunks/chunk-HOIHCO7S.js +0 -3
  104. package/dist-cli/chunks/chunk-KQWZZ56P.js +0 -2
  105. package/dist-cli/chunks/chunk-KSB6MSZ4.js +0 -34
  106. package/dist-cli/chunks/chunk-KXYKAYYB.js +0 -51
  107. package/dist-cli/chunks/chunk-MBFP2LVH.js +0 -3
  108. package/dist-cli/chunks/chunk-MPSZ5EWF.js +0 -16
  109. package/dist-cli/chunks/chunk-X2U72K7X.js +0 -1
  110. package/dist-cli/chunks/control-Y7TKKB6D.js +0 -2
  111. package/dist-cli/chunks/dev-ZUKCZQEX.js +0 -25
  112. package/dist-cli/chunks/dev-checkout-IEZVVTCN.js +0 -2
  113. package/dist-cli/chunks/drivers-46PFFIDF.js +0 -2
  114. package/dist-cli/chunks/electron-P2KOPX2S.js +0 -15
  115. package/dist-cli/chunks/flow-VVOF6UNC.js +0 -2
  116. package/dist-cli/chunks/install-EPUJX4AT.js +0 -67
  117. package/dist-cli/chunks/record-IE27Z2GA.js +0 -37
  118. package/dist-cli/chunks/screenshot-mode-SZQDNGYE.js +0 -17
  119. package/dist-cli/chunks/server-AN2G5KO4.js +0 -21
  120. package/dist-cli/chunks/store-PU5ES4YQ.js +0 -2
  121. package/dist-cli/chunks/upload-BYNPC54C.js +0 -2
  122. package/dist-cli/chunks/vite-plugin-5AEUUBKP.js +0 -9
@@ -1,119 +0,0 @@
1
- /*! sootsim v0.0.1 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- var w={bridgePort:7668,defaultShellUrl:"http://localhost:5173/"};function t(o,s){return{flag:o,description:s}}function e(o,s){return{command:o,description:s}}var x=[{id:"dev",slug:"dev",navTitle:"dev",title:"sootsim dev",description:"start dev mode wrapping a bundler (default command)",summary:"Wrap an existing One, Expo, or Metro dev command, boot sootsim beside it, and proxy the app bundle into the simulator shell.",order:10,usage:["sootsim [--driver <id>] [--headless] dev [-- <bundler-command>]"],options:[t("--driver <id>","launch via a specific driver (electron|chromium|playwright|system). must be a global flag before `dev`"),t("--headless","run the launched driver in headless mode where supported. global flag, before `dev`")],examples:[e("sootsim dev -- pnpm dev","wrap a project-local task runner"),e("sootsim dev -- npx expo start","wrap Expo directly"),e("sootsim dev -- bunx one dev","wrap a One app"),e("sootsim --driver chromium --headless dev -- pnpm dev","run everything headless for CI")],related:["open","electron","install"]},{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:["dev","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 session or manage a draft from live inspect actions",summary:"Use flow files when you already have them, or start a draft session 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/session 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 session before running the flow"),t("--session <id>","target a specific current session"),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-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 session 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("--session <tab-id>","target a specific bridge tab")],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:"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: `profile` before, make a change, `profile` after, then diff in DevTools.",order:65,usage:["sootsim 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("--session <tab-id>","target a specific bridge tab")],examples:[e("sootsim profile --duration 3","save to /tmp/sootsim.cpuprofile"),e("sootsim profile --duration 10 --output /tmp/after.cpuprofile.gz"),e("sootsim profile --duration 3 -o /tmp/before.cpuprofile","capture a baseline before a code change")],related:["record","flow"]},{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 sootsim session \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("--session <tab-id>","reuse a live sootsim session 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 --session tab-9","reuse an already-open visible session"),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 session 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:"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("--session <session-id>","target a specific connected browser or desktop window"),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 --session tab-2")],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 --filter <substring>","sootsim network --tail","sootsim network get <id>","sootsim network clear"],options:[t("--failed","only show errored or non-2xx responses"),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("--session <session-id>","target a specific connected session"),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 --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("--session <session-id>","target a specific connected session"),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 sootsim session.",order:90,usage:["sootsim debug <subcommand> [args]"],options:[t("--session <session-id>","target a specific connected session"),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"]},{id:"open",slug:"open",navTitle:"open",title:"sootsim open",description:"load a target into the current session or open a new one",summary:"Resolve a port, dev-server URL, bundle URL, or full shell URL, then load it into the current session by default. Use `--new` when you explicitly want another tab or desktop window.",order:100,usage:["sootsim open <port|target|bundle-url|sootsim-url> [--new]"],options:[t("--new","open a fresh session instead of reusing the current one"),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 http://localhost:8082")],related:["list","use","close","electron"]},{id:"list",slug:"list",navTitle:"list",title:"sootsim list",description:"list connected sootsim sessions or available launch drivers",summary:"Show every connected browser tab / desktop window, 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 [--session <session-id>] [--json]","sootsim list --drivers"],options:[t("--port <number>","bridge port (defaults to {{bridgePort}})"),t("--session <session-id>","highlight a specific session in the output"),t("--drivers, -D","list available launch drivers instead of connected sessions"),t("--json","emit array of session objects for scripts")],examples:[e("sootsim list"),e("sootsim list --session tab-6"),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 sootsim session",summary:"Make one connected session current by focusing it and saving it as the default target for later bridge-backed commands.",order:120,usage:["sootsim use [session-id]"],aliases:["focus"],options:[t("--port <number>","bridge port (defaults to {{bridgePort}})"),t("--session <session-id>","target a specific connected session")],examples:[e("sootsim use"),e("sootsim use tab-6")],related:["list","open","close"]},{id:"claim",slug:"claim",navTitle:"claim",title:"sootsim claim",description:"take an exclusive lease on a sootsim session",summary:"Reserve a connected browser tab 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 [session-id] [--force]"],options:[t("--port <number>","bridge port (defaults to {{bridgePort}})"),t("--session <session-id>","target a specific connected session"),t("--force","take the lease even if another agent currently holds it")],examples:[e("sootsim claim"),e("sootsim claim tab-6"),e("sootsim claim tab-6 --force")],related:["list","use","open","close"]},{id:"close",slug:"close",navTitle:"close",title:"sootsim close",description:"close a connected sootsim session",summary:"Request that a session close itself over the bridge and wait for it to disappear from the session registry.",order:130,usage:["sootsim close [session-id]"],options:[t("--port <number>","bridge port (defaults to {{bridgePort}})"),t("--session <session-id>","target a specific connected session")],examples:[e("sootsim close"),e("sootsim close tab-6")],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:["install","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:["install"]},{id:"electron",slug:"electron",navTitle:"electron",title:"sootsim electron",description:"launch the desktop companion",summary:"Launch the installed desktop companion directly, optionally with a specific dev URL or shell port, without going through browser discovery.",order:160,usage:["sootsim electron [--port <number>] [--driver <id>]"],options:[t("--port <number>","load a specific local renderer URL like http://localhost:5173"),t("--driver <id>","override the launch driver (electron|chromium|playwright|system)")],examples:[e("sootsim electron"),e("sootsim electron --port 5173"),e("sootsim electron --driver system","fall back to the OS default browser")],related:["open","list","debug"]},{id:"install",slug:"install",navTitle:"install",title:"sootsim install",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 install [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 install"),e("sootsim install --yes"),e("sootsim install --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:"Install `sootsim server` as a launchd (macOS) or systemd --user (linux) login agent so the bridge is always running. The agent points at whichever sootsim binary resolves at install time, so `npm update -g sootsim` + `sootsim daemon restart` picks up upgrades cleanly.",order:173,usage:["sootsim daemon install [--force]","sootsim daemon uninstall","sootsim daemon status","sootsim daemon restart","sootsim daemon start","sootsim daemon stop"],examples:[e("sootsim daemon install"),e("sootsim daemon status"),e("sootsim daemon restart")],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 sootsim tab (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:"install-desktop",slug:"install-desktop",navTitle:"install-desktop",title:"sootsim install-desktop",description:"download and install the desktop companion",summary:"Fetch the right platform artifact (dmg / AppImage / exe) from the public release feed and install it into the OS-native location. 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","install","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 sootsim session \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 session for this run"),t("--record","record a webm while the flow runs"),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 sootsim session and publish it to a shareable `/preview/<id>` URL. Unlisted-public (unguessable id), requires a signed-in desktop session. 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("--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("--session <tab-id>","target a specific sootsim tab (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-session.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 session 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 session",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 current desktop session is signed in as (or report that no session is active).",order:192,usage:["sootsim whoami"],examples:[e("sootsim whoami")],related:["login","logout"]}],B=new Map(x.map(o=>[o.id,o])),D=new Map;for(let o of x){D.set(o.id,o);for(let s of o.aliases||[])D.set(s,o)}function A(o){return D.get(o)}function i(o,s,n,a,r,l){return{verb:o,description:s,usage:n,examples:a,notes:r,...l}}var S=[i("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"]}),i("find","unified finder (text, testid, role, type, pressable, visible)",["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"],[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")],"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.",{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("--verbose, --dump","emit full node JSON for each result, not a one-liner"),t("--json","emit structured JSON")],seeAlso:["describe","node","selector"]}),i("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")]}),i("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"}),i("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"]}),i("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"]}),i("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"]}),i("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}),i("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}),i("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}),i("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"],order:40}),i("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}),i("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}),i("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}),i("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}),i("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}),i("dismiss","dismiss the keyboard",["sootsim do dismiss"],void 0,void 0,{group:"do",subgroup:"text input",shortDescription:"dismiss the iOS keyboard",order:60}),i("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}),i("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}),i("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}),i("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}),i("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}),i("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}),i("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}),i("state","dump raw runtime state (shell, worker, keyboard, node, scroll, hit, gesture)",["sootsim debug state <subcommand> [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"]}),i("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, bridgeId, ...), chrome (shell, headless, engineBase, ...).",{group:"debug",subgroup:"eval",shortDescription:"evaluate JS in the engine realm (reach into SootSim.*)",seeAlso:["eval","state"]}),i("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"]}),i("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"]}),i("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"]}),i("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"]}),i("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"]}),i("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"]}),i("state","session dashboard: url, active animation counts, diagnostics, shell scene",["sootsim get state"],[e("sootsim get state","what is this session doing right now")],'one-shot JSON snapshot answering "what is happening in this tab right now" \u2014 intended as the first call of a debugging session. separate from `sootsim debug state <kind>`, which dumps raw runtime state for one subsystem.',{group:"get",subgroup:"diagnostics",shortDescription:"session dashboard snapshot",seeAlso:["animations","errors","keyboard"]}),i("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"]}),i("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"}),i("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 session: `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"]}),i("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"]}),i("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"]}),i("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")]}),i("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}),i("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"}),i("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}),i("wait","wait for a browser/tab to reconnect",["sootsim do wait [seconds]"],[e("sootsim do wait 30")],void 0,{group:"do",subgroup:"lifecycle",shortDescription:"wait for browser/tab reconnect",seeAlso:["ready","idle","settle"],order:20}),i("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}),i("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}),i("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}),i("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}),i("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}),i("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"]}),i("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 k(o){return L.get(o)}function f(o,s=w){return o.replaceAll("{{bridgePort}}",String(s.bridgePort)).replaceAll("{{defaultShellUrl}}",s.defaultShellUrl)}function R(){return x.slice().sort((o,s)=>o.order-s.order)}function y(o,s){let n=Math.max(...s.map(r=>r.name.length),0)+2,a=s.map(r=>` ${r.name.padEnd(n)}${r.desc}`);return` ${o}
3
- ${a.join(`
4
- `)}`}function T(o,s){let n=o.usage[0]??`sootsim ${o.verb}`,a=[s?`sootsim ${s} `:"","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(s=>s.group===o)}function $(o){let s=I(o);if(!s.length)return"";let n=new Map;for(let l of s){let d=l.subgroup??"",p=n.get(d)??[];p.push(l),n.set(d,p)}let a=[];for(let l of s){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 p=d.map(g=>({name:T(g,o),desc:C(g)})),h=Math.max(...p.map(g=>g.name.length),0)+2,m=l?` ${l}:`:"",u=p.map(g=>` ${g.name.padEnd(h)}${g.desc}`);r.push([m,...u].filter(Boolean).join(`
5
- `))}return r.join(`
6
-
7
- `)}function H(o,s=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=$(o);if(!l)return null;let d=I(o).flatMap(u=>u.examples??[]).slice(0,8).map(u=>` ${f(u.command,s)}`).join(`
8
- `),p=`
9
- options:
10
- --port <port> WS bridge port (default: ${s.bridgePort})
11
- --session <id> target a specific connected browser tab
12
- --timeout <ms> per-command timeout (default: 15000)${n?`
13
- `+n:""}`,m=`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
- `:""}${p}
21
-
22
- ${m}
23
- `.trim()}function K(o=w,s=""){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=k("find"),d=r.length?r.map(c=>({name:T(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}))],p=C(k("describe")??{verb:"describe",description:"",usage:[]}),h=c=>I(c).slice().sort((b,j)=>{let M=b.order??Number.POSITIVE_INFINITY,O=j.order??Number.POSITIVE_INFINITY;return M!==O?M-O:b.verb.localeCompare(j.verb)}).map(b=>({name:T(b,c),desc:C(b)})),m=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 launch app (scans for dev servers)
29
- sootsim [command] [options]
30
- sootsim [-- <bundler-command>]
31
-
32
- commands:
33
- ${a}
34
-
35
- runtime verbs:
36
- sootsim describe [filter] [--verbose] [--json]
37
- ${p}
38
-
39
- ${y("sootsim find <...> unified finder",d)}
40
-
41
- ${y("sootsim get <noun> read runtime state",m)}
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
- session targeting:
58
- sootsim list show every connected session and its id
59
- sootsim use <session-id> make one session current (saved default)
60
- sootsim --session <id> <cmd> override the saved default for one call
61
- sootsim claim <session-id> take an exclusive lease on a session
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
- ${s}
69
-
70
- examples:
71
- sootsim launch app, auto-discover projects
72
- sootsim -- pnpm dev wrap a dev command
73
- sootsim open 8081 load a running target into the current session
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 shell appearance dark set simulator appearance
80
- sootsim debug state shell dump shell transition/layout state
81
- sootsim debug snapshot before capture runtime state
82
- sootsim use tab-6 make one session current
83
- `.trimStart()}function N(o,s){if(!o?.length)return"";let n=Math.max(...o.map(r=>r.flag.length),0)+2;return`
84
- options:
85
- ${o.map(r=>` ${f(r.flag,s).padEnd(n)}${f(r.description,s)}`).join(`
86
- `)}
87
- `}function E(o,s){let n=N(o.options,s),a=o.examples?.length?`
88
- examples:
89
- ${o.examples.map(m=>` ${f(m.command,s)}`).join(`
90
- `)}
91
- `:"",r=o.notes?`
92
- note:
93
- ${o.notes}
94
- `:"",l=o.seeAlso?.length?`
95
- see also: ${o.seeAlso.map(m=>`sootsim ${m}`).join(", ")}
96
- `:"",d=A(o.verb)!=null,p=o.group?`sootsim ${o.group} --help`:null,h=p?`
97
- full list: ${p}`:d?"":`
98
- full list: sootsim inspect --help`;return`
99
- sootsim ${o.verb} \u2014 ${o.description}
100
-
101
- usage:
102
- ${o.usage.map(m=>f(m,s)).join(`
103
- `)}${n}${a}${r}${l}${h}`.trimEnd()}function X(o,s=w){let n=k(o);if(n)return E(n,s);let a=A(o);if(!a)return null;let r=a.aliases?.length?`
104
- aliases:
105
- ${a.aliases.join(", ")}
106
- `:"",l=a.options?.length?`
107
- options:
108
- ${a.options.map(p=>` ${f(p.flag,s).padEnd(24)}${f(p.description,s)}`).join(`
109
- `)}
110
- `:"",d=a.examples?.length?`
111
- examples:
112
- ${a.examples.map(p=>` ${f(p.command,s)}`).join(`
113
- `)}
114
- `:"";return`
115
- sootsim ${a.id} \u2014 ${f(a.description,s)}
116
-
117
- usage:
118
- ${a.usage.map(p=>f(p,s)).join(`
119
- `)}${r}${l}${d}`.trimEnd()}var P={"--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"}},U=new Set(["list","describe","find","get","do","wait","network","logs","shell"]),q={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(["dev","preview","test","detox","maestro","flow","record","profile","screenshot","screenshots","inspect","debug","shell","device","open","list","use","focus","claim","close","config","compat","scan","electron","login","logout","whoami","install","install-desktop","server","daemon","skills","upload","hints",...U,...Object.keys(q)]);function Z(o){let s=o.slice(2),n={command:null,commandArgs:[],globalFlags:{},help:!1,version:!1,verbose:!1},a=0;for(;a<s.length;){let r=s[a];if(r==="--"){n.commandArgs.push(...s.slice(a+1));break}let l=Object.entries(P).find(([d,p])=>d===r||p.short===r);if(l){let[d,p]=l,h=d.replace(/^--/,"");if(p.type==="boolean")n.globalFlags[h]=!0,a++;else{let m=s[a+1];if(m===void 0||m.startsWith("-")){console.error(` warning: ${r} requires a value`),a++;continue}if(p.type==="number"){let u=Number(m);if(Number.isNaN(u)){console.error(` warning: ${r} requires a number, got "${m}"`),a+=2;continue}n.globalFlags[h]=u}else n.globalFlags[h]=m;a+=2}continue}if(!n.command&&!r.startsWith("-")){if(F.has(r)){n.command=r;let d=s.slice(a+1);n.commandArgs=d[0]==="--"?d.slice(1):d;break}n.commandArgs=s.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,U as d,q as e,Z as f};
@@ -1,2 +0,0 @@
1
- /*! sootsim v0.0.1 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as o}from"./chunk-MBFP2LVH.js";function r(){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 install"),console.error("")}function t(n){let e=o();if(console.log(` note: no sootsim bridge detected on port ${n}`),e){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 install # wire sootsim into your own project")}export{r as a,t as b};
@@ -1,8 +0,0 @@
1
- /*! sootsim v0.0.1 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{spawn as B}from"child_process";import{createServer as C}from"http";import{WebSocket as u,WebSocketServer as v}from"ws";var A=new Set(["tap","keyboard","close"]);function I(p){return!p||typeof p.type!="string"?!1:p.acquireLock===!0?!0:p.readOnly===!0?!1:A.has(p.type)}var m=class p{port;openUrlHandler;httpServer=null;wss=null;nextCommandId=1;nextBrowserId=1;browsers=new Map;primaryBrowserId=null;pendingCommands=new Map;cliBySentId=new Map;cliBrowserBySocket=new Map;cliLastCommandAt=new Map;cliSessionKeyBySocket=new Map;cliLabelBySocket=new Map;restorableBrowsers=new Map;nextCliFallbackId=1;cliIdleTimer=null;static CLI_IDLE_TIMEOUT_MS=6e4;static CLI_LEASE_TTL_MS=6e5;static USER_ACTIVE_LEASE_TTL_MS=8e3;static BROWSER_RECONNECT_TTL_MS=3e4;constructor(e={}){this.port=e.port||7668,this.openUrlHandler=e.openUrl}start(e){if(this.httpServer||this.wss)return;let t=!1,r=s=>{if(s.code!=="EADDRINUSE")throw s;this.resetServerState(),!t&&!e?.silent&&process.stderr.write(`ws bridge port ${this.port} already in use
3
- `),t=!0};this.httpServer=C(),this.httpServer.on("error",r),this.wss=new v({server:this.httpServer}),this.wss.on("error",r),this.wss.on("connection",(s,o)=>{let a=o.headers.origin,c=a?"browser":"cli",n=null;if(c==="browser")n={id:`tab-${this.nextBrowserId++}`,ws:s,origin:a,connectedAt:Date.now(),lastSeenAt:Date.now(),lastActiveAt:0,recentActions:[]},this.browsers.set(n.id,n),this.shouldPromoteBrowser(n)&&(this.primaryBrowserId=n.id),process.stderr.write(`sootsim browser connected [${n.id}]${a?` (${a})`:""}${n.id===this.primaryBrowserId?" [primary]":""}
4
- `),this.broadcastBrowserAssignments(),this.broadcastBrowserClientStates();else{let g=`ws-${this.nextCliFallbackId++}`;this.cliSessionKeyBySocket.set(s,g)}s.on("message",g=>{let i;try{i=JSON.parse(g.toString())}catch{return}if(!(!i||typeof i!="object")){if(c==="browser"){if(n&&(n.lastSeenAt=Date.now()),i.type==="bridge:register"&&n){let l=i,w=this.tryRestoreBrowserId(n,l.browserId);n.url=l.url,n.title=l.title,n.userAgent=l.userAgent,w&&(process.stderr.write(`sootsim browser restored [${n.id}]
5
- `),this.broadcastBrowserAssignments(),this.broadcastBrowserClientStates());return}if(i.type==="bridge:user-focus-state"&&n){let l=i;this.updateUserFocusLease(n,l.focused===!0);return}if(i.type==="bridge:user-interact"&&n){this.updateUserActivity(n);return}if(i.type==="bridge:open-path"){let l=typeof i.path=="string"?i.path:"",w=typeof i.line=="number"&&Number.isFinite(i.line)?i.line:void 0,h=typeof i.column=="number"&&Number.isFinite(i.column)?i.column:void 0;l&&this.openPathInEditor(l,w,h);return}if(i.type==="bridge:boot-clients"&&n){let l=[];for(let[h,y]of this.cliBrowserBySocket)y===n.id&&l.push(h);for(let h of l){this.cliBrowserBySocket.delete(h);try{h.close(1e3,"booted by browser")}catch{}}let w=!!n.cliLease;n.cliLease=void 0,(l.length>0||w)&&(process.stderr.write(`sootsim booted ${l.length} cli client(s)${w?" + cleared lease":""} from [${n.id}]
6
- `),this.recordBrowserAction(n.id,"browser booted cli clients"),this.broadcastBrowserClientStates());return}let d=this.pendingCommands.get(i.id);if(d){this.pendingCommands.delete(i.id),i.error?d.reject(new Error(i.error)):d.resolve(i.result);return}let f=this.cliBySentId.get(i.id);if(f&&(this.cliBySentId.delete(i.id),f.ws.readyState===u.OPEN)){let l=this.getOtherCliSessionCount(f.ws,f.browserId),w=l>0?{...i,id:f.originalId,i:l}:{...i,id:f.originalId};f.ws.send(JSON.stringify(w))}return}(async()=>{this.cliLastCommandAt.set(s,Date.now());try{if(i.type==="bridge:bye"){let h=this.cliBrowserBySocket.delete(s);this.cliLastCommandAt.delete(s),this.cliSessionKeyBySocket.delete(s),this.cliLabelBySocket.delete(s);for(let[y,S]of this.cliBySentId)S.ws===s&&this.cliBySentId.delete(y);h&&this.broadcastBrowserClientStates();return}if(i.type==="bridge:hello"){let h=typeof i.cliSessionKey=="string"&&i.cliSessionKey.trim()?i.cliSessionKey.trim():this.cliSessionKeyBySocket.get(s)||`ws-${this.nextCliFallbackId++}`;this.cliSessionKeyBySocket.set(s,h),typeof i.cliLabel=="string"&&i.cliLabel.trim()&&this.cliLabelBySocket.set(s,i.cliLabel.trim()),s.readyState===u.OPEN&&s.send(JSON.stringify({id:i.id,result:{cliSessionKey:h,leaseTtlMs:p.CLI_LEASE_TTL_MS,leasing:!0}}));return}if(i.type==="bridge:list-browsers"){s.readyState===u.OPEN&&s.send(JSON.stringify({id:i.id,result:this.listBrowsers()}));return}if(i.type==="bridge:open"){if(typeof i.url!="string"||!i.url)throw new Error("bridge:open requires a url");await this.openUrl(i.url),s.readyState===u.OPEN&&s.send(JSON.stringify({id:i.id,result:{ok:!0,url:i.url}}));return}if(i.type==="bridge:claim"){let h=await this.waitForBrowser(i.browserId),y=this.tryAcquireLease(s,h,{force:i.force===!0});if(!y.granted){s.readyState===u.OPEN&&s.send(JSON.stringify({id:i.id,error:`tab ${h.id} is locked by another cli`,s:y.lock}));return}this.setCliBrowserTarget(s,h.id),this.recordBrowserAction(h.id,y.bootedCount>0?`cli force-claimed tab (booted ${y.bootedCount})`:"cli claimed tab"),s.readyState===u.OPEN&&s.send(JSON.stringify({id:i.id,result:{browserId:h.id,lockedBy:y.lease.cliSessionKey,lockExpiresAt:y.lease.expiresAt,bootedCount:y.bootedCount}}));return}let d=await this.waitForBrowser(i.browserId);if(I(i)){let h=this.tryAcquireLease(s,d);if(!h.granted){s.readyState===u.OPEN&&s.send(JSON.stringify({id:i.id,error:`tab ${d.id} is locked by another cli \u2014 use \`sootsim claim ${d.id} --force\` or \`sootsim open --new\``,s:h.lock}));return}}else this.ensureCliSessionKey(s);this.setCliBrowserTarget(s,d.id),this.recordBrowserAction(d.id,this.describeForwardedCommand(i));let f=this.nextCommandId++;this.cliBySentId.set(f,{browserId:d.id,ws:s,originalId:i.id});let{browserId:l,...w}=i;d.ws.send(JSON.stringify({...w,id:f}))}catch(d){s.readyState===u.OPEN&&s.send(JSON.stringify({id:i.id,error:d instanceof Error?d.message:String(d)}))}})()}}),s.on("close",()=>{if(c==="browser"&&n){process.stderr.write(`sootsim browser disconnected [${n.id}]
7
- `),this.rememberDisconnectedBrowser(n),this.primaryBrowserId===n.id&&(this.primaryBrowserId=this.getOpenBrowser()?.id??null);for(let[g,i]of this.pendingCommands)i.browserId===n.id&&(i.reject(new Error("browser disconnected")),this.pendingCommands.delete(g));for(let[g,i]of this.cliBySentId)i.browserId===n.id&&(i.ws.readyState===u.OPEN&&i.ws.send(JSON.stringify({id:i.originalId,error:"browser disconnected before responding"})),this.cliBySentId.delete(g));this.broadcastBrowserAssignments(),this.broadcastBrowserClientStates()}else if(c==="cli"){let g=this.cliBrowserBySocket.delete(s);this.cliLastCommandAt.delete(s),this.cliSessionKeyBySocket.delete(s),this.cliLabelBySocket.delete(s);for(let[i,d]of this.cliBySentId)d.ws===s&&this.cliBySentId.delete(i);g&&this.broadcastBrowserClientStates()}})}),this.httpServer.listen(this.port,()=>{process.stderr.write(`ws bridge listening on port ${this.port}
8
- `)}),this.cliIdleTimer=setInterval(()=>this.sweepIdleCliClients(),3e4),this.cliIdleTimer.unref()}sweepIdleCliClients(){let e=Date.now(),t=!1;for(let[r,s]of this.cliBrowserBySocket){let o=this.cliLastCommandAt.get(r)??0;if(!(e-o<p.CLI_IDLE_TIMEOUT_MS)){this.cliBrowserBySocket.delete(r),this.cliLastCommandAt.delete(r);for(let[a,c]of this.cliBySentId)c.ws===r&&this.cliBySentId.delete(a);try{r.close(1e3,"idle timeout")}catch{}t=!0}}t&&this.broadcastBrowserClientStates(),this.sweepRestorableBrowsers(e)}listBrowsers(){return Array.from(this.browsers.values()).sort((e,t)=>e.id===this.primaryBrowserId?-1:t.id===this.primaryBrowserId?1:e.connectedAt-t.connectedAt).map(e=>this.describeBrowser(e))}async sendCommand(e){let t=await this.waitForBrowser(e.browserId),r=this.nextCommandId++;return new Promise((s,o)=>{let a=setTimeout(()=>{this.pendingCommands.delete(r),this.broadcastBrowserClientStates(),o(new Error("command timed out after 30s"))},3e4);this.pendingCommands.set(r,{browserId:t.id,resolve:g=>{clearTimeout(a),this.pendingCommands.delete(r),this.broadcastBrowserClientStates(),s(g)},reject:g=>{clearTimeout(a),this.pendingCommands.delete(r),this.broadcastBrowserClientStates(),o(g)}}),this.broadcastBrowserClientStates();let{browserId:c,...n}=e;t.ws.send(JSON.stringify({...n,id:r}))})}async evaluate(e,t){return this.sendCommand({type:"evaluate",code:e,browserId:t})}async focusBrowser(e){return this.sendCommand({type:"focus",browserId:e})}async closeBrowser(e){return this.sendCommand({type:"close",browserId:e})}async openPathInEditor(e,t,r){let s=t!=null?`:${t}${r!=null?`:${r}`:""}`:"",o=`${e}${s}`,a=(n,g)=>new Promise(i=>{try{let d=B(n,g,{detached:!0,stdio:"ignore"}),f=!1;d.on("error",()=>{f||(f=!0,i(!1))}),d.on("spawn",()=>{f||(f=!0,d.unref(),i(!0))})}catch{i(!1)}}),c=process.env.REACT_EDITOR||process.env.EDITOR;if(c){let n=c.split(" ").filter(Boolean);if(n.length&&await a(n[0],[...n.slice(1),"-g",o]))return}await a("cursor",["-g",o])||await a("code",["-g",o])||await this.openUrl(e)}async openUrl(e){if(this.openUrlHandler){await this.openUrlHandler(e);return}if(process.platform==="darwin"){B("open",["-g",e],{detached:!0,stdio:"ignore"}).unref();return}if(process.platform==="win32"){B("cmd",["/c","start","",e],{detached:!0,stdio:"ignore"}).unref();return}B("xdg-open",[e],{detached:!0,stdio:"ignore"}).unref()}async close(){this.cliIdleTimer&&(clearInterval(this.cliIdleTimer),this.cliIdleTimer=null);for(let[r,s]of this.pendingCommands)s.reject(new Error("server closing")),this.pendingCommands.delete(r);for(let r of this.browsers.values())r.ws.close();this.browsers.clear(),this.primaryBrowserId=null;let e=this.wss,t=this.httpServer;if(this.wss=null,this.httpServer=null,e)try{e.close()}catch{}if(t)try{t.close()}catch{}}describeBrowser(e){let t;try{t=e.ws.readyState}catch{t=u.CLOSED}let r=this.getActiveLease(e);return{id:e.id,origin:e.origin,url:e.url,title:e.title,userAgent:e.userAgent,connectedAt:e.connectedAt,lastSeenAt:e.lastSeenAt,lastActiveAt:e.lastActiveAt||void 0,isPrimary:e.id===this.primaryBrowserId,readyState:t===u.OPEN?"open":t===u.CLOSING?"closing":"closed",attachedCliCount:this.getAttachedCliCount(e.id),lockedBy:r?r.cliLabel||r.cliSessionKey:void 0,lockedByKind:r?r.kind:void 0,lockExpiresAt:r?r.expiresAt:void 0,userFocused:e.userFocused||void 0}}getActiveLease(e){let t=e.cliLease;return t?Date.now()>=t.expiresAt?(e.cliLease=void 0,null):t:null}tryAcquireLease(e,t,r={}){let s=this.cliSessionKeyBySocket.get(e)??(()=>{let d=`ws-${this.nextCliFallbackId++}`;return this.cliSessionKeyBySocket.set(e,d),d})(),o=this.cliLabelBySocket.get(e),a=Date.now(),c=this.getActiveLease(t),n=c&&c.cliSessionKey===s,g=0;if(c&&!n&&!r.force)return{granted:!1,lease:c,lock:{by:c.cliLabel||c.cliSessionKey,expiresInMs:Math.max(0,c.expiresAt-a)},bootedCount:0};if(c&&!n&&r.force)for(let[d,f]of this.cliBrowserBySocket){if(f!==t.id)continue;let l=this.cliSessionKeyBySocket.get(d);if(l&&l!==s){this.cliBrowserBySocket.delete(d);try{d.close(1e3,"lease claimed by another cli")}catch{}g++}}let i={kind:"cli",cliSessionKey:s,cliLabel:o,expiresAt:a+p.CLI_LEASE_TTL_MS};return t.cliLease=i,{granted:!0,lease:i,bootedCount:g}}updateUserFocusLease(e,t){let r=t;e.userFocused!==r&&(e.userFocused=r,this.broadcastBrowserClientStates())}updateUserActivity(e){let t=this.getActiveLease(e);t&&t.kind==="cli"||(e.cliLease={kind:"user-active",cliSessionKey:"__user-active__",cliLabel:"active user",expiresAt:Date.now()+p.USER_ACTIVE_LEASE_TTL_MS},this.broadcastBrowserClientStates())}ensureCliSessionKey(e){let t=this.cliSessionKeyBySocket.get(e);if(t)return t;let r=`ws-${this.nextCliFallbackId++}`;return this.cliSessionKeyBySocket.set(e,r),r}getOpenBrowser(e){if(e){let r=this.browsers.get(e);return r?.ws.readyState===u.OPEN?r:null}let t=this.primaryBrowserId!=null?this.browsers.get(this.primaryBrowserId):null;if(t?.ws.readyState===u.OPEN)return t;for(let r of this.browsers.values())if(r.ws.readyState===u.OPEN)return r;return null}async waitForBrowser(e,t={}){let r=t.attempts??10,s=t.intervalMs??200;for(let o=0;o<r;o++){let a=this.getOpenBrowser(e);if(a)return a;await new Promise(c=>setTimeout(c,s))}throw new Error(e?`no browser connected with id ${e}`:"no browser connected")}shouldPromoteBrowser(e){let t=this.primaryBrowserId?this.browsers.get(this.primaryBrowserId):null,r=e.origin?.includes(":5173"),s=t?.origin?.includes(":5173");return!t||t.ws.readyState!==u.OPEN||!!r||!s}broadcastBrowserAssignments(){for(let e of this.browsers.values())e.ws.readyState===u.OPEN&&e.ws.send(JSON.stringify({type:"bridge:welcome",browserId:e.id,isPrimary:e.id===this.primaryBrowserId}))}broadcastBrowserClientStates(){for(let e of this.browsers.values()){if(e.ws.readyState!==u.OPEN)continue;let t=this.getActiveLease(e),r={type:"bridge:client-state",attachedCliCount:this.getAttachedCliCount(e.id),activeAgentCommandCount:this.getActiveAgentCommandCount(e.id),recentActions:e.recentActions,lockedBy:t?t.cliLabel||t.cliSessionKey:void 0,lockedByKind:t?t.kind:void 0,lockExpiresAt:t?t.expiresAt:void 0,userFocused:e.userFocused||void 0};e.ws.send(JSON.stringify(r))}}setCliBrowserTarget(e,t){let r=this.cliBrowserBySocket.get(e);r!==t&&(this.cliBrowserBySocket.set(e,t),this.recordBrowserAction(t,r?"cli switched tabs":"cli connected",!1),this.broadcastBrowserClientStates())}recordBrowserAction(e,t,r=!0){let s=t?.trim();if(!s)return;let o=this.browsers.get(e);if(!o)return;let a=Date.now();o.lastActiveAt=a,o.recentActions=[{label:s,at:a},...o.recentActions.filter(c=>c.label!==s)].slice(0,4),r&&this.broadcastBrowserClientStates()}describeForwardedCommand(e){switch(e?.type){case"evaluate":return"evaluated page state";case"screenshot":return"captured screenshot";case"tap":return"sent tap event";case"keyboard":return e?.action==="type"?"typed text":"used keyboard";case"tree":return"dumped tree";case"focus":return"focused tab";case"close":return"requested close";default:return typeof e?.type=="string"?e.type:null}}getAttachedCliCount(e){let t=new Set;for(let[r,s]of this.cliBrowserBySocket){if(s!==e||r.readyState!==u.OPEN)continue;let o=this.cliSessionKeyBySocket.get(r);t.add(o??`ws-unknown-${t.size}`)}return t.size}getOtherCliSessionCount(e,t){let r=this.cliSessionKeyBySocket.get(e),s=new Set;for(let[o,a]of this.cliBrowserBySocket){if(a!==t||o.readyState!==u.OPEN)continue;let c=this.cliSessionKeyBySocket.get(o);c&&c===r||s.add(c??`ws-unknown-${s.size}`)}return s.size}getActiveAgentCommandCount(e){let t=0;for(let r of this.pendingCommands.values())r.browserId===e&&t++;return t}tryRestoreBrowserId(e,t){let r=t?.trim();if(!r||r===e.id)return!1;let s=this.browsers.get(r);if(s&&s!==e&&s.ws.readyState===u.OPEN)return!1;let o=this.getRestorableBrowserState(r),a=e.id;this.browsers.delete(a),e.id=r,o&&(e.recentActions=o.recentActions.map(c=>({...c})),e.lastActiveAt=o.lastActiveAt,e.cliLease=o.cliLease?{...o.cliLease}:void 0,this.restorableBrowsers.delete(r)),this.browsers.set(e.id,e),this.primaryBrowserId===a&&(this.primaryBrowserId=e.id);for(let[c,n]of this.cliBrowserBySocket)n===a&&this.cliBrowserBySocket.set(c,e.id);return!0}rememberDisconnectedBrowser(e){let t=this.getActiveLease(e);this.restorableBrowsers.set(e.id,{recentActions:e.recentActions.map(r=>({...r})),lastActiveAt:e.lastActiveAt,cliLease:t&&t.kind==="cli"?{...t}:void 0,expiresAt:Date.now()+p.BROWSER_RECONNECT_TTL_MS}),this.browsers.delete(e.id)}getRestorableBrowserState(e){let t=this.restorableBrowsers.get(e);return t?t.expiresAt<=Date.now()?(this.restorableBrowsers.delete(e),null):(t.cliLease&&t.cliLease.expiresAt<=Date.now()&&(t.cliLease=void 0),t):null}sweepRestorableBrowsers(e=Date.now()){for(let[t,r]of this.restorableBrowsers)if(!(r.expiresAt>e)){this.restorableBrowsers.delete(t);for(let[s,o]of this.cliBrowserBySocket)o===t&&this.cliBrowserBySocket.delete(s)}}resetServerState(){this.cliIdleTimer&&(clearInterval(this.cliIdleTimer),this.cliIdleTimer=null);let e=this.wss,t=this.httpServer;if(this.wss=null,this.httpServer=null,e)try{e.close()}catch{}if(t)try{t.close()}catch{}}};export{m as a};
@@ -1 +0,0 @@
1
- /*! sootsim v0.0.1 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */