sootsim 0.0.2 → 0.0.4
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.
- package/dist-cli/bin.js +7 -7
- package/dist-cli/chunks/{agent-3T4BJEZM.js → agent-PJAOF4JS.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-WCYNLWHZ.js → agent-wrapper-STO7PLQD.js} +2 -2
- package/dist-cli/chunks/{assert-FPFJEFF3.js → assert-P47NW4AF.js} +2 -2
- package/dist-cli/chunks/auto-bootstrap-SC2LMI2H.js +2 -0
- package/dist-cli/chunks/{chunk-KZ2LIDW6.js → chunk-432TMHBG.js} +1 -1
- package/dist-cli/chunks/{chunk-6IPY24VM.js → chunk-47S5DXXX.js} +3 -3
- package/dist-cli/chunks/{chunk-DW54UPRZ.js → chunk-4VXB2DBA.js} +1 -1
- package/dist-cli/chunks/{chunk-ET3NNZAR.js → chunk-4XBPZQLW.js} +2 -2
- package/dist-cli/chunks/{chunk-TGDP3D3V.js → chunk-5TTQKPGH.js} +1 -1
- package/dist-cli/chunks/{chunk-RJUBGX5M.js → chunk-6SZMLFCR.js} +1 -1
- package/dist-cli/chunks/{chunk-LOV766MI.js → chunk-AFQBSK2J.js} +1 -1
- package/dist-cli/chunks/{chunk-CZZB4DWG.js → chunk-AUR2LTNX.js} +2 -2
- package/dist-cli/chunks/{chunk-SLCVEGTW.js → chunk-BQRM4E66.js} +2 -2
- package/dist-cli/chunks/chunk-C3QLIYCS.js +16 -0
- package/dist-cli/chunks/{chunk-5C5I5OFM.js → chunk-DQKQYPIG.js} +2 -2
- package/dist-cli/chunks/{chunk-ISAMAM3I.js → chunk-EHMSE3Q3.js} +1 -1
- package/dist-cli/chunks/chunk-F4ARVCRR.js +1 -0
- package/dist-cli/chunks/chunk-HAKR72LJ.js +2 -0
- package/dist-cli/chunks/chunk-HGFIS26A.js +2 -0
- package/dist-cli/chunks/{chunk-EWMYTXM2.js → chunk-I6XGFZPA.js} +2 -2
- package/dist-cli/chunks/{chunk-4372UQHZ.js → chunk-MQDPKSCK.js} +3 -3
- package/dist-cli/chunks/chunk-MZPAJ5PQ.js +1 -0
- package/dist-cli/chunks/{chunk-AS4V7TZU.js → chunk-OAHMYSMD.js} +1 -1
- package/dist-cli/chunks/{chunk-EWEKADK4.js → chunk-QIP7LYQI.js} +2 -2
- package/dist-cli/chunks/{chunk-NHA3G6A3.js → chunk-QQOBLF7O.js} +2 -2
- package/dist-cli/chunks/{chunk-B5R4K2DG.js → chunk-SY74J6F4.js} +1 -1
- package/dist-cli/chunks/{chunk-NE62JSI6.js → chunk-UKYK63H6.js} +1 -1
- package/dist-cli/chunks/{chunk-GQUOQNTP.js → chunk-UNFERMZ3.js} +2 -2
- package/dist-cli/chunks/{chunk-HORCHQT7.js → chunk-UQ3N6FZF.js} +2 -2
- package/dist-cli/chunks/{chunk-K6YUSCAC.js → chunk-VGXARPIH.js} +2 -2
- package/dist-cli/chunks/{chunk-TSZBQS6W.js → chunk-W3TYN64D.js} +2 -2
- package/dist-cli/chunks/{chunk-XXUAOYYT.js → chunk-W7CYWXRZ.js} +1 -1
- package/dist-cli/chunks/{chunk-CXTA5VGA.js → chunk-WRF43M33.js} +1 -1
- package/dist-cli/chunks/{chunk-LXCFGKL2.js → chunk-WVBPATRA.js} +1 -1
- package/dist-cli/chunks/{chunk-K7LDP7JL.js → chunk-WWDJCKMI.js} +1 -1
- package/dist-cli/chunks/{chunk-HBNVKYSC.js → chunk-XJF46GU2.js} +2 -2
- package/dist-cli/chunks/{chunk-YVSZHVLU.js → chunk-ZF5FCFLD.js} +2 -2
- package/dist-cli/chunks/chunk-ZKNI5MRD.js +1 -0
- package/dist-cli/chunks/{compat-3HMKLGXL.js → compat-ILLJ7VDL.js} +2 -2
- package/dist-cli/chunks/{config-IJQ3KANN.js → config-CDIAJIIT.js} +2 -2
- package/dist-cli/chunks/control-7QGKUCAX.js +2 -0
- package/dist-cli/chunks/{daemon-BBEQJLRY.js → daemon-4BLYGM5N.js} +2 -2
- package/dist-cli/chunks/{debug-SGZ5ZFQI.js → debug-6SMCTPMC.js} +3 -3
- package/dist-cli/chunks/demo-app-registry-HLI5UGGI.js +2 -0
- package/dist-cli/chunks/{detox-PK74V2Y7.js → detox-R4G5INNB.js} +2 -2
- package/dist-cli/chunks/{device-MWNFX54L.js → device-YSLCWS4E.js} +2 -2
- package/dist-cli/chunks/drivers-YIXRFFBQ.js +2 -0
- package/dist-cli/chunks/{electron-3NIHSU2K.js → electron-JZOFO37G.js} +3 -3
- package/dist-cli/chunks/flow-L7X5FGIN.js +2 -0
- package/dist-cli/chunks/{hints-XZJLBIXW.js → hints-O4QR6UGI.js} +2 -2
- package/dist-cli/chunks/{home-paths-BNRMUBJA.js → home-paths-4YJJYGR6.js} +2 -2
- package/dist-cli/chunks/{inspect-FGTUAK4C.js → inspect-DRFAUJUH.js} +29 -29
- package/dist-cli/chunks/{install-LCXALH26.js → install-BATRTWRI.js} +2 -2
- package/dist-cli/chunks/{install-desktop-U3RQ6XUX.js → install-desktop-6X474IQ3.js} +2 -2
- package/dist-cli/chunks/{install-dev-desktop-BLKRFI42.js → install-dev-desktop-CAJHPRNP.js} +2 -2
- package/dist-cli/chunks/{keys-N5LBDSD5.js → keys-OWQ7SOTM.js} +2 -2
- package/dist-cli/chunks/{launch-NIMSJH5I.js → launch-WUEDHSO5.js} +3 -3
- package/dist-cli/chunks/{login-CQV2XBRM.js → login-54YJ2KH6.js} +2 -2
- package/dist-cli/chunks/{logout-R56NWAWQ.js → logout-XECXLEXW.js} +2 -2
- package/dist-cli/chunks/{maestro-ZYUVTM7H.js → maestro-PMHK6EHI.js} +2 -2
- package/dist-cli/chunks/{preview-AOAWAYEQ.js → preview-4RVHA2PP.js} +2 -2
- package/dist-cli/chunks/{profile-DDADDPRW.js → profile-3IVNHUS6.js} +2 -2
- package/dist-cli/chunks/{record-3OIOTHP6.js → record-KEWLM5JR.js} +2 -2
- package/dist-cli/chunks/{runtime-JTLZYEXK.js → runtime-PJKHEB36.js} +3 -3
- package/dist-cli/chunks/{screenshot-Q6N2V5LL.js → screenshot-BXRAQERZ.js} +2 -2
- package/dist-cli/chunks/{screenshot-mode-WWLWJWQD.js → screenshot-mode-5IXEDIUS.js} +2 -2
- package/dist-cli/chunks/{screenshots-2JEPJGZO.js → screenshots-T4MQF3TB.js} +2 -2
- package/dist-cli/chunks/{server-VH34RVAX.js → server-CIP3LH45.js} +2 -2
- package/dist-cli/chunks/{skills-PU4627FY.js → skills-DJA6QEVR.js} +2 -2
- package/dist-cli/chunks/store-SPC247DB.js +2 -0
- package/dist-cli/chunks/{test-AECE56E7.js → test-IWUHNFXV.js} +3 -3
- package/dist-cli/chunks/upload-UPD2RSYF.js +2 -0
- package/dist-cli/chunks/{whoami-NCGRRR7X.js → whoami-MCXFWKIH.js} +2 -2
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +1 -1
- package/dist-lib/index.cjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/vite-base.cjs +1 -1
- package/dist-lib/vite.cjs +1 -1
- package/package.json +1 -1
- package/dist-cli/chunks/auto-bootstrap-HDW6N77H.js +0 -2
- package/dist-cli/chunks/chunk-3HBBSRLE.js +0 -2
- package/dist-cli/chunks/chunk-4GWEO5CL.js +0 -1
- package/dist-cli/chunks/chunk-EIZCWDRE.js +0 -1
- package/dist-cli/chunks/chunk-FUQ4XA6I.js +0 -16
- package/dist-cli/chunks/chunk-NXWCDGWS.js +0 -2
- package/dist-cli/chunks/chunk-XKDQEYTE.js +0 -1
- package/dist-cli/chunks/control-3RAFI4AW.js +0 -2
- package/dist-cli/chunks/demo-app-registry-NCYP3WA6.js +0 -2
- package/dist-cli/chunks/drivers-EXUREU4B.js +0 -2
- package/dist-cli/chunks/flow-6Y3E6E5P.js +0 -2
- package/dist-cli/chunks/store-U2VDD2S4.js +0 -2
- package/dist-cli/chunks/upload-KPP7KG6E.js +0 -2
package/dist-cli/bin.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/*! sootsim v0.0.
|
|
3
|
-
import{a as
|
|
2
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
3
|
+
import{a as p}from"./chunks/chunk-4XBPZQLW.js";import"./chunks/chunk-EHMSE3Q3.js";import{a as w,b as h,c as b,d as f,e as d,f as A}from"./chunks/chunk-4VXB2DBA.js";import{a as c}from"./chunks/chunk-HGFIS26A.js";import"./chunks/chunk-MZPAJ5PQ.js";import"./chunks/chunk-WWDJCKMI.js";function m(){let o=h({bridgePort:7668,defaultShellUrl:c},p());console.log(`${o}
|
|
4
4
|
|
|
5
5
|
auth:
|
|
6
6
|
sootsim login sign in for desktop uploads
|
|
7
7
|
sootsim logout clear the shared desktop session
|
|
8
8
|
sootsim whoami show the current shared desktop account
|
|
9
|
-
`)}function
|
|
9
|
+
`)}function k(){console.log(`sootsim \u2014 iOS simulator for web/react native apps
|
|
10
10
|
|
|
11
11
|
getting started:
|
|
12
12
|
npm i -g sootsim
|
|
@@ -16,8 +16,8 @@ optional desktop companion:
|
|
|
16
16
|
sootsim install-desktop # download the packaged Electron GUI (prod)
|
|
17
17
|
sootsim install-dev-desktop # create a Spotlight-launchable sootsim-dev.app
|
|
18
18
|
# that wraps this checkout's unpacked electron
|
|
19
|
-
`)}function
|
|
20
|
-
`),!0):!1}function
|
|
19
|
+
`)}function S(o){let t=w(o,{bridgePort:7668,defaultShellUrl:c},p());return t?(console.log(`${t}
|
|
20
|
+
`),!0):!1}function v(o){if(o==="login"){console.log(`sootsim login
|
|
21
21
|
|
|
22
22
|
sign in for desktop uploads using the shared desktop session.
|
|
23
23
|
opens the browser and stores the resulting bearer token for both the CLI and electron.
|
|
@@ -27,9 +27,9 @@ clear the shared desktop session used by the CLI and electron.
|
|
|
27
27
|
`);return}if(o==="whoami"){console.log(`sootsim whoami
|
|
28
28
|
|
|
29
29
|
show the current shared desktop account used by the CLI and electron.
|
|
30
|
-
`);return}let t=
|
|
30
|
+
`);return}let t=b(o,{bridgePort:7668,defaultShellUrl:c});if(!t){console.log(`unknown command: ${o}`);return}console.log(`${t}
|
|
31
31
|
`)}var _=typeof __SOOTSIM_STANDALONE__<"u"&&__SOOTSIM_STANDALONE__,O=new Set(["preview","electron","install-desktop","install-dev-desktop"]);function x(o){!_||!O.has(o)||(process.stderr.write(` sootsim ${o} isn't available in the standalone binary \u2014
|
|
32
32
|
it needs vite / electron / playwright from a project's node_modules.
|
|
33
33
|
install the npm package and run via bun or node instead:
|
|
34
34
|
npm i -g sootsim && sootsim ${o}
|
|
35
|
-
`),process.exit(1))}var e=
|
|
35
|
+
`),process.exit(1))}var e=A(process.argv);if(e.version){let{readFileSync:o}=await import("fs"),{fileURLToPath:t}=await import("url"),n=t(import.meta.resolve("sootsim/package.json")),i=JSON.parse(o(n,"utf8"));console.log(`sootsim v${i.version}`),process.exit(0)}e.help&&!e.command&&(m(),process.exit(0));var a=e.globalFlags.port,r=e.verbose,l=e.globalFlags.device,g=e.globalFlags.theme,L=e.globalFlags.driver,M=e.globalFlags.headless===!0;if(l||g){let{settingsStore:o}=await import("./chunks/store-SPC247DB.js"),t={};l&&(t.deviceModel=l),g&&(t.colorScheme=g),o.apply(t)}!e.command&&e.commandArgs.length===0&&(k(),m(),process.exit(0));var s=e.command??"";s||(m(),process.exit(e.commandArgs.length>0?1:0));if(s in d){let o=d[s],t=e.commandArgs.length>0?` ${e.commandArgs.join(" ")}`:"";console.error(` \`sootsim ${s}\` was removed. use \`${o}\` instead.`),t&&console.error(` \u2192 ${o}${t}`),console.error(" run `sootsim --help` to see the new verb groups."),process.exit(1)}if(e.help||e.commandArgs.includes("--help")||e.commandArgs.includes("-h")){let o=s==="do"||s==="get"||s==="debug"||s==="shell"||s==="wait",t=e.commandArgs.find(n=>!n.startsWith("-"));s==="shell"||(o&&!t?S(s)&&process.exit(0):(v(o&&t?t:s),process.exit(0)))}x(s);if(f.has(s)){let{ensureSootsimReady:o,resolveBootstrapPort:t}=await import("./chunks/auto-bootstrap-SC2LMI2H.js"),n=t(e.commandArgs,a);await o(n);let{runInspect:i}=await import("./chunks/inspect-DRFAUJUH.js");await i([s,...e.commandArgs],{port:n,verbose:r})}else switch(s){case"assert":{let{runAssert:o}=await import("./chunks/assert-P47NW4AF.js");await o(e.commandArgs);break}case"flow":{let{runFlow:o}=await import("./chunks/flow-L7X5FGIN.js"),t=await o(e.commandArgs);process.exit(typeof t=="number"?t:0)}case"test":{let{runTest:o}=await import("./chunks/test-IWUHNFXV.js");await o(e.commandArgs,{port:a,verbose:r});break}case"detox":{let{runDetox:o}=await import("./chunks/detox-R4G5INNB.js");await o(e.commandArgs,{port:a,verbose:r});break}case"maestro":{let{runMaestro:o}=await import("./chunks/maestro-PMHK6EHI.js"),t=await o(e.commandArgs,{port:a,verbose:r});process.exit(typeof t=="number"?t:0)}case"preview":{let{runPreview:o}=await import("./chunks/preview-4RVHA2PP.js");await o(e.commandArgs,{port:a,verbose:r});break}case"record":{let{runRecord:o}=await import("./chunks/record-KEWLM5JR.js");await o(e.commandArgs,{port:a,verbose:r});break}case"profile":{let{runProfile:o}=await import("./chunks/profile-3IVNHUS6.js"),t=await o(e.commandArgs,{port:a,verbose:r});process.exit(typeof t=="number"?t:0)}case"screenshot":{let{runScreenshot:o}=await import("./chunks/screenshot-BXRAQERZ.js");await o(e.commandArgs,{port:a,verbose:r});break}case"screenshots":{let{runScreenshots:o}=await import("./chunks/screenshots-T4MQF3TB.js"),t=await o(e.commandArgs,{port:a,verbose:r});process.exit(typeof t=="number"?t:0)}case"screenshot-mode":{let{runScreenshotMode:o}=await import("./chunks/screenshot-mode-5IXEDIUS.js");await o(e.commandArgs,{port:a,verbose:r});break}case"inspect":{let{runInspect:o}=await import("./chunks/inspect-DRFAUJUH.js");await o(e.commandArgs,{port:a,verbose:r});break}case"debug":{let{runDebug:o}=await import("./chunks/debug-6SMCTPMC.js");await o(e.commandArgs,{port:a,verbose:r});break}case"open":{let{ensureSootsimReady:o,resolveBootstrapPort:t}=await import("./chunks/auto-bootstrap-SC2LMI2H.js"),n=t(e.commandArgs,a);await o(n);let{runOpenCommand:i}=await import("./chunks/control-7QGKUCAX.js");await i(e.commandArgs,{port:n});break}case"use":case"focus":{let{runUseCommand:o}=await import("./chunks/control-7QGKUCAX.js");await o(e.commandArgs,{port:a});break}case"claim":{let{runClaimCommand:o}=await import("./chunks/control-7QGKUCAX.js");await o(e.commandArgs,{port:a});break}case"close":{let{runCloseCommand:o}=await import("./chunks/control-7QGKUCAX.js");await o(e.commandArgs,{port:a});break}case"config":{let{runConfig:o}=await import("./chunks/config-CDIAJIIT.js");await o(e.commandArgs);break}case"device":{let{runDeviceCommand:o}=await import("./chunks/device-YSLCWS4E.js");await o(e.commandArgs,{port:a});break}case"scan":case"compat":{let{runCompat:o}=await import("./chunks/compat-ILLJ7VDL.js");await o(e.commandArgs);break}case"electron":{let{runElectron:o}=await import("./chunks/electron-JZOFO37G.js");await o(e.commandArgs,{port:a,device:l,driver:L});break}case"login":{let{runLogin:o}=await import("./chunks/login-54YJ2KH6.js");await o(e.commandArgs);break}case"logout":{let{runLogout:o}=await import("./chunks/logout-XECXLEXW.js");await o();break}case"whoami":{let{runWhoami:o}=await import("./chunks/whoami-MCXFWKIH.js");await o();break}case"keys":{let{runKeys:o}=await import("./chunks/keys-OWQ7SOTM.js");await o(e.commandArgs);break}case"install":{let{runInstall:o}=await import("./chunks/install-BATRTWRI.js");await o(e.commandArgs);break}case"install-desktop":{let{runInstallDesktop:o}=await import("./chunks/install-desktop-6X474IQ3.js");await o(e.commandArgs);break}case"install-dev-desktop":{let{runInstallDevDesktop:o}=await import("./chunks/install-dev-desktop-CAJHPRNP.js");await o(e.commandArgs);break}case"server":{let{runServer:o}=await import("./chunks/server-CIP3LH45.js");await o(e.commandArgs,{port:a});break}case"daemon":{let{runDaemon:o}=await import("./chunks/daemon-4BLYGM5N.js");await o(e.commandArgs,{port:a});break}case"runtime":{let{runRuntime:o}=await import("./chunks/runtime-PJKHEB36.js");await o(e.commandArgs);break}case"launch":{let{runLaunch:o}=await import("./chunks/launch-WUEDHSO5.js");await o(e.commandArgs);break}case"agent":{let{runAgentCommand:o}=await import("./chunks/agent-PJAOF4JS.js"),t=await o(e.commandArgs);process.exit(t)}case"agent-wrapper":{let{runAgentWrapper:o}=await import("./chunks/agent-wrapper-STO7PLQD.js"),t=await o(e.commandArgs);process.exit(t)}case"skills":{let{runSkills:o}=await import("./chunks/skills-DJA6QEVR.js");await o(e.commandArgs);break}case"upload":{let{runUpload:o}=await import("./chunks/upload-UPD2RSYF.js");await o(e.commandArgs,{port:a,verbose:r});break}case"hints":{let{runHints:o}=await import("./chunks/hints-O4QR6UGI.js");o(e.commandArgs);break}default:console.error(` unknown command: ${s}`),console.error(" run `sootsim --help` to see the full surface."),process.exit(1)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
2
|
-
import{a as y,b as $,c as b,d as l,e as E,g as A,h as j,j as S,m as I,n as T,o as x,p as g,q as k,r as D}from"./chunk-
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import{a as y,b as $,c as b,d as l,e as E,g as A,h as j,j as S,m as I,n as T,o as x,p as g,q as k,r as D}from"./chunk-BQRM4E66.js";import"./chunk-432TMHBG.js";import"./chunk-MZPAJ5PQ.js";import"./chunk-WWDJCKMI.js";import w from"node:fs";import d from"node:path";import{spawn as N}from"node:child_process";import U from"node:net";import{WebSocket as O}from"ws";var c=class extends Error{code;constructor(e,t){super(e),this.name="AgentDaemonError",this.code=t}},f=class{ws;port;commandTimeoutMs;ready;closed=!1;nextId=1;pending=new Map;eventListeners=new Set;statusListeners=new Set;disconnectListeners=new Set;constructor(e={}){this.port=e.port??7668,this.commandTimeoutMs=e.commandTimeoutMs??15e3,this.ws=new O(`ws://localhost:${this.port}`),this.ready=new Promise((t,s)=>{let r=()=>{this.ws.off("error",o),t()},o=i=>{this.ws.off("open",r),s(new c(`could not connect to sootsim daemon on port ${this.port}: ${i.message}`,"NO_DAEMON"))};this.ws.once("open",r),this.ws.once("error",o)}),this.ws.on("message",t=>this.handleMessage(t)),this.ws.on("close",()=>this.handleClose())}async waitReady(){return this.ready}async listProjects(){return this.send("agent:list-projects")}async upsertProject(e){return this.send("agent:upsert-project",{input:e})}async deleteProject(e){return this.send("agent:delete-project",{projectId:e})}async autoAttachForUrl(e){return this.send("agent:auto-attach-for-url",{input:e})}async listSessions(e){return this.send("agent:list-sessions",{projectId:e})}async startSession(e){return this.send("agent:start-session",{input:e})}async sendPrompt(e,t){return this.send("agent:send-prompt",{sessionId:e,prompt:t})}async endSession(e){return this.send("agent:end-session",{sessionId:e})}async getTranscript(e){return this.send("agent:get-transcript",{sessionId:e})}async getPaths(){return this.send("agent:get-paths")}async subscribeEvents(e){return this.send("agent:subscribe-events",{sessionId:e})}async unsubscribeEvents(e){return this.send("agent:unsubscribe-events",{sessionId:e})}onAgentEvent(e){return this.eventListeners.add(e),()=>this.eventListeners.delete(e)}onSessionStatusChange(e){return this.statusListeners.add(e),()=>this.statusListeners.delete(e)}onDisconnect(e){return this.disconnectListeners.add(e),()=>this.disconnectListeners.delete(e)}close(){if(!this.closed){this.closed=!0;try{this.ws.close()}catch{}}}async send(e,t={}){if(await this.ready,this.closed||this.ws.readyState!==O.OPEN)throw new c("daemon connection is closed","NO_DAEMON");let s=this.nextId++;return new Promise((r,o)=>{let i=setTimeout(()=>{this.pending.delete(s),o(new c(`${e} timed out after ${Math.round(this.commandTimeoutMs/1e3)}s`,"TIMEOUT"))},this.commandTimeoutMs);this.pending.set(s,{resolve:r,reject:o,timer:i});try{this.ws.send(JSON.stringify({id:s,type:e,...t}))}catch(a){clearTimeout(i),this.pending.delete(s),o(a instanceof Error?a:new Error(String(a)))}})}handleMessage(e){let t;try{t=JSON.parse(String(e))}catch{return}if(!t||typeof t!="object")return;if(t.type==="agent:event"){for(let r of this.eventListeners)try{r({sessionId:t.sessionId,event:t.event})}catch{}return}if(t.type==="agent:session-status"){for(let r of this.statusListeners)try{r(t.session)}catch{}return}if(typeof t.id!="number")return;let s=this.pending.get(t.id);s&&(this.pending.delete(t.id),clearTimeout(s.timer),t.error?s.reject(new c(t.error,t.code)):s.resolve(t.result))}handleClose(){if(!this.closed){this.closed=!0;for(let[,e]of this.pending)clearTimeout(e.timer),e.reject(new c("daemon disconnected","DISCONNECT"));this.pending.clear();for(let e of this.disconnectListeners)try{e()}catch{}}}};function R(n=7668,e=400){return new Promise(t=>{let s=new U.Socket,r=!1,o=i=>{r||(r=!0,s.destroy(),t(i))};s.setTimeout(e),s.once("connect",()=>o(!0)),s.once("timeout",()=>o(!1)),s.once("error",()=>o(!1)),s.connect(n,"127.0.0.1")})}async function _(n={}){let e=n.port??7668;if(await R(e))return{alreadyRunning:!0};let{cmd:t,prefixArgs:s}=D(),r=[...s,"server","--quiet"];e!==7668&&r.push("--port",String(e));let o=N(t,r,{detached:!0,stdio:"ignore",env:process.env,cwd:process.cwd()});o.unref();let i=Date.now()+(n.startupTimeoutMs??5e3);for(;Date.now()<i;){if(await R(e))return{alreadyRunning:!1,pid:o.pid};await new Promise(a=>setTimeout(a,100))}throw new c(`spawned sootsim daemon on port ${e} but it did not come up in time. run \`sootsim server\` manually to diagnose.`,"SPAWN_TIMEOUT")}async function h(n={}){await _({port:n.port,startupTimeoutMs:n.startupTimeoutMs});let e=new f(n);try{await e.waitReady()}catch(t){throw e.close(),t}return e}async function ue(n){let[e,...t]=n;try{switch(e){case void 0:case"--help":case"-h":case"help":return C(),0;case"attach":return await F(t);case"projects":return await B();case"project":return await W(t);case"sessions":return await J(t);case"start":return await V(t);case"prompt":return await q(t);case"watch":return await G(t);case"transcript":return await H(t);case"end":return await K(t);case"paths":return await z();default:return process.stderr.write(`unknown agent subcommand: ${e}
|
|
3
3
|
`),C(),2}}catch(s){if(s instanceof c)return process.stderr.write(`${s.message}
|
|
4
4
|
`),1;throw s}}async function v(n){let e=await h({clientLabel:"sootsim-agent-cli"});try{return await n(e)}finally{e.close()}}function C(){process.stdout.write(`sootsim agent \u2014 attached projects + local agent sessions
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
2
|
-
import{b as M}from"./chunk-
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import{b as M}from"./chunk-432TMHBG.js";import"./chunk-WWDJCKMI.js";import{execFile as q,spawn as L}from"node:child_process";import{randomUUID as X}from"node:crypto";import{constants as R,createReadStream as V,createWriteStream as j,existsSync as S,openSync as J}from"node:fs";import Y from"node:fs/promises";import z from"node:path";import K from"node:readline";import{promisify as Q}from"node:util";import{spawn as H}from"node:child_process";import G from"node:readline";var O=class extends Error{code;data;constructor(n,r,p){super(n),this.name="CodexRpcError",this.code=r,this.data=p}};function W(a){let n=H(a.bin,["app-server"],{cwd:a.cwd,env:{...process.env,...a.env},stdio:["pipe","pipe","pipe"]}),r=new Map,p=new Map,u=1,m=!1,d=new Promise(s=>{n.on("exit",(i,e)=>{m=!0;let g=new Error(`codex app-server exited (code=${i}, signal=${e??""})`);for(let{reject:f}of r.values())f(g);r.clear(),s({code:i,signal:e})})});G.createInterface({input:n.stdout,crlfDelay:1/0}).on("line",s=>{let i=s.trim();if(!i)return;let e;try{e=JSON.parse(i)}catch{return}if(e.id!=null&&(e.result!==void 0||e.error!==void 0)){let g=typeof e.id=="string"?Number(e.id):e.id,f=g!=null?r.get(g):void 0;if(!f)return;r.delete(g),e.error?f.reject(new O(e.error.message,e.error.code,e.error.data)):f.resolve(e.result);return}if(e.method){let g=p.get(e.method);if(!g)return;for(let f of g)try{f(e.params)}catch(l){console.error(`[codex-client] handler for "${e.method}" threw:`,l instanceof Error?l.stack??l.message:l)}}}),n.stderr.setEncoding("utf8"),n.stderr.on("data",s=>{let i=p.get("__stderr__");if(i)for(let e of i)try{e({text:s})}catch{}});function t(s){if(!m)try{n.stdin.write(JSON.stringify(s)+`
|
|
3
3
|
`)}catch{}}return{exited:d,on(s,i){let e=p.get(s);return e||(e=new Set,p.set(s,e)),e.add(i),()=>{e?.delete(i)}},request(s,i){if(m)return Promise.reject(new Error(`codex app-server closed; cannot call ${s}`));let e=u++;return new Promise((g,f)=>{r.set(e,{resolve:l=>g(l),reject:f,method:s}),t({jsonrpc:"2.0",id:e,method:s,params:i})})},notify(s,i){t({jsonrpc:"2.0",method:s,params:i})},async shutdown(s=1500){if(m)return;try{n.stdin.end()}catch{}let i=setTimeout(()=>{if(!m)try{n.kill("SIGTERM")}catch{}},s);try{await d}finally{clearTimeout(i)}},kill(s="SIGTERM"){if(!m)try{n.kill(s)}catch{}}}}var b="never",F="danger-full-access",Z={type:"dangerFullAccess"},D="fast",ee="medium",te=Q(q);async function ne(a,n){let r=n||a;if(r.includes("/")||r.includes("\\"))return S(r)?{ok:!0,path:r}:{ok:!1,message:`agent binary not found at path: ${r}`};try{let{stdout:p}=await te("which",[r],{timeout:1500}),u=p.trim();return u?{ok:!0,path:u}:{ok:!1,message:`${r} not found on PATH`}}catch{return{ok:!1,message:`${r} not found on PATH. install the ${a} CLI and retry, or pass --${a}-bin /path/to/${a} to agent start.`}}}function re(a){let n={"--session-id":"sessionId","--project-id":"projectId","--provider":"provider","--cwd":"cwd","--prompt-in":"promptIn","--events-out":"eventsOut","--transcript":"transcript","--codex-bin":"codexBin","--claude-bin":"claudeBin","--claude-session-uuid":"claudeSessionUuid"},r={};for(let p=0;p<a.length;p++){let u=a[p],m=n[u];m&&(r[m]=a[p+1],p++)}return r}async function Se(a){let n=re(a);if(!n.sessionId||!n.projectId||!n.provider||!n.cwd||!n.promptIn||!n.eventsOut)return process.stderr.write(`usage: sootsim agent-wrapper --session-id <id> --project-id <id>
|
|
4
4
|
--provider codex|claude --cwd <path>
|
|
5
5
|
--prompt-in <fifo> --events-out <fifo>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
2
|
-
import"./chunk-
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import"./chunk-WWDJCKMI.js";import{spawn as x}from"child_process";var w=new Set(["describe","find","list","network","logs","count","tree","url","a11y","node","layout","keyboard","errors","warnings","requests","animations","animation","state","sample-color"]),S=new Set(["--exists","--empty","--negate","--quiet","--allow-timeout","!"]),q=new Set(["--count","--count-at-least","--count-at-most","--contains","--not-contains","--matches","--not-matches","--equals","--jq","--has-path","--within"]),P=new Set(["--path-equals"]);function j(s){let r=[],u=[],n=0,e=!1,t=!1,i=!1;for(let o=0;o<s.length;o++){let a=s[o];if(a==="--within"){let c=Number(s[o+1]);if(!Number.isFinite(c)||c<0)return{error:`--within requires a non-negative number, got: ${s[o+1]}`};n=c,o++;continue}if(a==="--negate"||a==="!"){e=!0;continue}if(a==="--quiet"){t=!0;continue}if(a==="--allow-timeout"){i=!0;continue}if(S.has(a)){u.push({kind:a,args:[]});continue}if(q.has(a)){let c=s[o+1];if(c==null)return{error:`${a} requires a value`};u.push({kind:a,args:[c]}),o++;continue}if(P.has(a)){let c=s[o+1],f=s[o+2];if(c==null||f==null)return{error:`${a} requires <path> <value>`};u.push({kind:a,args:[c,f]}),o+=2;continue}r.push(a)}return r.length===0?{error:"assert requires a verb (e.g. find, describe, get errors)"}:{verbArgs:r,predicates:u,withinMs:n,negate:e,quiet:t,allowTimeout:i}}function N(s){return s[0]==="get"||s[0]==="debug"?w.has(s[1])?s[1]:null:s[0]==="do"||s[0]==="wait"?null:w.has(s[0])?s[0]:null}async function R(s){let u=[process.argv[1],...s,"--json"];return new Promise(n=>{let e=x(process.execPath,u,{stdio:["ignore","pipe","pipe"],env:process.env}),t="",i="";e.stdout.on("data",o=>t+=o.toString()),e.stderr.on("data",o=>i+=o.toString()),e.on("error",o=>{n({ok:!1,payload:null,reason:`spawn failed: ${o.message}`})}),e.on("close",o=>{if(o!==0){let a=i.trim().split(`
|
|
3
3
|
`).slice(-2).join(" | ").slice(0,200);n({ok:!1,payload:null,reason:`verb exited ${o}${a?`: ${a}`:""}`});return}if(!t.trim()){n({ok:!1,payload:null,reason:"verb produced no output"});return}try{let a=JSON.parse(t);n({ok:!0,payload:a})}catch{n({ok:!1,payload:null,reason:`verb output was not valid json: ${t.slice(0,120)}`})}})})}function y(s,r){let u=r.split(".").filter(e=>e.length>0),n=s;for(let e of u){if(n==null)return{found:!1};if(Array.isArray(n)){let t=Number(e);if(!Number.isInteger(t)||t<0||t>=n.length)return{found:!1};n=n[t];continue}if(typeof n=="object"){let t=n;if(!(e in t))return{found:!1};n=t[e];continue}return{found:!1}}return{found:!0,value:n}}function d(s){return Array.isArray(s)?s.length:s&&typeof s=="object"?Object.keys(s).length:null}async function A(s,r){let{kind:u,args:n}=s;switch(u){case"--exists":return r==null?{ok:!1,reason:"payload is null"}:d(r)===0?{ok:!1,reason:"payload is empty"}:{ok:!0,reason:"exists"};case"--empty":{if(r==null)return{ok:!0,reason:"payload is null"};let e=d(r);return e===0?{ok:!0,reason:"payload is empty"}:{ok:!1,reason:`payload has ${e} item(s)`}}case"--count":{let e=Number(n[0]),t=d(r);return t==null?{ok:!1,reason:`payload not countable (${typeof r})`}:{ok:t===e,reason:`count=${t}, want=${e}`}}case"--count-at-least":{let e=Number(n[0]),t=d(r);return t==null?{ok:!1,reason:"payload not countable"}:{ok:t>=e,reason:`count=${t}, want>=${e}`}}case"--count-at-most":{let e=Number(n[0]),t=d(r);return t==null?{ok:!1,reason:"payload not countable"}:{ok:t<=e,reason:`count=${t}, want<=${e}`}}case"--contains":{let e=n[0],t=JSON.stringify(r);return{ok:t.includes(e),reason:t.includes(e)?`contains "${e}"`:`missing "${e}"`}}case"--not-contains":{let e=n[0],t=JSON.stringify(r);return{ok:!t.includes(e),reason:t.includes(e)?`unexpected "${e}"`:`not contains "${e}"`}}case"--matches":{let e=new RegExp(n[0]),t=JSON.stringify(r),i=e.test(t);return{ok:i,reason:i?`matches /${n[0]}/`:`no match for /${n[0]}/`}}case"--not-matches":{let e=new RegExp(n[0]),t=JSON.stringify(r),i=!e.test(t);return{ok:i,reason:i?`not matches /${n[0]}/`:`unexpected /${n[0]}/`}}case"--equals":{let e=n[0],t=typeof r=="string"?r:typeof r=="number"||typeof r=="boolean"?String(r):JSON.stringify(r);return{ok:t===e,reason:`got=${JSON.stringify(t)}, want=${JSON.stringify(e)}`}}case"--has-path":{let e=y(r,n[0]);return{ok:e.found,reason:e.found?`path ${n[0]} present`:`path ${n[0]} missing`}}case"--path-equals":{let[e,t]=n,i=y(r,e);if(!i.found)return{ok:!1,reason:`path ${e} missing`};let o=typeof i.value=="string"||typeof i.value=="number"||typeof i.value=="boolean"?String(i.value):JSON.stringify(i.value);return{ok:o===t,reason:`${e}=${o}, want=${t}`}}case"--jq":{let e=n[0];try{let i=(await O(e,r)).trim();return{ok:i.length>0&&i!=="null"&&i!=="false"&&i!=='""',reason:`jq: ${i.slice(0,120)}`}}catch(t){throw new Error(`jq evaluation failed: ${t.message}`)}}default:return{ok:!1,reason:`unknown predicate ${u}`}}}function O(s,r){return new Promise((u,n)=>{let e=x("jq",[s],{stdio:["pipe","pipe","pipe"]}),t="",i="";e.stdout.on("data",o=>t+=o.toString()),e.stderr.on("data",o=>i+=o.toString()),e.on("error",n),e.on("close",o=>{if(o!==0){n(new Error(i.trim()||`jq exited ${o}`));return}u(t)}),e.stdin.end(JSON.stringify(r))})}async function T(s){(s[0]==="--help"||s[0]==="-h"||s.length===0)&&(J(),process.exit(s.length===0?2:0));let r=j(s);"error"in r&&(process.stderr.write(` assert: ${r.error}
|
|
4
4
|
`),process.exit(2));let{verbArgs:u,predicates:n,withinMs:e,negate:t,quiet:i,allowTimeout:o}=r;N(u)||(process.stderr.write(` assert: unknown or non-read verb: ${u.join(" ")}
|
|
5
5
|
supported: ${Array.from(w).sort().join(", ")}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import{a,b,c,d}from"./chunk-HAKR72LJ.js";import"./chunk-C3QLIYCS.js";import"./chunk-W7CYWXRZ.js";import"./chunk-MZPAJ5PQ.js";import"./chunk-WRF43M33.js";import"./chunk-WWDJCKMI.js";export{c as ensureDaemonRunning,a as ensureRuntimeInstalled,d as ensureSootsimReady,b as resolveBootstrapPort};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
var c="sootsim-agent-prompt-v1";function r(n){return typeof n=="string"?n.trim():""}function m(n){let e=r(n.text);if(!e)return"";let t=r(n.displayText),s=r(n.inspectSummary),o=r(n.inspectTrace);return!!s||!!o||/[\r\n]/.test(e)||!!t&&t!==e?JSON.stringify({__sootsimAgentPrompt:c,text:e,displayText:t,inspectSummary:s,inspectTrace:o}):e}function u(n){let e=r(n);if(!e)return null;try{let t=JSON.parse(e);if(t.__sootsimAgentPrompt!==c)return{text:e};let s=r(typeof t.text=="string"?t.text:void 0);if(!s)return null;let o=r(typeof t.displayText=="string"?t.displayText:void 0),i=r(typeof t.inspectSummary=="string"?t.inspectSummary:void 0),p=r(typeof t.inspectTrace=="string"?t.inspectTrace:void 0);return{text:s,...o?{displayText:o}:{},...i?{inspectSummary:i}:{},...p?{inspectTrace:p}:{}}}catch{return{text:e}}}export{m as a,u as b};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
2
|
-
import{a as T,b as B,c as S,d as Z}from"./chunk-
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import{a as T,b as B,c as S,d as Z}from"./chunk-OAHMYSMD.js";import{b as k}from"./chunk-5TTQKPGH.js";import{a as te}from"./chunk-WVBPATRA.js";import{a as K}from"./chunk-HGFIS26A.js";import{b as Y,c as y,d as h,e as v}from"./chunk-C3QLIYCS.js";import{d as N,e as g,f as H}from"./chunk-W7CYWXRZ.js";import{u as V,v as X}from"./chunk-WRF43M33.js";import{g as oe}from"./chunk-ZF5FCFLD.js";import{a as I,b as ee}from"./chunk-SY74J6F4.js";import be from"node:fs";import $e from"node:os";import z from"node:path";function ue(o){return o.replace(/^\[|\]$/g,"").toLowerCase()}function M(o){let t=ue(o);return t==="localhost"||t.endsWith(".localhost")||t==="0.0.0.0"||t==="::1"||/^127(?:\.\d{1,3}){3}$/.test(t)}function re(o){return new Error(`could not resolve a native bundle for ${o}. pass an explicit bundle URL or open Connect and choose the app there.`)}function me(o,t){return o?.includes("/one/metro-entry.bundle")?"one":typeof t=="string"&&t?"expo":"unknown"}function ne(o,t,e){return`${o}//${t}:${e}`}function pe(o){if(typeof window>"u")return!1;try{let t=new URL(o);return M(t.hostname)&&M(window.location.hostname)&&t.origin!==window.location.origin}catch{return!1}}async function C(o,t){let e={...t,cache:t?.cache??"no-store"};return pe(o)?fetch(`/__fetch-proxy?url=${encodeURIComponent(o)}`,e):fetch(o,e)}function fe(o){return o==="https:"?443:80}function ge(o){let t=o.pathname||"/";return(t==="/"||t==="")&&!o.search&&!o.hash}async function he(o){try{return(await C(`${o}${B}`,{method:"HEAD"})).ok}catch{return!1}}async function se(o,t){let e=o.replace(/\/+$/,"");try{let r=await C(`${e}/`,{headers:{"expo-platform":"ios"}});if(r.ok){let n=await r.json(),s=n?.extra?.expoClient||n?.extra||{},i=typeof n?.launchAsset?.url=="string"?n.launchAsset.url:void 0;if(i||s.name){let a=me(i,s.sdkVersion);return{bundleUrl:a==="one"&&await he(e)?`${e}${B}`:Z(i||`${e}${S}`),port:t,framework:a,projectName:s.name}}}}catch{}try{if((await C(`${e}${B}`,{method:"HEAD"})).ok)return{bundleUrl:`${e}${B}`,port:t,framework:"one"}}catch{}try{if((await C(`${e}${S}`,{method:"HEAD"})).ok)return{bundleUrl:`${e}${S}`,port:t,framework:"metro"}}catch{}try{let r=await C(`${e}/status`);if(r.ok&&(await r.text()).includes("packager-status:running"))return{bundleUrl:`${e}${S}`,port:t,framework:"metro"}}catch{}return null}async function we(o){return se(ne("http:","localhost",o),o)}async function F(o){let t=o.trim();if(/^\d+$/.test(t)){let a=parseInt(t,10),c=await we(a);if(c)return c;throw re(`localhost:${a}`)}let e=t.startsWith("http")?t:`http://${t}`,r;try{r=new URL(e)}catch{throw new Error(`could not parse "${o}". pass a dev-server port, a dev-server base URL, or a full bundle URL.`)}let n=r.protocol||"http:",s=r.port?parseInt(r.port,10):fe(n),i=ne(n,r.hostname,s);if(M(r.hostname)&&ge(r)){let a=await se(i,s);if(a)return a;throw re(i)}return{bundleUrl:r.toString(),port:s,framework:"unknown"}}function Ae(){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 ie(o){let t=I();if(console.log(` note: no sootsim bridge detected on port ${o}`),t){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")}function Ne(o){console.error(""),console.error(` no sootsim session is connected to the bridge on port ${o}.`),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 ye={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 x(o){return new Promise(t=>setTimeout(t,o))}function G(o){return o.trim()}function q(o){try{let t=new URL(o),e=t.pathname.replace(/\/+$/,"")||"/";return t.searchParams.has("open")||t.searchParams.has("port")||t.searchParams.has("bundle")||t.searchParams.has("demo")||t.pathname.includes("/sootsim/index.html")||e==="/sootsim"||t.pathname==="/__soot"||t.pathname==="/__soot/"||e==="/rn"||/^\/rn\/[^/]+$/i.test(e)||/^\/app\/[^/]+$/i.test(e)||e==="/__soot/rn"||/^\/__soot\/rn\/[^/]+$/i.test(e)||/^\/__soot\/app\/[^/]+$/i.test(e)}catch{return!1}}async function oo(o){let t=G(o);return(await F(t)).bundleUrl}function Q(o){let t=o.replace(/\/+$/,"")||"/";return t==="/__soot"||t.startsWith("/__soot/")?"/__soot":""}function ve(o,t){let e=G(o),r=new URL(t),n=Q(r.pathname);return r.pathname=`${n}/rn`,r.searchParams.delete("bundle"),r.searchParams.delete("demo"),r.searchParams.delete("app"),r.searchParams.delete("open"),r.searchParams.delete("port"),/^\d+$/.test(e)?r.pathname=`${n}/rn/${e}`:(r.pathname=`${n}/rn`,r.searchParams.set("open",e)),r.toString()}function Be(o){let t=G(o);return/^\d+$/.test(t)||/^https?:\/\//i.test(t)?!0:/^(localhost|127\.0\.0\.1|\[::1\]|[^/]+\.localhost):\d+(?:\/.*)?$/i.test(t)}async function Se(o,t){let e=await F(o),r=new URL(t),n=Q(r.pathname);return r.pathname=`${n}/rn`,r.searchParams.delete("open"),r.searchParams.delete("port"),r.searchParams.delete("demo"),r.searchParams.delete("app"),r.searchParams.set("bundle",e.bundleUrl),r.toString()}function Ce(o){return o.startsWith("~/")?z.join($e.homedir(),o.slice(2)):z.isAbsolute(o)?o:z.resolve(process.cwd(),o)}function xe(o){let t={};for(let e=0;e<o.length;e++){if(o[e]!=="--replace")continue;let r=o[e+1];r||(console.error(" sootsim open: --replace expects <module>=<file>"),process.exit(1));let n=r.indexOf("=");(n<=0||n===r.length-1)&&(console.error(" sootsim open: --replace expects <module>=<file>"),process.exit(1));let s=r.slice(0,n).trim(),i=Ce(r.slice(n+1).trim());be.existsSync(i)||(console.error(` sootsim open: replacement file not found: ${i}`),process.exit(1)),t[s]={file:i},e++}return Object.keys(t).length>0?{modules:t}:void 0}function ce(){let o=V();return X(o)&&o.runtimePort>0?`http://localhost:${o.runtimePort}/`:K}async function _(o,t=ce()){if(!o)return new URL(t).toString();if(q(o))return new URL(o).toString();let e=ye[o.toLowerCase()];if(e){let r=new URL(t),n=Q(r.pathname);return r.pathname=`${n}${e}`,r.toString()}return Be(o)?Se(o,t):ve(o,t)}function ae(o,t){let e=o?.url||o?.origin||t;try{let r=new URL(e);return r.searchParams.delete("bundle"),r.searchParams.delete("demo"),r.searchParams.delete("app"),r.searchParams.delete("open"),r.searchParams.delete("port"),r.searchParams.delete("inspectOpen"),r.toString()}catch{return t}}async function Pe(o,t,e){let r=new URL(await _(o,t));return r.searchParams.set("inspectOpen",e),r.toString()}async function Ue(o,t,e,r={}){let n=r.attempts??30,s=r.intervalMs??500,i=r.minNodeCount??10;for(let a=0;a<n;a++){let c=h(o,{commandTimeoutMs:t,browserId:e,browserIdSource:"flag"});try{let d=await c.send({type:"evaluate",code:"(async () => (await window.__sootsimTest?.getNodeCount()) || 0)()"});if(typeof d=="number"&&d>i)return{bridge:c,count:d}}catch{}c.close(),await x(s)}return null}function ke(o){if(!o)return null;try{let t=new URL(o);if(t.searchParams.has("bundle")){let e=t.searchParams.get("bundle")||"";try{let r=new URL(e),n=r.pathname.length>36?`...${r.pathname.slice(-36)}`:r.pathname;return`bundle ${r.host}${n}`}catch{return"bundle"}}return t.searchParams.has("port")?`connect :${t.searchParams.get("port")||""}`:t.searchParams.has("open")?`connect ${t.searchParams.get("open")||""}`:t.searchParams.has("demo")?`demo ${t.searchParams.get("demo")||"default"}`:t.pathname.includes("/sootsim/index.html")||t.pathname==="/sootsim/"||t.pathname==="/sootsim"?"embedded sootsim":null}catch{return null}}function Te(o,t){if(o.length===0){console.log(" no browser tabs connected");return}console.log(` connected browser tabs (${o.length}):
|
|
3
3
|
`);for(let e of o){let r=e.lockedBy&&e.lockExpiresAt?`locked by ${e.lockedBy} (${Math.max(0,Math.round((e.lockExpiresAt-Date.now())/1e3))}s)`:"",n=[e.isPrimary?"primary":"",e.id===t?"selected":"",e.readyState,e.attachedCliCount&&e.attachedCliCount>0?"in use":"",e.userFocused?"focused":"",r].filter(Boolean);console.log(` ${e.id}${n.length?` [${n.join(", ")}]`:""}`);let s=ke(e.url);if(s&&console.log(` loaded: ${s}`),e.url?console.log(` url: ${e.url}`):e.origin&&console.log(` origin: ${e.origin}`),e.title&&console.log(` title: ${e.title}`),console.log(` connected: ${new Date(e.connectedAt).toISOString()}`),e.lastActiveAt&&e.lastActiveAt>0){let i=Math.round((Date.now()-e.lastActiveAt)/1e3),a=i<60?`${i}s ago`:i<3600?`${Math.round(i/60)}m ago`:`${Math.round(i/3600)}h ago`;console.log(` last active: ${a}`)}}}async function j(o,t,e,r={}){let n=r.attempts??30,s=r.intervalMs??500;for(let i=0;i<n;i++){let a=h(o,{commandTimeoutMs:t});try{let d=(await a.listBrowsers()).find(e);if(d)return d}catch{}finally{a.close()}await x(s)}return null}async function Ie(o,t,e,r={}){let n=r.attempts??20,s=r.intervalMs??250;for(let i=0;i<n;i++){let a=h(o,{commandTimeoutMs:t});try{let d=(await a.listBrowsers()).find(b=>b.id===e);if(!d||d.readyState!=="open")return!0}catch{return!0}finally{a.close()}await x(s)}return!1}function E(o,t){if(t){let n=o.find(s=>s.id===t);if(!n)throw new Error(`no browser connected with id ${t}`);return n}let e=o.find(n=>n.isPrimary&&n.readyState==="open");if(e)return e;let r=o.find(n=>n.readyState==="open");if(r)return r;throw new Error("no browser connected")}function R(o,t,e){console.log(` ${e==="current session"?"loaded":"opened"}: ${o} [${e}]`),console.log(` active session: ${t.id}`),console.log(JSON.stringify({id:t.id,url:t.url},null,2))}async function O(o,t,e){if(e.includes("--no-describe"))return;let r=process.env.SOOTSIM_QUIET_TARGET_NOTICE;process.env.SOOTSIM_QUIET_TARGET_NOTICE="1";try{await Me(o,t,{stableMs:150,maxMs:400});let n=h(o,{commandTimeoutMs:3e3,browserId:t,cliLabel:"open --describe",browserIdSource:"flag"});try{let i=`(async () => {
|
|
4
4
|
const t = window.__sootsimTest
|
|
5
5
|
const ms = window.SootSim?.bridges?.mainShell
|
|
@@ -8,4 +8,4 @@ import{a as T,b as B,c as S,d as Z}from"./chunk-AS4V7TZU.js";import{b as k}from"
|
|
|
8
8
|
try { shell = ms?.getState ? await ms.getState() : null } catch {}
|
|
9
9
|
const tree = await t.dumpTree(12, ${JSON.stringify({describe:!0,verbose:!1,filter:""})})
|
|
10
10
|
return { tree, shell }
|
|
11
|
-
})()`,a=await n.send({type:"evaluate",code:i});if(!a?.tree)return;if(console.log(""),a.shell?.state){let c=[`state=${a.shell.state}`,a.shell.activeApp?`app=${a.shell.activeApp}`:null].filter(Boolean);console.log(` shell: ${c.join(" ")}`)}console.log(a.tree)}finally{n.close()}}catch{}finally{r===void 0?delete process.env.SOOTSIM_QUIET_TARGET_NOTICE:process.env.SOOTSIM_QUIET_TARGET_NOTICE=r}}async function Me(o,t,e){let r=Date.now()+e.maxMs,n="(async () => (await window.__sootsimTest?.getNodeCount?.()) || 0)()",s=h(o,{commandTimeoutMs:2e3,browserId:t,browserIdSource:"flag"});try{let i=-1,a=0;for(;Date.now()<r;){let c=-1;try{let d=await s.send({type:"evaluate",code:n});typeof d=="number"&&(c=d)}catch{}if(c>=0&&c===i){if(Date.now()-a>=e.stableMs)return}else i=c,a=Date.now();await x(50)}}finally{s.close()}}async function
|
|
11
|
+
})()`,a=await n.send({type:"evaluate",code:i});if(!a?.tree)return;if(console.log(""),a.shell?.state){let c=[`state=${a.shell.state}`,a.shell.activeApp?`app=${a.shell.activeApp}`:null].filter(Boolean);console.log(` shell: ${c.join(" ")}`)}console.log(a.tree)}finally{n.close()}}catch{}finally{r===void 0?delete process.env.SOOTSIM_QUIET_TARGET_NOTICE:process.env.SOOTSIM_QUIET_TARGET_NOTICE=r}}async function Me(o,t,e){let r=Date.now()+e.maxMs,n="(async () => (await window.__sootsimTest?.getNodeCount?.()) || 0)()",s=h(o,{commandTimeoutMs:2e3,browserId:t,browserIdSource:"flag"});try{let i=-1,a=0;for(;Date.now()<r;){let c=-1;try{let d=await s.send({type:"evaluate",code:n});typeof d=="number"&&(c=d)}catch{}if(c>=0&&c===i){if(Date.now()-a>=e.stableMs)return}else i=c,a=Date.now();await x(50)}}finally{s.close()}}async function to(o,t={}){let e=y(o,{port:t.port,commandTimeoutMs:t.timeoutMs}),r=v(e);try{let n=await r.listBrowsers();Te(n,e.browserId)}finally{r.close()}}async function ro(o,t={}){let e=y(o,{port:t.port,commandTimeoutMs:t.timeoutMs,stripBooleanFlags:["--new","--headless"],stripValueFlags:["--base-url","--replace","--driver"]}),r=o.includes("--new"),n=xe(o);r&&e.browserIdSource==="flag"&&(console.error(" sootsim open: --new cannot be combined with --session/--id"),process.exit(1));let s=e.positional[0]||"",i=o.find((l,p)=>o[p-1]==="--driver")||"",a=o.includes("--headless"),c=o.find((l,p)=>o[p-1]==="--base-url")||ce(),d=o.includes("--base-url"),b=N();if(!r&&(e.browserIdSource==="flag"||e.browserIdSource==="saved"&&!!b)){let l=v(e),p=e.browserId?` --session ${e.browserId}`:"",u=!1;try{let m=null;try{let f=await l.listBrowsers();if(e.browserIdSource==="saved"?(m=f.find($=>$.id===b&&$.readyState==="open")??null,m||H()):m=E(f,e.browserId),!m)if(e.browserIdSource==="saved")u=!0;else throw new Error("no browser connected");if(!u&&m){let $=d||q(s)?c:ae(m,c),U=await _(s,T($,n));l.send({type:"evaluate",browserId:m.id,code:`window.location.href = ${JSON.stringify(U)}`}).catch(()=>{})}}catch(f){console.error(` open failed: ${f instanceof Error?f.message:String(f)}`),await k(l,{errorsCommand:`sootsim get errors 5${p}`,warningsCommand:`sootsim get warnings 5${p}`,requestsCommand:`sootsim get requests 5${p}`}),process.exit(1)}if(!u&&m){await x(1500);let f=await Ue(e.wsPort,e.commandTimeoutMs,m.id);f||(console.error(" timed out waiting for current session to load target"),process.exit(1)),f.bridge.close(),g(m.id);let $=d||q(s)?c:ae(m,c),U=await _(s,T($,n));R(U,{...m,url:U},"current session"),await O(e.wsPort,m.id,o);return}}finally{l.close()}}let W=T(c,n),de=await _(s,W),J=`cli-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`,w=await Pe(s,W,J),D=l=>l.url?.includes(`inspectOpen=${J}`)??!1;if(i){let l=oe(i);l||(console.error(` unknown driver "${i}" \u2014 run \`sootsim list --drivers\``),process.exit(1));let p=await l.launch({url:w,headless:a});p.launched||(console.error(` ${l.name} driver: ${p.message}`),process.exit(1));let u=await j(e.wsPort,e.commandTimeoutMs,D,{attempts:60,intervalMs:500});u||(console.error(" timed out waiting for opened tab to connect"),process.exit(1)),g(u.id),R(w,u,`${l.name} driver`),await O(e.wsPort,u.id,o);return}let L=!1,A=!1;try{let l=h(e.wsPort,{commandTimeoutMs:2e3});try{await l.listBrowsers(),L=!0}finally{l.close()}}catch{}if(L)try{let l=h(e.wsPort,{commandTimeoutMs:3e3});try{await l.openUrl(w),A=!0}finally{l.close()}}catch{}if(!A){let l=I();if(l)try{if((await ee(w,l)).launched){let u=await j(e.wsPort,e.commandTimeoutMs,D,{attempts:20,intervalMs:500});if(u){g(u.id),R(w,u,"desktop companion"),await O(e.wsPort,u.id,o);return}}}catch{}if(await te(w),!L){console.log(` opened: ${de}`),ie(e.wsPort);return}}let P=await j(e.wsPort,e.commandTimeoutMs,D,{attempts:30,intervalMs:500});P||(console.error(" timed out waiting for opened tab to connect"),process.exit(1)),g(P.id),R(w,P,A?"bridge":"direct shell open"),await O(e.wsPort,P.id,o)}async function le(o,t={}){let e=y(o,{port:t.port,commandTimeoutMs:t.timeoutMs}),r=v(e),n=e.browserId?` --session ${e.browserId}`:"";try{try{let s=await r.listBrowsers(),i=E(s,e.positional[0]||e.browserId);await r.focusBrowser(i.id),g(i.id),console.log(` using: ${i.id}`)}catch(s){console.error(` use failed: ${s instanceof Error?s.message:String(s)}`),await k(r,{errorsCommand:`sootsim get errors 5${n}`,warningsCommand:`sootsim get warnings 5${n}`,requestsCommand:`sootsim get requests 5${n}`}),process.exit(1)}}finally{r.close()}}async function no(o,t={}){await le(o,t)}async function so(o,t={}){await le(o,t)}async function io(o,t={}){let e=y(o,{port:t.port,commandTimeoutMs:t.timeoutMs,stripBooleanFlags:["--force"]}),r=o.includes("--force"),n=v(e);try{try{let s=await n.listBrowsers(),i=E(s,e.positional[0]||e.browserId),a=r&&i.lockedBy&&i.lockedByKind!=="user-active"?i.lockedBy:null,c=await n.claim(i.id,{force:r});g(i.id);let d=Math.max(0,Math.round((c.lockExpiresAt-Date.now())/1e3)),b=c.bootedCount>0?` (booted ${c.bootedCount})`:"";console.log(` claimed: ${c.browserId} [${d}s]${b}`),a&&console.log(` took over from: ${a}`)}catch(s){if(s instanceof Y){let i=Math.max(0,Math.round(s.lock.expiresInMs/1e3));console.error(` claim failed: locked by ${s.lock.by} for ${i}s more`),console.error(" use --force to take it, or `sootsim open --new` for a fresh session"),process.exit(1)}console.error(` claim failed: ${s instanceof Error?s.message:String(s)}`),process.exit(1)}}finally{n.close()}}async function ao(o,t={}){let e=y(o,{port:t.port,commandTimeoutMs:t.timeoutMs}),r=v(e),n=e.browserId?` --session ${e.browserId}`:"";try{try{let s=await r.listBrowsers(),i=E(s,e.positional[0]||e.browserId),a=s.find(d=>d.id!==i.id&&d.readyState==="open");if(await r.closeBrowser(i.id),!await Ie(e.wsPort,e.commandTimeoutMs,i.id)){console.log(` close requested: ${i.id} (still connected)`);return}N()===i.id&&(a?g(a.id):H()),console.log(` closed: ${i.id}`)}catch(s){console.error(` close failed: ${s instanceof Error?s.message:String(s)}`),await k(r,{errorsCommand:`sootsim get errors 5${n}`,warningsCommand:`sootsim get warnings 5${n}`,requestsCommand:`sootsim get requests 5${n}`}),process.exit(1)}}finally{r.close()}}export{Ae as a,Ne as b,oo as c,ce as d,_ as e,Pe as f,ke as g,Te as h,j as i,to as j,ro as k,no as l,so as m,io as n,ao as o};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
var w={bridgePort:7668,defaultShellUrl:"http://localhost:5173/"};function t(s,i){return{flag:s,description:i}}function e(s,i){return{command:s,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 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("--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("--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:"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("--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 opening a local Metro/dev-server port through the same shell URL resolver as `sootsim open`.",order:160,usage:["sootsim electron [--port <number>] [--driver <id>]"],options:[t("--port <number>","open a local app server through the sootsim shell, for example Metro on 8081"),t("--driver <id>","override the launch driver (electron|chromium|playwright|system)")],examples:[e("sootsim electron"),e("sootsim electron --port 8081"),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:"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:"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","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(k.map(s=>[s.id,s])),D=new Map;for(let s of k){D.set(s.id,s);for(let i of s.aliases||[])D.set(i,s)}function A(s){return D.get(s)}function o(s,i,r,a,n,l){return{verb:s,description:i,usage:r,examples:a,notes:n,...l}}var S=[o("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"]}),o("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"]}),o("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")]}),o("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"}),o("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"]}),o("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"]}),o("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"]}),o("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}),o("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}),o("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}),o("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}),o("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}),o("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}),o("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}),o("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}),o("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}),o("dismiss","dismiss the keyboard",["sootsim do dismiss"],void 0,void 0,{group:"do",subgroup:"text input",shortDescription:"dismiss the iOS keyboard",order:60}),o("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}),o("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}),o("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}),o("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}),o("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}),o("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}),o("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}),o("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"]}),o("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"]}),o("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"]}),o("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"]}),o("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"]}),o("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"]}),o("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"]}),o("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"]}),o("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"]}),o("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 session, 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"]}),o("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"]}),o("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"}),o("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"]}),o("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"]}),o("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"]}),o("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")]}),o("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}),o("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"}),o("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}),o("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}),o("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}),o("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}),o("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}),o("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}),o("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}),o("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"]}),o("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"]})],O=new Map(S.map(s=>[s.verb,s]));function x(s){return O.get(s)}function f(s,i=w){return s.replaceAll("{{bridgePort}}",String(i.bridgePort)).replaceAll("{{defaultShellUrl}}",i.defaultShellUrl)}function R(){return k.slice().sort((s,i)=>s.order-i.order)}function y(s,i){let r=Math.max(...i.map(n=>n.name.length),0)+2,a=i.map(n=>` ${n.name.padEnd(r)}${n.desc}`);return` ${s}
|
|
3
3
|
${a.join(`
|
|
4
4
|
`)}`}function j(s,i){let r=s.usage[0]??`sootsim ${s.verb}`,a=[i?`sootsim ${i} `:"","sootsim inspect ","sootsim "].filter(Boolean),n=r;for(let l of a)if(n.startsWith(l)){n=n.slice(l.length);break}return n}function C(s){return s.shortDescription??s.description}function I(s){return S.filter(i=>i.group===s)}function $(s){let i=I(s);if(!i.length)return"";let r=new Map;for(let l of i){let d=l.subgroup??"",m=r.get(d)??[];m.push(l),r.set(d,m)}let a=[];for(let l of i){let d=l.subgroup??"";a.includes(d)||a.push(d)}let n=[];for(let l of a){let d=r.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,s),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}`);n.push([p,...u].filter(Boolean).join(`
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
2
|
-
import{i as n}from"./chunk-
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import{i as n}from"./chunk-EHMSE3Q3.js";var i=[];for(let[t,e]of Object.entries(n))e.cliFlag&&i.push({name:`--${e.cliFlag}`,short:e.cliFlagShort?`-${e.cliFlagShort}`:void 0,key:t,type:e.type==="enum"?"string":e.type,description:e.description,options:e.options,default:e.default});var a=i;function g(){let t=[];for(let e of a){let s=e.short?`${e.short}, ${e.name}`:` ${e.name}`,o=e.options?` [${e.options.join("|")}]`:"",r=e.default!==void 0?` (default: ${e.default})`:"";t.push(` ${s.padEnd(24)}${e.description}${o}${r}`)}return t.join(`
|
|
3
3
|
`)}export{g as a};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
function d(t){let e=t.displayUrl||t.url;return t.status!=null?`${t.method} ${e} -> ${t.status}${t.statusText?` ${t.statusText}`:""}`:t.error?`${t.method} ${e} -> ${t.error}`:`${t.method} ${e}`}async function u(t,e,...o){return t.send({type:"call",path:`__sootsimTest.${e}`,args:o})}async function g(t,e={}){let o=await t.send({type:"evaluate",code:"window.__sootsimConsole?.count?.() || { errors: 0, warnings: 0, total: 0 }"});if(!o||typeof o!="object")return;let n=Math.max(0,Number(o.errors)||0),s=Math.max(0,Number(o.warnings)||0);if(n===0&&s===0)return;let r=[];if(n>0&&r.push(`${n} console error${n===1?"":"s"}`),s>0&&r.push(`${s} console warning${s===1?"":"s"}`),console.log(`
|
|
3
3
|
console: ${r.join(", ")}`),e.errorsCommand&&console.log(` inspect: ${e.errorsCommand}`),s>0&&e.warningsCommand&&console.log(` inspect: ${e.warningsCommand}`),!e.includeTail||n===0)return;let i=await t.send({type:"evaluate",code:"window.__sootsimConsole?.getErrors?.(5) || []"});if(!(!Array.isArray(i)||i.length===0)){console.log(`
|
|
4
4
|
recent console errors:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
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
3
|
${r}
|
|
4
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,4 +1,4 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
function t(_){return`(async () => {
|
|
3
3
|
const __t = window.__sootsimTest
|
|
4
4
|
if (!__t) throw new Error('__sootsimTest not available')
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
2
|
-
import{a as s}from"./chunk-
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import{a as s}from"./chunk-UKYK63H6.js";import{chmodSync as a,existsSync as l,mkdirSync as p,readFileSync as d,rmSync as h,writeFileSync as m}from"node:fs";import{homedir as f,platform as u}from"node:os";import{dirname as g,join as r,resolve as _}from"node:path";function o(){let t=f();if(!t)throw new Error("could not determine home directory");let e=u()==="darwin"?r(t,"Library","Application Support","sootsim"):u()==="win32"?r(process.env.APPDATA||r(t,"AppData","Roaming"),"sootsim"):r(process.env.XDG_CONFIG_HOME||r(t,".config"),"sootsim");return r(e,"credentials.json")}function k(){let t=o();if(!l(t))return null;try{let e=JSON.parse(d(t,"utf8"));return e.version!==1||typeof e.apiKey!="string"||!e.apiKey.startsWith("sk_sootsim_")?null:e.apiKey}catch{return null}}function K(t){if(!t.startsWith("sk_sootsim_"))throw new Error("api key must start with sk_sootsim_");let e=o();p(g(_(e)),{recursive:!0}),m(e,JSON.stringify({version:1,apiKey:t,savedAt:new Date().toISOString()},null,2)+`
|
|
3
3
|
`);try{a(e,384)}catch{}}function b(){h(o(),{force:!0})}var y=["ghs_","ghp_","gho_","ghu_","github_pat_"];function A(t){return!!(t&&t.length>=20&&t.length<=256&&y.some(e=>t.startsWith(e)))}function S(){let t=process.env.SOOT_INSTALLATION_TOKEN?.trim(),e=process.env.GITHUB_TOKEN?.trim(),i=t||e;if(!A(i))return null;let n=(process.env.SOOT_REPO||process.env.GITHUB_REPOSITORY||"").trim();if(!n)return null;let c=(process.env.SOOT_INSTALLATION_ID||process.env.GITHUB_APP_INSTALLATION_ID||"").trim();return{kind:"github",token:i,repoId:n,installationId:c||null,source:t?"soot-runner":"github-actions"}}function E(){let t=process.env.SOOTSIM_API_KEY?.trim();if(t&&t.startsWith("sk_sootsim_"))return{kind:"api-key",secret:t,source:"env"};let e=S();if(e)return e;let i=k();if(i)return{kind:"api-key",secret:i,source:"file"};let n=s();return n?.token?{kind:"session",token:n.token,origin:n.origin}:null}function w(t){return t.kind==="api-key"?`Bearer ${t.secret}`:`Bearer ${t.token}`}export{k as a,K as b,b as c,E as d,w as e};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
2
|
-
import{a as T}from"./chunk-KZ2LIDW6.js";import{a as I}from"./chunk-K7LDP7JL.js";function G(e){if(!e||typeof e!="object")return!1;let t=e;return typeof t.type=="string"&&typeof t.ts=="number"}function O(e){let t=e.trim();if(!t)return null;try{let r=JSON.parse(t);return G(r)?r:null}catch{return null}}import{randomBytes as H,createHash as Y}from"node:crypto";import f from"node:fs";import q from"node:os";import p from"node:path";var R=null;function _(){if(R)return R;let e=process.env.SOOTSIM_USER_DATA_DIR;if(e)return e;try{let t=I("electron");if(t.app?.getPath)return t.app.getPath("userData")}catch{}return X()}function X(){let e=q.homedir();if(process.platform==="darwin")return p.join(e,"Library","Application Support","sootsim");if(process.platform==="win32")return p.join(process.env.APPDATA||e,"sootsim");let t=process.env.XDG_CONFIG_HOME||p.join(e,".config");return p.join(t,"sootsim")}function h(){return _()}function $(){return p.join(_(),"attached-projects.json")}function N(){return{version:1,attachedProjects:[],previewAttachments:[],agentSessions:[]}}function S(){let e=$(),t;try{t=f.readFileSync(e,"utf8")}catch(r){if(r.code==="ENOENT")return N();throw r}try{let r=JSON.parse(t);if(!r||typeof r!="object")throw new Error("not an object");return{version:1,attachedProjects:Array.isArray(r.attachedProjects)?r.attachedProjects:[],previewAttachments:Array.isArray(r.previewAttachments)?r.previewAttachments:[],agentSessions:Array.isArray(r.agentSessions)?r.agentSessions:[]}}catch(r){let n=`${e}.corrupt-${Date.now()}`;try{f.renameSync(e,n),console.warn(`[sootsim] attached-projects.json was unparseable; quarantined to ${n}. original error: ${r.message}`)}catch{}return N()}}function K(e){let t=$();f.mkdirSync(p.dirname(t),{recursive:!0});let r=`${t}.tmp-${process.pid}-${Date.now()}`,n=f.openSync(r,"w",384);try{f.writeFileSync(n,JSON.stringify(e,null,2)),f.fsyncSync(n)}finally{f.closeSync(n)}f.renameSync(r,t)}function w(e){let t=S();return e(t),K(t),t}function F(e){return Y("sha256").update(p.resolve(e)).digest("hex").slice(0,16)}function Q(){return`s_${H(10).toString("hex")}`}function ye(e){let t=p.resolve(e.cwd),r=F(t),n;return w(o=>{let s=o.attachedProjects.find(c=>c.id===r);if(s){let c={...s,...e,id:r,cwd:t,sourceRoots:e.sourceRoots??s.sourceRoots,knownBundleUrls:e.knownBundleUrls??s.knownBundleUrls,pinnedSourceResolutions:e.pinnedSourceResolutions??s.pinnedSourceResolutions,telemetry:e.telemetry??s.telemetry,updatedAt:Date.now(),createdAt:s.createdAt},y=o.attachedProjects.indexOf(s);o.attachedProjects[y]=c,n=c;return}let a=Date.now(),d={id:r,name:e.name??p.basename(t),cwd:t,repoRoot:e.repoRoot,sourceRoots:e.sourceRoots??[t],framework:e.framework??"unknown",bundleId:e.bundleId,knownBundleUrls:e.knownBundleUrls??[],preferredProvider:e.preferredProvider??"codex",preferredTransport:e.preferredTransport??"tmux",editorOpenCommand:e.editorOpenCommand,moshiWebhookToken:e.moshiWebhookToken,pinnedSourceResolutions:e.pinnedSourceResolutions??{},isolateDiscovery:e.isolateDiscovery,git:e.git,telemetry:e.telemetry??{lastOpened:0,runsCompleted:0},createdAt:a,updatedAt:a};o.attachedProjects.push(d),n=d}),n}function U(e){return S().attachedProjects.find(t=>t.id===e)??null}function he(){return S().attachedProjects}var V=336*60*60*1e3;function Se(e,t={}){w(r=>{let n=r.attachedProjects.find(s=>s.id===e);if(!n)return;let o=t.ts??Date.now();if(n.telemetry.runsCompleted=(n.telemetry.runsCompleted??0)+1,typeof t.usd=="number"&&Number.isFinite(t.usd)&&t.usd>=0){let s=n.telemetry.costHistory??[],a=o-V,d=s.filter(c=>c.ts>=a);d.push({ts:o,usd:t.usd}),n.telemetry.costHistory=d}n.updatedAt=o})}function we(e,t=Date.now()){let r=t-6048e5,n=e.telemetry.costHistory??[],o=0;for(let s of n)s.ts>=r&&(o+=s.usd);return o}function ve(e){w(t=>{t.attachedProjects=t.attachedProjects.filter(r=>r.id!==e),t.agentSessions=t.agentSessions.filter(r=>r.projectId!==e),t.previewAttachments=t.previewAttachments.filter(r=>r.projectId!==e)})}function B(e){let t;return w(r=>{if(e.id){let a=r.agentSessions.find(d=>d.id===e.id);if(a){let d={...a,...e,lastSeenAt:Date.now()},c=r.agentSessions.indexOf(a);r.agentSessions[c]=d,t=d;return}}let n=r.attachedProjects.find(a=>a.id===e.projectId);if(!n)throw new Error(`upsertSession: no AttachedProject with id=${e.projectId}`);let o=Date.now(),s={id:e.id??Q(),projectId:e.projectId,provider:e.provider,transport:e.transport??n.preferredTransport,cwd:e.cwd??n.cwd,claudeSessionUuid:e.claudeSessionUuid,tmuxSessionName:e.tmuxSessionName,wrapperPid:e.wrapperPid,status:e.status??"idle",needsAttention:e.needsAttention??!1,lastPrompt:e.lastPrompt,lastSummary:e.lastSummary,lastTurnFiles:e.lastTurnFiles,currentlyEditing:e.currentlyEditing,lastSeenAt:o,createdAt:o};r.agentSessions.push(s),t=s}),t}function k(e){return S().agentSessions.find(t=>t.id===e)??null}function M(e){let t=S().agentSessions;return e?t.filter(r=>r.projectId===e):t}function v(e,t){w(r=>{let n=r.agentSessions.find(s=>s.id===e);if(!n)return;let o=r.agentSessions.indexOf(n);r.agentSessions[o]={...n,...t,id:n.id,projectId:n.projectId,createdAt:n.createdAt,lastSeenAt:Date.now()}})}async function Ae(){if(S().attachedProjects.length>0)return;let t;try{t=(await import("./demo-app-registry-NCYP3WA6.js")).APPS}catch(n){console.warn("[sootsim] seedFromDemoAppRegistry: could not load demo registry:",n.message);return}if(!Array.isArray(t))return;let r=t;w(n=>{for(let o of r){if(!f.existsSync(o.dir))continue;let s=p.resolve(o.dir),a=F(s);if(n.attachedProjects.some(c=>c.id===a))continue;let d=Date.now();n.attachedProjects.push({id:a,name:o.label,cwd:s,sourceRoots:[s],framework:o.framework,knownBundleUrls:[`http://localhost:${o.preferredPort}/index.bundle`],preferredProvider:"codex",preferredTransport:"tmux",pinnedSourceResolutions:{},telemetry:{lastOpened:0,runsCompleted:0},createdAt:d,updatedAt:d})}})}import{spawn as z,spawnSync as Z}from"node:child_process";import{randomUUID as ee}from"node:crypto";import i,{constants as g}from"node:fs";import u from"node:path";import te from"node:readline";function b(e){return u.join(h(),"sessions",e)}function L(e){return u.join(b(e),"prompt.in")}function W(e){return u.join(b(e),"events.out")}function re(e){return u.join(h(),"transcripts",`${e}.log`)}function E(e,t){if(!e)return!1;try{process.kill(e,0)}catch{return!1}return!(t&&!i.existsSync(b(t)))}function ne(){if(process.env.SOOTSIM_BIN)return{cmd:process.env.SOOTSIM_BIN,prefixArgs:[]};if(process.versions.electron){let n=process.resourcesPath;if(n){let o=[u.join(n,"bin","sootsim"),u.join(n,"bin",`sootsim-${process.platform}-${process.arch}`)];for(let s of o)if(i.existsSync(s))return{cmd:s,prefixArgs:[]}}}let e=se();if(e)return e;let t=process.argv[0],r=process.argv[1];if(r&&/\.(ts|tsx|mjs|cjs|js)$/.test(r))return{cmd:t,prefixArgs:[r]};if(!r||r.includes("/.bin/"))throw new Error("sootsim CLI not found. set SOOTSIM_BIN to the path of the sootsim binary, or build the workspace CLI via `bun run --cwd packages/sootsim build:cli`.");return{cmd:t,prefixArgs:[]}}function se(){try{let e=oe();if(!e)return null;let t=`sootsim-${process.platform}-${process.arch}`,r=u.join(e,"dist-bin",t);if(i.existsSync(r))return{cmd:r,prefixArgs:[]};let n=u.join(e,"dist-cli","bin.js");if(i.existsSync(n)){try{let o=u.join(e,"cli","commands","agent-wrapper.ts");if(i.existsSync(o)){let s=i.statSync(o).mtimeMs;i.statSync(n).mtimeMs<s&&console.warn("[sootsim] dist-cli/bin.js is older than agent-wrapper.ts \u2014 rebuild with `bun run --cwd packages/sootsim build:cli` (watch:cli:binary builds dist-bin/ instead).")}}catch{}return{cmd:process.execPath,prefixArgs:[n]}}return null}catch{return null}}function oe(){try{let r=I.resolve("sootsim/package.json");return u.dirname(r)}catch{}let e=ie();if(!e)return null;let t=u.dirname(e);for(let r=0;r<8;r++){let n=u.join(t,"package.json");try{if(i.existsSync(n)&&JSON.parse(i.readFileSync(n,"utf8")).name==="sootsim")return t}catch{}let o=u.dirname(t);if(o===t)break;t=o}return null}function ie(){try{let e=import.meta.url;return!e||!e.startsWith("file://")?null:decodeURIComponent(e.slice(7))}catch{return null}}async function ce(e,t,r){let n=u.join(h(),"locks");i.mkdirSync(n,{recursive:!0});try{i.chmodSync(n,448)}catch{}let o=u.join(n,`start-${e}-${t}.lock`),s=Date.now()+4e3,a=null;for(;a===null;)try{a=i.openSync(o,g.O_WRONLY|g.O_CREAT|g.O_EXCL,384)}catch(d){if(d.code!=="EEXIST")throw d;try{let c=Number(i.readFileSync(o,"utf8").trim());if(c&&!ae(c)){i.unlinkSync(o);continue}}catch{}if(Date.now()>s)throw new Error(`another start is in progress for project=${e} provider=${t} (lock: ${o})`);await new Promise(c=>setTimeout(c,50))}try{return i.writeFileSync(a,String(process.pid)),await r()}finally{try{i.closeSync(a)}catch{}try{i.unlinkSync(o)}catch{}}}function ae(e){try{return process.kill(e,0),!0}catch{return!1}}function C(e){let t=u.dirname(e);i.mkdirSync(t,{recursive:!0});try{i.chmodSync(t,448)}catch{}if(i.existsSync(e))try{if(i.statSync(e).isFIFO()){try{i.chmodSync(e,384)}catch{}return}i.unlinkSync(e)}catch{i.unlinkSync(e)}let r=Z("mkfifo",["-m","600",e]);if(r.status!==0)throw new Error(`mkfifo(${e}) failed: ${r.stderr?.toString().trim()||"unknown error"}`)}var l=class extends Error{code;constructor(t,r){super(r),this.code=t}};async function Te(e){let t=U(e.projectId);if(!t)throw new l("NO_PROJECT",`no project with id=${e.projectId}`);let r=e.provider||t.preferredProvider||"codex";return ce(t.id,r,async()=>{let n=M(t.id).find(m=>m.provider===r&&m.status!=="ended"&&E(m.wrapperPid,m.id));if(n)throw new l("ALREADY_RUNNING",`session already running for project=${t.id} provider=${r} (session ${n.id}, pid ${n.wrapperPid}). end it first with \`sootsim agent end <sessionId>\`.`);let o=r==="claude"?ee():void 0,s=B({projectId:t.id,provider:r,transport:"pty",cwd:t.cwd,status:"idle",claudeSessionUuid:o}),a=L(s.id),d=W(s.id),c=re(s.id);C(a),C(d);let y=u.dirname(c);i.mkdirSync(y,{recursive:!0});try{i.chmodSync(y,448)}catch{}let{cmd:A,prefixArgs:J}=ne(),j=[...J,"agent-wrapper","--session-id",s.id,"--project-id",t.id,"--provider",r,"--cwd",t.cwd,"--prompt-in",a,"--events-out",d,"--transcript",c];e.codexBin&&j.push("--codex-bin",e.codexBin),e.claudeBin&&j.push("--claude-bin",e.claudeBin),o&&j.push("--claude-session-uuid",o);let P=z(A,j,{detached:!0,stdio:"ignore",env:{...process.env,SOOTSIM_USER_DATA_DIR:h()}});P.unref();let D=e.readyTimeoutMs??6e3,x=await de(d,m=>m.type==="ready"||m.type==="error",D);if(!x||x.type==="error"){if(P.pid)try{process.kill(P.pid,"SIGTERM")}catch{}try{i.rmSync(b(s.id),{recursive:!0,force:!0})}catch{}v(s.id,{status:"ended"});let m=x&&x.type==="error"?x.message:`no ready event within ${D}ms`;throw new l("WRAPPER_FAILED",m)}return v(s.id,{wrapperPid:P.pid,status:"idle"}),{session:k(s.id),wrapperPid:P.pid}})}async function Re(e,t){let r=k(e);if(!r)throw new l("NO_SESSION",`no session with id=${e}`);if(!E(r.wrapperPid,e))throw v(e,{status:"ended"}),new l("NOT_ALIVE",`session wrapper is not alive (pid=${r.wrapperPid}). start a new session.`);let n=L(e);if(!i.existsSync(n))throw new l("NO_FIFO",`prompt FIFO missing: ${n}`);let o=i.openSync(n,g.O_WRONLY);try{let s=T(t);if(!s)throw new l("EMPTY_PROMPT","prompt text is empty");i.writeSync(o,s+`
|
|
1
|
+
/*! sootsim v0.0.4 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import{a as T}from"./chunk-432TMHBG.js";import{a as I}from"./chunk-WWDJCKMI.js";function G(e){if(!e||typeof e!="object")return!1;let t=e;return typeof t.type=="string"&&typeof t.ts=="number"}function O(e){let t=e.trim();if(!t)return null;try{let r=JSON.parse(t);return G(r)?r:null}catch{return null}}import{randomBytes as H,createHash as Y}from"node:crypto";import f from"node:fs";import q from"node:os";import p from"node:path";var R=null;function _(){if(R)return R;let e=process.env.SOOTSIM_USER_DATA_DIR;if(e)return e;try{let t=I("electron");if(t.app?.getPath)return t.app.getPath("userData")}catch{}return X()}function X(){let e=q.homedir();if(process.platform==="darwin")return p.join(e,"Library","Application Support","sootsim");if(process.platform==="win32")return p.join(process.env.APPDATA||e,"sootsim");let t=process.env.XDG_CONFIG_HOME||p.join(e,".config");return p.join(t,"sootsim")}function h(){return _()}function $(){return p.join(_(),"attached-projects.json")}function N(){return{version:1,attachedProjects:[],previewAttachments:[],agentSessions:[]}}function S(){let e=$(),t;try{t=f.readFileSync(e,"utf8")}catch(r){if(r.code==="ENOENT")return N();throw r}try{let r=JSON.parse(t);if(!r||typeof r!="object")throw new Error("not an object");return{version:1,attachedProjects:Array.isArray(r.attachedProjects)?r.attachedProjects:[],previewAttachments:Array.isArray(r.previewAttachments)?r.previewAttachments:[],agentSessions:Array.isArray(r.agentSessions)?r.agentSessions:[]}}catch(r){let n=`${e}.corrupt-${Date.now()}`;try{f.renameSync(e,n),console.warn(`[sootsim] attached-projects.json was unparseable; quarantined to ${n}. original error: ${r.message}`)}catch{}return N()}}function K(e){let t=$();f.mkdirSync(p.dirname(t),{recursive:!0});let r=`${t}.tmp-${process.pid}-${Date.now()}`,n=f.openSync(r,"w",384);try{f.writeFileSync(n,JSON.stringify(e,null,2)),f.fsyncSync(n)}finally{f.closeSync(n)}f.renameSync(r,t)}function w(e){let t=S();return e(t),K(t),t}function F(e){return Y("sha256").update(p.resolve(e)).digest("hex").slice(0,16)}function Q(){return`s_${H(10).toString("hex")}`}function ye(e){let t=p.resolve(e.cwd),r=F(t),n;return w(o=>{let s=o.attachedProjects.find(c=>c.id===r);if(s){let c={...s,...e,id:r,cwd:t,sourceRoots:e.sourceRoots??s.sourceRoots,knownBundleUrls:e.knownBundleUrls??s.knownBundleUrls,pinnedSourceResolutions:e.pinnedSourceResolutions??s.pinnedSourceResolutions,telemetry:e.telemetry??s.telemetry,updatedAt:Date.now(),createdAt:s.createdAt},y=o.attachedProjects.indexOf(s);o.attachedProjects[y]=c,n=c;return}let a=Date.now(),d={id:r,name:e.name??p.basename(t),cwd:t,repoRoot:e.repoRoot,sourceRoots:e.sourceRoots??[t],framework:e.framework??"unknown",bundleId:e.bundleId,knownBundleUrls:e.knownBundleUrls??[],preferredProvider:e.preferredProvider??"codex",preferredTransport:e.preferredTransport??"tmux",editorOpenCommand:e.editorOpenCommand,moshiWebhookToken:e.moshiWebhookToken,pinnedSourceResolutions:e.pinnedSourceResolutions??{},isolateDiscovery:e.isolateDiscovery,git:e.git,telemetry:e.telemetry??{lastOpened:0,runsCompleted:0},createdAt:a,updatedAt:a};o.attachedProjects.push(d),n=d}),n}function U(e){return S().attachedProjects.find(t=>t.id===e)??null}function he(){return S().attachedProjects}var V=336*60*60*1e3;function Se(e,t={}){w(r=>{let n=r.attachedProjects.find(s=>s.id===e);if(!n)return;let o=t.ts??Date.now();if(n.telemetry.runsCompleted=(n.telemetry.runsCompleted??0)+1,typeof t.usd=="number"&&Number.isFinite(t.usd)&&t.usd>=0){let s=n.telemetry.costHistory??[],a=o-V,d=s.filter(c=>c.ts>=a);d.push({ts:o,usd:t.usd}),n.telemetry.costHistory=d}n.updatedAt=o})}function we(e,t=Date.now()){let r=t-6048e5,n=e.telemetry.costHistory??[],o=0;for(let s of n)s.ts>=r&&(o+=s.usd);return o}function ve(e){w(t=>{t.attachedProjects=t.attachedProjects.filter(r=>r.id!==e),t.agentSessions=t.agentSessions.filter(r=>r.projectId!==e),t.previewAttachments=t.previewAttachments.filter(r=>r.projectId!==e)})}function B(e){let t;return w(r=>{if(e.id){let a=r.agentSessions.find(d=>d.id===e.id);if(a){let d={...a,...e,lastSeenAt:Date.now()},c=r.agentSessions.indexOf(a);r.agentSessions[c]=d,t=d;return}}let n=r.attachedProjects.find(a=>a.id===e.projectId);if(!n)throw new Error(`upsertSession: no AttachedProject with id=${e.projectId}`);let o=Date.now(),s={id:e.id??Q(),projectId:e.projectId,provider:e.provider,transport:e.transport??n.preferredTransport,cwd:e.cwd??n.cwd,claudeSessionUuid:e.claudeSessionUuid,tmuxSessionName:e.tmuxSessionName,wrapperPid:e.wrapperPid,status:e.status??"idle",needsAttention:e.needsAttention??!1,lastPrompt:e.lastPrompt,lastSummary:e.lastSummary,lastTurnFiles:e.lastTurnFiles,currentlyEditing:e.currentlyEditing,lastSeenAt:o,createdAt:o};r.agentSessions.push(s),t=s}),t}function k(e){return S().agentSessions.find(t=>t.id===e)??null}function M(e){let t=S().agentSessions;return e?t.filter(r=>r.projectId===e):t}function v(e,t){w(r=>{let n=r.agentSessions.find(s=>s.id===e);if(!n)return;let o=r.agentSessions.indexOf(n);r.agentSessions[o]={...n,...t,id:n.id,projectId:n.projectId,createdAt:n.createdAt,lastSeenAt:Date.now()}})}async function Ae(){if(S().attachedProjects.length>0)return;let t;try{t=(await import("./demo-app-registry-HLI5UGGI.js")).APPS}catch(n){console.warn("[sootsim] seedFromDemoAppRegistry: could not load demo registry:",n.message);return}if(!Array.isArray(t))return;let r=t;w(n=>{for(let o of r){if(!f.existsSync(o.dir))continue;let s=p.resolve(o.dir),a=F(s);if(n.attachedProjects.some(c=>c.id===a))continue;let d=Date.now();n.attachedProjects.push({id:a,name:o.label,cwd:s,sourceRoots:[s],framework:o.framework,knownBundleUrls:[`http://localhost:${o.preferredPort}/index.bundle`],preferredProvider:"codex",preferredTransport:"tmux",pinnedSourceResolutions:{},telemetry:{lastOpened:0,runsCompleted:0},createdAt:d,updatedAt:d})}})}import{spawn as z,spawnSync as Z}from"node:child_process";import{randomUUID as ee}from"node:crypto";import i,{constants as g}from"node:fs";import u from"node:path";import te from"node:readline";function b(e){return u.join(h(),"sessions",e)}function L(e){return u.join(b(e),"prompt.in")}function W(e){return u.join(b(e),"events.out")}function re(e){return u.join(h(),"transcripts",`${e}.log`)}function E(e,t){if(!e)return!1;try{process.kill(e,0)}catch{return!1}return!(t&&!i.existsSync(b(t)))}function ne(){if(process.env.SOOTSIM_BIN)return{cmd:process.env.SOOTSIM_BIN,prefixArgs:[]};if(process.versions.electron){let n=process.resourcesPath;if(n){let o=[u.join(n,"bin","sootsim"),u.join(n,"bin",`sootsim-${process.platform}-${process.arch}`)];for(let s of o)if(i.existsSync(s))return{cmd:s,prefixArgs:[]}}}let e=se();if(e)return e;let t=process.argv[0],r=process.argv[1];if(r&&/\.(ts|tsx|mjs|cjs|js)$/.test(r))return{cmd:t,prefixArgs:[r]};if(!r||r.includes("/.bin/"))throw new Error("sootsim CLI not found. set SOOTSIM_BIN to the path of the sootsim binary, or build the workspace CLI via `bun run --cwd packages/sootsim build:cli`.");return{cmd:t,prefixArgs:[]}}function se(){try{let e=oe();if(!e)return null;let t=`sootsim-${process.platform}-${process.arch}`,r=u.join(e,"dist-bin",t);if(i.existsSync(r))return{cmd:r,prefixArgs:[]};let n=u.join(e,"dist-cli","bin.js");if(i.existsSync(n)){try{let o=u.join(e,"cli","commands","agent-wrapper.ts");if(i.existsSync(o)){let s=i.statSync(o).mtimeMs;i.statSync(n).mtimeMs<s&&console.warn("[sootsim] dist-cli/bin.js is older than agent-wrapper.ts \u2014 rebuild with `bun run --cwd packages/sootsim build:cli` (watch:cli:binary builds dist-bin/ instead).")}}catch{}return{cmd:process.execPath,prefixArgs:[n]}}return null}catch{return null}}function oe(){try{let r=I.resolve("sootsim/package.json");return u.dirname(r)}catch{}let e=ie();if(!e)return null;let t=u.dirname(e);for(let r=0;r<8;r++){let n=u.join(t,"package.json");try{if(i.existsSync(n)&&JSON.parse(i.readFileSync(n,"utf8")).name==="sootsim")return t}catch{}let o=u.dirname(t);if(o===t)break;t=o}return null}function ie(){try{let e=import.meta.url;return!e||!e.startsWith("file://")?null:decodeURIComponent(e.slice(7))}catch{return null}}async function ce(e,t,r){let n=u.join(h(),"locks");i.mkdirSync(n,{recursive:!0});try{i.chmodSync(n,448)}catch{}let o=u.join(n,`start-${e}-${t}.lock`),s=Date.now()+4e3,a=null;for(;a===null;)try{a=i.openSync(o,g.O_WRONLY|g.O_CREAT|g.O_EXCL,384)}catch(d){if(d.code!=="EEXIST")throw d;try{let c=Number(i.readFileSync(o,"utf8").trim());if(c&&!ae(c)){i.unlinkSync(o);continue}}catch{}if(Date.now()>s)throw new Error(`another start is in progress for project=${e} provider=${t} (lock: ${o})`);await new Promise(c=>setTimeout(c,50))}try{return i.writeFileSync(a,String(process.pid)),await r()}finally{try{i.closeSync(a)}catch{}try{i.unlinkSync(o)}catch{}}}function ae(e){try{return process.kill(e,0),!0}catch{return!1}}function C(e){let t=u.dirname(e);i.mkdirSync(t,{recursive:!0});try{i.chmodSync(t,448)}catch{}if(i.existsSync(e))try{if(i.statSync(e).isFIFO()){try{i.chmodSync(e,384)}catch{}return}i.unlinkSync(e)}catch{i.unlinkSync(e)}let r=Z("mkfifo",["-m","600",e]);if(r.status!==0)throw new Error(`mkfifo(${e}) failed: ${r.stderr?.toString().trim()||"unknown error"}`)}var l=class extends Error{code;constructor(t,r){super(r),this.code=t}};async function Te(e){let t=U(e.projectId);if(!t)throw new l("NO_PROJECT",`no project with id=${e.projectId}`);let r=e.provider||t.preferredProvider||"codex";return ce(t.id,r,async()=>{let n=M(t.id).find(m=>m.provider===r&&m.status!=="ended"&&E(m.wrapperPid,m.id));if(n)throw new l("ALREADY_RUNNING",`session already running for project=${t.id} provider=${r} (session ${n.id}, pid ${n.wrapperPid}). end it first with \`sootsim agent end <sessionId>\`.`);let o=r==="claude"?ee():void 0,s=B({projectId:t.id,provider:r,transport:"pty",cwd:t.cwd,status:"idle",claudeSessionUuid:o}),a=L(s.id),d=W(s.id),c=re(s.id);C(a),C(d);let y=u.dirname(c);i.mkdirSync(y,{recursive:!0});try{i.chmodSync(y,448)}catch{}let{cmd:A,prefixArgs:J}=ne(),j=[...J,"agent-wrapper","--session-id",s.id,"--project-id",t.id,"--provider",r,"--cwd",t.cwd,"--prompt-in",a,"--events-out",d,"--transcript",c];e.codexBin&&j.push("--codex-bin",e.codexBin),e.claudeBin&&j.push("--claude-bin",e.claudeBin),o&&j.push("--claude-session-uuid",o);let P=z(A,j,{detached:!0,stdio:"ignore",env:{...process.env,SOOTSIM_USER_DATA_DIR:h()}});P.unref();let D=e.readyTimeoutMs??6e3,x=await de(d,m=>m.type==="ready"||m.type==="error",D);if(!x||x.type==="error"){if(P.pid)try{process.kill(P.pid,"SIGTERM")}catch{}try{i.rmSync(b(s.id),{recursive:!0,force:!0})}catch{}v(s.id,{status:"ended"});let m=x&&x.type==="error"?x.message:`no ready event within ${D}ms`;throw new l("WRAPPER_FAILED",m)}return v(s.id,{wrapperPid:P.pid,status:"idle"}),{session:k(s.id),wrapperPid:P.pid}})}async function Re(e,t){let r=k(e);if(!r)throw new l("NO_SESSION",`no session with id=${e}`);if(!E(r.wrapperPid,e))throw v(e,{status:"ended"}),new l("NOT_ALIVE",`session wrapper is not alive (pid=${r.wrapperPid}). start a new session.`);let n=L(e);if(!i.existsSync(n))throw new l("NO_FIFO",`prompt FIFO missing: ${n}`);let o=i.openSync(n,g.O_WRONLY);try{let s=T(t);if(!s)throw new l("EMPTY_PROMPT","prompt text is empty");i.writeSync(o,s+`
|
|
3
3
|
`)}finally{i.closeSync(o)}v(e,{lastPrompt:t.displayText??t.text,status:"working"})}async function Ne(e){let t=k(e);if(!t)throw new l("NO_SESSION",`no session with id=${e}`);if(E(t.wrapperPid,e))try{process.kill(t.wrapperPid,"SIGTERM")}catch{}let r=b(e),n=h();if(r.startsWith(n))try{i.rmSync(r,{recursive:!0,force:!0})}catch{}v(e,{status:"ended",wrapperPid:void 0})}function _e(e,t){let r=W(e);if(!i.existsSync(r))throw new l("NO_FIFO",`events FIFO missing: ${r}`);let n=i.openSync(r,g.O_RDWR),o=i.createReadStream("",{fd:n,autoClose:!0}),s=te.createInterface({input:o,crlfDelay:1/0});s.on("line",d=>{let c=O(d);c&&t(c)});let a=!1;return()=>{if(!a){a=!0;try{s.close()}catch{}try{o.destroy()}catch{}}}}async function de(e,t,r){let n=i.openSync(e,g.O_RDWR|g.O_NONBLOCK),o=Buffer.alloc(8192),s="",a=Date.now()+r;try{for(;Date.now()<a;){let d=0;try{d=i.readSync(n,o,0,o.length,null)}catch(c){if(c.code!=="EAGAIN")throw c;d=0}if(d>0){s+=o.subarray(0,d).toString("utf8");let c;for(;(c=s.indexOf(`
|
|
4
4
|
`))>=0;){let y=s.slice(0,c);s=s.slice(c+1);let A=O(y);if(A&&t(A))return A}}else await new Promise(c=>setTimeout(c,30))}return null}finally{i.closeSync(n)}}export{O as a,h as b,ye as c,U as d,he as e,Se as f,we as g,ve as h,k as i,M as j,v as k,Ae as l,b as m,L as n,W as o,re as p,E as q,ne as r,l as s,Te as t,Re as u,Ne as v,_e as w};
|