sootsim 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +197 -0
  2. package/dist-cli/bin.js +29 -0
  3. package/dist-cli/chunks/bridge-host-2EY7Z4AO.js +2 -0
  4. package/dist-cli/chunks/chunk-3C3ZH7PP.js +4 -0
  5. package/dist-cli/chunks/chunk-3R4ZZESY.js +119 -0
  6. package/dist-cli/chunks/chunk-64TOMNZX.js +37 -0
  7. package/dist-cli/chunks/chunk-74XPLOV4.js +2 -0
  8. package/dist-cli/chunks/chunk-7LMDCMSI.js +8 -0
  9. package/dist-cli/chunks/chunk-7X6OPSRD.js +2 -0
  10. package/dist-cli/chunks/chunk-A2CZQIWO.js +1 -0
  11. package/dist-cli/chunks/chunk-CKZ376AY.js +322 -0
  12. package/dist-cli/chunks/chunk-E522F5JW.js +2 -0
  13. package/dist-cli/chunks/chunk-E5UBZEYR.js +2 -0
  14. package/dist-cli/chunks/chunk-G5MR66EB.js +180 -0
  15. package/dist-cli/chunks/chunk-GPVPHE2B.js +3 -0
  16. package/dist-cli/chunks/chunk-HOIHCO7S.js +3 -0
  17. package/dist-cli/chunks/chunk-J2S3OCWA.js +69 -0
  18. package/dist-cli/chunks/chunk-JSF5LPNT.js +176 -0
  19. package/dist-cli/chunks/chunk-KQWZZ56P.js +2 -0
  20. package/dist-cli/chunks/chunk-KSACMDXK.js +3 -0
  21. package/dist-cli/chunks/chunk-KSB6MSZ4.js +34 -0
  22. package/dist-cli/chunks/chunk-KXYKAYYB.js +51 -0
  23. package/dist-cli/chunks/chunk-MBFP2LVH.js +3 -0
  24. package/dist-cli/chunks/chunk-MPSZ5EWF.js +16 -0
  25. package/dist-cli/chunks/chunk-OROM7DZI.js +2 -0
  26. package/dist-cli/chunks/chunk-PWXPA745.js +3 -0
  27. package/dist-cli/chunks/chunk-QOBRRY5X.js +4 -0
  28. package/dist-cli/chunks/chunk-X2U72K7X.js +1 -0
  29. package/dist-cli/chunks/chunk-YCETS3B3.js +2 -0
  30. package/dist-cli/chunks/compat-MRN2ORY5.js +33 -0
  31. package/dist-cli/chunks/config-CO5IYWUY.js +45 -0
  32. package/dist-cli/chunks/control-Y7TKKB6D.js +2 -0
  33. package/dist-cli/chunks/daemon-G4XVRFHM.js +49 -0
  34. package/dist-cli/chunks/debug-ZNSZTWT6.js +182 -0
  35. package/dist-cli/chunks/detox-JEGYNTYV.js +49 -0
  36. package/dist-cli/chunks/dev-ZUKCZQEX.js +25 -0
  37. package/dist-cli/chunks/dev-checkout-IEZVVTCN.js +2 -0
  38. package/dist-cli/chunks/device-BS34FAFM.js +16 -0
  39. package/dist-cli/chunks/drivers-46PFFIDF.js +2 -0
  40. package/dist-cli/chunks/electron-P2KOPX2S.js +15 -0
  41. package/dist-cli/chunks/flow-VVOF6UNC.js +2 -0
  42. package/dist-cli/chunks/hints-7Z656W4H.js +2 -0
  43. package/dist-cli/chunks/inspect-NAHXP2M5.js +1042 -0
  44. package/dist-cli/chunks/install-EPUJX4AT.js +67 -0
  45. package/dist-cli/chunks/install-desktop-PYIZIH67.js +19 -0
  46. package/dist-cli/chunks/login-Z5Z54HUJ.js +26 -0
  47. package/dist-cli/chunks/logout-T2QDYGCB.js +2 -0
  48. package/dist-cli/chunks/maestro-4AXTS7OE.js +75 -0
  49. package/dist-cli/chunks/preview-NMGWHWMX.js +17 -0
  50. package/dist-cli/chunks/profile-6RGJA4FR.js +22 -0
  51. package/dist-cli/chunks/record-IE27Z2GA.js +37 -0
  52. package/dist-cli/chunks/screenshot-R3GCCSCI.js +26 -0
  53. package/dist-cli/chunks/screenshot-mode-SZQDNGYE.js +17 -0
  54. package/dist-cli/chunks/screenshots-4UQJE4NC.js +70 -0
  55. package/dist-cli/chunks/server-AN2G5KO4.js +21 -0
  56. package/dist-cli/chunks/skills-2PPKPL4B.js +10 -0
  57. package/dist-cli/chunks/store-PU5ES4YQ.js +2 -0
  58. package/dist-cli/chunks/test-5LFKOQ4M.js +31 -0
  59. package/dist-cli/chunks/upload-BYNPC54C.js +2 -0
  60. package/dist-cli/chunks/vite-plugin-5AEUUBKP.js +9 -0
  61. package/dist-cli/chunks/whoami-H6FW34JS.js +2 -0
  62. package/package.json +56 -0
@@ -0,0 +1,1042 @@
1
+ /*! sootsim v0.0.1 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
+ import{a as Y}from"./chunk-GPVPHE2B.js";import{a as xe,b as $e}from"./chunk-J2S3OCWA.js";import{a as z,b as B,c as q,d as K,e as re,f as X,g as Q,h as Me,i as Ne,j as ue}from"./chunk-64TOMNZX.js";import{a as be,e as ne}from"./chunk-JSF5LPNT.js";import{a as ce,c as de,d as he}from"./chunk-3R4ZZESY.js";import{f as _e}from"./chunk-HOIHCO7S.js";import{a as we}from"./chunk-KSB6MSZ4.js";import"./chunk-OROM7DZI.js";import{b as ve,d as ke,e as Se,g as Te}from"./chunk-MPSZ5EWF.js";import"./chunk-3C3ZH7PP.js";import{a as ye}from"./chunk-E5UBZEYR.js";import"./chunk-X2U72K7X.js";import"./chunk-74XPLOV4.js";import"./chunk-MBFP2LVH.js";import"./chunk-E522F5JW.js";import{existsSync as it,mkdirSync as lt,readFileSync as ct,rmSync as Ie,writeFileSync as dt}from"fs";import{tmpdir as ut}from"os";import{dirname as mt,join as pt,resolve as ft}from"path";var ee=1,gt="SOOTSIM_INSPECT_NOTICE_PATH",yt=300*1e3,ht=15e3;function Fe(){return ft(process.env[gt]||pt(ut(),"sootsim-inspect-notice-state.json"))}function bt(s,c){return Object.fromEntries(Object.entries(s).filter(([,i])=>typeof i?.signature=="string"&&Number.isFinite(i?.updatedAt)&&c-i.updatedAt<=yt))}function wt(s){let c=Fe();if(!it(c))return{version:ee,entries:{}};try{let i=JSON.parse(ct(c,"utf8"));return i.version!==ee||!i.entries||typeof i.entries!="object"?(Ie(c,{force:!0}),{version:ee,entries:{}}):{version:ee,entries:bt(i.entries,s)}}catch{return Ie(c,{force:!0}),{version:ee,entries:{}}}}function xt(s){let c=Fe();lt(mt(c),{recursive:!0}),dt(c,JSON.stringify(s,null,2)+`
3
+ `)}function $t(s,c){let i=c.trim()||"default";return`${s}:${i}`}function me(s,c,i,d={}){let y=d.nowMs??Date.now(),a=d.cooldownMs??ht,h=wt(y),S=$t(s,c),x=h.entries[S];return x&&x.signature===i&&y-x.updatedAt<a?!1:(h.entries[S]={signature:i,updatedAt:y},xt(h),!0)}async function Ae(s,c={args:[]}){let i=await s.send({type:"evaluate",code:"(async () => await window.__sootsimTest.getNodeCount())()"}),d=typeof i=="number"?i:0;if(B(c.args)){q({nodes:d});return}console.log(` nodes: ${d}`)}function pe(s,c){let i=s.indexOf(c);return i>=0&&i+1<s.length?s[i+1]:null}async function Pe(s){let{bridge:c,args:i,positional:d}=s,y=i.includes("--verbose")||i.includes("-v"),a=B(i),h=y&&!a,S=i.includes("--watch")||i.includes("-w"),x=1e3,T=i.includes("--compact"),f=i.includes("--no-xy"),v=pe(i,"--testid-like"),_=pe(i,"--only"),k=pe(i,"--subtree"),C=d[1],l=C?/[*?]/.test(C):!1,E=!l&&!_?C:void 0,R=_??(l?C:void 0),H=async()=>{await re(c,{verbose:h});let V=`(async () => {
4
+ const t = window.__sootsimTest
5
+ const mainShell = window.SootSim?.bridges?.mainShell
6
+ const kb = window.__sootsimKeyboard
7
+ if (!t) return { error: 'no test bridge' }
8
+
9
+ let shell = null
10
+ try {
11
+ shell = typeof mainShell?.getState === 'function' ? await mainShell.getState() : null
12
+ } catch {}
13
+
14
+ const tree = await t.dumpTree(12, ${JSON.stringify({describe:!0,verbose:y,filter:E||"",testIdLike:v||void 0,onlyGlob:R||void 0,subtreeRoot:k||void 0,compact:T,hideXy:f})})
15
+ const nodeCount = (await t.getNodeCount?.()) || 0
16
+ const keyboard = kb && typeof kb.getLayout === 'function' ? kb.getLayout() : null
17
+ return { tree, shell, nodeCount, keyboard }
18
+ })()`,$=await c.send({type:"evaluate",code:V}),j=$?.tree,J=$?.shell,U=$?.keyboard;if(a){q({shell:J,tree:j??"",keyboard:U});return}if(J&&typeof J=="object"){let e=[J.state?`state=${J.state}`:null,J.activeApp?`app=${J.activeApp}`:null,J.showSwitcher?"switcher":null,J.switcherPhase&&J.switcherPhase!=="idle"?`phase=${J.switcherPhase}`:null].filter(Boolean);e.length>0&&console.log(` shell: ${e.join(" ")}`)}if(typeof j=="string"&&j.startsWith("__SUBTREE_NOT_FOUND__:")){let e=j.slice(22);console.log(` subtree root not found: ${e}`),Y("subtree-root-not-found",e);return}if(!j){let e=$?.nodeCount??0;console.log(" no matching nodes found"),!(E||v||R||k)&&e<10&&Y("app-still-loading",e);return}if(console.log(j),!(E||v||R||k)&&!S&&j.split(`
19
+ `).length>=80&&Y("describe-use-filters"),U&&U.visible){let e=U.spec,t=[e?.keyboardType?`type=${e.keyboardType}`:null,e?.returnKeyType&&e.returnKeyType!=="default"?`return=${e.returnKeyType}`:null,U.mode!=="letters"?`mode=${U.mode}`:null,U.shifted?"shift":null,U.capsLock?"caps":null,e?.autoCapitalize&&e.autoCapitalize!=="sentences"?`autoCap=${e.autoCapitalize}`:null,U.accessoryBarId?`accessory=${U.accessoryBarId}`:null].filter(Boolean);console.log(`
20
+ keyboard: ${t.join(" ")||"visible"}`)}};if(S)for(console.log(` watching... (Ctrl+C to stop)
21
+ `);;)console.clear(),await H(),await z(x);else await H()}var vt=["SOOTSIM_AGENT","CLAUDECODE","CLAUDE_CODE_ENTRYPOINT","CLAUDE_CODE_SESSION_ID","CODEX_THREAD_ID","CURSOR_TRACE_ID","AIDER_MODEL"];function Oe(){if(process.env.SOOTSIM_AGENT==="0")return!1;for(let s of vt){let c=process.env[s];if(c&&c.trim()&&c!=="0")return!0}return!1}async function Ee(s){let{bridge:c,args:i,effectiveArgs:d,positional:y,inspectUsage:a}=s,h=$=>{let j=d.indexOf($);return j>=0&&j+1<d.length?d[j+1]:null},S=$=>d.includes($),x=h("--testid")||h("--test-id"),T=h("--role"),f=h("--type"),v=h("--text"),_=S("--pressable"),k=S("--visible"),C=!x&&!T&&!f&&!v&&!_&&!k?y[1]:null,l=v??C,E,R;x?(R="testid",E=`(async () => {
22
+ const t = window.__sootsimTest
23
+ return (await t.findByTestId(${JSON.stringify(x)})) || (await t.findById(${JSON.stringify(x)}))
24
+ })()`):T?(R="role",E=`(async () => await window.__sootsimTest.queryAll({ hasRole: ${JSON.stringify(T)}, pruneHidden: true }))()`):f?(R="type",E=`(async () => await window.__sootsimTest.queryAll({ type: ${JSON.stringify(f)}, pruneHidden: true }))()`):_?(R="pressable",E=`(async () => {
25
+ const t = window.__sootsimTest
26
+ const all = await t.queryAll({ pruneHidden: true })
27
+ return all.filter(n => n.pressable)
28
+ })()`):k?(R="visible",E=`(async () => {
29
+ const all = await window.__sootsimTest.queryAll({ pruneHidden: true })
30
+ return all.filter(n => n.layout && n.layout.width > 0 && n.layout.height > 0)
31
+ })()`):l?(R="text",E=`(async () => await window.__sootsimTest.findByText(${JSON.stringify(l)}))()`):(console.error(a("find","<text> | --text <t> | --testid <id> | --role <r> | --type <t> | --pressable | --visible")),process.exit(1));let H=await c.send({type:"evaluate",code:E}),oe=B(i),V=i.includes("--verbose")||i.includes("--dump");if(oe)q(H??null);else if(Array.isArray(H))if(H.length===0){console.log(` no ${R} nodes found`);let $=await c.send({type:"evaluate",code:"(async () => (await window.__sootsimTest?.getNodeCount?.()) || 0)()"});typeof $=="number"&&$<10&&Y("app-still-loading",$)}else{console.log(` found ${H.length} node${H.length===1?"":"s"} (${R}):`);for(let $ of H.slice(0,20)){let j=$.absolutePosition?`@(${Math.round($.absolutePosition.x)},${Math.round($.absolutePosition.y)})`:"",J=$.layout?`${Math.round($.layout.width)}x${Math.round($.layout.height)}`:"?x?",U=$.text?` "${$.text.slice(0,30)}"`:"",Z=$.testID?` #${$.testID}`:"",e=$.pressable?" (tap)":"",t=$.accessibilityRole?`[${$.accessibilityRole}]`:$.type;console.log(` ${t}${U}${Z} ${J} ${j}${e}`),V&&console.log(Ce(JSON.stringify($,null,2)," "))}H.length>20&&console.log(` ... and ${H.length-20} more`)}else if(H==null)console.log(` not found: ${l||x||T||f||""||R}`),x&&Y("wait-selector-for-missing-testid",x);else{let $=H;if($.type&&$.absolutePosition){let j=`@(${Math.round($.absolutePosition.x)},${Math.round($.absolutePosition.y)})`,J=$.layout?`${Math.round($.layout.width)}x${Math.round($.layout.height)}`:"?x?",U=$.text?` "${$.text.slice(0,40)}"`:"",Z=$.testID?` #${$.testID}`:"",e=$.pressable?" (tap)":"",t=$.accessibilityRole?`[${$.accessibilityRole}]`:$.type;console.log(` ${t}${U}${Z} ${J} ${j}${e}`),V&&console.log(Ce(JSON.stringify($,null,2)," "))}else console.log(JSON.stringify(H,null,2))}}function Ce(s,c){return s.split(`
32
+ `).map(i=>c+i).join(`
33
+ `)}async function Re(s,c={}){let i=await s.send({type:"evaluate",code:`(() => {
34
+ const kb = window.__sootsimKeyboard
35
+ if (!kb || typeof kb.getLayout !== 'function') {
36
+ return { error: 'keyboard bridge getLayout() not available' }
37
+ }
38
+ return kb.getLayout()
39
+ })()`});if("error"in i&&(console.error(i.error),process.exit(1)),c.json){console.log(JSON.stringify(i,null,2));return}let{visible:d,spec:y,mode:a,shifted:h,capsLock:S,accessoryBarId:x}=i,T=[];T.push(`keyboard: ${d?"visible":"hidden"}`),y?(T.push(` type: ${y.keyboardType}`),T.push(` returnKey: ${y.returnKeyType}`),T.push(` autoCap: ${y.autoCapitalize}`),T.push(` autoCorrect: ${y.autoCorrect?"on":"off"}`),T.push(` appearance: ${y.keyboardAppearance}`),y.secureTextEntry&&T.push(" secureTextEntry: true"),y.enablesReturnKeyAutomatically&&T.push(` return: ${y.currentTextIsEmpty?"disabled (empty)":"enabled"}`)):T.push(" spec: <none> (shown via dev-tools with no TextInput)"),T.push(` mode: ${a}${h?" (shifted)":""}${S?" (caps)":""}`),x&&T.push(` accessoryBar: ${x}`),console.log(T.join(`
40
+ `))}async function De(s){let c=await s.bridge.listBrowsers();if(B(s.args)){q(c.map(i=>({...i,active:i.id===s.browserId})));return}_e(c,s.browserId)}function te(s){let c=s.indexOf("--testid");if(c>=0&&s[c+1])return{mode:"testid",value:s[c+1]};let i=s.indexOf("--test-id");if(i>=0&&s[i+1])return{mode:"testid",value:s[i+1]};let d=s.indexOf("--text");return d>=0&&s[d+1]?{mode:"text",value:s[d+1]}:null}async function ae(s,c){let i=JSON.stringify(c.value),d=c.mode==="testid"?`(await t.findByTestId(${i})) || (await t.findById(${i}))`:`await t.findByText(${i})`;return await s.send({type:"evaluate",code:`(async () => {
41
+ const t = window.__sootsimTest
42
+ if (!t) return null
43
+ const n = ${d}
44
+ if (!n || !n.absolutePosition || !n.layout) return null
45
+ const resolved =
46
+ typeof n.nodeId === 'number' && typeof t.resolveTapTarget === 'function'
47
+ ? await t.resolveTapTarget(n.nodeId)
48
+ : null
49
+ const cx =
50
+ typeof resolved?.cx === 'number'
51
+ ? resolved.cx
52
+ : n.absolutePosition.x + (n.layout.width || 0) / 2
53
+ const cy =
54
+ typeof resolved?.cy === 'number'
55
+ ? resolved.cy
56
+ : n.absolutePosition.y + (n.layout.height || 0) / 2
57
+ return { x: cx, y: cy, testID: n.testID, text: n.text }
58
+ })()`})??null}async function Be(s,c={}){let i=await s.send({type:"evaluate",code:`(async () => {
59
+ const test = window.__sootsimTest
60
+ const kb = window.__sootsimKeyboard
61
+ const navSnap =
62
+ test && typeof test.getNavigationSnapshot === 'function'
63
+ ? test.getNavigationSnapshot()
64
+ : null
65
+ const keyboard =
66
+ kb && typeof kb.getLayout === 'function'
67
+ ? (() => {
68
+ const layout = kb.getLayout()
69
+ return layout ? {
70
+ visible: layout.visible,
71
+ mode: layout.mode,
72
+ spec: layout.spec
73
+ ? {
74
+ keyboardType: layout.spec.keyboardType,
75
+ returnKeyType: layout.spec.returnKeyType,
76
+ }
77
+ : null,
78
+ } : null
79
+ })()
80
+ : null
81
+ return { nav: navSnap, keyboard }
82
+ })()`}),d=i?.nav??null,y=i?.keyboard??null,a=await X(s,500).catch(()=>null);if(c.json){console.log(JSON.stringify({shell:a??null,nav:d,keyboard:y},null,2));return}let h=[];if(a){let S=a.activeApp??a.state??"<none>",x=a.showSwitcher?" (app switcher open)":"",T=typeof a.launchProgress=="number"&&a.launchProgress<.98?` launching (${Math.round(a.launchProgress*100)}%)`:"";h.push(`shell: ${S}${x}${T}`)}else h.push("shell: <unavailable>");if(d){let S=d.transitionPhase!=="idle"?` (${d.transitionPhase}, ${d.activeTransitionCount} active)`:"";if(h.push(`nav: phase=${d.transitionPhase}${S}`),d.screens.length===0)h.push(" <no registered screens \u2014 app may not use react-native-screens>");else for(let x of d.screens){let T=x.isActive?"\u25B6":" ",f=x.routeName?` ${x.routeName}`:"",v=x.headerHeight>0?` header=${x.headerHeight}`:"",_=x.largeTitleState&&x.largeTitleState!=="expanded"?` large-title=${x.largeTitleState}`:"";h.push(` ${T} #${x.id}${f}${v}${_}`)}}else h.push("nav: <runtime not available>");if(y&&y.visible){let S=y.spec?.keyboardType??"default",x=y.spec?.returnKeyType??"default";h.push(`keyboard: visible (${S}, return=${x}, mode=${y.mode??"?"})`)}else h.push("keyboard: hidden");console.log(h.join(`
83
+ `))}async function Le(s){let{bridge:c,args:i,positional:d}=s,y=d[1]?Number(d[1])*1e3:3e3,a=i.includes("--strict"),h=Date.now(),S=!1,x="",T=0;for(;Date.now()-h<y;){let v=await c.send({type:"evaluate",code:`(async () => {
84
+ let animating = false
85
+ try {
86
+ const stats = await window.__sootsimRenderHost?.queryStats?.()
87
+ if (stats) {
88
+ animating =
89
+ stats.hasActiveAnims === true ||
90
+ stats.hasActiveNativeAnimations === true ||
91
+ stats.hasPendingAnimationFrames === true
92
+ }
93
+ } catch {}
94
+ const root = window.__sootsimRoot
95
+ const nodes = []
96
+ if (root) {
97
+ const walk = (n) => {
98
+ if (n.layout && n.layout.width > 0) {
99
+ nodes.push(Math.round(n.layout.x) + ',' + Math.round(n.layout.y) + ',' + Math.round(n.layout.width))
100
+ }
101
+ for (const c of n.children || []) walk(c)
102
+ }
103
+ walk(root)
104
+ }
105
+ return { layout: nodes.join(';'), animating }
106
+ })()`}),{layout:_,animating:k}=v;if((!a||!k)&&_===x){if(T++,T>=3){S=!0;break}}else T=0;x=_,await z(50)}let f=Date.now()-h;console.log(S?` settled in ${f}ms`:` timed out after ${f}ms (may still be animating)`)}async function je(s){let c=s.positional[1]?Number(s.positional[1]):.5;(!Number.isFinite(c)||c<0)&&(console.error(s.inspectUsage("sleep","[seconds]")),process.exit(1)),await z(c*1e3),console.log(` slept ${c}s`)}async function Je(s){let{bridge:c,args:i,positional:d}=s,y=d[1]?Number(d[1]):5,a=await c.send({type:"evaluate",code:`(async () => await window.__sootsimTest.dumpTree(${y}))()`});if(B(i)){q({depth:y,tree:a??null});return}console.log(typeof a=="string"?a:JSON.stringify(a,null,2))}async function qe(s,c={args:[]}){let i=await s.send({type:"evaluate",code:"window.location.href"}),d=typeof i=="string"?i:"";if(B(c.args)){q({url:d});return}console.log(d)}async function He(s){let{wsPort:c,commandTimeoutMs:i,browserId:d,positional:y}=s,a=y[1]?Number(y[1]):30,h=Math.max(1e3,(Number.isFinite(a)?a:30)*1e3),S=Math.max(1,Math.ceil(h/500));console.log(" waiting for browser reconnect...");let x=await Me(c,i,d,{attempts:S});x||(console.error(" timed out waiting for browser reconnect"),process.exit(1)),x.bridge.close(),ne({source:"inspect wait",step:{wait:h},summary:`wait ${Math.round(h/1e3)}s`}),console.log(` ready: ${x.count} nodes`)}async function We(s){let{bridge:c,args:i}=s,d=i.includes("--strict"),y=i.indexOf("--max-ms"),a=y>=0&&i[y+1]?Math.max(100,Number(i[y+1])):3e3,h=Date.now(),S=!1,x="",T=0;for(;Date.now()-h<a;){let v=await c.send({type:"evaluate",code:`(async () => {
107
+ let animating = false
108
+ try {
109
+ const stats = await window.__sootsimRenderHost?.queryStats?.()
110
+ if (stats) {
111
+ animating =
112
+ stats.hasActiveAnims === true ||
113
+ stats.hasActiveNativeAnimations === true ||
114
+ stats.hasPendingAnimationFrames === true
115
+ }
116
+ } catch {}
117
+ const root = window.__sootsimRoot
118
+ const nodes = []
119
+ if (root) {
120
+ const walk = (n) => {
121
+ if (n.layout && n.layout.width > 0) {
122
+ nodes.push(Math.round(n.layout.x) + ',' + Math.round(n.layout.y) + ',' + Math.round(n.layout.width))
123
+ }
124
+ for (const c of n.children || []) walk(c)
125
+ }
126
+ walk(root)
127
+ }
128
+ return { layout: nodes.join(';'), animating }
129
+ })()`}),{layout:_,animating:k}=v;if((!d||!k)&&_===x){if(T++,T>=3){S=!0;break}}else T=0;x=_,await z(50)}let f=Date.now()-h;S?console.log(` idle in ${f}ms`):(console.error(` \u26A0 wait idle timed out after ${f}ms (may still be animating)`),process.exit(1))}async function Ke(s){let{bridge:c,args:i}=s,d=2e4,y=i.indexOf("--max-ms");y>=0&&i[y+1]&&(d=Math.max(100,Number(i[y+1])));let a=Date.now(),h=a+d,S=`(async () => {
130
+ const t = window.__sootsimTest
131
+ let nodes = 0
132
+ try { nodes = (await t?.getNodeCount?.()) || 0 } catch {}
133
+ return {
134
+ flag: (window).__sootsimExternalAppReady,
135
+ at: (window).__sootsimExternalAppReadyAt || 0,
136
+ nodes,
137
+ }
138
+ })()`,x={flag:void 0,at:0,nodes:0};for(;Date.now()<h;){try{x=await c.send({type:"evaluate",code:S})??x}catch{}if(x.flag===!0){console.log(` ready in ${Date.now()-a}ms: ${x.nodes} nodes (flag)`);return}await new Promise(T=>setTimeout(T,150))}console.error(` \u26A0 wait ready timed out after ${Date.now()-a}ms \u2014 guest app did not emit sootsim:externalAppReady (nodes: ${x.nodes})`),process.exit(1)}async function Ue(s){let{bridge:c,args:i,positional:d,inspectUsage:y}=s,a=d[1];a||(console.error(y("wait selector","<testid> [--max-ms 5000]")),process.exit(1));let h=i.indexOf("--max-ms"),S=h>=0&&i[h+1]?Math.max(100,Number(i[h+1])):5e3,x=await c.send({type:"evaluate",code:`(async () => {
139
+ const start = Date.now()
140
+ const deadline = start + ${S}
141
+ const t = window.__sootsimTest
142
+ const find = async () => {
143
+ try {
144
+ return (await t?.findByTestId?.(${JSON.stringify(a)})) ||
145
+ (await t?.findById?.(${JSON.stringify(a)})) || null
146
+ } catch {}
147
+ return null
148
+ }
149
+ while (Date.now() < deadline) {
150
+ const node = await find()
151
+ if (node && node.layout && node.layout.width > 0 && node.layout.height > 0) {
152
+ return { found: true, node, elapsed: Date.now() - start }
153
+ }
154
+ await new Promise((r) => setTimeout(r, 80))
155
+ }
156
+ return { found: false, elapsed: Date.now() - start }
157
+ })()`}),{found:T,node:f,elapsed:v}=x??{};if(T&&f){let _=f.absolutePosition?`@(${Math.round(f.absolutePosition.x)},${Math.round(f.absolutePosition.y)})`:"",k=f.layout?`${Math.round(f.layout.width)}x${Math.round(f.layout.height)}`:"?x?";console.log(` found #${a} in ${v}ms ${k} ${_}`)}else console.error(` \u26A0 wait selector #${a} timed out after ${v??S}ms`),process.exit(1)}function Qe(s){return s==null?"\u2014":s<1024?`${s}B`:s<1024*1024?`${(s/1024).toFixed(1)}K`:`${(s/1024/1024).toFixed(1)}M`}function et(s){return s==null?" \u2026":s<1e3?`${s}ms`.padStart(5):`${(s/1e3).toFixed(2)}s`.padStart(5)}function St(s){return s.error?"err":s.status==null?" \u2026 ":String(s.status)}function ze(s){let c=new Date(s.startTs).toLocaleTimeString(),i=St(s).padEnd(3),d=s.method.padEnd(5),y=Qe(s.size).padStart(6),a=et(s.durationMs);console.log(` [${c}] ${i} ${d} ${y} ${a} ${s.displayUrl}`),s.error&&console.log(` error: ${s.error}`)}function Tt(s){let c=[["id",s.id],["source",s.source],["kind",s.kind],["method",s.method],["status",s.error?`error: ${s.error}`:`${s.status??"\u2014"} ${s.statusText??""}`.trim()],["url",s.url],["started",new Date(s.startTs).toLocaleTimeString()],["duration",et(s.durationMs).trim()],["size",Qe(s.size)],["content-type",s.type??"\u2014"]];for(let[i,d]of c)console.log(` ${i.padEnd(13)} ${d}`)}var Mt={error:"\x1B[31m",warn:"\x1B[33m",info:"\x1B[36m",debug:"\x1B[35m",log:"\x1B[37m"},Ye="\x1B[0m",Nt="\x1B[2m";function Ge(s,c){let i=new Date(s.ts).toLocaleTimeString(),d=s.level.toUpperCase().padEnd(5),y=s.args.join(" ");if(c){let a=Mt[s.level];console.log(` ${Nt}[${i}]${Ye} ${a}${d}${Ye} ${y}`)}else console.log(` [${i}] ${d} ${y}`);if(s.stack&&s.level==="error"){let a=s.stack.split(`
158
+ `).slice(0,5);for(let h of a)console.log(` ${h.trim()}`)}}var G="__sootsimCliPerf",_t=120;async function Xe(s,c){let i=s.find((_,k)=>s[k-1]==="--id"),d=s.find((_,k)=>s[k-1]==="--text");if(i||d){let _=await c.send({type:"evaluate",code:xe({id:i,text:d})});if(!_)throw new Error(i?`no node with id "${i}"`:`no node matching text "${d}"`);let{x:k,y:C,w:l,h:E}=_;return{x:k,y:C,w:l,h:E}}let y=s.find((_,k)=>s[k-1]==="--area");if(y){let _=y.split(",").map(R=>Number(R.trim()));if(_.length!==4||_.some(R=>!Number.isFinite(R)))throw new Error(`--area expects x,y,w,h (got "${y}")`);let[k,C,l,E]=_;return{x:k,y:C,w:l,h:E}}let a=_=>{let k=s.find((l,E)=>s[E-1]===_);if(k==null)return null;let C=Number(k);return Number.isFinite(C)?C:null},h=a("--x"),S=a("--y"),x=a("--w"),T=a("--h");if(h!=null||S!=null||x!=null||T!=null)return{x:h??0,y:S??0,w:x??1,h:T??1};let v=s.filter((_,k)=>k>0&&!_.startsWith("-")&&s[k-1]!=="--output"&&s[k-1]!=="--area"&&s[k-1]!=="--id"&&s[k-1]!=="--text"&&s[k-1]!=="--x"&&s[k-1]!=="--y"&&s[k-1]!=="--w"&&s[k-1]!=="--h").map(Number).filter(_=>Number.isFinite(_));if(v.length>=2){let[_,k,C=1,l=1]=v;return{x:_,y:k,w:C,h:l}}return null}function fe(s){let c={"<8":0,"8-12":0,"12-16":0,"16-20":0,"20-33":0,">33":0};for(let i of s)i<8?c["<8"]++:i<12?c["8-12"]++:i<16?c["12-16"]++:i<20?c["16-20"]++:i<33?c["20-33"]++:c[">33"]++;console.log(" histogram:");for(let[i,d]of Object.entries(c)){let y="\u2588".repeat(Math.ceil(d/s.length*40));console.log(` ${i.padEnd(6)} ${y} ${d}`)}}async function ie(s,c,i){let d=Date.now()+c,y=await X(s,c);for(;;){if(i(y))return{settled:!0,state:y};if(Date.now()>=d)return{settled:!1,state:y};await z(16),y=await X(s)}}async function ge(s){return s.send({type:"evaluate",code:`(async () => {
159
+ const kb = window.__sootsimKeyboard
160
+ const test = window.__sootsimTest
161
+ if (!kb) return { error: 'keyboard bridge not available' }
162
+ const visible = kb.isVisible()
163
+ const mode = kb.getMode()
164
+ let focused = null
165
+ if (test && typeof test.getFocusedNode === 'function') {
166
+ try {
167
+ focused = await test.getFocusedNode()
168
+ } catch {}
169
+ }
170
+ let runtimeSnapshot = null
171
+ if (test && typeof test.getFocusKeyboardSnapshot === 'function') {
172
+ try {
173
+ runtimeSnapshot = await test.getFocusKeyboardSnapshot()
174
+ } catch {}
175
+ }
176
+ return {
177
+ visible,
178
+ mode,
179
+ focusedInput: focused ? {
180
+ nodeId: focused.nodeId ?? null,
181
+ testID: focused.testID || null,
182
+ id: focused.id || null,
183
+ placeholder: focused.placeholder || null,
184
+ text: focused.text || null,
185
+ } : null,
186
+ phase: runtimeSnapshot?.keyboard?.phase ?? null,
187
+ frame: runtimeSnapshot?.keyboard?.frame ?? null,
188
+ focusedRect: runtimeSnapshot?.focused?.rect ?? null,
189
+ }
190
+ })()`})}async function It(s,c=600){let i=Date.now()+c;for(;Date.now()<=i;){let d=await ge(s);if(d.visible)return d;await z(30)}return ge(s)}async function le(s,c){let i=await ge(s);if(i.visible)return i;console.error(` ${c} requires the iOS keyboard to be visible. focus an input first with sootsim do tap-id/tap-text or sootsim do type-into.`),process.exit(1)}async function Ve(s,c,i){return c==="appearance"?s.send({type:"evaluate",code:`(async () => {
191
+ const requested = ${JSON.stringify(i??"toggle")}
192
+ const rootBg = (document.documentElement?.style?.background || '').toLowerCase()
193
+ const inferredCurrent = rootBg.includes('33') ? 'dark' : 'light'
194
+ let applied = requested
195
+ if (requested === 'toggle') {
196
+ applied = inferredCurrent === 'dark' ? 'light' : 'dark'
197
+ } else if (requested === 'auto') {
198
+ applied = window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ? 'dark' : 'light'
199
+ }
200
+ window.postMessage({ type: 'soot-action', action: 'set-appearance', value: applied }, '*')
201
+ return { ok: true, requested, applied }
202
+ })()`}):s.send({type:"evaluate",code:`(async () => {
203
+ window.dispatchEvent(new CustomEvent(${JSON.stringify(c==="lock"?"sootsim:toggleLock":"sootsim:shake")}))
204
+ return { ok: true, action: ${JSON.stringify(c)} }
205
+ })()`})}function Ft(s){let c={Enter:"return",NumpadEnter:"return",Backspace:"delete",Delete:"delete",Space:"space",ShiftLeft:"shift",ShiftRight:"shift"};if(c[s])return c[s];let i=s.match(/^Digit([0-9])$/);if(i)return i[1];let d=s.match(/^Key([A-Z])$/);return d?d[1].toLowerCase():null}function At(s){if(typeof s!="string")return null;let c=s.replace(/\s+/g," ").trim();return c?c.slice(0,80):null}function tt(...s){for(let c of s){if(typeof c!="string")continue;let i=c.trim();if(i)return i}return null}async function L(s,c,i){ne({source:s,step:c,summary:i})}function Pt(s,c,i){if(!i||i.hit===!1)return null;let d=tt(i.responderTestID,i.testID);if(d)return{step:{tapOn:{id:d}},summary:`tap #${d}`};let y=At(i.text);return y?{step:{tapOn:y},summary:`tap "${y}"`}:{step:{tapAtCoords:{x:s,y:c}},summary:`tap @${Math.round(s)},${Math.round(c)}`}}function Ze(s,c,i){let d=tt(c?.testID,c?.id);return d?{step:{tapOn:{id:d}},summary:`tap #${d}`}:i==="id"?{step:{tapOn:{id:s}},summary:`tap #${s}`}:{step:{tapOn:s},summary:`tap "${s}"`}}async function Yo(s,c){let i=s[0]==="get"||s[0]==="do"||s[0]==="debug"||s[0]==="wait"?s[0]:null,d=i?s.slice(1):s,y=ve(d,{port:c.port,commandTimeoutMs:c.timeoutMs,stripBooleanFlags:["--verbose","-v","--help","-h","--clear-state","--json","--all","--watch","-w","--strict","--no-wait","--dump","--failed","--tail","-f","--internal"],stripValueFlags:["--output","--nth","--index","--testid","--test-id","--text","--max-ms","--filter","--limit","--level"]}),a=y.positional,h=a[0],S=i==="get"||i==="do"||i==="debug"||i==="wait"?i:"inspect",x=typeof d[0]=="string"&&he.has(d[0]),T=x?d[0]:null,f=e=>x&&e===d[0]?`sootsim ${e}`:`sootsim ${S} ${e}`,v=(e,t)=>` usage: ${f(e)}${t?` ${t}`:""}`;if(!h||s.includes("--help")||s.includes("-h")){let e={bridgePort:7668,defaultShellUrl:ye};if(S==="do"||S==="get"||S==="debug"||S==="wait"){let r=ce(S,e);r&&(console.log(`${r}
206
+ `),process.exit(0))}if(T==="shell"){let r=de("shell",e);r&&(console.log(`${r}
207
+ `),process.exit(0))}let t=de("inspect",e),o=["do","get","debug","wait"].map(r=>ce(r,e)).filter(r=>r!=null).join(`
208
+
209
+ `);console.log(`${t??""}
210
+
211
+ ${o}
212
+ `),process.exit(0)}let _=y.wsPort,k=y.browserId,C=y.commandTimeoutMs;if(h==="list"&&d.some(e=>e==="--drivers"||e==="-D")){let{buildDriverListRows:e}=await import("./drivers-46PFFIDF.js"),t=e();console.log(` available drivers (${t.length}):
213
+ `);let o=Math.max(...t.map(n=>n.id.length),6),r=Math.max(...t.map(n=>n.kind.length),4);for(let n of t){let u=n.available?"\u2713":"\u2717",m=n.id.padEnd(o),b=n.kind.padEnd(r);console.log(` ${u} ${m} ${b} ${n.description}`),n.available&&n.detail?console.log(` ${n.detail}`):!n.available&&n.reason&&console.log(` unavailable: ${n.reason}`)}return}let l=ke(y),E=k||"default",R=new Set(["errors","warnings","requests","js","eval","reload","globals","perf","list","wait","sleep"]);function H(e){let t=e.displayUrl||e.url;return e.status!=null?`${e.method} ${t} -> ${e.status}${e.statusText?` ${e.statusText}`:""}`:e.error?`${e.method} ${t} -> ${e.error}`:`${e.method} ${t}`}async function oe(e){let t=Oe()?1200:350;try{let o=await e.send({type:"evaluate",code:`(async () => {
214
+ const start = Date.now()
215
+ const deadline = start + ${t}
216
+ const hash = () => {
217
+ const root = window.__sootsimRoot
218
+ if (!root) return ''
219
+ const nodes = []
220
+ const walk = (n) => {
221
+ if (n.layout && n.layout.width > 0) {
222
+ nodes.push(Math.round(n.layout.x) + ',' + Math.round(n.layout.y) + ',' + Math.round(n.layout.width))
223
+ }
224
+ for (const c of n.children || []) walk(c)
225
+ }
226
+ walk(root)
227
+ return nodes.join(';')
228
+ }
229
+ let last = ''
230
+ let stable = 0
231
+ while (Date.now() < deadline) {
232
+ const h = hash()
233
+ if (h === last) {
234
+ stable++
235
+ if (stable >= 2) return { settled: true, elapsed: Date.now() - start }
236
+ } else {
237
+ stable = 0
238
+ }
239
+ last = h
240
+ await new Promise((r) => setTimeout(r, 32))
241
+ }
242
+ return { settled: false, elapsed: Date.now() - start }
243
+ })()`}),{settled:r,elapsed:n}=o??{};r===!1&&process.stderr.write(` \u26A0 auto-wait timed out after ${n??t}ms \u2014 next command may see mid-animation state. use \`sootsim do settle\` for a longer wait.
244
+ `)}catch{}}async function V(){try{return await l.send({type:"evaluate",code:`(() => ({
245
+ console: window.__sootsimConsole?.count?.() || null,
246
+ requests: window.__sootsimTest?.getRequestCounts?.() || null,
247
+ }))()`})||{console:null,requests:null}}catch{return{console:null,requests:null}}}async function $(e={}){let t=e.counts!==void 0?e.counts:await K(l,"getRequestCounts");if(!t||typeof t!="object")return;let o=Math.max(0,Number(t.failed)||0);if(o===0||!e.includeTail&&!me("requests",E,String(o))||(console.log(`
248
+ network: ${o} failed request${o===1?"":"s"}`),console.log(` inspect: ${f("requests")} 5`),!e.includeTail))return;let r=await K(l,"getFailedRequests",5);if(!(!Array.isArray(r)||r.length===0)){console.log(`
249
+ recent failed requests:
250
+ `);for(let n of r){let u=new Date(n.timestamp).toLocaleTimeString();console.log(` [${u}] ${H(n)}`),n.responseBody?console.log(` ${n.responseBody}`):n.error&&console.log(` ${n.error}`)}}}async function j(e={}){let t=e.counts!==void 0?e.counts:await l.send({type:"evaluate",code:"window.__sootsimConsole?.count?.() || { errors: 0, warnings: 0, total: 0 }"});if(!t||typeof t!="object")return;let o=Math.max(0,Number(t.errors)||0),r=Math.max(0,Number(t.warnings)||0);if(o===0&&r===0||!e.includeTail&&!me("console",E,`${o}:${r}`))return;let n=[];if(o>0&&n.push(`${o} console error${o===1?"":"s"}`),r>0&&n.push(`${r} console warning${r===1?"":"s"}`),console.log(`
251
+ console: ${n.join(", ")}`),console.log(` inspect: ${f("errors")} 5`),r>0&&console.log(` inspect: ${f("warnings")} 5`),!e.includeTail||o===0)return;let u=await l.send({type:"evaluate",code:"window.__sootsimConsole?.getErrors?.(5) || []"});if(!(!Array.isArray(u)||u.length===0)){console.log(`
252
+ recent console errors:
253
+ `);for(let m of u){let b=new Date(m.timestamp).toLocaleTimeString(),p=Array.isArray(m.args)?m.args.map(w=>typeof w=="object"?JSON.stringify(w):String(w)).join(" "):String(m);console.log(` [${b}] ${p}`)}}}let J=new Set(["tap","double-tap","tap-text","tap-id","type","type-into","key","key-sequence","keycode","drag","swipe","long-press","touch","gesture","pinch","scroll","shell"]),U=new Set(["a11y","capture","count","double-tap","drag","find","gesture","layout","long-press","node","pinch","sample-color","scroll","screenshot","swipe","tap","tap-id","tap-text","touch","tree","type-into"]),Z=(s.includes("--verbose")||s.includes("-v"))&&!s.includes("--json");S==="do"&&h==="shell"&&(console.error(" `sootsim do shell` was removed. use `sootsim shell ...` instead."),process.exit(1)),J.has(h)&&await Se(l),U.has(h)&&await re(l,{verbose:Z});try{switch(h){case"list":{await De({bridge:l,browserId:k,args:d});break}case"tree":{await Je({bridge:l,args:d,positional:a});break}case"a11y":{let e=a[1]?Number(a[1]):5,t=await l.send({type:"evaluate",code:`(async () => {
254
+ const t = window.__sootsimTest
255
+ if (!t) return []
256
+ const all = await t.queryAll({ pruneHidden: true })
257
+ return all.filter(n => {
258
+ // skip zero-size nodes
259
+ if (!n.layout || n.layout.width <= 0 || n.layout.height <= 0) return false
260
+ // keep nodes with explicit accessibility roles or labels
261
+ if (n.accessibilityRole) return true
262
+ if (n.accessibilityHint) return true
263
+ if (n.isTextInput) return true
264
+ if (n.pressable) return true
265
+ // keep text leaf nodes
266
+ if (n.type === 'text' && n.text) return true
267
+ // keep views with intentional labels (not auto-derived)
268
+ if (n.accessibilityLabel && n.accessibilityLabel.length <= 30
269
+ && n.accessibilityLabel !== n.text) return true
270
+ // skip container views without role (just concatenated child text)
271
+ return false
272
+ }).map(n => ({
273
+ role: n.accessibilityRole || (n.pressable ? 'button' : n.isTextInput ? 'textfield' : n.type === 'text' ? 'statictext' : 'none'),
274
+ label: n.accessibilityLabel || n.text || null,
275
+ hint: n.accessibilityHint || null,
276
+ state: n.accessibilityState || null,
277
+ testID: n.testID || null,
278
+ position: n.absolutePosition ? { x: Math.round(n.absolutePosition.x), y: Math.round(n.absolutePosition.y) } : null,
279
+ size: n.layout ? { w: Math.round(n.layout.width), h: Math.round(n.layout.height) } : null,
280
+ }))
281
+ })()`});if(!Array.isArray(t)||t.length===0){console.log(" no accessible nodes found");break}if(s.includes("--json"))console.log(JSON.stringify(t,null,2));else{console.log(` accessibility tree (${t.length} nodes):
282
+ `);for(let o of t){let r=[];if(r.push(`[${o.role}]`),o.label){let n=o.label.length>50?o.label.slice(0,47)+"...":o.label;r.push(`"${n}"`)}if(o.hint&&r.push(`(hint: "${o.hint}")`),o.testID&&r.push(`#${o.testID}`),o.state){let n=[];o.state.disabled&&n.push("disabled"),o.state.selected&&n.push("selected"),o.state.checked===!0&&n.push("checked"),o.state.checked==="mixed"&&n.push("mixed"),o.state.busy&&n.push("busy"),o.state.expanded===!0&&n.push("expanded"),o.state.expanded===!1&&n.push("collapsed"),n.length&&r.push(`{${n.join(", ")}}`)}o.position&&r.push(`@(${o.position.x},${o.position.y})`),o.size&&r.push(`${o.size.w}x${o.size.h}`),console.log(" "+r.join(" "))}}break}case"find":{await Ee({bridge:l,args:s,effectiveArgs:d,positional:a,inspectUsage:v});break}case"count":{await Ae(l,{args:d});break}case"keyboard":{await Re(l,{json:s.includes("--json")});break}case"screens":{await Be(l,{json:s.includes("--json")});break}case"wait":{await He({wsPort:_,commandTimeoutMs:C,browserId:k,positional:a});break}case"sleep":{await je({positional:a,inspectUsage:v});break}case"settle":{await Le({bridge:l,args:s,positional:a});break}case"ready":{await Ke({bridge:l,args:s});break}case"idle":{await We({bridge:l,args:s,positional:a});break}case"selector":{await Ue({bridge:l,args:s,positional:a,inspectUsage:v});break}case"layout":{let e=a[1];e||(console.error(v("layout","<id>")),process.exit(1));let t=await l.send({type:"evaluate",code:`(async () => await window.__sootsimTest.getLayout(${JSON.stringify(e)}))()`});console.log(JSON.stringify(t,null,2));break}case"capture":case"screenshot":{let t=s.find((b,p)=>s[p-1]==="--output")||"/tmp/sootsim-inspect.png",o=await Xe(s,l),r={type:"screenshot"};o&&(r.crop=o);let u=(await l.send(r)).replace(/^data:image\/png;base64,/,"");o&&console.log(` area: x=${o.x} y=${o.y} w=${o.w} h=${o.h}`),(await import("fs")).writeFileSync(t,Buffer.from(u,"base64")),console.log(` saved: ${t}`);break}case"sample-color":{let e=await Xe(s,l);e||(console.error(v("sample-color","<x> <y> [w] [h] | --id <testID> | --text <text>")),console.error(" samples an averaged color from the canvas. coords are logical sootsim units."),process.exit(1));let t=await l.send({type:"evaluate",code:$e(e)});if(s.includes("--json"))console.log(JSON.stringify(t,null,2));else{let{r:o,g:r,b:n,a:u,hex:m,samples:b}=t,p=e.w===1&&e.h===1?`@(${e.x},${e.y})`:`@(${e.x},${e.y}) ${e.w}x${e.h}`;console.log(` ${m} rgba(${o}, ${r}, ${n}, ${u}) ${p} ${b} samples`)}break}case"node":{let e=a[1];e||(console.error(v("node","<matcher>")),console.error(" resolves testID, id, then text \u2014 dumps full node info as JSON"),process.exit(1));let t=await l.send({type:"evaluate",code:`(async () => {
283
+ const t = window.__sootsimTest
284
+ const q = ${JSON.stringify(e)}
285
+ let node = null
286
+ let via = null
287
+ if (t.findByTestId) { node = await t.findByTestId(q); if (node) via = 'testID' }
288
+ if (!node && t.findById) { node = await t.findById(q); if (node) via = 'id' }
289
+ if (!node && t.findByText) { node = await t.findByText(q); if (node) via = 'text' }
290
+ if (!node) return { matcher: q, found: false }
291
+
292
+ // read the resolved transform (if any) off the style \u2014 useful
293
+ // because canvas nodes often animate via transform and describe
294
+ // output strips that.
295
+ const transform =
296
+ node.style && Array.isArray(node.style.transform)
297
+ ? node.style.transform
298
+ : node.style && node.style.transform
299
+ ? node.style.transform
300
+ : null
301
+
302
+ // parent chain \u2014 walk up from the node so the JSON dump is
303
+ // self-describing.
304
+ const parentChain = []
305
+ const root = window.__sootsimRoot
306
+ if (root && node.id != null) {
307
+ const findPath = (n, targetId, path) => {
308
+ if (!n) return null
309
+ if (n.id === targetId) return path
310
+ if (n.children) {
311
+ for (const child of n.children) {
312
+ const nextPath = [
313
+ ...path,
314
+ {
315
+ type: n.type || 'view',
316
+ testID: n.props?.testID || null,
317
+ text: n.text || null,
318
+ },
319
+ ]
320
+ const found = findPath(child, targetId, nextPath)
321
+ if (found) return found
322
+ }
323
+ }
324
+ return null
325
+ }
326
+ const chain = findPath(root, node.id, [])
327
+ if (chain) parentChain.push(...chain)
328
+ }
329
+
330
+ return {
331
+ matcher: q,
332
+ found: true,
333
+ resolvedVia: via,
334
+ node,
335
+ transform,
336
+ parentChain,
337
+ }
338
+ })()`});console.log(JSON.stringify(t,null,2));break}case"tap":{let e=Number(a[1]),t=Number(a[2]),o=te(s);if(o){let u=await ae(l,o);u||(console.error(` not found: ${o.value}`),o.mode==="testid"&&Y("wait-selector-for-missing-testid",o.value),process.exit(1)),e=u.x,t=u.y}(!Number.isFinite(e)||!Number.isFinite(t))&&(console.error(v("tap","<x> <y> | --testid <id> | --text <t>")),process.exit(1));let r=await l.send({type:"tap",x:e,y:t}),n=Pt(e,t,r);n&&await L("inspect tap",n.step,n.summary),console.log(JSON.stringify(r,null,2));break}case"drag":case"swipe":{let e=Number(a[1]),t=Number(a[2]),o=Number(a[3]),r=Number(a[4]),n=h==="swipe"?10:12,u=h==="swipe"?8:16,m=a[5]?Number(a[5]):n,b=a[6]?Number(a[6]):u;(!Number.isFinite(e)||!Number.isFinite(t)||!Number.isFinite(o)||!Number.isFinite(r)||!Number.isFinite(m)||!Number.isFinite(b))&&(console.error(v(h,"<x1> <y1> <x2> <y2> [steps] [stepMs]")),process.exit(1));let p=await l.send({type:"evaluate",code:`(async () => {
339
+ const interact = window.__sootsimInteract
340
+ if (!interact?.drag) return { ok: false, reason: 'no interact.drag' }
341
+ const value = await interact.drag(${e}, ${t}, ${o}, ${r}, ${Math.max(1,Math.round(m))}, ${Math.max(0,Math.round(b))})
342
+ return { ok: !!value, value }
343
+ })()`});if(p?.ok){let w=Math.max(1,Math.round(Math.max(1,m)*Math.max(0,b)));await L(`inspect ${h}`,{swipe:{start:`${e}, ${t}`,end:`${o}, ${r}`,duration:w}},`${h} ${e},${t} -> ${o},${r}`)}console.log(JSON.stringify(p,null,2));break}case"pinch":{let e=Number(a[1]),t=Number(a[2]),o=Number(a[3]),r=Number(a[4]),n=Number(a[5]),u=Number(a[6]),m=Number(a[7]),b=Number(a[8]),p=a[9]?Number(a[9]):12,w=a[10]?Number(a[10]):16;(!Number.isFinite(e)||!Number.isFinite(t)||!Number.isFinite(o)||!Number.isFinite(r)||!Number.isFinite(n)||!Number.isFinite(u)||!Number.isFinite(m)||!Number.isFinite(b)||!Number.isFinite(p)||!Number.isFinite(w))&&(console.error(v("pinch","<x1> <y1> <x2> <y2> <x1'> <y1'> <x2'> <y2'> [steps] [stepMs]")),process.exit(1));let O=await l.send({type:"evaluate",code:`(async () => {
344
+ const interact = window.__sootsimInteract
345
+ if (!interact?.pinch) return { ok: false, reason: 'no interact.pinch' }
346
+ const value = await interact.pinch(${e}, ${t}, ${o}, ${r}, ${n}, ${u}, ${m}, ${b}, ${Math.max(1,Math.round(p))}, ${Math.max(0,Math.round(w))})
347
+ return { ok: !!value, value }
348
+ })()`});O?.ok&&await L("inspect pinch",{pinch:{from:[e,t,o,r],to:[n,u,m,b],steps:Math.max(1,Math.round(p)),stepMs:Math.max(0,Math.round(w))}},`pinch (${e},${t}) (${o},${r}) -> (${n},${u}) (${m},${b})`),console.log(JSON.stringify(O,null,2));break}case"tap-text":{let e=a[1];e||(console.error(v("tap-text","<text>")),process.exit(1));let t=F=>{let P=s.indexOf(F);return P>=0&&P+1<s.length?s[P+1]:null},o=F=>s.includes(F),r=t("--nth")??t("--index"),n=r!==null?Number(r):null;n!==null&&!Number.isFinite(n)&&(console.error(` --nth/--index requires an integer, got: ${r}`),process.exit(1));let u=t("--within"),m=t("--role"),b=o("--exact"),p=o("--first"),w=t("--min-y"),O=t("--max-y"),D=t("--min-x"),I=t("--max-x");for(let[F,P]of[["--min-y",w],["--max-y",O],["--min-x",D],["--max-x",I]])P!==null&&!Number.isFinite(Number(P))&&(console.error(` ${F} requires a number, got: ${P}`),process.exit(1));let N=s.indexOf("--near"),A=null;if(N>=0){let F=Number(s[N+1]),P=Number(s[N+2]);(!Number.isFinite(F)||!Number.isFinite(P))&&(console.error(" --near requires two numbers: --near <x> <y>"),process.exit(1)),A={x:F,y:P}}let W=JSON.stringify({query:e,exact:b,role:m,within:u,minX:D!==null?Number(D):null,maxX:I!==null?Number(I):null,minY:w!==null?Number(w):null,maxY:O!==null?Number(O):null,near:A,nth:n,first:p}),g=await l.send({type:"evaluate",code:`(async () => {
349
+ const t = window.__sootsimTest
350
+ if (!t) return { error: 'bridge-not-ready' }
351
+ const F = ${W}
352
+
353
+ const res = await t.queryTextCandidates({
354
+ query: F.query,
355
+ exact: !!F.exact,
356
+ ...(F.role ? { role: F.role } : {}),
357
+ })
358
+
359
+ let candidates = res.candidates || []
360
+
361
+ candidates = candidates.filter((c) => {
362
+ const ax = c.info.absolutePosition && c.info.absolutePosition.x
363
+ const ay = c.info.absolutePosition && c.info.absolutePosition.y
364
+ if (F.minX !== null && !(ax >= F.minX)) return false
365
+ if (F.maxX !== null && !(ax <= F.maxX)) return false
366
+ if (F.minY !== null && !(ay >= F.minY)) return false
367
+ if (F.maxY !== null && !(ay <= F.maxY)) return false
368
+ if (F.within && !c.ancestorTestIDs.includes(F.within)) return false
369
+ return true
370
+ })
371
+
372
+ if (F.near) {
373
+ candidates.sort((a, b) => {
374
+ const ax = (a.info.absolutePosition && a.info.absolutePosition.x) || 0
375
+ const ay = (a.info.absolutePosition && a.info.absolutePosition.y) || 0
376
+ const bx = (b.info.absolutePosition && b.info.absolutePosition.x) || 0
377
+ const by = (b.info.absolutePosition && b.info.absolutePosition.y) || 0
378
+ return (
379
+ Math.hypot(ax - F.near.x, ay - F.near.y) -
380
+ Math.hypot(bx - F.near.x, by - F.near.y)
381
+ )
382
+ })
383
+ } else {
384
+ candidates.sort((a, b) => {
385
+ const ay = (a.info.absolutePosition && a.info.absolutePosition.y) || 0
386
+ const by = (b.info.absolutePosition && b.info.absolutePosition.y) || 0
387
+ if (Math.abs(ay - by) > 2) return ay - by
388
+ const ax = (a.info.absolutePosition && a.info.absolutePosition.x) || 0
389
+ const bx = (b.info.absolutePosition && b.info.absolutePosition.x) || 0
390
+ return ax - bx
391
+ })
392
+ }
393
+
394
+ const total = candidates.length
395
+ if (total === 0) return { matched: 0, total: 0 }
396
+
397
+ let idx = 0
398
+ if (F.nth !== null) {
399
+ idx = F.nth < 0 ? total + F.nth : F.nth
400
+ if (idx < 0 || idx >= total) {
401
+ return { matched: 0, total, nthOutOfRange: true, nth: F.nth }
402
+ }
403
+ } else if (total > 1 && !F.first && !F.near) {
404
+ return {
405
+ ambiguous: true,
406
+ total,
407
+ candidates: candidates.slice(0, 10).map((c, i) => ({
408
+ idx: i,
409
+ nodeId: c.info.nodeId,
410
+ type: c.info.type,
411
+ testID: c.info.testID,
412
+ text: (c.info.text || '').slice(0, 60),
413
+ abs: c.info.absolutePosition,
414
+ layout: c.info.layout
415
+ ? {
416
+ width: Math.round(c.info.layout.width || 0),
417
+ height: Math.round(c.info.layout.height || 0),
418
+ }
419
+ : null,
420
+ ancestorTestIDs: (c.ancestorTestIDs || []).slice(0, 5),
421
+ })),
422
+ }
423
+ }
424
+
425
+ const picked = candidates[idx]
426
+ const n = picked.info
427
+ if (!n.absolutePosition || !n.layout) return { matched: 0, total }
428
+
429
+ const resolved =
430
+ typeof n.nodeId === 'number' &&
431
+ typeof t.resolveTapTarget === 'function'
432
+ ? await t.resolveTapTarget(n.nodeId)
433
+ : null
434
+ const target = (resolved && resolved.target) || n
435
+ const cx =
436
+ resolved && typeof resolved.cx === 'number'
437
+ ? resolved.cx
438
+ : n.absolutePosition.x + (n.layout.width || 0) / 2
439
+ const cy =
440
+ resolved && typeof resolved.cy === 'number'
441
+ ? resolved.cy
442
+ : n.absolutePosition.y + (n.layout.height || 0) / 2
443
+ return {
444
+ cx,
445
+ cy,
446
+ match: {
447
+ nodeId: n.nodeId ?? null,
448
+ id: n.id,
449
+ testID: n.testID,
450
+ type: n.type,
451
+ },
452
+ target: {
453
+ nodeId: target.nodeId ?? null,
454
+ id: target.id,
455
+ testID: target.testID,
456
+ type: target.type,
457
+ },
458
+ strategy: (resolved && resolved.strategy) || 'matched-node',
459
+ total,
460
+ idx,
461
+ }
462
+ })()`});if(g?.error==="bridge-not-ready"&&(console.error(" sootsim test bridge not ready"),process.exit(1)),g?.ambiguous){let F=g.candidates;console.error(` ambiguous: ${g.total} matches for "${e}"`);for(let P of F){let se=P.abs?`@(${Math.round(P.abs.x)},${Math.round(P.abs.y)})`:"",ot=P.layout?` ${P.layout.width}x${P.layout.height}`:"",st=P.testID?` #${P.testID}`:"",nt=P.text?` "${P.text}"`:"",rt=P.ancestorTestIDs.length>0?` within ${P.ancestorTestIDs.slice(0,3).map(at=>`#${at}`).join(" > ")}`:"";console.error(` [${P.idx}] <${P.type}>${nt}${st} ${se}${ot}${rt}`)}g.total>F.length&&console.error(` ... and ${g.total-F.length} more`),console.error(" pick one:"),console.error(" --nth <index> pick the nth match (top-to-bottom, left-to-right; negatives from end)"),console.error(" --within <testID> narrow to descendants of a node"),console.error(" --min-y / --max-y geometric filter (pixels, absolute)"),console.error(" --min-x / --max-x geometric filter (pixels, absolute)"),console.error(" --near <x> <y> pick the closest match to a point"),console.error(" --exact exact text match (default is substring)"),console.error(" --role <role> narrow to accessibilityRole"),console.error(" --first keep the old pick-first-silently behavior"),process.exit(2)}g?.nthOutOfRange&&(console.error(` not found: nth ${g.nth} of ${g.total} match${g.total===1?"":"es"} for "${e}"`),process.exit(1)),(!g||typeof g.cx!="number")&&(console.error(` not found: ${e}`),process.exit(1));let M=await l.send({type:"tap",x:g.cx,y:g.cy});if(M?.hit!==!1&&M?.ok!==!1){let F=Ze(e,{id:g.target?.id??null,testID:g.target?.testID??null,type:g.target?.type??null,cx:g.cx,cy:g.cy},"text");await L("inspect tap-text",F.step,F.summary)}console.log(JSON.stringify({matched:g.match,tapped:{nodeId:g.target?.nodeId??null,id:g.target?.id??null,testID:g.target?.testID??null,type:g.target?.type??null,cx:g.cx,cy:g.cy},...g.strategy&&g.strategy!=="matched-node"?{strategy:g.strategy}:{},...g.total>1||n!==null?{nth:{index:g.idx,total:g.total}}:{},result:M},null,2));break}case"tap-id":{let e=a[1];e||(console.error(v("tap-id","<id>")),process.exit(1));let t=JSON.stringify(e),o=await l.send({type:"evaluate",code:`(async () => {
463
+ const t = window.__sootsimTest
464
+ if (!t) return null
465
+ const n = (await t.findByTestId(${t})) || (await t.findById(${t}))
466
+ if (!n || !n.absolutePosition || !n.layout) return { cx: null }
467
+ const resolved =
468
+ typeof n.nodeId === 'number' && typeof t.resolveTapTarget === 'function'
469
+ ? await t.resolveTapTarget(n.nodeId)
470
+ : null
471
+ const target = (resolved && resolved.target) || n
472
+ const cx =
473
+ resolved && typeof resolved.cx === 'number'
474
+ ? resolved.cx
475
+ : n.absolutePosition.x + (n.layout.width || 0) / 2
476
+ const cy =
477
+ resolved && typeof resolved.cy === 'number'
478
+ ? resolved.cy
479
+ : n.absolutePosition.y + (n.layout.height || 0) / 2
480
+ return {
481
+ cx,
482
+ cy,
483
+ match: {
484
+ nodeId: n.nodeId ?? null,
485
+ id: n.id,
486
+ testID: n.testID,
487
+ type: n.type,
488
+ },
489
+ target: {
490
+ nodeId: target.nodeId ?? null,
491
+ id: target.id,
492
+ testID: target.testID,
493
+ type: target.type,
494
+ },
495
+ strategy: (resolved && resolved.strategy) || 'matched-node',
496
+ }
497
+ })()`});(!o||typeof o.cx!="number")&&(console.error(` not found: ${e}`),process.exit(1));let r=await l.send({type:"tap",x:o.cx,y:o.cy});if(r?.hit!==!1&&r?.ok!==!1){let n=Ze(e,{id:o.target?.id??null,testID:o.target?.testID??null,type:o.target?.type??null,cx:o.cx,cy:o.cy},"id");await L("inspect tap-id",n.step,n.summary)}console.log(JSON.stringify({matched:o.match,tapped:{nodeId:o.target?.nodeId??null,id:o.target?.id??null,testID:o.target?.testID??null,type:o.target?.type??null,cx:o.cx,cy:o.cy},...o.strategy&&o.strategy!=="matched-node"?{strategy:o.strategy}:{},result:r},null,2));break}case"type-into":{let e=a[1],t=a.slice(2).join(" ");(!e||!t)&&(console.error(v("type-into","<id> <text>")),process.exit(1));let o=JSON.stringify(e),r=await l.send({type:"evaluate",code:`(async () => {
498
+ const t = window.__sootsimTest
499
+ if (!t) return null
500
+ const n = await (t.findByTestId(${o}) || t.findById(${o}))
501
+ if (!n || !n.absolutePosition || !n.layout) return null
502
+ return {
503
+ cx: n.absolutePosition.x + (n.layout.width || 0) / 2,
504
+ cy: n.absolutePosition.y + (n.layout.height || 0) / 2,
505
+ testID: n.testID,
506
+ isTextInput: !!n.isTextInput,
507
+ placeholder: n.placeholder || null,
508
+ }
509
+ })()`});(!r||typeof r.cx!="number")&&(console.error(` not found: ${e}`),process.exit(1)),r.isTextInput||console.error(` warning: ${e} is not a text input (isTextInput: false)`);let n=await l.send({type:"tap",x:r.cx,y:r.cy}),u=await It(l);u.visible||(console.error(` keyboard did not open after tapping ${e}`),process.exit(1));let m=u.focusedInput;m&&(m.testID===e||m.id===e||(console.error(` focus routing mismatch after tap: requested ${JSON.stringify(e)} but focus is on ${JSON.stringify(m.testID??m.id??null)}. did the tap land on an outer Pressable wrapper?`),process.exit(1))),await l.send({type:"keyboard",action:"type",text:t}),await L("inspect type-into",{tapOn:{id:e},inputText:t},`type-into #${e} ${JSON.stringify(t)}`),console.log(JSON.stringify({target:e,isTextInput:r.isTextInput,keyboardOpened:u.visible??n?.keyboardOpened??!1,focusedInput:u.focusedInput??null,typed:t},null,2));break}case"type":{let e=a.slice(1).join(" ");e||(console.error(v("type","<text>")),process.exit(1)),await le(l,"type"),await l.send({type:"keyboard",action:"type",text:e}),await L("inspect type",{inputText:e},`type ${JSON.stringify(e)}`),console.log(` typed: ${JSON.stringify(e)}`);break}case"key":{let e=a[1];e||(console.error(v("key","<name>")),process.exit(1)),await le(l,"key"),await l.send({type:"keyboard",action:"press",text:e}),await L("inspect key",{pressKey:e},`key ${e}`),console.log(` pressed: ${e}`);break}case"key-sequence":{let e=a.slice(1);e.length===0&&(console.error(v("key-sequence","<key> [<key> ...]")),process.exit(1)),await le(l,"key-sequence");for(let t of e)await l.send({type:"keyboard",action:"press",text:t});await L("inspect key-sequence",{pressKey:e.join(" ")},`key-sequence ${e.join(" ")}`),console.log(` pressed: ${e.join(", ")}`);break}case"keycode":{let e=a.slice(1);e.length===0&&(console.error(v("keycode","<code> [<code> ...]")),process.exit(1));let t=e.map(r=>({code:r,key:Ft(r)})),o=t.filter(r=>!r.key);o.length>0&&(console.error(` unsupported keycode(s): ${o.map(r=>r.code).join(", ")}`),process.exit(1)),await le(l,"keycode");for(let r of t)await l.send({type:"keyboard",action:"press",text:r.key});await L("inspect keycode",{pressKey:t.map(r=>r.key).join(" ")},`keycode ${e.join(" ")}`),console.log(` pressed: ${e.join(", ")}`);break}case"dispatch":{let e=a[1];e||(console.error(v("dispatch","<char>")),process.exit(1)),await l.send({type:"keyboard",action:"dispatchKey",text:e}),await L("inspect dispatch",{dispatchKey:e},`dispatch ${JSON.stringify(e)}`),console.log(` dispatched: ${e}`);break}case"dismiss":{await l.send({type:"keyboard",action:"dismiss"}),await L("inspect dismiss",{hideKeyboard:!0},"dismiss keyboard"),console.log(" keyboard dismissed");break}case"double-tap":{let e=Number(a[1]),t=Number(a[2]),o=te(s);if(o){let m=await ae(l,o);m||(console.error(` not found: ${o.value}`),o.mode==="testid"&&Y("wait-selector-for-missing-testid",o.value),process.exit(1)),e=m.x,t=m.y}let r=a[3]?Number(a[3]):80;(!Number.isFinite(e)||!Number.isFinite(t)||!Number.isFinite(r))&&(console.error(v("double-tap","<x> <y> [gapMs] | --testid <id>")),process.exit(1));let n=Math.max(0,Math.round(r)),u=await l.send({type:"evaluate",code:`(async () => {
510
+ const interact = window.__sootsimInteract
511
+ if (interact?.doubleTap) {
512
+ return {
513
+ ok: !!(await interact.doubleTap(${e}, ${t}, ${n})),
514
+ gapMs: ${n},
515
+ }
516
+ }
517
+ if (!interact?.tap) {
518
+ return { ok: false, reason: 'no interact.tap', gapMs: ${n} }
519
+ }
520
+ const first = await interact.tap(${e}, ${t})
521
+ if (!first || first.hit === false) {
522
+ return { ok: false, reason: 'first tap missed', gapMs: ${n}, first }
523
+ }
524
+ await new Promise((resolve) => setTimeout(resolve, ${n}))
525
+ const second = await interact.tap(${e}, ${t})
526
+ return {
527
+ ok: !!second && second.hit !== false,
528
+ gapMs: ${n},
529
+ first,
530
+ second,
531
+ }
532
+ })()`});u?.ok&&await L("inspect double-tap",{doubleTapAtCoords:{x:e,y:t,gapMs:n}},`double-tap @${e},${t}`),console.log(JSON.stringify(u,null,2));break}case"long-press":{let e=Number(a[1]),t=Number(a[2]),o=te(s);if(o){let u=await ae(l,o);u||(console.error(` not found: ${o.value}`),o.mode==="testid"&&Y("wait-selector-for-missing-testid",o.value),process.exit(1)),e=u.x,t=u.y}let r=a[3]?Number(a[3]):600;(!Number.isFinite(e)||!Number.isFinite(t)||!Number.isFinite(r))&&(console.error(v("long-press","<x> <y> [durationMs] | --testid <id>")),process.exit(1));let n=await l.send({type:"evaluate",code:`(async () => {
533
+ const interact = window.__sootsimInteract
534
+ if (!interact?.longPress) return { ok: false, reason: 'no interact.longPress' }
535
+ const value = await interact.longPress(${e}, ${t}, ${Math.max(0,Math.round(r))})
536
+ return { ok: !!value, value }
537
+ })()`});n?.ok&&await L("inspect long-press",{tapAtCoords:{x:e,y:t}},`long-press @${e},${t}`),console.log(JSON.stringify(n,null,2));break}case"touch":{let e=a[1],t=Number(a[2]),o=Number(a[3]),r=a[4]?Number(a[4]):999,n=e==="down"?"touchDown":e==="move"?"touchMove":e==="up"?"touchUp":e==="cancel"?"touchCancel":null;n||(console.error(v("touch","<down|move|up|cancel> <x> <y> [pointerId]")),process.exit(1)),e!=="cancel"&&(!Number.isFinite(t)||!Number.isFinite(o))&&(console.error(v("touch","<down|move|up|cancel> <x> <y> [pointerId]")),process.exit(1));let u=e==="down"?"tap":e==="move"?"move":null,m=u&&Number.isFinite(t)&&Number.isFinite(o)?`window.dispatchEvent(new CustomEvent('sootsim:agentAction', { detail: { type: '${u}', x: ${t}, y: ${o} } }));`:"",b=await l.send({type:"evaluate",code:`(async () => {
538
+ ${m}
539
+ const interact = window.__sootsimInteract
540
+ if (!interact?.${n}) return { ok: false, reason: 'no interact.${n}' }
541
+ const value = ${e==="cancel"?`await interact.${n}(${Math.max(1,Math.round(r))})`:`await interact.${n}(${t}, ${o}, ${Math.max(1,Math.round(r))})`}
542
+ return { ok: !!value, value }
543
+ })()`});b?.ok&&e!=="cancel"&&await L("inspect touch",{tapAtCoords:{x:t,y:o}},`touch ${e} @${t},${o}`),console.log(JSON.stringify(b,null,2));break}case"gesture":{let e=a[1],t=a[2]?Number(a[2]):220;(!e||!Number.isFinite(t))&&(console.error(v("gesture","<preset> [durationMs]")),process.exit(1));let o=await l.send({type:"evaluate",code:`(async () => {
544
+ const spec = globalThis.__sootsimDeviceSpec || {}
545
+ return {
546
+ width: spec.width || window.innerWidth || 393,
547
+ height: spec.height || window.innerHeight || 852,
548
+ statusBarHeight: spec.statusBarHeight || 0,
549
+ homeIndicatorHeight: spec.homeIndicatorHeight || 0,
550
+ }
551
+ })()`}),r=Number(o?.width)||393,n=Number(o?.height)||852,u=Number(o?.statusBarHeight)||0,m=Number(o?.homeIndicatorHeight)||0,b=Math.round(r/2),p=Math.round(n/2),w=Math.max(24,u+18),O=Math.max(24,m+18),D=18,I=Math.min(220,Math.round(n*.24)),N=Math.min(180,Math.round(r*.32)),A=b,W=p,g=b,M=p;switch(e){case"scroll-up":W=p+Math.round(I/2),M=p-Math.round(I/2);break;case"scroll-down":W=p-Math.round(I/2),M=p+Math.round(I/2);break;case"scroll-left":A=b+Math.round(N/2),g=b-Math.round(N/2);break;case"scroll-right":A=b-Math.round(N/2),g=b+Math.round(N/2);break;case"swipe-from-left-edge":A=D,W=p,g=Math.min(r-D,D+N);break;case"swipe-from-right-edge":A=r-D,W=p,g=Math.max(D,r-D-N);break;case"swipe-from-top-edge":A=b,W=w,M=Math.min(n-O,w+I);break;case"swipe-from-bottom-edge":A=b,W=n-O,M=Math.max(w,n-O-I);break;default:console.error(` unknown gesture preset: ${e}`),process.exit(1)}let F=Math.max(8,Math.round(t/16)),P=Math.max(1,Math.round(t/F)),se=await l.send({type:"evaluate",code:`(async () => {
552
+ const interact = window.__sootsimInteract
553
+ if (!interact?.drag) return { ok: false, reason: 'no interact.drag' }
554
+ const value = await interact.drag(${A}, ${W}, ${g}, ${M}, ${F}, ${P})
555
+ return { ok: !!value, value }
556
+ })()`});se?.ok&&await L("inspect gesture",{swipe:{start:`${A}, ${W}`,end:`${g}, ${M}`,duration:Math.max(1,Math.round(t))}},`gesture ${e}`),console.log(JSON.stringify({preset:e,from:{x:A,y:W},to:{x:g,y:M},result:se},null,2));break}case"scroll":{let e=te(s),t=e?.mode==="testid"?e.value:a[1],o=Number(a[e?1:2]),r=Number(a[e?2:3]);(!t||!Number.isFinite(o)||!Number.isFinite(r))&&(console.error(v("scroll","<id> <dx> <dy> | --testid <id> <dx> <dy>")),process.exit(1));let n=await l.send({type:"evaluate",code:`(async () => {
557
+ const t = window.__sootsimTest
558
+ if (!t) return null
559
+ const n = await t.findByTestId(${JSON.stringify(t)})
560
+ || await t.findById(${JSON.stringify(t)})
561
+ if (!n || !n.absolutePosition || !n.layout) return null
562
+ return {
563
+ cx: n.absolutePosition.x + (n.layout.width || 0) / 2,
564
+ cy: n.absolutePosition.y + (n.layout.height || 0) / 2,
565
+ }
566
+ })()`}),u=await K(l,"scrollTo",t,o,r,!1);u?.ok&&await L("inspect scroll",{scrollTo:{id:t,x:o,y:r}},`scroll ${t} -> ${o},${r}`),console.log(JSON.stringify({...u,...n?{at:{x:n.cx,y:n.cy}}:{}},null,2));break}case"state":{let e=a[1];if(i==="get"&&!e){let o=await K(l,"getSessionState"),r=await l.send({type:"evaluate",code:`({
567
+ errors: window.__sootsimConsole?.getErrors?.()?.length ?? 0,
568
+ warnings: window.__sootsimConsole?.getWarnings?.()?.length ?? 0,
569
+ })`});o&&typeof o=="object"&&o.diagnostics&&(o.diagnostics.errors=r?.errors??0,o.diagnostics.warnings=r?.warnings??0),console.log(JSON.stringify(o,null,2));break}if(!e||e==="--help"||e==="-h"){console.log(`
570
+ ${f("state")} \u2014 dump raw runtime state
571
+
572
+ subcommands:
573
+ shell dump shell transition/layout state
574
+ worker dump render-worker host/animation state
575
+ keyboard dump keyboard visibility, mode, and focused input
576
+ ownership dump surface ownership + pointerOnSurface delivery stats
577
+ node <id> dump raw node info by id or testID
578
+ scroll <id> dump scroll metrics and runtime state
579
+ scroll-hit <x> <y> dump the nearest scroll ancestor at coordinates
580
+ hit <x> <y> dump the hit-test ancestry at coordinates
581
+ gesture <x> <y> dump gesture routing/debug info at coordinates
582
+
583
+ examples:
584
+ ${f("state")} shell
585
+ ${f("state")} worker
586
+ ${f("state")} keyboard
587
+ ${f("state")} ownership
588
+ ${f("state")} node photos
589
+ ${f("state")} scroll feed
590
+ ${f("state")} scroll-hit 360 420
591
+ ${f("state")} hit 200 720
592
+ `);break}let t;switch(e){case"shell":t=await X(l,500);break;case"worker":t=await Te(l,"__sootsimRenderHost.queryStats");break;case"ownership":t=await l.send({type:"evaluate",code:`(() => {
593
+ const h = window.__sootsimRenderHost
594
+ if (!h || typeof h.getOwnershipSnapshot !== 'function') {
595
+ return { error: 'getOwnershipSnapshot not available' }
596
+ }
597
+ return h.getOwnershipSnapshot()
598
+ })()`});break;case"keyboard":t=await l.send({type:"evaluate",code:`(async () => {
599
+ const kb = window.__sootsimKeyboard
600
+ const test = window.__sootsimTest
601
+ if (!kb) return { error: 'keyboard bridge not available' }
602
+ const layout = typeof kb.getLayout === 'function' ? kb.getLayout() : null
603
+ const visible = kb.isVisible()
604
+ const mode = kb.getMode()
605
+ let focused = null
606
+ if (test && typeof test.getFocusedNode === 'function') {
607
+ try {
608
+ focused = await test.getFocusedNode()
609
+ } catch {}
610
+ }
611
+ return {
612
+ visible,
613
+ mode,
614
+ layout,
615
+ focusedInput: focused ? {
616
+ nodeId: focused.nodeId ?? null,
617
+ testID: focused.testID || null,
618
+ id: focused.id || null,
619
+ placeholder: focused.placeholder || null,
620
+ text: focused.text || null,
621
+ } : null,
622
+ }
623
+ })()`});break;case"node":{let o=a[2];o||(console.error(` usage: ${f("state")} node <id>`),process.exit(1)),t=await K(l,"findByTestId",o)||await K(l,"findById",o);break}case"scroll":{let o=a[2];o||(console.error(` usage: ${f("state")} scroll <id>`),process.exit(1)),t=await K(l,"getScrollState",o);break}case"scroll-hit":{let o=Number(a[2]),r=Number(a[3]);(!Number.isFinite(o)||!Number.isFinite(r))&&(console.error(` usage: ${f("state")} scroll-hit <x> <y>`),process.exit(1)),t=await K(l,"getScrollStateAt",o,r);break}case"hit":{let o=Number(a[2]),r=Number(a[3]);(!Number.isFinite(o)||!Number.isFinite(r))&&(console.error(` usage: ${f("state")} hit <x> <y>`),process.exit(1)),t=await K(l,"debugHitAt",o,r);break}case"gesture":{let o=Number(a[2]),r=Number(a[3]);(!Number.isFinite(o)||!Number.isFinite(r))&&(console.error(` usage: ${f("state")} gesture <x> <y>`),process.exit(1)),t=await K(l,"debugGestureAt",o,r);break}default:console.error(` unknown state subcommand: ${e}`),process.exit(1)}console.log(JSON.stringify(t,null,2));break}case"shell":{let e=a[1];if(!e||e==="--help"||e==="-h"){console.log(`
624
+ ${f("shell")} \u2014 run built-in shell commands
625
+
626
+ subcommands:
627
+ launch <appId> [waitMs] [--clear-state]
628
+ launch app and wait for settled shell state
629
+ home [waitMs] go home and wait for settled shell state
630
+ switcher [waitMs] open switcher and wait for settled shell state
631
+ open-card <appId> [waitMs]
632
+ open a specific switcher card and wait for app settle
633
+ appearance <light|dark|auto|toggle>
634
+ update simulator appearance
635
+ lock toggle device lock state
636
+ shake trigger the simulator shake gesture
637
+
638
+ examples:
639
+ ${f("shell")} launch photos
640
+ ${f("shell")} launch rn --clear-state
641
+ ${f("shell")} launch photos 1500
642
+ ${f("shell")} home 500
643
+ ${f("shell")} switcher 800
644
+ ${f("shell")} open-card clock 800
645
+ ${f("shell")} appearance dark
646
+ ${f("shell")} lock
647
+ `);break}let t=e==="launch"||e==="open-card"||e==="home"||e==="switcher",o=e==="launch"||e==="open-card"?a[3]:a[2],r=o?Number(o):350;t&&(!Number.isFinite(r)||r<0)&&(console.error(v("shell",e==="launch"||e==="open-card"?"<launch|open-card> <appId> [settleMs]":"<home|switcher> [settleMs]")),process.exit(1));let n=!1,u=!1,m=null,b=s.includes("--clear-state");if(e==="launch"){let p=a[2];p||(console.error(v("shell","launch <appId> [settleMs] [--clear-state]")),process.exit(1)),b&&await l.send({type:"evaluate",code:be}),n=!!await Q(l,"launchApp",r,p),{settled:u,state:m}=await ie(l,Math.round(r),w=>!!w&&w.state==="app"&&w.activeApp===p&&w.showSwitcher===!1&&w.switcherPhase==="idle"&&typeof w.launchProgress=="number"&&w.launchProgress>=.98),n&&await L("inspect shell launch",b?{launchApp:{clearState:!0}}:{launchApp:{}},b?"launch app (clear state)":"launch app")}else if(e==="home")n=!!await Q(l,"goHome",r),{settled:u,state:m}=await ie(l,Math.round(r),p=>!!p&&p.state==="home"&&p.activeApp==null&&p.showSwitcher===!1&&p.switcherPhase==="idle"&&typeof p.launchProgress=="number"&&p.launchProgress>=.98);else if(e==="switcher")n=!!await Q(l,"openSwitcher",r),{settled:u,state:m}=await ie(l,Math.round(r),p=>!!p&&p.state==="app"&&p.showSwitcher===!0&&p.switcherPhase==="idle"&&typeof p.zoomLevel=="number"&&Math.abs(p.zoomLevel)<=.02&&typeof p.horizontalZoom=="number"&&Math.abs(p.horizontalZoom)<=.02),u&&(await z(_t),m=await X(l));else if(e==="open-card"){let p=a[2];p||(console.error(v("shell","open-card <appId> [settleMs]")),process.exit(1)),n=!!await Q(l,"openSwitcherCard",r,p),{settled:u,state:m}=await ie(l,Math.round(r),w=>!!w&&w.state==="app"&&w.activeApp===p&&w.showSwitcher===!1&&w.switcherPhase==="idle"&&typeof w.zoomLevel=="number"&&w.zoomLevel>=.98&&typeof w.horizontalZoom=="number"&&w.horizontalZoom>=.98),n&&await L("inspect shell open-card",{openSwitcherCard:{appId:p}},`open switcher card ${p}`)}else if(e==="appearance"){let p=a[2];(!p||!["light","dark","auto","toggle"].includes(p))&&(console.error(v("shell","appearance <light|dark|auto|toggle>")),process.exit(1));let w=await Ve(l,"appearance",p);n=!!w?.ok,m={appearance:w}}else if(e==="lock"||e==="shake"){let p=await Ve(l,e);n=!!p?.ok,m={[e]:p}}else console.error(` unknown shell subcommand: ${e}`),process.exit(1);console.log(JSON.stringify({ok:n,settled:u,state:m},null,2));break}case"url":{await qe(l,{args:d});break}case"reload":{let o=!1,r=!1;try{await l.send({type:"evaluate",code:"window.__sootsimConsole?.clear()"});let m=await l.send({type:"evaluate",code:`;(() => {
648
+ const reloadExternalApp = window.SootSim?.bridges?.hotRemount?.reloadExternalApp
649
+ if (typeof reloadExternalApp === 'function') {
650
+ reloadExternalApp()
651
+ return { kind: 'external-app' }
652
+ }
653
+ window.location.reload()
654
+ return { kind: 'page' }
655
+ })()`});r=!!m&&m.kind==="external-app",o=!0}catch{}console.log(" reloading...");let n=l,u=null;if(r)u=await ue(l,{timeoutMs:1e4,errorGraceMs:3e3});else{o&&await z(300);let m=await Ne(_,C,k,{timeoutMs:1e4});m?(n=m,u=await ue(m,{timeoutMs:1e4,errorGraceMs:3e3})):(console.log(" \u26A0 reload: bridge never reconnected within 10000ms"),n=null)}if(u)if(u.ready){let m=u.source==="nodes-fallback"?" (no ready signal, node-count fallback)":"";console.log(` ready in ${u.elapsedMs}ms: ${u.nodes} nodes${m}`)}else u.source==="error-bail"?console.log(` \u26A0 reload bailed after ${u.elapsedMs}ms: ${u.errors} console error(s), ready signal never fired`):console.log(` \u26A0 reload timed out after ${u.elapsedMs}ms (${u.nodes} nodes, ${u.errors} errors)`);if(n)try{let m=await n.send({type:"evaluate",code:"window.__sootsimConsole?.getErrors(10) || []"});if(n!==l&&n.close(),Array.isArray(m)&&m.length>0){console.log(`
656
+ \u26A0 ${m.length} error(s) during mount:
657
+ `);for(let b of m){let p=b.args.map(w=>typeof w=="object"?JSON.stringify(w):w).join(" ");if(console.log(` ${p}`),b.stack){let w=b.stack.split(`
658
+ `).slice(0,2);for(let O of w)console.log(` ${O.trim()}`)}}}}catch{}u&&!u.ready&&(process.exitCode=1);break}case"eval":case"js":{let e=a.slice(1).join(" ");e||(console.error(v("js","<javascript>")),console.error(""),console.error(" runs the snippet in the engine realm. SootSim is the"),console.error(" canonical state surface \u2014 reach into it directly."),console.error(""),console.error(" examples:"),console.error(` ${f("js")} SootSim.bridges.test.findByText("Sign in")`),console.error(` ${f("js")} SootSim.bridges.debug.snapshot("before")`),console.error(` ${f("js")} SootSim.bridges.keyboard.type("hello")`),console.error(` ${f("js")} SootSim.state.root.children.length`),process.exit(1));let t=e;t.startsWith("(async")||(t=`(async () => ${t})()`);let o=await l.send({type:"evaluate",code:t});console.log(JSON.stringify(o,null,2));let r=e.toLowerCase(),n=[];(r.includes("sootsim:gohome")||r.includes("gohome"))&&n.push("sootsim shell home"),(r.includes("sootsim:appswitcher")||r.includes("appswitcher"))&&n.push("sootsim shell switcher"),(r.includes("keyboard.isvisible")||r.includes("keyboard.getmode"))&&n.push("sootsim debug state keyboard"),r.includes("interact.tap")&&n.push("sootsim do tap <x> <y>"),r.includes("keyboard.type")&&n.push("sootsim do type <text>"),(r.includes("keyboard.press")||r.includes("keyboard.dispatchkey"))&&n.push("sootsim do key <name>"),r.includes("keyboard.dismiss")&&n.push("sootsim do dismiss"),r.includes("dumptree")&&n.push("sootsim get tree"),r.includes("dumpaccessibilitytree")&&n.push("sootsim get a11y"),r.includes("getnodecount")&&n.push("sootsim get count"),r.includes("findbytext")&&n.push("sootsim find <text>"),(r.includes("findbytestid")||r.includes("findbyid"))&&n.push("sootsim find --testid <id>"),r.includes("document.hidden")&&n.push("sootsim debug state keyboard (includes tab health)"),n.length>0&&Y("prefer-cli-over-eval",n);break}case"globals":{let e=await l.send({type:"evaluate",code:`(async () => {
659
+ const globals = {}
660
+
661
+ // test bridge (proxy in worker mode)
662
+ const testMethods = [
663
+ 'findById', 'findByTestId', 'findByText', 'findByLabel', 'findByRole',
664
+ 'findAllByRole', 'findByA11yState', 'findAllByA11yState', 'findByHint',
665
+ 'findPressable', 'getStyle', 'getLayout', 'getAbsolutePosition', 'isVisible',
666
+ 'queryAll', 'dumpTree', 'dumpAccessibilityTree', 'getNodeCount', 'getShellState',
667
+ 'getScrollState', 'getScrollStateAt', 'scrollTo', 'waitForTree',
668
+ 'waitForScreenTransitions', 'debugByText', 'debugByTestId', 'debugHitAt',
669
+ 'debugGestureAt'
670
+ ]
671
+ globals['test (\u2192 __sootsimTest)'] = testMethods
672
+
673
+ // debug
674
+ if (window.__sootsimDebug) {
675
+ globals['debug (\u2192 __sootsimDebug)'] = Object.keys(window.__sootsimDebug)
676
+ }
677
+
678
+ // interact
679
+ if (window.__sootsimInteract) {
680
+ globals['interact (\u2192 __sootsimInteract)'] = Object.keys(window.__sootsimInteract)
681
+ }
682
+
683
+ // keyboard
684
+ if (window.__sootsimKeyboard) {
685
+ globals['keyboard (\u2192 __sootsimKeyboard)'] = Object.keys(window.__sootsimKeyboard)
686
+ }
687
+
688
+ // other globals
689
+ globals['other'] = [
690
+ 'root (\u2192 __sootsimRoot) - live node tree',
691
+ 'render() (\u2192 __sootsimForceRender) - force re-render'
692
+ ]
693
+
694
+ return globals
695
+ })()`});console.log(` sootsim JS API:
696
+ `);for(let[t,o]of Object.entries(e)){console.log(` ${t}:`);for(let r of o)console.log(` .${r}`);console.log("")}console.log(` use: ${f("js")} <expression>`),console.log(` example: ${f("js")} test.findByText("Sign in")`);break}case"describe":{await Pe({bridge:l,args:s,positional:a});break}case"perf":{let e=a[1];if(!e||e==="--help"||e==="-h"){console.log(`
697
+ ${f("perf")} \u2014 performance profiling
698
+
699
+ subcommands:
700
+ stats one-shot stats (zero overhead query)
701
+ start begin recording frame times
702
+ stop stop recording and report results
703
+ frames [n] get last N frame times (default: 50)
704
+ transition <e> profile a shell transition (goHome, appSwitcher, lockScreen)
705
+
706
+ examples:
707
+ ${f("perf")} stats
708
+ ${f("perf")} start
709
+ # ... interact with the app ...
710
+ ${f("perf")} stop
711
+ ${f("perf")} transition goHome
712
+ `);break}switch(e){case"stats":{let t=await l.send({type:"evaluate",code:`(async () => {
713
+ // worker mode (host exposes these)
714
+ if (window.__sootsimPerfStats) {
715
+ return await window.__sootsimPerfStats()
716
+ }
717
+ // main-thread mode
718
+ const perf = window.__sootsimPerf
719
+ const a11y = window.__sootsimA11y
720
+ if (!perf && !a11y) return { error: 'no perf globals available' }
721
+
722
+ const frameStats = perf?.getFrameStats?.() || {}
723
+ const profile = a11y?.profile?.() || {}
724
+ const nodeCount = await window.__sootsimTest?.getNodeCount?.() || 0
725
+
726
+ return {
727
+ frames: frameStats.frames || profile.syncCount || 0,
728
+ avgMs: frameStats.totalMs && frameStats.frames
729
+ ? (frameStats.totalMs / frameStats.frames).toFixed(2)
730
+ : profile.avgSyncMs?.toFixed(2) || '?',
731
+ maxMs: frameStats.maxMs?.toFixed(2) || profile.maxSyncMs?.toFixed(2) || '?',
732
+ layoutMs: frameStats.layoutTotalMs?.toFixed(2) || '?',
733
+ nodeCount,
734
+ jankFrames: frameStats.recentFrames?.filter(f => f > 16.67).length || 0,
735
+ recentCount: frameStats.recentFrames?.length || 0,
736
+ }
737
+ })()`});t.error&&(console.error(` error: ${t.error}`),process.exit(1));let o=t.avgMs!=="?"?(1e3/parseFloat(t.avgMs)).toFixed(1):"?";console.log(" perf stats:"),console.log(` frames: ${t.frames}`),console.log(` avg: ${t.avgMs}ms (${o} fps)`),console.log(` max: ${t.maxMs}ms`),console.log(` layout: ${t.layoutMs}ms total`),console.log(` nodes: ${t.nodeCount}`),t.recentCount>0&&console.log(` jank: ${t.jankFrames}/${t.recentCount} frames >16.67ms`);break}case"start":{await l.send({type:"evaluate",code:`(async () => {
738
+ // worker mode
739
+ if (window.__sootsimPerfStart && window.__sootsimRenderHost) {
740
+ const result = window.__sootsimPerfStart()
741
+ window.${G} = {
742
+ active: true,
743
+ mode: 'render-worker',
744
+ lastSamples: [],
745
+ startedAt: performance.now(),
746
+ }
747
+ return result
748
+ }
749
+ // main-thread mode
750
+ window.__sootsimPerf?.enableFrameStats?.(true)
751
+ window.__sootsimA11y?.resetProfile?.()
752
+ window.${G} = {
753
+ active: true,
754
+ mode: 'main-thread',
755
+ lastSamples: [],
756
+ startedAt: performance.now(),
757
+ }
758
+ return { started: true }
759
+ })()`}),console.log(` profiling started \u2014 interact with the app, then run '${f("perf")} stop'`);break}case"stop":{let t=await l.send({type:"evaluate",code:`(async () => {
760
+ // worker mode
761
+ if (window.__sootsimRenderHost) {
762
+ const session = window.${G} || {}
763
+ const priorSamples = session.active && Array.isArray(session.lastSamples)
764
+ ? session.lastSamples
765
+ : []
766
+ const flushedSamples = await window.__sootsimRenderHost.flushSamples()
767
+ const samples = priorSamples.concat(flushedSamples)
768
+ window.${G} = {
769
+ ...session,
770
+ active: false,
771
+ mode: 'render-worker',
772
+ lastSamples: samples,
773
+ stoppedAt: performance.now(),
774
+ }
775
+
776
+ const frameTimes = samples.map(sample => sample[1])
777
+ const layoutTimes = samples.map(sample => sample[2])
778
+ const renderTimes = samples.map(sample => sample[3])
779
+ const copyTimes = samples.map(sample => sample[4])
780
+ const auxTimes = samples.map(sample => sample[5] || 0)
781
+ const otherTimes = samples.map(sample => sample[6] || 0)
782
+ const sorted = [...frameTimes].sort((a, b) => a - b)
783
+ const total = frameTimes.reduce((a, b) => a + b, 0)
784
+ const avg = frameTimes.length > 0 ? total / frameTimes.length : 0
785
+ const max = Math.max(...frameTimes, 0)
786
+ const layoutTotal = layoutTimes.reduce((a, b) => a + b, 0)
787
+ const renderTotal = renderTimes.reduce((a, b) => a + b, 0)
788
+ const copyTotal = copyTimes.reduce((a, b) => a + b, 0)
789
+ const auxTotal = auxTimes.reduce((a, b) => a + b, 0)
790
+ const otherTotal = otherTimes.reduce((a, b) => a + b, 0)
791
+
792
+ return {
793
+ mode: 'render-worker',
794
+ frames: frameTimes.length,
795
+ totalMs: total,
796
+ avgMs: avg,
797
+ maxMs: max,
798
+ layoutTotalMs: layoutTotal,
799
+ renderTotalMs: renderTotal,
800
+ copyTotalMs: copyTotal,
801
+ auxTotalMs: auxTotal,
802
+ otherTotalMs: otherTotal,
803
+ layoutAvgMs: frameTimes.length > 0 ? layoutTotal / frameTimes.length : 0,
804
+ renderAvgMs: frameTimes.length > 0 ? renderTotal / frameTimes.length : 0,
805
+ copyAvgMs: frameTimes.length > 0 ? copyTotal / frameTimes.length : 0,
806
+ auxAvgMs: frameTimes.length > 0 ? auxTotal / frameTimes.length : 0,
807
+ otherAvgMs: frameTimes.length > 0 ? otherTotal / frameTimes.length : 0,
808
+ p50: sorted[Math.floor(sorted.length * 0.5)] || 0,
809
+ p95: sorted[Math.floor(sorted.length * 0.95)] || 0,
810
+ p99: sorted[Math.floor(sorted.length * 0.99)] || 0,
811
+ jankFrames: frameTimes.filter((f) => f > 16.67).length,
812
+ sampleCount: frameTimes.length,
813
+ }
814
+ }
815
+ // main-thread mode
816
+ const perf = window.__sootsimPerf
817
+ const a11y = window.__sootsimA11y
818
+ if (!perf && !a11y) return { error: 'no perf globals' }
819
+
820
+ const frameStats = perf?.getFrameStats?.() || {}
821
+ const profile = a11y?.profile?.() || {}
822
+
823
+ // calculate distribution
824
+ const recent = frameStats.recentFrames || []
825
+ const sorted = [...recent].sort((a, b) => a - b)
826
+ const p50 = sorted[Math.floor(sorted.length * 0.5)] || 0
827
+ const p95 = sorted[Math.floor(sorted.length * 0.95)] || 0
828
+ const p99 = sorted[Math.floor(sorted.length * 0.99)] || 0
829
+
830
+ // disable collection
831
+ perf?.disableFrameStats?.()
832
+ window.${G} = {
833
+ ...(window.${G} || {}),
834
+ active: false,
835
+ mode: 'main-thread',
836
+ lastSamples: recent.map((ms, index) => [index, ms, 0, 0, 0, 0, 0]),
837
+ stoppedAt: performance.now(),
838
+ }
839
+
840
+ return {
841
+ mode: 'main-thread',
842
+ frames: frameStats.frames || profile.syncCount || 0,
843
+ totalMs: frameStats.totalMs || 0,
844
+ avgMs: frameStats.totalMs && frameStats.frames
845
+ ? frameStats.totalMs / frameStats.frames
846
+ : profile.avgSyncMs || 0,
847
+ maxMs: frameStats.maxMs || profile.maxSyncMs || 0,
848
+ layoutTotalMs: frameStats.layoutTotalMs || 0,
849
+ p50, p95, p99,
850
+ jankFrames: recent.filter(f => f > 16.67).length,
851
+ sampleCount: recent.length,
852
+ }
853
+ })()`});t.error&&(console.error(` error: ${t.error}`),process.exit(1));let o=t.avgMs>0?(1e3/t.avgMs).toFixed(1):"?",r=t.sampleCount>0?(t.jankFrames/t.sampleCount*100).toFixed(1):"0";console.log(` profiling stopped:
854
+ `),console.log(` frames: ${t.frames}`),console.log(` total: ${t.totalMs.toFixed(1)}ms`),console.log(` avg: ${t.avgMs.toFixed(2)}ms (${o} fps)`),console.log(` max: ${t.maxMs.toFixed(2)}ms`),console.log(""),t.layoutAvgMs!==void 0&&(console.log(" breakdown (avg per frame):"),console.log(` layout: ${t.layoutAvgMs.toFixed(2)}ms`),console.log(` render: ${t.renderAvgMs.toFixed(2)}ms`),console.log(` copy: ${t.copyAvgMs.toFixed(2)}ms`),t.auxAvgMs!==void 0&&console.log(` aux: ${t.auxAvgMs.toFixed(2)}ms`),t.otherAvgMs!==void 0&&console.log(` other: ${t.otherAvgMs.toFixed(2)}ms`),console.log("")),console.log(` distribution (${t.sampleCount} samples):`),console.log(` p50: ${t.p50.toFixed(2)}ms`),console.log(` p95: ${t.p95.toFixed(2)}ms`),console.log(` p99: ${t.p99.toFixed(2)}ms`),console.log(` jank: ${t.jankFrames} frames (${r}%) >16.67ms`);break}case"frames":{let t=a[2]?Number(a[2]):50;(!Number.isFinite(t)||t<=0)&&(console.error(` error: expected a positive frame count, got "${a[2]}"`),process.exit(1));let o=await l.send({type:"evaluate",code:`(async () => {
855
+ if (window.__sootsimRenderHost) {
856
+ const session = window.${G} || {}
857
+ if (session.active) {
858
+ const priorSamples = Array.isArray(session.lastSamples) ? session.lastSamples : []
859
+ const flushedSamples = await window.__sootsimRenderHost.flushSamples()
860
+ const samples = priorSamples.concat(flushedSamples)
861
+ window.__sootsimRenderHost.startSampling()
862
+ window.${G} = {
863
+ ...session,
864
+ active: true,
865
+ mode: 'render-worker',
866
+ lastSamples: samples,
867
+ lastFlushAt: performance.now(),
868
+ }
869
+ return {
870
+ mode: 'render-worker',
871
+ live: true,
872
+ samples: samples.slice(-${t}),
873
+ }
874
+ }
875
+ return {
876
+ mode: 'render-worker',
877
+ live: false,
878
+ samples: (session.lastSamples || []).slice(-${t}),
879
+ }
880
+ }
881
+ // main-thread mode
882
+ const perf = window.__sootsimPerf
883
+ if (!perf) return { error: 'no __sootsimPerf (try "perf start" first)' }
884
+ const stats = perf.getFrameStats?.() || {}
885
+ return {
886
+ mode: 'main-thread',
887
+ frames: (stats.recentFrames || []).slice(-${t}),
888
+ }
889
+ })()`});if(o.error&&(console.error(` error: ${o.error}`),process.exit(1)),o.mode==="render-worker"){let n=Array.isArray(o.samples)?o.samples:[];if(n.length===0){console.log(` no frame data \u2014 run '${f("perf")} start' first`);break}console.log(` last ${n.length} sampled frames (ms):`),console.log(" total layout render copy aux other t+");for(let[u,m,b,p,w,O,D]of n)console.log(` ${m.toFixed(2).padStart(7)} ${b.toFixed(2).padStart(7)} ${p.toFixed(2).padStart(7)} ${w.toFixed(2).padStart(7)} ${O.toFixed(2).padStart(6)} ${D.toFixed(2).padStart(7)} ${String(u).padStart(5)}`);console.log(""),fe(n.map(u=>u[1])),o.live&&console.log(" sampling continues");break}let r=Array.isArray(o.frames)?o.frames:Array.isArray(o)?o:[];if(r.length===0){console.log(` no frame data \u2014 run '${f("perf")} start' first`);break}console.log(` last ${r.length} frame times (ms):`),console.log(` ${r.map(n=>n.toFixed(2)).join(", ")}`),fe(r);break}case"worst":{let t=a[2]?Number(a[2]):20;(!Number.isFinite(t)||t<=0)&&(console.error(` error: expected a positive frame count, got "${a[2]}"`),process.exit(1));let o=await l.send({type:"evaluate",code:`(async () => {
890
+ if (window.__sootsimRenderHost) {
891
+ const session = window.${G} || {}
892
+ if (session.active) {
893
+ const priorSamples = Array.isArray(session.lastSamples) ? session.lastSamples : []
894
+ const flushedSamples = await window.__sootsimRenderHost.flushSamples()
895
+ const samples = priorSamples.concat(flushedSamples)
896
+ window.__sootsimRenderHost.startSampling()
897
+ window.${G} = {
898
+ ...session,
899
+ active: true,
900
+ mode: 'render-worker',
901
+ lastSamples: samples,
902
+ lastFlushAt: performance.now(),
903
+ }
904
+ return {
905
+ mode: 'render-worker',
906
+ live: true,
907
+ samples: samples
908
+ .slice()
909
+ .sort((a, b) => b[1] - a[1])
910
+ .slice(0, ${t}),
911
+ }
912
+ }
913
+ const samples = Array.isArray(session.lastSamples) ? session.lastSamples : []
914
+ return {
915
+ mode: 'render-worker',
916
+ live: false,
917
+ samples: samples
918
+ .slice()
919
+ .sort((a, b) => b[1] - a[1])
920
+ .slice(0, ${t}),
921
+ }
922
+ }
923
+ const perf = window.__sootsimPerf
924
+ if (!perf) return { error: 'no __sootsimPerf (try "perf start" first)' }
925
+ const stats = perf.getFrameStats?.() || {}
926
+ const recent = Array.isArray(stats.recentFrames) ? stats.recentFrames : []
927
+ return {
928
+ mode: 'main-thread',
929
+ frames: recent.slice().sort((a, b) => b - a).slice(0, ${t}),
930
+ }
931
+ })()`});if(o.error&&(console.error(` error: ${o.error}`),process.exit(1)),o.mode==="render-worker"){let n=Array.isArray(o.samples)?o.samples:[];if(n.length===0){console.log(` no frame data \u2014 run '${f("perf")} start' first`);break}console.log(` worst ${n.length} sampled frames (ms):`),console.log(" total layout render copy aux other t+");for(let[u,m,b,p,w,O,D]of n)console.log(` ${m.toFixed(2).padStart(7)} ${b.toFixed(2).padStart(7)} ${p.toFixed(2).padStart(7)} ${w.toFixed(2).padStart(7)} ${O.toFixed(2).padStart(6)} ${D.toFixed(2).padStart(7)} ${String(u).padStart(5)}`);o.live&&(console.log(""),console.log(" sampling continues"));break}let r=Array.isArray(o.frames)?o.frames:Array.isArray(o)?o:[];if(r.length===0){console.log(` no frame data \u2014 run '${f("perf")} start' first`);break}console.log(` worst ${r.length} frame times (ms):`),console.log(` ${r.map(n=>n.toFixed(2)).join(", ")}`);break}case"transition":{let t=a[2];if(!t||!["goHome","appSwitcher","lockScreen"].includes(t)){console.log(`
932
+ ${f("perf")} transition <event> \u2014 profile a shell transition
933
+
934
+ events:
935
+ goHome swipe-to-home animation
936
+ appSwitcher app switcher card animation
937
+ lockScreen lock screen transition
938
+
939
+ note: uses 600ms capture window \u2014 may need --timeout 10000 flag
940
+
941
+ examples:
942
+ ${f("perf")} transition goHome --timeout 10000
943
+ ${f("perf")} transition appSwitcher
944
+ `);break}let r=`sootsim:${t}`;console.log(` profiling ${t} transition...`),console.log(" (use --timeout 10000 if this times out)");let n=await l.send({type:"evaluate",code:`(async () => {
945
+ // only supported in render-worker mode
946
+ if (!window.__sootsimRenderHost) {
947
+ return { error: 'transition profiling requires render-worker mode' }
948
+ }
949
+
950
+ // start sampling
951
+ window.__sootsimRenderHost.startSampling()
952
+
953
+ // give a frame for sampling to start
954
+ await new Promise(r => requestAnimationFrame(() => r(undefined)))
955
+
956
+ // dispatch the shell event
957
+ window.dispatchEvent(new Event('${r}'))
958
+
959
+ // wait 600ms for transition to complete (shell animations are ~300-500ms)
960
+ // using fixed timing avoids complex animation-end detection
961
+ await new Promise(r => setTimeout(r, 600))
962
+
963
+ // flush samples and compute stats
964
+ const samples = await window.__sootsimRenderHost.flushSamples()
965
+ const frameTimes = samples.map(s => s[1])
966
+ const layoutTimes = samples.map(s => s[2])
967
+ const renderTimes = samples.map(s => s[3])
968
+ const copyTimes = samples.map(s => s[4])
969
+ const auxTimes = samples.map(s => s[5] || 0)
970
+ const otherTimes = samples.map(s => s[6] || 0)
971
+ const sorted = [...frameTimes].sort((a, b) => a - b)
972
+ const total = frameTimes.reduce((a, b) => a + b, 0)
973
+ const avg = frameTimes.length > 0 ? total / frameTimes.length : 0
974
+ const max = Math.max(...frameTimes, 0)
975
+ const layoutTotal = layoutTimes.reduce((a, b) => a + b, 0)
976
+ const renderTotal = renderTimes.reduce((a, b) => a + b, 0)
977
+ const copyTotal = copyTimes.reduce((a, b) => a + b, 0)
978
+ const auxTotal = auxTimes.reduce((a, b) => a + b, 0)
979
+ const otherTotal = otherTimes.reduce((a, b) => a + b, 0)
980
+
981
+ return {
982
+ event: '${t}',
983
+ frames: frameTimes.length,
984
+ totalMs: total,
985
+ avgMs: avg,
986
+ maxMs: max,
987
+ layoutTotalMs: layoutTotal,
988
+ renderTotalMs: renderTotal,
989
+ copyTotalMs: copyTotal,
990
+ auxTotalMs: auxTotal,
991
+ otherTotalMs: otherTotal,
992
+ layoutAvgMs: frameTimes.length > 0 ? layoutTotal / frameTimes.length : 0,
993
+ renderAvgMs: frameTimes.length > 0 ? renderTotal / frameTimes.length : 0,
994
+ copyAvgMs: frameTimes.length > 0 ? copyTotal / frameTimes.length : 0,
995
+ auxAvgMs: frameTimes.length > 0 ? auxTotal / frameTimes.length : 0,
996
+ otherAvgMs: frameTimes.length > 0 ? otherTotal / frameTimes.length : 0,
997
+ p50: sorted[Math.floor(sorted.length * 0.5)] || 0,
998
+ p95: sorted[Math.floor(sorted.length * 0.95)] || 0,
999
+ p99: sorted[Math.floor(sorted.length * 0.99)] || 0,
1000
+ jankFrames: frameTimes.filter(f => f > 16.67).length,
1001
+ samples,
1002
+ }
1003
+ })()`});if(n.error&&(console.error(` error: ${n.error}`),process.exit(1)),n.warning&&console.log(` warning: ${n.warning}`),n.frames===0){console.log(" no frames captured");break}let u=n.avgMs>0?(1e3/n.avgMs).toFixed(1):"?",m=n.frames>0?(n.jankFrames/n.frames*100).toFixed(1):"0";console.log(` ${t} transition profiled:
1004
+
1005
+ frames: ${n.frames}
1006
+ total: ${n.totalMs.toFixed(1)}ms
1007
+ avg: ${n.avgMs.toFixed(2)}ms (${u} fps)
1008
+ max: ${n.maxMs.toFixed(2)}ms
1009
+
1010
+ breakdown (avg per frame):
1011
+ layout: ${n.layoutAvgMs?.toFixed(2)||"?"}ms
1012
+ render: ${n.renderAvgMs?.toFixed(2)||"?"}ms
1013
+ copy: ${n.copyAvgMs?.toFixed(2)||"?"}ms
1014
+ aux: ${n.auxAvgMs?.toFixed(2)||"?"}ms
1015
+ other: ${n.otherAvgMs?.toFixed(2)||"?"}ms
1016
+
1017
+ distribution (${n.frames} samples):
1018
+ p50: ${n.p50.toFixed(2)}ms
1019
+ p95: ${n.p95.toFixed(2)}ms
1020
+ p99: ${n.p99.toFixed(2)}ms
1021
+ jank: ${n.jankFrames} frames (${m}%) >16.67ms`),Array.isArray(n.samples)&&n.samples.length>0&&(console.log(""),fe(n.samples.map(b=>b[1])));break}default:console.error(` unknown perf subcommand: ${e}`),process.exit(1)}break}case"errors":{let e=a[1];if(e==="clear"){await l.send({type:"evaluate",code:'window.__sootsimConsole?.clear(); "cleared"'}),B(d)?q({cleared:!0}):console.log(" error buffer cleared");break}let t=e?Number(e):20,o=await l.send({type:"evaluate",code:`window.__sootsimConsole?.getErrors(${t}) || []`}),r=Array.isArray(o)?o:[];if(B(d)){q(r);break}if(r.length===0){console.log(" no errors captured");break}console.log(` ${r.length} error(s):
1022
+ `);for(let n of r){let u=new Date(n.timestamp).toLocaleTimeString(),m=n.args.map(b=>typeof b=="object"?JSON.stringify(b):b).join(" ");if(console.log(` [${u}] ${m}`),n.stack){let b=n.stack.split(`
1023
+ `).slice(0,3);for(let p of b)console.log(` ${p.trim()}`)}}break}case"warnings":{let e=a[1]?Number(a[1]):20,t=await l.send({type:"evaluate",code:`window.__sootsimConsole?.getWarnings(${e}) || []`}),o=Array.isArray(t)?t:[];if(B(d)){q(o);break}if(o.length===0){console.log(" no warnings captured");break}console.log(` ${o.length} warning(s):
1024
+ `);for(let r of o){let n=new Date(r.timestamp).toLocaleTimeString(),u=r.args.map(m=>typeof m=="object"?JSON.stringify(m):m).join(" ");console.log(` [${n}] ${u}`)}break}case"animations":{let e=await K(l,"listAnimations")??[];if(s.includes("--json")){console.log(JSON.stringify(e,null,2));break}if(e.length===0){console.log(" no active animations");break}console.log(` ${e.length} active animation(s):
1025
+ `);for(let t of e){let o=String(t.kind).padEnd(6),r=`${Number(t.from).toFixed(2)}\u2192${Number(t.to).toFixed(2)}`,n=Number(t.current??0).toFixed(2),u=`${Math.round((t.progress??0)*100)}%`,m=`${Math.round(t.elapsedMs??0)}ms`,b=t.loop?" loop":"",p=t.layoutBound?" layout":"";console.log(` #${t.id} ${o} ${r.padEnd(14)} cur=${n.padEnd(7)} ${u.padStart(4)} ${m}${b}${p}`)}break}case"animation":{let e=a[1];(!e||e==="--help"||e==="-h")&&(console.error(` usage: ${f("animation")} <id>`),process.exit(1));let t=Number(e);Number.isFinite(t)||(console.error(` invalid id: ${e}`),process.exit(1));let o=await K(l,"getAnimation",t);console.log(JSON.stringify(o,null,2));break}case"stop-animation":{let e=a[1];(!e||e==="--help"||e==="-h")&&(console.error(` usage: ${f("stop-animation")} <id|all>`),process.exit(1));let t=e==="all"?"all":Number(e);t!=="all"&&!Number.isFinite(t)&&(console.error(` invalid id: ${e}`),process.exit(1));let o=await K(l,"stopAnimation",t);console.log(` stopped ${o??0} animation(s)`);break}case"requests":{let e=a[1];if(e==="clear"){await K(l,"clearRequests"),B(d)?q({cleared:!0}):console.log(" request buffer cleared");break}let t=e==="all",o=t?a[2]:e,r=o?Number(o):20,n=t?await K(l,"getRequests",r):await K(l,"getFailedRequests",r),u=Array.isArray(n)?n:[];if(B(d)){q(u);break}if(u.length===0){console.log(t?" no requests captured":" no failed requests captured");break}console.log(` ${u.length} ${t?"request(s)":"failed request(s)"}:
1026
+ `);for(let m of u){let b=new Date(m.timestamp).toLocaleTimeString();console.log(` [${b}] ${H(m)}`),m.responseBody?console.log(` ${m.responseBody}`):m.error&&console.log(` ${m.error}`)}break}case"network":{let e=a[1],t=null,o=null,r=!1,n=!1,u=!1;for(let I=0;I<d.length;I++){let N=d[I];if(N==="--filter")t=d[I+1]??null,I++;else if(N==="--limit"){let A=Number(d[I+1]);Number.isFinite(A)&&(o=A),I++}else N==="--failed"?r=!0:N==="--tail"||N==="-f"?n=!0:N==="--json"&&(u=!0)}if(e==="clear"){await l.send({type:"evaluate",code:'window.__sootsimObservability?.network.clear(); "cleared"'}),console.log(" network buffer cleared");break}if(e==="get"){let I=a[2];I||(console.error(" usage: sootsim network get <id>"),process.exit(1));let N=await l.send({type:"evaluate",code:`(() => {
1027
+ const obs = window.__sootsimObservability;
1028
+ if (!obs) return null;
1029
+ return obs.network.getSnapshot().find(e => e.id === ${JSON.stringify(I)}) || null;
1030
+ })()`});N||(console.error(` no entry with id ${I}`),process.exit(1)),u?console.log(JSON.stringify(N,null,2)):Tt(N);break}let m=o??(n?200:e?Number(e):20);Number.isFinite(m)||(console.error(` invalid limit: ${e}`),process.exit(1));let b=async()=>{let I=await l.send({type:"evaluate",code:`(() => {
1031
+ const obs = window.__sootsimObservability;
1032
+ if (!obs) return { ok: false };
1033
+ return { ok: true, entries: obs.network.getSnapshot() };
1034
+ })()`});if(!I||!I.ok)throw new Error("observability bridge not installed \u2014 is the engine running?");return I.entries??[]},p=I=>{let N=I;if(r&&(N=N.filter(A=>!!A.error||A.status!=null&&A.status>=400)),t){let A=t.toLowerCase();N=N.filter(W=>(W.displayUrl||W.url).toLowerCase().includes(A))}return N};if(!n){let I=await b(),N=p(I).slice(-m);if(u){console.log(JSON.stringify(N,null,2));break}if(N.length===0){console.log(I.length===0?" no network requests captured":" no matching requests");break}console.log(` ${N.length} request(s):
1035
+ `);for(let A of N)ze(A);break}console.log(` tailing network (ctrl-c to stop)...
1036
+ `);let w=new Set,O=!0,D=()=>{O=!1};process.on("SIGINT",D);try{for(;O;){let I=await b(),N=p(I);for(let A of N)A.durationMs!=null&&(w.has(A.id)||(w.add(A.id),u?console.log(JSON.stringify(A)):ze(A)));await z(250)}}finally{process.off("SIGINT",D)}break}case"logs":{let e=a[1],t=null,o=null,r=null,n=!1,u=!1,m=!1;for(let g=0;g<d.length;g++){let M=d[g];if(M==="--filter")t=d[g+1]??null,g++;else if(M==="--limit"){let F=Number(d[g+1]);Number.isFinite(F)&&(o=F),g++}else M==="--level"?(r=d[g+1]??null,g++):M==="--tail"||M==="-f"?n=!0:M==="--json"?u=!0:(M==="--internal"||M==="--all")&&(m=!0)}let b=r?new Set(r.split(",").map(g=>g.trim()).filter(g=>g==="log"||g==="info"||g==="warn"||g==="error"||g==="debug")):null;if(e==="clear"){await l.send({type:"evaluate",code:'window.__sootsimObservability?.logs.clear(); "cleared"'}),console.log(" log buffer cleared");break}let p=!u&&process.stdout.isTTY===!0,w=o??(n?500:e?Number(e):50);Number.isFinite(w)||(console.error(` invalid limit: ${e}`),process.exit(1));let O=async()=>{let g=await l.send({type:"evaluate",code:`(() => {
1037
+ const obs = window.__sootsimObservability;
1038
+ if (!obs) return { ok: false };
1039
+ return { ok: true, entries: obs.logs.getSnapshot() };
1040
+ })()`});if(!g||!g.ok)throw new Error("observability bridge not installed \u2014 is the engine running?");return g.entries??[]},D=g=>{let M=g.args[0];return typeof M!="string"?!1:M.startsWith("[sootsim]")},I=g=>{let M=g;if(m||(M=M.filter(F=>!D(F))),b&&(M=M.filter(F=>b.has(F.level))),t){let F=t.toLowerCase();M=M.filter(P=>P.args.join(" ").toLowerCase().includes(F))}return M};if(!n){let g=await O(),M=I(g).slice(-w);if(u){console.log(JSON.stringify(M,null,2));break}if(M.length===0){console.log(g.length===0?" no logs captured":" no matching logs");break}console.log(` ${M.length} log(s):
1041
+ `);for(let F of M)Ge(F,p);break}console.log(` tailing logs (ctrl-c to stop)...
1042
+ `);let N=new Set,A=!0,W=()=>{A=!1};process.on("SIGINT",W);try{for(;A;){let g=await O(),M=I(g);for(let F of M)N.has(F.id)||(N.add(F.id),u?console.log(JSON.stringify(F)):Ge(F,p));await z(250)}}finally{process.off("SIGINT",W)}break}default:console.error(` unknown subcommand: ${h}`),process.exit(1)}if(J.has(h)&&!s.includes("--no-wait")&&process.env.SOOTSIM_NO_AUTO_WAIT!=="1"&&await oe(l),!R.has(h)&&!B(d)){let e=await V();try{await j({counts:e.console})}catch{}try{await $({counts:e.requests})}catch{}}}catch(e){console.error(` ${h??"inspect"} failed: ${e.message}`);try{await we(l)}catch{}try{await j({includeTail:!0})}catch{}try{await $({includeTail:!0})}catch{}process.exit(1)}finally{l.close()}}export{Yo as runInspect};