sootsim 0.1.174 → 0.1.176

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 (148) hide show
  1. package/dist-cli/bin.js +3 -3
  2. package/dist-cli/chunks/{agent-EHUPKIHD.js → agent-XZERN2SO.js} +2 -2
  3. package/dist-cli/chunks/{agent-wrapper-L5F5DCXN.js → agent-wrapper-SAIMSLSO.js} +2 -2
  4. package/dist-cli/chunks/{app-fonts-K4FD7ITP.js → app-fonts-SQVNXLI5.js} +2 -2
  5. package/dist-cli/chunks/{assert-IAGOGXRA.js → assert-EWL6BYVH.js} +2 -2
  6. package/dist-cli/chunks/beta-2CPZK3F2.js +2 -0
  7. package/dist-cli/chunks/{camera-YRT5HULX.js → camera-FFYSKETF.js} +2 -2
  8. package/dist-cli/chunks/{chunk-SCM3ERN4.js → chunk-2SUR2RR2.js} +1 -1
  9. package/dist-cli/chunks/chunk-3LI5PWSP.js +2 -0
  10. package/dist-cli/chunks/{chunk-SWKBOGBZ.js → chunk-4WLI2STY.js} +2 -2
  11. package/dist-cli/chunks/{chunk-DY55G3EZ.js → chunk-55LUU75I.js} +2 -2
  12. package/dist-cli/chunks/{chunk-X7OTWLOA.js → chunk-63VEHUYI.js} +2 -2
  13. package/dist-cli/chunks/{chunk-GERUGIKH.js → chunk-6BU5E655.js} +12 -12
  14. package/dist-cli/chunks/{chunk-7E24LDJL.js → chunk-6S2MBFBN.js} +1 -1
  15. package/dist-cli/chunks/chunk-6WURQDZI.js +1 -0
  16. package/dist-cli/chunks/{chunk-7QNSKZ5Y.js → chunk-BGP3OZID.js} +1 -1
  17. package/dist-cli/chunks/{chunk-7A4YXD3W.js → chunk-CEMRS6UL.js} +1 -1
  18. package/dist-cli/chunks/{chunk-MMIENHCP.js → chunk-CF3NJNBJ.js} +2 -2
  19. package/dist-cli/chunks/{chunk-6BM25DJX.js → chunk-CFPELTGN.js} +2 -2
  20. package/dist-cli/chunks/{chunk-HOQZJLG7.js → chunk-D6GUXY2L.js} +2 -2
  21. package/dist-cli/chunks/{chunk-M25NFKL5.js → chunk-EU54PRND.js} +2 -2
  22. package/dist-cli/chunks/{chunk-YA46Q5HY.js → chunk-GBZNVWFP.js} +2 -2
  23. package/dist-cli/chunks/{chunk-O2PWKVDV.js → chunk-HAW6OMNW.js} +2 -2
  24. package/dist-cli/chunks/{chunk-CBNM2JQX.js → chunk-HZSJ6FL3.js} +2 -2
  25. package/dist-cli/chunks/{chunk-RPRFPJKT.js → chunk-J2BOYJ2U.js} +2 -2
  26. package/dist-cli/chunks/{chunk-TH3AEL45.js → chunk-JXZYXTBM.js} +2 -2
  27. package/dist-cli/chunks/{chunk-J2O2AEFO.js → chunk-L2CTRBGR.js} +1 -1
  28. package/dist-cli/chunks/{chunk-STSULGMI.js → chunk-MWTWVX32.js} +2 -2
  29. package/dist-cli/chunks/{chunk-NEIN26HI.js → chunk-N53VJ3FY.js} +2 -2
  30. package/dist-cli/chunks/{chunk-MZP77ZME.js → chunk-NA2KJSP6.js} +1 -1
  31. package/dist-cli/chunks/{chunk-KLPYSMAV.js → chunk-NMP6YTXS.js} +1 -1
  32. package/dist-cli/chunks/chunk-NPPSB4KE.js +1 -0
  33. package/dist-cli/chunks/{chunk-SFHH65G2.js → chunk-OAOQVBIR.js} +2 -2
  34. package/dist-cli/chunks/{chunk-6OROZMOR.js → chunk-OORRMRMI.js} +1 -1
  35. package/dist-cli/chunks/{chunk-4UEZKS2B.js → chunk-OQ3N5IOK.js} +3 -3
  36. package/dist-cli/chunks/{chunk-IX2RHMWB.js → chunk-PA5EARX2.js} +2 -2
  37. package/dist-cli/chunks/{chunk-JE2Q25A4.js → chunk-PPUEG6C4.js} +1 -1
  38. package/dist-cli/chunks/{chunk-7MVFXNE7.js → chunk-PVIMLBMX.js} +3 -3
  39. package/dist-cli/chunks/{chunk-IIEAMA66.js → chunk-Q4YNZFJF.js} +2 -2
  40. package/dist-cli/chunks/{chunk-N5CGRRGJ.js → chunk-SR7DWNZ3.js} +1 -1
  41. package/dist-cli/chunks/chunk-STWES7DO.js +2 -0
  42. package/dist-cli/chunks/{chunk-H4FFM26I.js → chunk-T3576IDX.js} +1 -1
  43. package/dist-cli/chunks/{chunk-KERGT5NG.js → chunk-VBUURELX.js} +2 -2
  44. package/dist-cli/chunks/{chunk-YA4XRTIG.js → chunk-W2AZ7ASM.js} +1 -1
  45. package/dist-cli/chunks/{chunk-OXREO6LV.js → chunk-W5QHYSDJ.js} +2 -2
  46. package/dist-cli/chunks/{chunk-3SU6H3TH.js → chunk-XC3EDXUI.js} +1 -1
  47. package/dist-cli/chunks/{chunk-FFKK3UYP.js → chunk-XKNXS6EC.js} +1 -1
  48. package/dist-cli/chunks/{chunk-W3SYJD3H.js → chunk-XKVI3H2G.js} +2 -2
  49. package/dist-cli/chunks/{chunk-7NRQVOIK.js → chunk-Y2ZSLUGO.js} +2 -2
  50. package/dist-cli/chunks/{chunk-TPE7XEGU.js → chunk-Y3NDHGEY.js} +2 -2
  51. package/dist-cli/chunks/{chunk-UARZTJ6K.js → chunk-ZAAITWNI.js} +1 -1
  52. package/dist-cli/chunks/{chunk-U7BX2XYL.js → chunk-ZHFUIBAE.js} +1 -1
  53. package/dist-cli/chunks/cli-version-ZQBDNAUR.js +2 -0
  54. package/dist-cli/chunks/{compat-SJW4SLDF.js → compat-ZFBUC4BH.js} +3 -3
  55. package/dist-cli/chunks/{control-HS3KMAUF.js → control-3JJLR4W5.js} +2 -2
  56. package/dist-cli/chunks/{cpu-profile-E46RYKZY.js → cpu-profile-FCEFZTYY.js} +2 -2
  57. package/dist-cli/chunks/daemon-3GBPTTR7.js +2 -0
  58. package/dist-cli/chunks/{debug-TO5VRPFP.js → debug-CUBIZBSH.js} +3 -3
  59. package/dist-cli/chunks/{detox-IWT2ZC6E.js → detox-4225EYR3.js} +2 -2
  60. package/dist-cli/chunks/{device-MM2473ZQ.js → device-S6BWDHPM.js} +2 -2
  61. package/dist-cli/chunks/{diagnose-5COWPUYY.js → diagnose-6CIKXRQI.js} +2 -2
  62. package/dist-cli/chunks/drivers-LI6YV5M7.js +2 -0
  63. package/dist-cli/chunks/{electron-HQGMUN3T.js → electron-37AEV4TD.js} +3 -3
  64. package/dist-cli/chunks/flow-VBH4BQSB.js +2 -0
  65. package/dist-cli/chunks/help-23IS4JFP.js +2 -0
  66. package/dist-cli/chunks/{hints-G6LSQHOA.js → hints-NNX3XRHL.js} +2 -2
  67. package/dist-cli/chunks/{home-paths-WVKPC676.js → home-paths-GOXDTTLM.js} +2 -2
  68. package/dist-cli/chunks/inspect-YSYKYPAO.js +987 -0
  69. package/dist-cli/chunks/{install-desktop-CWTQXAHN.js → install-desktop-OHGSHF3I.js} +3 -3
  70. package/dist-cli/chunks/{keys-DCWFESUB.js → keys-U7AANCE7.js} +2 -2
  71. package/dist-cli/chunks/{login-WTZRKO4L.js → login-HQ7IGHOQ.js} +4 -4
  72. package/dist-cli/chunks/{logout-3XCA6HOX.js → logout-CDVV56NI.js} +2 -2
  73. package/dist-cli/chunks/{maestro-5VKHW67Y.js → maestro-ZSO5MEL7.js} +2 -2
  74. package/dist-cli/chunks/optional-demo-registry-RG5TBVMN.js +2 -0
  75. package/dist-cli/chunks/{preview-ZBD2J4MR.js → preview-QHRLVFZ3.js} +2 -2
  76. package/dist-cli/chunks/{profile-3ENYOSN4.js → profile-JQ2VTWDK.js} +2 -2
  77. package/dist-cli/chunks/{react-DASWRMSI.js → react-FZAOHKVR.js} +2 -2
  78. package/dist-cli/chunks/{record-EICRO6TY.js → record-6SKJBVDV.js} +4 -4
  79. package/dist-cli/chunks/runtime-KFHRVRZQ.js +2 -0
  80. package/dist-cli/chunks/{screenshot-HE6MAIMM.js → screenshot-ZWWD7RKD.js} +2 -2
  81. package/dist-cli/chunks/{screenshot-mode-OQDAMXSB.js → screenshot-mode-AVGIIPOT.js} +2 -2
  82. package/dist-cli/chunks/{screenshots-5WZVXZ5W.js → screenshots-HNAQHHEZ.js} +2 -2
  83. package/dist-cli/chunks/{screenshots-capture-UZP6TEXX.js → screenshots-capture-GR3JCQEV.js} +2 -2
  84. package/dist-cli/chunks/{serve-BAXU4D42.js → serve-E5JSN7LL.js} +10 -10
  85. package/dist-cli/chunks/{setup-DRMJTUMK.js → setup-2M6ZQHFS.js} +2 -2
  86. package/dist-cli/chunks/{skills-R6NXAFPT.js → skills-EK6EIZKG.js} +2 -2
  87. package/dist-cli/chunks/{slides-SGIOOUVU.js → slides-7LMX75H7.js} +2 -2
  88. package/dist-cli/chunks/store-VOGIKQFW.js +2 -0
  89. package/dist-cli/chunks/telemetry-7TUUNCCB.js +2 -0
  90. package/dist-cli/chunks/{test-7HAXG4AU.js → test-GTYC4F55.js} +3 -3
  91. package/dist-cli/chunks/{three-mode-GBC3JNV6.js → three-mode-2PYZFZSU.js} +2 -2
  92. package/dist-cli/chunks/{timeline-C3HHCWU7.js → timeline-MIFLVHUE.js} +2 -2
  93. package/dist-cli/chunks/{upgrade-AJ7FIJO2.js → upgrade-ZVV5XJUB.js} +2 -2
  94. package/dist-cli/chunks/upload-AW66MDC5.js +2 -0
  95. package/dist-cli/chunks/{version-RYF5DY5P.js → version-IDZEL6JB.js} +2 -2
  96. package/dist-cli/chunks/{web-VPQD3D5K.js → web-IGOSYQWE.js} +2 -2
  97. package/dist-cli/chunks/{what-happened-QRPH35TD.js → what-happened-2YDLA4TU.js} +2 -2
  98. package/dist-cli/chunks/{whoami-BPGQUMYX.js → whoami-MHMILIEP.js} +2 -2
  99. package/dist-lib/agent-daemon-client.cjs +1 -1
  100. package/dist-lib/agent-events.cjs +1 -1
  101. package/dist-lib/agent-sessions.cjs +1 -1
  102. package/dist-lib/attached-projects.cjs +1 -1
  103. package/dist-lib/auth/shared-session.cjs +1 -1
  104. package/dist-lib/backend-origin.cjs +1 -1
  105. package/dist-lib/beta.cjs +1 -1
  106. package/dist-lib/beta.mjs +1 -1
  107. package/dist-lib/bridge-constants.cjs +7 -1
  108. package/dist-lib/cli-constants.cjs +1 -1
  109. package/dist-lib/config.cjs +1 -1
  110. package/dist-lib/detox/index.cjs +1 -1
  111. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  112. package/dist-lib/home-paths.cjs +1 -1
  113. package/dist-lib/host/bridge-host.cjs +3 -2
  114. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  115. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  116. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  117. package/dist-lib/host/websocket-proxy.cjs +1 -1
  118. package/dist-lib/index.cjs +1 -1
  119. package/dist-lib/jump-to-source-babel.cjs +1 -1
  120. package/dist-lib/menu.cjs +1 -1
  121. package/dist-lib/menu.mjs +1 -1
  122. package/dist-lib/metro.cjs +1 -1
  123. package/dist-lib/profiles.cjs +1 -1
  124. package/dist-lib/render-mode.cjs +1 -1
  125. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  126. package/dist-lib/sdk.cjs +28 -1
  127. package/dist-lib/sdk.mjs +27 -1
  128. package/dist-lib/skills.cjs +10 -3
  129. package/dist-lib/vite.cjs +1 -1
  130. package/package.json +1 -1
  131. package/src/bridge-constants.ts +9 -0
  132. package/src/host/bridge-host.ts +7 -4
  133. package/dist-cli/chunks/beta-U7KMLBHU.js +0 -2
  134. package/dist-cli/chunks/chunk-3FGEZ5ZM.js +0 -1
  135. package/dist-cli/chunks/chunk-C77TGXJ6.js +0 -1
  136. package/dist-cli/chunks/chunk-GPC5MNUW.js +0 -2
  137. package/dist-cli/chunks/chunk-QWKK2QEY.js +0 -2
  138. package/dist-cli/chunks/cli-version-YJOJPTJN.js +0 -2
  139. package/dist-cli/chunks/daemon-ZTEQIFR3.js +0 -2
  140. package/dist-cli/chunks/drivers-VFOM6SJP.js +0 -2
  141. package/dist-cli/chunks/flow-Z5DTMEGY.js +0 -2
  142. package/dist-cli/chunks/help-GYJH5S4Y.js +0 -2
  143. package/dist-cli/chunks/inspect-LTFGONJD.js +0 -978
  144. package/dist-cli/chunks/optional-demo-registry-VKTF3JLY.js +0 -2
  145. package/dist-cli/chunks/runtime-FLNHVIWT.js +0 -2
  146. package/dist-cli/chunks/store-ZQ4F3GEO.js +0 -2
  147. package/dist-cli/chunks/telemetry-YTN2XUOV.js +0 -2
  148. package/dist-cli/chunks/upload-JXH5MBZO.js +0 -2
@@ -1,978 +0,0 @@
1
- /*! sootsim v0.1.174 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
- import{a as vt,b as kt}from"./chunk-H4FFM26I.js";import{a as Ae,g as ke,j as te}from"./chunk-M25NFKL5.js";import{b as Tt,c as Mt,j as It}from"./chunk-IIEAMA66.js";import"./chunk-6BM25DJX.js";import{a as ee,b as A,c as E,d as Be,e as ie,f as G,g as de,h as ge,i as xt,j as $t,k as ve,l as St}from"./chunk-STSULGMI.js";import{A as ut,B as mt,F as pt,H as ce,I as ft,O as gt,a as $e,b as Xe,c as Ve,d as Qe,e as Ze,f as et,g as tt,h as ot,i as st,j as nt,k as Ee,l as Pe,q as Re,r as rt,s as it,t as fe,u as at,v as Se,w as De,x as lt,y as ct,z as dt}from"./chunk-GERUGIKH.js";import"./chunk-7QNSKZ5Y.js";import"./chunk-W3SYJD3H.js";import"./chunk-UARZTJ6K.js";import"./chunk-HOQZJLG7.js";import"./chunk-YA4XRTIG.js";import"./chunk-N5CGRRGJ.js";import{c as yt,e as bt,f as ht,g as wt,h as Ce}from"./chunk-O2PWKVDV.js";import"./chunk-JE2Q25A4.js";import{a as ze}from"./chunk-FFKK3UYP.js";import{a as _e,c as Oe}from"./chunk-MZP77ZME.js";import{a as Y}from"./chunk-CBNM2JQX.js";import{a as Ge}from"./chunk-GPC5MNUW.js";import"./chunk-QWKK2QEY.js";import{b as Ye}from"./chunk-3SU6H3TH.js";import"./chunk-6OROZMOR.js";import"./chunk-U7BX2XYL.js";import{existsSync as go,mkdirSync as yo,readFileSync as bo,rmSync as Nt,writeFileSync as ho}from"fs";import{tmpdir as wo}from"os";import{dirname as xo,join as $o,resolve as So}from"path";var ye=1,vo="SOOTSIM_INSPECT_NOTICE_PATH",ko=300*1e3,To=15e3;function Ft(){return So(process.env[vo]||$o(wo(),"sootsim-inspect-notice-state.json"))}function Mo(e,l){return Object.fromEntries(Object.entries(e).filter(([,r])=>typeof r?.signature=="string"&&Number.isFinite(r?.updatedAt)&&l-r.updatedAt<=ko))}function Io(e){let l=Ft();if(!go(l))return{version:ye,entries:{}};try{let r=JSON.parse(bo(l,"utf8"));return r.version!==ye||!r.entries||typeof r.entries!="object"?(Nt(l,{force:!0}),{version:ye,entries:{}}):{version:ye,entries:Mo(r.entries,e)}}catch{return Nt(l,{force:!0}),{version:ye,entries:{}}}}function No(e){let l=Ft();yo(xo(l),{recursive:!0}),ho(l,JSON.stringify(e,null,2)+`
3
- `)}function Fo(e,l){let r=l.trim()||"default";return`${e}:${r}`}function je(e,l,r,c={}){let p=c.nowMs??Date.now(),a=c.cooldownMs??To,g=Io(p),y=Fo(e,l),T=g.entries[y];return T&&T.signature===r&&p-T.updatedAt<a?!1:(g.entries[y]={signature:r,updatedAt:p},No(g),!0)}var _o={initialWaitMs:3e3,deadlineMs:5e3,retryWaitMs:700},Oo={initialWaitMs:1200,deadlineMs:2500,retryWaitMs:700};function Ao(e,l={}){return{...e?_o:Oo,...l}}function ue(e){return!(!e||e.hit===!1||e.ok===!1||e.pointerTapHandled===!1&&!e.keyboardOpened&&!e.isTextInput)}function Eo(e,l){return{id:e.target?.id??e.match?.id??e.node?.id??null,testID:e.target?.testID??e.match?.testID??e.node?.testID??null,text:e.target?.text??e.target?.accessibilityLabel??e.match?.text??e.match?.accessibilityLabel??e.node?.text??l??null,type:e.target?.type??e.match?.type??e.node?.type??null}}async function be(e,l){let r=Ao(!!l.agent,l.timing),c=0,p=null,a=null;try{await te({bridge:e,maxMs:r.initialWaitMs,pollMs:32,stablePolls:2})}catch{}let g=Date.now()+r.deadlineMs;for(;Date.now()<=g||c===0;){c++;let y=await l.resolve();if(p=y,y?.error==="bridge-not-ready"||y?.ambiguous||y?.nthOutOfRange)return{payload:y,result:null,attempts:c,failure:"special"};if(y&&typeof y.cx=="number"&&typeof y.cy=="number"){let M=await e.send({type:"tap",x:y.cx,y:y.cy,target:Eo(y,l.textFallback)});if(a=M,ue(M))return{payload:y,result:M,attempts:c}}let T=g-Date.now();if(T<=0)break;try{await te({bridge:e,maxMs:Math.min(T,r.retryWaitMs),pollMs:32,stablePolls:2})}catch{await new Promise(M=>setTimeout(M,Math.min(120,T)))}}return{payload:p,result:a,attempts:c,failure:p&&typeof p.cx=="number"?"missed":"not-found"}}async function _t(e,l,r,c){return e.send({type:"tap",x:l,y:r,...c?{target:c}:{}})}async function Ot(e,l,r={}){let c=JSON.stringify(l);return be(e,{agent:r.agent,timing:r.timing,resolve:()=>e.send({type:"evaluate",code:`(async () => {
4
- const t = window.__sootsimTest
5
- if (!t) return null
6
- const n = (await t.findByTestId(${c})) || (await t.findById(${c}))
7
- if (!n || !n.absolutePosition || !n.layout) return { cx: null }
8
- const resolved =
9
- typeof n.nodeId === 'number' && typeof t.resolveTapTarget === 'function'
10
- ? await t.resolveTapTarget(n.nodeId)
11
- : null
12
- const target = (resolved && resolved.target) || n
13
- const cx =
14
- resolved && typeof resolved.cx === 'number'
15
- ? resolved.cx
16
- : n.absolutePosition.x + (n.layout.width || 0) / 2
17
- const cy =
18
- resolved && typeof resolved.cy === 'number'
19
- ? resolved.cy
20
- : n.absolutePosition.y + (n.layout.height || 0) / 2
21
- return {
22
- cx,
23
- cy,
24
- match: {
25
- nodeId: n.nodeId ?? null,
26
- id: n.id,
27
- testID: n.testID,
28
- text: n.text ?? n.accessibilityLabel ?? null,
29
- type: n.type,
30
- },
31
- target: {
32
- nodeId: target.nodeId ?? null,
33
- id: target.id,
34
- testID: target.testID,
35
- text:
36
- target.text ??
37
- target.accessibilityLabel ??
38
- n.text ??
39
- n.accessibilityLabel ??
40
- null,
41
- type: target.type,
42
- },
43
- strategy: (resolved && resolved.strategy) || 'matched-node',
44
- }
45
- })()`})})}async function At(e,l,r={},c={}){let p=JSON.stringify({query:l,exact:!!r.exact,role:r.role??null,within:r.within??null,minX:r.minX??null,maxX:r.maxX??null,minY:r.minY??null,maxY:r.maxY??null,near:r.near??null,nth:r.nth??null,first:!!r.first});return be(e,{agent:c.agent,timing:c.timing,textFallback:l,resolve:()=>e.send({type:"evaluate",code:`(async () => {
46
- const t = window.__sootsimTest
47
- if (!t) return { error: 'bridge-not-ready' }
48
- const F = ${p}
49
-
50
- const res = await t.queryTextCandidates({
51
- query: F.query,
52
- exact: !!F.exact,
53
- ...(F.role ? { role: F.role } : {}),
54
- })
55
-
56
- let candidates = res.candidates || []
57
-
58
- candidates = candidates.filter((c) => {
59
- const ax = c.info.absolutePosition && c.info.absolutePosition.x
60
- const ay = c.info.absolutePosition && c.info.absolutePosition.y
61
- if (F.minX !== null && !(ax >= F.minX)) return false
62
- if (F.maxX !== null && !(ax <= F.maxX)) return false
63
- if (F.minY !== null && !(ay >= F.minY)) return false
64
- if (F.maxY !== null && !(ay <= F.maxY)) return false
65
- if (F.within && !c.ancestorTestIDs.includes(F.within)) return false
66
- return true
67
- })
68
-
69
- if (F.near) {
70
- candidates.sort((a, b) => {
71
- const ax = (a.info.absolutePosition && a.info.absolutePosition.x) || 0
72
- const ay = (a.info.absolutePosition && a.info.absolutePosition.y) || 0
73
- const bx = (b.info.absolutePosition && b.info.absolutePosition.x) || 0
74
- const by = (b.info.absolutePosition && b.info.absolutePosition.y) || 0
75
- return (
76
- Math.hypot(ax - F.near.x, ay - F.near.y) -
77
- Math.hypot(bx - F.near.x, by - F.near.y)
78
- )
79
- })
80
- } else {
81
- candidates.sort((a, b) => {
82
- const ay = (a.info.absolutePosition && a.info.absolutePosition.y) || 0
83
- const by = (b.info.absolutePosition && b.info.absolutePosition.y) || 0
84
- if (Math.abs(ay - by) > 2) return ay - by
85
- const ax = (a.info.absolutePosition && a.info.absolutePosition.x) || 0
86
- const bx = (b.info.absolutePosition && b.info.absolutePosition.x) || 0
87
- return ax - bx
88
- })
89
- }
90
-
91
- const total = candidates.length
92
- if (total === 0) return { matched: 0, total: 0 }
93
-
94
- let idx = 0
95
- if (F.nth !== null) {
96
- idx = F.nth < 0 ? total + F.nth : F.nth
97
- if (idx < 0 || idx >= total) {
98
- return { matched: 0, total, nthOutOfRange: true, nth: F.nth }
99
- }
100
- } else if (total > 1 && !F.first && !F.near) {
101
- return {
102
- ambiguous: true,
103
- total,
104
- candidates: candidates.slice(0, 10).map((c, i) => ({
105
- idx: i,
106
- nodeId: c.info.nodeId,
107
- type: c.info.type,
108
- testID: c.info.testID,
109
- text: (c.info.text || '').slice(0, 60),
110
- abs: c.info.absolutePosition,
111
- layout: c.info.layout
112
- ? {
113
- width: Math.round(c.info.layout.width || 0),
114
- height: Math.round(c.info.layout.height || 0),
115
- }
116
- : null,
117
- ancestorTestIDs: (c.ancestorTestIDs || []).slice(0, 5),
118
- })),
119
- }
120
- }
121
-
122
- const picked = candidates[idx]
123
- const n = picked.info
124
- if (!n.absolutePosition || !n.layout) return { matched: 0, total }
125
-
126
- const resolved =
127
- typeof n.nodeId === 'number' &&
128
- typeof t.resolveTapTarget === 'function'
129
- ? await t.resolveTapTarget(n.nodeId)
130
- : null
131
- const target = (resolved && resolved.target) || n
132
- const cx =
133
- resolved && typeof resolved.cx === 'number'
134
- ? resolved.cx
135
- : n.absolutePosition.x + (n.layout.width || 0) / 2
136
- const cy =
137
- resolved && typeof resolved.cy === 'number'
138
- ? resolved.cy
139
- : n.absolutePosition.y + (n.layout.height || 0) / 2
140
- return {
141
- cx,
142
- cy,
143
- match: {
144
- nodeId: n.nodeId ?? null,
145
- id: n.id,
146
- testID: n.testID,
147
- type: n.type,
148
- },
149
- target: {
150
- nodeId: target.nodeId ?? null,
151
- id: target.id,
152
- testID: target.testID,
153
- text:
154
- target.text ??
155
- target.accessibilityLabel ??
156
- n.text ??
157
- n.accessibilityLabel ??
158
- null,
159
- type: target.type,
160
- },
161
- strategy: (resolved && resolved.strategy) || 'matched-node',
162
- total,
163
- idx,
164
- }
165
- })()`})})}async function Et(e,l,r={}){let c=JSON.stringify(l);return be(e,{agent:r.agent,timing:r.timing,textFallback:l,resolve:async()=>{let p=await e.send({type:"evaluate",code:`(async () => {
166
- const t = window.__sootsimTest
167
- if (!t) return { error: 'bridge-not-ready' }
168
- const byTestId =
169
- (await t.findByTestId(${c})) || (await t.findById(${c}))
170
- if (byTestId && byTestId.absolutePosition && byTestId.layout) {
171
- return {
172
- strategy: 'testid',
173
- node: {
174
- nodeId: byTestId.nodeId ?? null,
175
- id: byTestId.id,
176
- testID: byTestId.testID,
177
- type: byTestId.type,
178
- text: byTestId.text,
179
- absolutePosition: byTestId.absolutePosition,
180
- layout: byTestId.layout,
181
- },
182
- }
183
- }
184
- const byText = await t.findByText(${c})
185
- if (byText && byText.absolutePosition && byText.layout) {
186
- return {
187
- strategy: 'text',
188
- node: {
189
- nodeId: byText.nodeId ?? null,
190
- id: byText.id,
191
- testID: byText.testID,
192
- type: byText.type,
193
- text: byText.text,
194
- absolutePosition: byText.absolutePosition,
195
- layout: byText.layout,
196
- },
197
- }
198
- }
199
- return { strategy: 'none' }
200
- })()`});if(!p||!("node"in p))return p;let a=p.node,g=a.absolutePosition.x+a.layout.width/2,y=a.absolutePosition.y+a.layout.height/2;return{...p,cx:g,cy:y,target:{id:a.id,testID:a.testID,text:p.strategy==="text"?l:a.text,type:a.type}}}})}async function Pt(e,l={args:[]}){let r=await Ve(e);if(A(l.args)){E(r);return}console.log(` nodes: ${r.nodes}`)}function Le(e,l){let r=e.indexOf(l);return r>=0&&r+1<e.length?e[r+1]:null}async function Rt(e){let{bridge:l,args:r,positional:c}=e,p=r.includes("--verbose")||r.includes("-v"),a=A(r),g=p&&!a,y=r.includes("--watch")||r.includes("-w"),T=1e3,M=r.includes("--compact"),h=r.includes("--no-xy"),k=r.includes("--no-clipped"),_=r.includes("--include-occluded"),I=Le(r,"--testid-like"),P=Le(r,"--only"),j=Le(r,"--subtree"),d=c[1]&&!c[1].startsWith("-")?c[1]:void 0,z=d?/[*?]/.test(d):!1,B=!z&&!P?d:void 0,D=P??(z?d:void 0),Q=async()=>{await de(l,{verbose:g});let S=await et(l,{describe:!0,verbose:p,filter:B||"",testIdLike:I||void 0,onlyGlob:D||void 0,subtreeRoot:j||void 0,compact:M,hideXy:h,includeOccluded:_}),N=S?.tree,J=S?.shell,W=S?.keyboard;if(a){E({shell:J,tree:N??"",keyboard:W});return}if(J&&typeof J=="object"){let C=[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);C.length>0&&console.log(` shell: ${C.join(" ")}`)}if(typeof N=="string"&&N.startsWith("__SUBTREE_NOT_FOUND__:")){let C=N.slice(22);console.log(` subtree root not found: ${C}`),Y("subtree-root-not-found",C);return}if(!N){let C=S?.nodeCount??0;console.log(" no matching nodes found"),!(B||I||D||j)&&C<10&&Y("app-still-loading",C);return}let V=N,Z=0;if(k&&typeof V=="string"){let C=V.split(`
201
- `),le=[];for(let o of C){if(o.includes("(clipped:")){Z+=1;continue}le.push(o)}V=le.join(`
202
- `)}console.log(V),Z>0&&console.log(` ${Z} clipped row(s) hidden (omit --no-clipped to see)`);let re=B||I||D||j;if((B||I||D)&&!y&&Y("describe-filter-context"),!re&&!y&&N.split(`
203
- `).length>=80&&Y("describe-use-filters"),W&&W.visible){let C=W.spec,le=[C?.keyboardType?`type=${C.keyboardType}`:null,C?.returnKeyType&&C.returnKeyType!=="default"?`return=${C.returnKeyType}`:null,W.mode!=="letters"?`mode=${W.mode}`:null,W.shifted?"shift":null,W.capsLock?"caps":null,C?.autoCapitalize&&C.autoCapitalize!=="sentences"?`autoCap=${C.autoCapitalize}`:null,W.accessoryBarId?`accessory=${W.accessoryBarId}`:null].filter(Boolean);console.log(`
204
- keyboard: ${le.join(" ")||"visible"}`)}};if(y)for(console.log(` watching... (Ctrl+C to stop)
205
- `);;)console.clear(),await Q(),await ee(T);else await Q()}var Po=["SOOTSIM_AGENT","CLAUDECODE","CLAUDE_CODE_ENTRYPOINT","CLAUDE_CODE_SESSION_ID","CODEX_THREAD_ID","CURSOR_TRACE_ID","AIDER_MODEL"];function me(){if(process.env.SOOTSIM_AGENT==="0")return!1;for(let e of Po){let l=process.env[e];if(l&&l.trim()&&l!=="0")return!0}return!1}async function Dt(e){let{bridge:l,args:r,effectiveArgs:c,positional:p,inspectUsage:a}=e,g=S=>{let N=c.indexOf(S);return N>=0&&N+1<c.length?c[N+1]:null},y=S=>c.includes(S),T=g("--testid")||g("--test-id"),M=g("--role"),h=g("--type"),k=g("--text"),_=y("--pressable"),I=y("--visible"),P=y("--interactive-targets")||y("--actions"),j=!T&&!M&&!h&&!k&&!_&&!I&&!P?p[1]:null,d=k??j,z=await nt(l,{testId:T,role:M,type:h,text:d,pressable:_,visible:I,interactive:P});z||(console.error(a("find","<text> | --text <t> | --testid <id> | --role <r> | --type <t> | --pressable | --visible | --interactive-targets")),process.exit(1));let{mode:B,result:D}=z,Q=A(r),oe=r.includes("--verbose")||r.includes("--dump");if(Q)B==="interactive-targets"&&Array.isArray(D)?E(Ee(D).map(S=>({...S,tap:Pe(S)}))):E(D??null);else if(Array.isArray(D))if(D.length===0){console.log(` no ${B} nodes found`);let S=await l.send({type:"evaluate",code:"(async () => (await window.__sootsimTest?.getNodeCount?.()) || 0)()"});typeof S=="number"&&S<10&&Y("app-still-loading",S)}else if(B==="interactive-targets"){let S=Ee(D);console.log(` found ${S.length} interactive target${S.length===1?"":"s"} (sorted by score):`);for(let N of S.slice(0,20)){let J=N.absolutePosition?`@(${Math.round(N.absolutePosition.x)},${Math.round(N.absolutePosition.y)})`:"",W=N.layout?`${Math.round(N.layout.width)}x${Math.round(N.layout.height)}`:"?x?",V=N.text?` "${N.text.slice(0,30)}"`:"",Z=N.testID?` #${N.testID}`:"",re=N.accessibilityLabel?` \u24D8"${String(N.accessibilityLabel).slice(0,24)}"`:"",pe=N.accessibilityRole?`[${N.accessibilityRole}]`:N.type,C=Pe(N);console.log(` ${pe}${V}${re}${Z} ${W} ${J}`),console.log(` \u2192 ${C}`),oe&&console.log(Je(JSON.stringify(N,null,2)," "))}S.length>20&&console.log(` ... and ${S.length-20} more`)}else{console.log(` found ${D.length} node${D.length===1?"":"s"} (${B}):`);for(let S of D.slice(0,20)){let N=S.absolutePosition?`@(${Math.round(S.absolutePosition.x)},${Math.round(S.absolutePosition.y)})`:"",J=S.layout?`${Math.round(S.layout.width)}x${Math.round(S.layout.height)}`:"?x?",W=S.text?` "${S.text.slice(0,30)}"`:"",V=S.testID?` #${S.testID}`:"",Z=S.pressable?" (tap)":"",re=S.accessibilityRole?`[${S.accessibilityRole}]`:S.type;console.log(` ${re}${W}${V} ${J} ${N}${Z}`),oe&&console.log(Je(JSON.stringify(S,null,2)," "))}D.length>20&&console.log(` ... and ${D.length-20} more`)}else if(D==null)console.log(` not found: ${d||T||M||h||""||B}`),T&&Y("wait-selector-for-missing-testid",T);else{let S=D;if(S.type&&S.absolutePosition){let N=`@(${Math.round(S.absolutePosition.x)},${Math.round(S.absolutePosition.y)})`,J=S.layout?`${Math.round(S.layout.width)}x${Math.round(S.layout.height)}`:"?x?",W=S.text?` "${S.text.slice(0,40)}"`:"",V=S.testID?` #${S.testID}`:"",Z=S.pressable?" (tap)":"",re=S.accessibilityRole?`[${S.accessibilityRole}]`:S.type;console.log(` ${re}${W}${V} ${J} ${N}${Z}`),oe&&console.log(Je(JSON.stringify(S,null,2)," "))}else console.log(JSON.stringify(D,null,2))}}function Je(e,l){return e.split(`
206
- `).map(r=>l+r).join(`
207
- `)}async function Ct(e){let{bridge:l,args:r}=e,c=A(r);await de(l,{verbose:!c});let p=r.includes("--styling"),a=await tt(l,{styling:p});if(c){E({count:a.length,elements:a});return}console.log(` layout (${a.length} element${a.length===1?"":"s"}):
208
- `),console.log(ot(a,{styling:p}))}async function Bt(e,l={}){let r=await pt(e);if("error"in r&&(console.error(r.error),process.exit(1)),l.json){console.log(JSON.stringify(r,null,2));return}let{visible:c,spec:p,mode:a,shifted:g,capsLock:y,accessoryBarId:T}=r,M=[];M.push(`keyboard: ${c?"visible":"hidden"}`),p?(M.push(` type: ${p.keyboardType}`),M.push(` returnKey: ${p.returnKeyType}`),M.push(` autoCap: ${p.autoCapitalize}`),M.push(` autoCorrect: ${p.autoCorrect?"on":"off"}`),M.push(` appearance: ${p.keyboardAppearance}`),p.secureTextEntry&&M.push(" secureTextEntry: true"),p.enablesReturnKeyAutomatically&&M.push(` return: ${p.currentTextIsEmpty?"disabled (empty)":"enabled"}`)):M.push(" spec: <none> (shown via dev-tools with no TextInput)"),M.push(` mode: ${a}${g?" (shifted)":""}${y?" (caps)":""}`),T&&M.push(` accessoryBar: ${T}`),console.log(M.join(`
209
- `))}async function jt(e){let l=await e.bridge.listSims(),r=e.args.includes("--all"),c=e.args.find((y,T)=>e.args[T-1]==="--bundle"),p=e.args.find((y,T)=>e.args[T-1]==="--app-port"),a=e.args.includes("--primary"),g=l.filter(y=>!(a&&!y.isPrimary||c&&!(y.url??"").includes(c)||p&&!(y.url??"").includes(`/rn/${p}`)||!r&&!c&&!p&&!a&&!(y.url&&(y.url.includes("bundle=")||y.url.includes("/index.bundle")))&&y.id!==e.simId));if(A(e.args)){E(g.map(y=>({...y,active:y.id===e.simId})));return}It(g,e.simId),g.length<l.length&&!A(e.args)&&console.log(` (${l.length-g.length} more hidden \u2014 pass --all to show)`)}function ae(e){return e<1024?`${e}B`:e<1024*1024?`${(e/1024).toFixed(1)}KB`:`${(e/1024/1024).toFixed(1)}MB`}function Te(e,l){return l<=0?"?":`${(e/l*100).toFixed(0)}%`}async function Lt(e,l={args:[]}){let r=await gt(e);if(A(l.args)){E(r);return}if(console.log(" memory:"),r.imageLoader){let c=r.imageLoader;console.log(" image-loader cache"),console.log(` entries: ${c.cacheEntries} / ${c.cacheMaxEntries} (${Te(c.cacheEntries,c.cacheMaxEntries)})`),console.log(` pixel bytes: ${ae(c.cachePixelBytes)} / ${ae(c.cachePixelBudget)} (${Te(c.cachePixelBytes,c.cachePixelBudget)})`),console.log(` pending: ${c.pendingFetches} fetches, ${c.pendingBytes} bytes`),console.log(` failed uris: ${c.failedUris}`),console.log(` snapshots: ${c.snapshots}`),console.log(` live frames: ${c.liveFrames}`)}else console.log(" image-loader cache: not available (engine pre-rebuild?)");if(r.workerHeap){let c=r.workerHeap;console.log(" worker heap (chrome only)"),console.log(` used: ${ae(c.usedJSHeapSize)} / ${ae(c.jsHeapSizeLimit)} (${Te(c.usedJSHeapSize,c.jsHeapSizeLimit)})`),console.log(` total: ${ae(c.totalJSHeapSize)}`)}if(r.hostHeap){let c=r.hostHeap;console.log(" host heap (chrome only)"),console.log(` used: ${ae(c.usedJSHeapSize)} / ${ae(c.jsHeapSizeLimit)} (${Te(c.usedJSHeapSize,c.jsHeapSizeLimit)})`),console.log(` total: ${ae(c.totalJSHeapSize)}`)}}var X="<redacted secure text>";function he(e){let l=e?.layout?.spec??e?.spec??null;return!!l?.secureTextEntry&&l.keyboardType!=="visible-password"}function We(e,l){return he(l)?X:e}function qe(e){if(!e||!he(e))return e;let l=e.focusedInput;return!l||typeof l!="object"?e:{...e,focusedInput:{...l,secureTextEntry:!0,text:typeof l.text=="string"?X:l.text}}}function we(e){let l=e.indexOf("--testid");if(l>=0&&e[l+1])return{mode:"testid",value:e[l+1]};let r=e.indexOf("--test-id");if(r>=0&&e[r+1])return{mode:"testid",value:e[r+1]};let c=e.indexOf("--text");return c>=0&&e[c+1]?{mode:"text",value:e[c+1]}:null}async function Me(e,l){let r=JSON.stringify(l.value),c=l.mode==="testid"?`(await t.findByTestId(${r})) || (await t.findById(${r}))`:`await t.findByText(${r})`;return await e.send({type:"evaluate",code:`(async () => {
210
- const t = window.__sootsimTest
211
- if (!t) return null
212
- const n = ${c}
213
- if (!n || !n.absolutePosition || !n.layout) return null
214
- const resolved =
215
- typeof n.nodeId === 'number' && typeof t.resolveTapTarget === 'function'
216
- ? await t.resolveTapTarget(n.nodeId)
217
- : null
218
- const cx =
219
- typeof resolved?.cx === 'number'
220
- ? resolved.cx
221
- : n.absolutePosition.x + (n.layout.width || 0) / 2
222
- const cy =
223
- typeof resolved?.cy === 'number'
224
- ? resolved.cy
225
- : n.absolutePosition.y + (n.layout.height || 0) / 2
226
- return {
227
- x: cx,
228
- y: cy,
229
- id: n.id ?? null,
230
- testID: n.testID ?? null,
231
- text: ${JSON.stringify(l.mode==="text")} ? ${r} : (n.text ?? n.accessibilityLabel ?? null),
232
- type: n.type ?? null,
233
- }
234
- })()`})??null}async function Jt(e,l={}){let{nav:r,keyboard:c,shell:p}=await ft(e);if(l.json){console.log(JSON.stringify({shell:p??null,nav:r,keyboard:c},null,2));return}let a=[];if(p){let g=p.activeApp??p.state??"<none>",y=p.showSwitcher?" (app switcher open)":"",T=typeof p.launchProgress=="number"&&p.launchProgress<.98?` launching (${Math.round(p.launchProgress*100)}%)`:"";a.push(`shell: ${g}${y}${T}`)}else a.push("shell: <unavailable>");if(r){let g=r.transitionPhase!=="idle"?` (${r.transitionPhase}, ${r.activeTransitionCount} active)`:"";if(a.push(`nav: phase=${r.transitionPhase}${g}`),r.screens.length===0)a.push(" <no registered screens \u2014 app may not use react-native-screens>");else for(let y of r.screens){let T=y.isActive?"\u25B6":" ",M=y.routeName?` ${y.routeName}`:"",h=y.headerHeight>0?` header=${y.headerHeight}`:"",k=y.largeTitleState&&y.largeTitleState!=="expanded"?` large-title=${y.largeTitleState}`:"";a.push(` ${T} #${y.id}${M}${h}${k}`)}}else a.push("nav: <runtime not available>");if(c&&c.visible){let g=c.spec?.keyboardType??"default",y=c.spec?.returnKeyType??"default";a.push(`keyboard: visible (${g}, return=${y}, mode=${c.mode??"?"})`)}else a.push("keyboard: hidden");console.log(a.join(`
235
- `))}async function Wt(e){let{bridge:l,args:r,positional:c}=e,p=c[1]?Number(c[1])*1e3:3e3,a=r.includes("--strict"),{elapsed:g,settled:y}=await te({bridge:l,maxMs:p,strict:a});console.log(y?` settled in ${g}ms`:` timed out after ${g}ms (may still be animating)`)}async function qt(e){let l=e.positional[1]?Number(e.positional[1]):.5;(!Number.isFinite(l)||l<0)&&(console.error(e.inspectUsage("sleep","[seconds]")),process.exit(1)),await ee(l*1e3),console.log(` slept ${l}s`)}async function Ht(e){let{bridge:l,args:r,positional:c}=e,p=c[1]?Number(c[1]):5,{tree:a}=await Qe(l,p);if(A(r)){E({depth:p,tree:a??null});return}console.log(typeof a=="string"?a:JSON.stringify(a,null,2))}async function Kt(e,l={args:[]}){let r=await Ze(e);if(A(l.args)){E(r);return}console.log(r.url)}async function Ut(e){let{wsPort:l,commandTimeoutMs:r,simId:c,simIdSource:p,positional:a}=e,g=a[1]?Number(a[1]):30,y=Math.max(1e3,(Number.isFinite(g)?g:30)*1e3),T=Math.max(1,Math.ceil(y/500));console.log(" waiting for sim reconnect...");let M=await xt(l,r,c,{attempts:T,simIdSource:p});M||(console.error(" timed out waiting for sim reconnect"),process.exit(1)),M.bridge.close(),ke({source:"inspect wait",step:{wait:y},summary:`wait ${Math.round(y/1e3)}s`}),console.log(` ready: ${M.count} nodes`)}var zt=new Set(["app-launch","toast","keyboard","screen","route","alert","actionsheet","picker","notification","fetch","console","shell","scroll","gesture","text-input","react-commit","animation","reanimated"]);function Ie(e,l){let r=e.indexOf(l);if(r>=0&&r+1<e.length)return e[r+1]}function Ro(e,l){if(!l.filter&&!l.equals)return!0;let r=e.data,c=[];if(r&&typeof r=="object")for(let a of["url","displayUrl","message","name","activeName","path","pathname","title","phase","event","type","kind"]){let g=r[a];typeof g=="string"&&g.length>0&&c.push(g)}let p=c.join(" ");return l.equals?c.some(a=>a===l.equals):l.filter?p.toLowerCase().includes(l.filter.toLowerCase()):!0}async function Gt(e){let{bridge:l,args:r,positional:c,inspectUsage:p}=e,a=c[1];a||(console.error(p("wait event","<kind> [--max-ms 5000] [--filter <substring>] [--equals <exact>] [--since now|cursor]")),process.exit(1)),zt.has(a)||console.error(` warning: '${a}' is not a known timeline kind \u2014 waiting anyway. known: ${[...zt].sort().join(", ")}`);let g=Ie(r,"--max-ms"),y=g&&Number.isFinite(Number(g))?Math.max(100,Number(g)):5e3,T=Ie(r,"--filter"),M=Ie(r,"--equals"),h=Ie(r,"--since")??"now",k=r.includes("--json"),_=Date.now(),I=_+y,P=200,j=_;for(;Date.now()<I;){let z={kinds:[a],since:h==="cursor"?void 0:j,limit:50},B=await l.send({type:"evaluate",code:`(async () => {
236
- const t = window.SootSim?.bridges?.timeline
237
- if (!t) return { ok: false, error: 'timeline bridge missing' }
238
- return { ok: true, result: await t.recent(${JSON.stringify(z)}) }
239
- })()`});(!B||!B.ok)&&(console.error(` could not query timeline: ${B&&"error"in B?B.error:"unknown"}`),process.exit(1));let D=B.result.events??[];for(let Q of D)if(Ro(Q,{filter:T,equals:M})){let oe=Date.now()-_;console.log(k?JSON.stringify({found:!0,elapsedMs:oe,event:Q}):` ${a} event after ${oe}ms${T?` (filter: ${T})`:""}${M?` (equals: ${M})`:""}`);return}B.result.watermark&&B.result.watermark>j&&(j=B.result.watermark),await new Promise(Q=>setTimeout(Q,P))}let d=Date.now()-_;k?console.log(JSON.stringify({found:!1,elapsedMs:d,kind:a,filter:T,equals:M})):console.error(` \u26A0 wait event ${a} timed out after ${d}ms${T?` (filter: ${T})`:""}${M?` (equals: ${M})`:""}`),process.exit(1)}async function Yt(e){let{bridge:l,args:r}=e,c=r.includes("--strict"),p=$e(r,3e3),{elapsed:a,settled:g}=await te({bridge:l,maxMs:p,strict:c});g?console.log(` idle in ${a}ms`):(console.error(` \u26A0 wait idle timed out after ${a}ms (may still be animating)`),process.exit(1))}async function Xt(e){let{bridge:l,args:r}=e,c=$e(r,2e4),{ready:p,elapsedMs:a,nodes:g,targets:y,flag:T,loadingText:M,externalReady:h,externalError:k,errors:_}=await rt(l,c,{onProgress(P){console.error(` still waiting after ${P.elapsedMs}ms \u2014 ${Re(P)} (nodes: ${P.nodes}, targets: ${P.targets}, errors: ${P.errors})`)}});if(p){let P=c-a,j=Math.max(100,Math.min(1e4,P)),d=await te({bridge:l,maxMs:j,pollMs:32,stablePolls:2});d.settled||(console.error(` \u26A0 wait ready timed out after ${a+d.elapsed}ms \u2014 app mounted but did not settle (nodes: ${g}, targets: ${y})`),process.exit(1)),console.log(` ready in ${a+d.elapsed}ms: ${g} nodes, ${y} targets`);return}let I=Re({externalError:k,loadingText:M,externalReady:h,flag:T,targets:y});console.error(` \u26A0 wait ready timed out after ${a}ms \u2014 ${I} (nodes: ${g}, targets: ${y}, errors: ${_})`),process.exit(1)}async function Vt(e){let{bridge:l,args:r,positional:c,inspectUsage:p}=e,a=c[1];a||(console.error(p("wait selector","<testid> [--max-ms 5000] [--gone]")),process.exit(1));let g=r.indexOf("--max-ms"),y=g>=0&&r[g+1]?Math.max(100,Number(r[g+1])):5e3,T=r.includes("--gone"),{found:M,node:h,elapsed:k}=await it(l,a,y,{gone:T});if(T){M?console.log(` #${a} gone after ${k}ms`):(console.error(` \u26A0 wait selector #${a} --gone timed out after ${k??y}ms (still present)`),process.exit(1));return}if(M&&h){let _=h.absolutePosition?`@(${Math.round(h.absolutePosition.x)},${Math.round(h.absolutePosition.y)})`:"",I=h.layout?`${Math.round(h.layout.width)}x${Math.round(h.layout.height)}`:"?x?";console.log(` found #${a} in ${k}ms ${I} ${_}`)}else console.error(` \u26A0 wait selector #${a} timed out after ${k??y}ms`),process.exit(1)}function io(e){return e==null?"\u2014":e<1024?`${e}B`:e<1024*1024?`${(e/1024).toFixed(1)}K`:`${(e/1024/1024).toFixed(1)}M`}function ao(e){return e==null?" \u2026":e<1e3?`${e}ms`.padStart(5):`${(e/1e3).toFixed(2)}s`.padStart(5)}function Qt(){process.stderr.write(` the sim is not responding. recover it with:
240
- sootsim close --sim <id> # force-close the wedged sim
241
- sootsim list # confirm it's gone
242
- `)}async function Co(e){try{return await e.send({type:"evaluate",code:"1"},{timeoutMs:3e3}),!0}catch{return!1}}async function Bo(e,l){try{let r=await e.send({type:"evaluate",code:`(async () => {
243
- const t = window.__sootsimTest
244
- if (!t || typeof t.queryAll !== 'function') return []
245
- try {
246
- // queryAll is async in render-worker mode (the canonical mode now)
247
- // because the call has to round-trip the worker \u2014 must await it.
248
- // a sync result is still handled fine since await on a non-Promise
249
- // is a no-op.
250
- const nodes = await t.queryAll({})
251
- if (!Array.isArray(nodes)) return []
252
- const out = new Set()
253
- for (const n of nodes) {
254
- const id = n && (n.testID || n.id)
255
- if (typeof id === 'string' && id) out.add(id)
256
- }
257
- return Array.from(out)
258
- } catch {
259
- return []
260
- }
261
- })()`});if(!Array.isArray(r)||r.length===0)return;let c=l.toLowerCase(),p=r.map(a=>({id:a,score:jo(c,a.toLowerCase())})).filter(a=>a.score<c.length+4).sort((a,g)=>a.score-g.score).slice(0,5);if(p.length===0)return;console.error(" similar testIDs:");for(let a of p)console.error(` ${a.id}`)}catch{}}function jo(e,l){if(l===e)return 0;if(l.includes(e))return 1;if(e.includes(l))return 2;let r=0;for(;r<e.length&&r<l.length&&e[r]===l[r];)r+=1;return Lo(e,l)-r}function Lo(e,l){if(e===l)return 0;if(!e.length)return l.length;if(!l.length)return e.length;let r=new Array(l.length+1),c=new Array(l.length+1);for(let p=0;p<=l.length;p++)r[p]=p;for(let p=1;p<=e.length;p++){c[0]=p;for(let g=1;g<=l.length;g++)c[g]=Math.min(r[g]+1,c[g-1]+1,r[g-1]+(e[p-1]===l[g-1]?0:1));let a=r;r=c,c=a}return r[l.length]}function Jo(e){return e.error?"err":e.status==null?" \u2026 ":String(e.status)}function Zt(e){return e.externalError?`guest app errored: ${e.externalError}`:e.loadingText?`still showing "${e.loadingText}"`:e.externalReady===!1?"guest app is still loading":e.flag!==!0?"guest app has not emitted sootsim:externalAppReady":e.targets<=0?"ready flag emitted but no visible app content is inspectable yet":"node tree is still changing"}function eo(e){let l=ie(e.startTs),r=Jo(e).padEnd(3),c=e.method.padEnd(5),p=io(e.size).padStart(6),a=ao(e.durationMs);console.log(` [${l}] ${r} ${c} ${p} ${a} ${e.displayUrl}`),e.error&&console.log(` error: ${e.error}`)}function Wo(e){let l=[["id",e.id],["source",e.source],["kind",e.kind],["method",e.method],["status",e.error?`error: ${e.error}`:`${e.status??"\u2014"} ${e.statusText??""}`.trim()],["url",e.url],["started",ie(e.startTs)],["duration",ao(e.durationMs).trim()],["size",io(e.size)],["content-type",e.type??"\u2014"]];for(let[r,c]of l)console.log(` ${r.padEnd(13)} ${c}`)}var qo={error:"\x1B[31m",warn:"\x1B[33m",info:"\x1B[36m",debug:"\x1B[35m",log:"\x1B[37m"},to="\x1B[0m",Ho="\x1B[2m";function oo(e,l){let r=ie(e.ts),c=e.level.toUpperCase().padEnd(5),p=e.args.join(" ");if(l){let a=qo[e.level];console.log(` ${Ho}[${r}]${to} ${a}${c}${to} ${p}`)}else console.log(` [${r}] ${c} ${p}`);if(e.stack&&e.level==="error"){let a=e.stack.split(`
262
- `).slice(0,5);for(let g of a)console.log(` ${g.trim()}`)}}var ne="__sootsimCliPerf",Ko=120;async function so(e,l){let r=e.find((_,I)=>e[I-1]==="--id"),c=e.find((_,I)=>e[I-1]==="--text");if(r||c){let _=await l.send({type:"evaluate",code:vt({id:r,text:c})});if(!_)throw new Error(r?`no node with id "${r}"`:`no node matching text "${c}"`);let{x:I,y:P,w:j,h:d}=_;return{x:I,y:P,w:j,h:d}}let p=e.find((_,I)=>e[I-1]==="--area");if(p){let _=p.split(",").map(z=>Number(z.trim()));if(_.length!==4||_.some(z=>!Number.isFinite(z)))throw new Error(`--area expects x,y,w,h (got "${p}")`);let[I,P,j,d]=_;return{x:I,y:P,w:j,h:d}}let a=_=>{let I=e.find((j,d)=>e[d-1]===_);if(I==null)return null;let P=Number(I);return Number.isFinite(P)?P:null},g=a("--x"),y=a("--y"),T=a("--w"),M=a("--h");if(g!=null||y!=null||T!=null||M!=null)return{x:g??0,y:y??0,w:T??1,h:M??1};let k=e.filter((_,I)=>I>0&&!_.startsWith("-")&&e[I-1]!=="--output"&&e[I-1]!=="--area"&&e[I-1]!=="--id"&&e[I-1]!=="--text"&&e[I-1]!=="--x"&&e[I-1]!=="--y"&&e[I-1]!=="--w"&&e[I-1]!=="--h").map(Number).filter(_=>Number.isFinite(_));if(k.length>=2){let[_,I,P=1,j=1]=k;return{x:_,y:I,w:P,h:j}}return null}function He(e){let l={"<8":0,"8-12":0,"12-16":0,"16-20":0,"20-33":0,">33":0};for(let r of e)r<8?l["<8"]++:r<12?l["8-12"]++:r<16?l["12-16"]++:r<20?l["16-20"]++:r<33?l["20-33"]++:l[">33"]++;console.log(" histogram:");for(let[r,c]of Object.entries(l)){let p="\u2588".repeat(Math.ceil(c/e.length*40));console.log(` ${r.padEnd(6)} ${p} ${c}`)}}async function Ne(e,l,r){let c=Date.now()+l,p=await ce(e,l);for(;;){if(r(p))return{settled:!0,state:p};if(Date.now()>=c)return{settled:!1,state:p};await ee(16),p=await ce(e)}}async function Ue(e){return e.send({type:"evaluate",code:`(async () => {
263
- const kb = window.__sootsimKeyboard
264
- const test = window.__sootsimTest
265
- if (!kb) return { error: 'keyboard bridge not available' }
266
- const layout = typeof kb.getLayout === 'function' ? kb.getLayout() : null
267
- const secureTextEntry =
268
- !!layout?.spec?.secureTextEntry && layout?.spec?.keyboardType !== 'visible-password'
269
- const visible = kb.isVisible()
270
- const mode = kb.getMode()
271
- let focused = null
272
- if (test && typeof test.getFocusedNode === 'function') {
273
- try {
274
- focused = await test.getFocusedNode()
275
- } catch {}
276
- }
277
- let runtimeSnapshot = null
278
- if (test && typeof test.getFocusKeyboardSnapshot === 'function') {
279
- try {
280
- runtimeSnapshot = await test.getFocusKeyboardSnapshot()
281
- } catch {}
282
- }
283
- return {
284
- visible,
285
- mode,
286
- layout,
287
- focusedInput: focused ? {
288
- nodeId: focused.nodeId ?? null,
289
- testID: focused.testID || null,
290
- id: focused.id || null,
291
- placeholder: focused.placeholder || null,
292
- secureTextEntry,
293
- text: secureTextEntry ? ${JSON.stringify(X)} : (focused.text || null),
294
- } : null,
295
- phase: runtimeSnapshot?.keyboard?.phase ?? null,
296
- frame: runtimeSnapshot?.keyboard?.frame ?? null,
297
- focusedRect: runtimeSnapshot?.focused?.rect ?? null,
298
- }
299
- })()`}).then(l=>qe(l))}function Uo(e,l,r){if(!e.visible)return!1;let c=e.focusedInput;return!(l&&c&&!(c.testID===l||c.id===l)||r!==null&&he(e)!==r)}async function zo(e,l=600,r={}){let c=Date.now()+l;for(;Date.now()<=c;){let p=await Ue(e);if(Uo(p,r.targetId??null,r.secureTextEntry??null))return p;await ee(30)}return Ue(e)}async function Fe(e,l){let r=await Ue(e);if(r.visible)return r;console.error(` ${l} 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 no(e,l,r){return l==="appearance"?e.send({type:"evaluate",code:`(async () => {
300
- const requested = ${JSON.stringify(r??"toggle")}
301
- // the engine owns toggle + auto resolution (settingsStore is the single
302
- // source of truth). never infer the current scheme client-side \u2014 the old
303
- // documentElement '#333333' sniff is never set in embedded / shell-chrome
304
- // contexts, so 'toggle' read 'light' forever and stuck on the boot scheme.
305
- window.postMessage({ type: 'soot-action', action: 'set-appearance', value: requested }, '*')
306
- // let the engine's message handler apply + publish window.__sootsimColorScheme
307
- await new Promise((r) => setTimeout(r, 60))
308
- const applied = window.__sootsimColorScheme?.resolved
309
- ?? (window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ? 'dark' : 'light')
310
- const setting = window.__sootsimColorScheme?.setting ?? requested
311
- return { ok: true, requested, setting, applied }
312
- })()`}):e.send({type:"evaluate",code:`(async () => {
313
- window.dispatchEvent(new CustomEvent(${JSON.stringify(l==="lock"?"sootsim:toggleLock":"sootsim:shake")}))
314
- return { ok: true, action: ${JSON.stringify(l)} }
315
- })()`})}function Go(e){let l={Enter:"return",NumpadEnter:"return",Backspace:"delete",Delete:"delete",Space:"space",ShiftLeft:"shift",ShiftRight:"shift"};if(l[e])return l[e];let r=e.match(/^Digit([0-9])$/);if(r)return r[1];let c=e.match(/^Key([A-Z])$/);return c?c[1].toLowerCase():null}function Yo(e){if(typeof e!="string")return null;let l=e.replace(/\s+/g," ").trim();return l?l.slice(0,80):null}function lo(...e){for(let l of e){if(typeof l!="string")continue;let r=l.trim();if(r)return r}return null}function Xo(e){let l=e.indexOf("--node-id");if(l<0)return null;let r=e[l+1];if(!r)return null;let c=Number(r);return Number.isInteger(c)&&c>0?c:null}async function L(e,l,r){let c=ke({source:e,step:l,summary:r});c.active&&(c.replaced?console.error(` draft: replaced unkept action "${c.replaced.summary}" \u2014 \`flow keep\` commits one action at a time`):console.error(" draft: action pending \u2014 `sootsim flow keep` to commit"))}function ro(e,l,r){if(!r||r.hit===!1)return null;let c=lo(r.responderTestID,r.testID);if(c)return{step:{tapOn:{id:c}},summary:`tap #${c}`};let p=Yo(r.text);return p?{step:{tapOn:p},summary:`tap "${p}"`}:{step:{tapAtCoords:{x:e,y:l}},summary:`tap @${Math.round(e)},${Math.round(l)}`}}function Ke(e,l,r){let c=lo(l?.testID,l?.id);return c?{step:{tapOn:{id:c}},summary:`tap #${c}`}:r==="id"?{step:{tapOn:{id:e}},summary:`tap #${e}`}:{step:{tapOn:e},summary:`tap "${e}"`}}async function Jn(e,l){let r=e[0]==="get"||e[0]==="do"||e[0]==="debug"||e[0]==="wait"?e[0]:null,c=r?e.slice(1):e,p=yt(c,{port:l.port,commandTimeoutMs:l.timeoutMs,stripBooleanFlags:["--verbose","-v","--help","-h","--clear-state","--json","--all","--watch","-w","--strict","--no-wait","--dump","--failed","--slow","--tail","-f","--interactive-targets","--actions","--internal","--compact","--no-xy","--no-clipped","--include-occluded"],stripValueFlags:["--output","--nth","--index","--testid","--test-id","--text","--node-id","--max-ms","--filter","--limit","--level","--threshold","--equals","--since","--testid-like","--only","--subtree"]}),a=p.positional,g=a[0],y=r==="get"||r==="do"||r==="debug"||r==="wait"?r:"inspect",T=typeof c[0]=="string"&&ze.has(c[0]),M=T?c[0]:null,h=o=>T&&o===c[0]?`sootsim ${o}`:`sootsim ${y} ${o}`,k=(o,t)=>` usage: ${h(o)}${t?` ${t}`:""}`;if(!g||e.includes("--help")||e.includes("-h")){let o={bridgePort:7668,defaultShellUrl:Ge};if(y==="do"||y==="get"||y==="debug"||y==="wait"){let n=_e(y,o);n&&(console.log(`${n}
316
- `),process.exit(0))}if(M==="shell"){let n=Oe("shell",o);n&&(console.log(`${n}
317
- `),process.exit(0))}let t=Oe("inspect",o),s=["do","get","debug","wait"].map(n=>_e(n,o)).filter(n=>n!=null).join(`
318
-
319
- `);console.log(`${t??""}
320
-
321
- ${s}
322
- `),process.exit(0)}let _=p.wsPort,I=p.simId,P=p.simIdSource,j=p.commandTimeoutMs;if(g==="list"&&c.some(o=>o==="--drivers"||o==="-D")){let{buildDriverListRows:o}=await import("./drivers-VFOM6SJP.js"),t=o();console.log(` available drivers (${t.length}):
323
- `);let s=Math.max(...t.map(i=>i.id.length),6),n=Math.max(...t.map(i=>i.kind.length),4);for(let i of t){let u=i.available?"\u2713":"\u2717",m=i.id.padEnd(s),b=i.kind.padEnd(n);console.log(` ${u} ${m} ${b} ${i.description}`),i.available&&i.detail?console.log(` ${i.detail}`):!i.available&&i.reason&&console.log(` unavailable: ${i.reason}`)}return}let d=bt(p),z=I||"default",B=new Set(["errors","warnings","requests","js","eval","reload","globals","perf","storage-clear","list","wait","sleep"]),D=200;function Q(o){let t=o.replace(/\s+/g," ").trim();if(!t)return"";if(/^<(!doctype html|html|\?xml)|<body[\s>]/i.test(t)){let n=/<title[^>]*>([^<]+)<\/title>/i.exec(o)?.[1]?.trim(),i=/<body[^>]*>([\s\S]*?)<\//i.exec(o)?.[1]?.replace(/<[^>]+>/g," ").replace(/\s+/g," ").trim().slice(0,80),u=n||i||"html error page";return`<html ${o.length}B> "${u}" (body elided \u2014 add --json for the full payload)`}return t.length<=D?t:`${t.slice(0,D)}\u2026 (+${t.length-D} more bytes)`}function oe(o){let t=o.displayUrl||o.url;return o.status!=null?`${o.method} ${t} -> ${o.status}${o.statusText?` ${o.statusText}`:""}`:o.error?`${o.method} ${t} -> ${o.error}`:`${o.method} ${t}`}async function S(o){let t=me()?5e3:1500;try{let{settled:s,elapsed:n}=await te({bridge:o,maxMs:t,pollMs:32,stablePolls:2});s||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.
324
- `)}catch{}}function N(o,t){console.error(` tap failed: ${o} stayed visible but did not receive a hittable press after ${t.attempts} attempt${t.attempts===1?"":"s"}`),t.result&&console.error(` last result: ${JSON.stringify(t.result)}`)}async function J(){try{return await d.send({type:"evaluate",code:`(() => ({
325
- console: ${Se},
326
- requests: window.__sootsimTest?.getRequestCounts?.() || null,
327
- }))()`})||{console:null,requests:null}}catch{return{console:null,requests:null}}}async function W(o={}){let t=o.counts!==void 0?o.counts:await G(d,"getRequestCounts");if(!t||typeof t!="object")return;let s=Math.max(0,Number(t.failed)||0);if(s===0||!o.includeTail&&!je("requests",z,String(s))||(console.log(`
328
- network: ${s} failed request${s===1?"":"s"}`),console.log(` inspect: ${h("requests")} 5`),!o.includeTail))return;let n=await G(d,"getFailedRequests",5);if(!(!Array.isArray(n)||n.length===0)){console.log(`
329
- recent failed requests:
330
- `);for(let i of n){let u=ie(i.timestamp);console.log(` [${u}] ${oe(i)}`),i.responseBody?console.log(` ${Q(i.responseBody)}`):i.error&&console.log(` ${i.error}`)}}}async function V(o={}){let t=o.counts!==void 0?o.counts:await d.send({type:"evaluate",code:Se});if(!t||typeof t!="object")return;let s=t,n=Math.max(0,Number(s.errors)||0),i=Math.max(0,Number(s.warnings)||0);if(n===0&&i===0||!o.includeTail&&!je("console",z,`${n}:${i}`))return;let u=[];if(n>0&&u.push(`${n} console error${n===1?"":"s"}`),i>0&&u.push(`${i} console warning${i===1?"":"s"}`),console.log(`
331
- console: ${u.join(", ")}`),console.log(` inspect: ${h("errors")} 5`),i>0&&console.log(` inspect: ${h("warnings")} 5`),!o.includeTail||n===0)return;let m=await fe(d,5);if(!(!Array.isArray(m)||m.length===0)){console.log(`
332
- recent console errors:
333
- `);for(let b of m){let f=ie(b.timestamp),w=Array.isArray(b.args)?b.args.map(O=>typeof O=="object"?JSON.stringify(O):String(O)).join(" "):String(b);console.log(` [${f}] ${w}`)}}}let Z=["console","fetch","toast","alert","notification","screen","app-launch","keyboard","route","actionsheet","picker","shell","scroll","gesture","text-input","animation","reanimated"];async function re(o){let t=Ye(),s=null;try{s=await wt(o,`(() => {
334
- const tl = window.SootSim && window.SootSim.bridges && window.SootSim.bridges.timeline
335
- if (!tl || typeof tl.summary !== 'function') return null
336
- const cursorKey = ${JSON.stringify(t)}
337
- const summary = tl.summary({ sinceCursor: cursorKey })
338
- let consoleSplit = null
339
- if (summary && summary.byKind && summary.byKind.console) {
340
- const events = tl.recent({ sinceCursor: cursorKey, kinds: 'console', limit: 100000 }).events
341
- consoleSplit = { error: 0, warn: 0 }
342
- for (const ev of events) {
343
- const lvl = ev && ev.data && ev.data.level
344
- if (lvl === 'error') consoleSplit.error++
345
- else if (lvl === 'warn') consoleSplit.warn++
346
- }
347
- }
348
- return summary ? { summary, consoleSplit } : null
349
- })()`)}catch{return}if(!s||!s.summary||!s.summary.total)return;let n=s.summary.byKind??{},i=[],u=new Set;for(let m of Z){let b=n[m];if(b)if(u.add(m),m==="console"&&s.consoleSplit){let{error:f,warn:w}=s.consoleSplit;f>0&&i.push(`${f} error${f===1?"":"s"}`),w>0&&i.push(`${w} warning${w===1?"":"s"}`)}else i.push(`${b} ${m}${b===1?"":"s"}`)}for(let[m,b]of Object.entries(n))!u.has(m)&&b&&i.push(`${b} ${m}${b===1?"":"s"}`);if(i.length!==0&&(console.log(`
350
- since last: ${i.join(" \xB7 ")} \u2014 sootsim what-happened`),s.summary.lastAt))try{await Ce(o,"SootSim.bridges.timeline.cursorAdvance",t,s.summary.lastAt)}catch{}}let pe=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","storage-clear"]),C=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"]),le=(e.includes("--verbose")||e.includes("-v"))&&!e.includes("--json");y==="do"&&g==="shell"&&(console.error(" `sootsim do shell` was removed. use `sootsim shell ...` instead."),process.exit(1)),pe.has(g)&&await ht(d),C.has(g)&&await de(d,{verbose:le});try{switch(g){case"list":{await jt({bridge:d,simId:I,args:c});break}case"tree":{await Ht({bridge:d,args:c,positional:a});break}case"a11y":{let o=await st(d);if(!Array.isArray(o)||o.length===0){console.log(" no accessible nodes found");break}if(e.includes("--json"))console.log(JSON.stringify(o,null,2));else{console.log(` accessibility tree (${o.length} nodes):
351
- `);for(let t of o){let s=[];if(s.push(`[${t.role}]`),t.label){let n=t.label.length>50?t.label.slice(0,47)+"...":t.label;s.push(`"${n}"`)}if(t.hint&&s.push(`(hint: "${t.hint}")`),t.testID&&s.push(`#${t.testID}`),t.state){let n=[];t.state.disabled&&n.push("disabled"),t.state.selected&&n.push("selected"),t.state.checked===!0&&n.push("checked"),t.state.checked==="mixed"&&n.push("mixed"),t.state.busy&&n.push("busy"),t.state.expanded===!0&&n.push("expanded"),t.state.expanded===!1&&n.push("collapsed"),n.length&&s.push(`{${n.join(", ")}}`)}t.position&&s.push(`@(${t.position.x},${t.position.y})`),t.size&&s.push(`${t.size.w}x${t.size.h}`),console.log(" "+s.join(" "))}}break}case"find":{await Dt({bridge:d,args:e,effectiveArgs:c,positional:a,inspectUsage:k});break}case"count":{await Pt(d,{args:c});break}case"keyboard":{await Bt(d,{json:e.includes("--json")});break}case"screens":{await Jt(d,{json:e.includes("--json")});break}case"memory":{await Lt(d,{args:c});break}case"wait":{await Ut({wsPort:_,commandTimeoutMs:j,simId:I,simIdSource:P,positional:a});break}case"sleep":{await qt({positional:a,inspectUsage:k});break}case"settle":{await Wt({bridge:d,args:e,positional:a});break}case"ready":{await Xt({bridge:d,args:e});break}case"idle":{await Yt({bridge:d,args:e,positional:a});break}case"selector":{await Vt({bridge:d,args:e,positional:a,inspectUsage:k});break}case"event":{await Gt({bridge:d,args:e,positional:a,inspectUsage:k});break}case"layout":{let o=a[1];if(!o){await Ct({bridge:d,args:c});break}let t=await d.send({type:"evaluate",code:`(async () => await window.__sootsimTest.getLayout(${JSON.stringify(o)}))()`});console.log(JSON.stringify(t,null,2));break}case"capture":case"screenshot":{let t=e.find((b,f)=>e[f-1]==="--output")||"/tmp/sootsim-inspect.png",s=await so(e,d),n={type:"screenshot"};s&&(n.crop=s);let u=(await d.send(n)).replace(/^data:image\/png;base64,/,"");s&&console.log(` area: x=${s.x} y=${s.y} w=${s.w} h=${s.h}`),(await import("fs")).writeFileSync(t,Buffer.from(u,"base64")),console.log(` saved: ${t}`);break}case"sample-color":{let o=await so(e,d);o||(console.error(k("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 d.send({type:"evaluate",code:kt(o)});if(e.includes("--json"))console.log(JSON.stringify(t,null,2));else{let{r:s,g:n,b:i,a:u,hex:m,samples:b}=t,f=o.w===1&&o.h===1?`@(${o.x},${o.y})`:`@(${o.x},${o.y}) ${o.w}x${o.h}`;console.log(` ${m} rgba(${s}, ${n}, ${i}, ${u}) ${f} ${b} samples`)}break}case"node":{let o=a[1];o||(console.error(k("node","<matcher>")),console.error(" resolves testID, id, then text \u2014 dumps full node info as JSON"),process.exit(1));let t=await d.send({type:"evaluate",code:`(async () => {
352
- const t = window.__sootsimTest
353
- const q = ${JSON.stringify(o)}
354
- let node = null
355
- let via = null
356
- if (t.findByTestId) { node = await t.findByTestId(q); if (node) via = 'testID' }
357
- if (!node && t.findById) { node = await t.findById(q); if (node) via = 'id' }
358
- if (!node && t.findByText) { node = await t.findByText(q); if (node) via = 'text' }
359
- if (!node) return { matcher: q, found: false }
360
-
361
- // read the resolved transform (if any) off the style \u2014 useful
362
- // because canvas nodes often animate via transform and describe
363
- // output strips that.
364
- const transform =
365
- node.style && Array.isArray(node.style.transform)
366
- ? node.style.transform
367
- : node.style && node.style.transform
368
- ? node.style.transform
369
- : null
370
-
371
- // parent chain \u2014 walk up from the node so the JSON dump is
372
- // self-describing.
373
- const parentChain = []
374
- const root = window.__sootsimRoot
375
- if (root && node.id != null) {
376
- const findPath = (n, targetId, path) => {
377
- if (!n) return null
378
- if (n.id === targetId) return path
379
- if (n.children) {
380
- for (const child of n.children) {
381
- const nextPath = [
382
- ...path,
383
- {
384
- type: n.type || 'view',
385
- testID: n.props?.testID || null,
386
- text: n.text || null,
387
- },
388
- ]
389
- const found = findPath(child, targetId, nextPath)
390
- if (found) return found
391
- }
392
- }
393
- return null
394
- }
395
- const chain = findPath(root, node.id, [])
396
- if (chain) parentChain.push(...chain)
397
- }
398
-
399
- return {
400
- matcher: q,
401
- found: true,
402
- resolvedVia: via,
403
- node,
404
- transform,
405
- parentChain,
406
- }
407
- })()`});console.log(JSON.stringify(t,null,2));break}case"tap":{let o=Number(a[1]),t=Number(a[2]),s=we(e);if(s){let u=await be(d,{agent:me(),textFallback:s.mode==="text"?s.value:void 0,resolve:async()=>{let f=await Me(d,s);return f?{cx:f.x,cy:f.y,match:{id:s.mode==="testid"?s.value:f.id??null,testID:s.mode==="testid"?s.value:f.testID??null,text:s.mode==="text"?s.value:f.text??null,type:f.type??null},target:{id:f.id??null,testID:f.testID??null,text:f.text??null,type:f.type??null}}:null}}),m=u.payload;(!m||typeof m.cx!="number")&&(console.error(` not found: ${s.value}`),s.mode==="testid"&&Y("wait-selector-for-missing-testid",s.value),process.exit(1)),ue(u.result)||(N(`${s.mode} "${s.value}"`,u),process.exit(1));let b=ro(m.cx,m.cy,u.result);b&&await L("inspect tap",b.step,b.summary),console.log(JSON.stringify({...u.attempts>1?{attempts:u.attempts}:{},...u.result},null,2));break}(!Number.isFinite(o)||!Number.isFinite(t))&&(console.error(k("tap","<x> <y> | --testid <id> | --text <t>")),process.exit(1));let n=await _t(d,o,t),i=ro(o,t,n);i&&await L("inspect tap",i.step,i.summary),console.log(JSON.stringify(n,null,2));break}case"drag":case"swipe":{let o=Number(a[1]),t=Number(a[2]),s=Number(a[3]),n=Number(a[4]),i=g==="swipe"?10:12,u=g==="swipe"?8:16,m=a[5]?Number(a[5]):i,b=a[6]?Number(a[6]):u;(!Number.isFinite(o)||!Number.isFinite(t)||!Number.isFinite(s)||!Number.isFinite(n)||!Number.isFinite(m)||!Number.isFinite(b))&&(console.error(k(g,"<x1> <y1> <x2> <y2> [steps] [stepMs]")),process.exit(1));let f=await d.send({type:"evaluate",code:`(async () => {
408
- const interact = window.__sootsimInteract
409
- if (!interact?.drag) return { ok: false, reason: 'no interact.drag' }
410
- const value = await interact.drag(${o}, ${t}, ${s}, ${n}, ${Math.max(1,Math.round(m))}, ${Math.max(0,Math.round(b))})
411
- return { ok: !!value, value }
412
- })()`});if(f?.ok){let w=Math.max(1,Math.round(Math.max(1,m)*Math.max(0,b)));await L(`inspect ${g}`,{swipe:{start:`${o}, ${t}`,end:`${s}, ${n}`,duration:w}},`${g} ${o},${t} -> ${s},${n}`)}console.log(JSON.stringify(f,null,2));break}case"pinch":{let o=Number(a[1]),t=Number(a[2]),s=Number(a[3]),n=Number(a[4]),i=Number(a[5]),u=Number(a[6]),m=Number(a[7]),b=Number(a[8]),f=a[9]?Number(a[9]):12,w=a[10]?Number(a[10]):16;(!Number.isFinite(o)||!Number.isFinite(t)||!Number.isFinite(s)||!Number.isFinite(n)||!Number.isFinite(i)||!Number.isFinite(u)||!Number.isFinite(m)||!Number.isFinite(b)||!Number.isFinite(f)||!Number.isFinite(w))&&(console.error(k("pinch","<x1> <y1> <x2> <y2> <x1'> <y1'> <x2'> <y2'> [steps] [stepMs]")),process.exit(1));let O=await d.send({type:"evaluate",code:`(async () => {
413
- const interact = window.__sootsimInteract
414
- if (!interact?.pinch) return { ok: false, reason: 'no interact.pinch' }
415
- const value = await interact.pinch(${o}, ${t}, ${s}, ${n}, ${i}, ${u}, ${m}, ${b}, ${Math.max(1,Math.round(f))}, ${Math.max(0,Math.round(w))})
416
- return { ok: !!value, value }
417
- })()`});O?.ok&&await L("inspect pinch",{pinch:{from:[o,t,s,n],to:[i,u,m,b],steps:Math.max(1,Math.round(f)),stepMs:Math.max(0,Math.round(w))}},`pinch (${o},${t}) (${s},${n}) -> (${i},${u}) (${m},${b})`),console.log(JSON.stringify(O,null,2));break}case"tap-text":{let o=a[1];o||(console.error(k("tap-text","<text>")),process.exit(1));let t=U=>{let R=e.indexOf(U);return R>=0&&R+1<e.length?e[R+1]:null},s=U=>e.includes(U),n=t("--nth")??t("--index"),i=n!==null?Number(n):null;i!==null&&!Number.isFinite(i)&&(console.error(` --nth/--index requires an integer, got: ${n}`),process.exit(1));let u=t("--within"),m=t("--role"),b=s("--exact"),f=s("--first"),w=t("--min-y"),O=t("--max-y"),q=t("--min-x"),H=t("--max-x");for(let[U,R]of[["--min-y",w],["--max-y",O],["--min-x",q],["--max-x",H]])R!==null&&!Number.isFinite(Number(R))&&(console.error(` ${U} requires a number, got: ${R}`),process.exit(1));let K=e.indexOf("--near"),F=null;if(K>=0){let U=Number(e[K+1]),R=Number(e[K+2]);(!Number.isFinite(U)||!Number.isFinite(R))&&(console.error(" --near requires two numbers: --near <x> <y>"),process.exit(1)),F={x:U,y:R}}let x={exact:b,role:m,within:u,minX:q!==null?Number(q):null,maxX:H!==null?Number(H):null,minY:w!==null?Number(w):null,maxY:O!==null?Number(O):null,near:F,nth:i,first:f},$=await At(d,o,x,{agent:me()}),v=$.payload;if(v?.error==="bridge-not-ready"&&(console.error(" sootsim test bridge not ready"),process.exit(1)),v?.ambiguous){let U=v.candidates;console.error(` ambiguous: ${v.total} matches for "${o}"`);for(let R of U){let xe=R.abs?`@(${Math.round(R.abs.x)},${Math.round(R.abs.y)})`:"",co=R.layout?` ${R.layout.width}x${R.layout.height}`:"",uo=R.testID?` #${R.testID}`:"",mo=R.text?` "${R.text}"`:"",po=R.ancestorTestIDs.length>0?` within ${R.ancestorTestIDs.slice(0,3).map(fo=>`#${fo}`).join(" > ")}`:"";console.error(` [${R.idx}] <${R.type}>${mo}${uo} ${xe}${co}${po}`)}v.total>U.length&&console.error(` ... and ${v.total-U.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)}v?.nthOutOfRange&&(console.error(` not found: nth ${v.nth} of ${v.total} match${v.total===1?"":"es"} for "${o}"`),process.exit(1)),(!v||typeof v.cx!="number")&&(console.error(` not found: ${o}`),process.exit(1)),ue($.result)||(N(`text "${o}"`,$),process.exit(1));let se=Ke(o,{id:v.target?.id??null,testID:v.target?.testID??null,type:v.target?.type??null,cx:v.cx,cy:v.cy},"text");await L("inspect tap-text",se.step,se.summary),console.log(JSON.stringify({matched:v.match,tapped:{nodeId:v.target?.nodeId??null,id:v.target?.id??null,testID:v.target?.testID??null,type:v.target?.type??null,cx:v.cx,cy:v.cy},...v.strategy&&v.strategy!=="matched-node"?{strategy:v.strategy}:{},...v.total>1||i!==null?{nth:{index:v.idx,total:v.total}}:{},...$.attempts>1?{attempts:$.attempts}:{},result:$.result},null,2));break}case"tap-best":{let o=a[1];o||(console.error(k("tap-best","<query>")),process.exit(1));let t=await Et(d,o,{agent:me()}),s=t.payload;s||(console.error(` tap-best: no testID or visible text matched "${o}". try \`sootsim find --interactive-targets\` to list candidates.`),process.exit(1)),"error"in s&&(console.error(` ${s.error}`),process.exit(1)),s.strategy==="none"&&(console.error(` tap-best: no testID or visible text matched "${o}". try \`sootsim find --interactive-targets\` to list candidates.`),process.exit(1));let n=s.node;ue(t.result)||(N(`best "${o}"`,t),process.exit(1));let i=Ke(o,{id:n.id,testID:n.testID,type:n.type,cx:s.cx,cy:s.cy},s.strategy==="testid"?"id":"text");await L("inspect tap-best",i.step,i.summary),console.log(JSON.stringify({matched:{strategy:s.strategy,nodeId:n.nodeId,id:n.id,testID:n.testID,type:n.type,text:n.text},tapped:{cx:s.cx,cy:s.cy},...t.attempts>1?{attempts:t.attempts}:{},result:t.result},null,2));break}case"tap-id":{let o=a[1];o||(console.error(k("tap-id","<id>")),process.exit(1));let t=await Ot(d,o,{agent:me()}),s=t.payload;(!s||typeof s.cx!="number")&&(console.error(` not found: ${o}`),await Bo(d,o),process.exit(1)),ue(t.result)||(N(`id "${o}"`,t),process.exit(1));let n=Ke(o,{id:s.target?.id??null,testID:s.target?.testID??null,type:s.target?.type??null,cx:s.cx,cy:s.cy},"id");await L("inspect tap-id",n.step,n.summary),console.log(JSON.stringify({matched:s.match,tapped:{nodeId:s.target?.nodeId??null,id:s.target?.id??null,testID:s.target?.testID??null,type:s.target?.type??null,cx:s.cx,cy:s.cy},...s.strategy&&s.strategy!=="matched-node"?{strategy:s.strategy}:{},...t.attempts>1?{attempts:t.attempts}:{},result:t.result},null,2));break}case"type-into":{let o=a[1],t=a.slice(2).join(" ");(!o||!t)&&(console.error(k("type-into","<id> <text>")),process.exit(1));let s=JSON.stringify(o),n=await d.send({type:"evaluate",code:`(async () => {
418
- const t = window.__sootsimTest
419
- if (!t) return null
420
- const n = await (t.findByTestId(${s}) || t.findById(${s}))
421
- if (!n || !n.absolutePosition || !n.layout) return null
422
- return {
423
- cx: n.absolutePosition.x + (n.layout.width || 0) / 2,
424
- cy: n.absolutePosition.y + (n.layout.height || 0) / 2,
425
- id: n.id,
426
- testID: n.testID,
427
- type: n.type,
428
- isTextInput: !!n.isTextInput,
429
- secureTextEntry: !!n.secureTextEntry,
430
- placeholder: n.placeholder || null,
431
- }
432
- })()`});(!n||typeof n.cx!="number")&&(console.error(` not found: ${o}`),process.exit(1)),n.isTextInput||console.error(` warning: ${o} is not a text input (isTextInput: false)`);let i=await d.send({type:"tap",x:n.cx,y:n.cy,target:{id:n.id??o,testID:n.testID??o,text:null,type:n.type??null}}),u=n.secureTextEntry===!0,m=await zo(d,1e3,{targetId:o,secureTextEntry:u});m.visible||(console.error(` keyboard did not open after tapping ${o}`),process.exit(1));let b=m.focusedInput;b&&(b.testID===o||b.id===o||(console.error(` focus routing mismatch after tap: requested ${JSON.stringify(o)} but focus is on ${JSON.stringify(b.testID??b.id??null)}. did the tap land on an outer Pressable wrapper?`),process.exit(1))),await d.send({type:"keyboard",action:"type",text:t});let f=u||he(m),w=f?X:We(t,m),O=f&&m.focusedInput?{...m.focusedInput,secureTextEntry:!0,text:typeof m.focusedInput.text=="string"?X:m.focusedInput.text}:m.focusedInput??null;await L("inspect type-into",{tapOn:{id:o},inputText:w},w===X?`type-into #${o} ${X}`:`type-into #${o} ${JSON.stringify(t)}`),console.log(JSON.stringify({target:o,isTextInput:n.isTextInput,secureTextEntry:f,keyboardOpened:m.visible??i?.keyboardOpened??!1,focusedInput:O,typed:w},null,2));break}case"type":{let o=a.slice(1).join(" ");o||(console.error(k("type","<text>")),process.exit(1));let t=await Fe(d,"type");await d.send({type:"keyboard",action:"type",text:o});let s=We(o,t);await L("inspect type",{inputText:s},s===X?`type ${X}`:`type ${JSON.stringify(o)}`),console.log(s===X?` typed: ${X}`:` typed: ${JSON.stringify(o)}`);break}case"key":{let o=a[1];o||(console.error(k("key","<name>")),process.exit(1)),await Fe(d,"key"),await d.send({type:"keyboard",action:"press",text:o}),await L("inspect key",{pressKey:o},`key ${o}`),console.log(` pressed: ${o}`);break}case"key-sequence":{let o=a.slice(1);o.length===0&&(console.error(k("key-sequence","<key> [<key> ...]")),process.exit(1)),await Fe(d,"key-sequence");for(let t of o)await d.send({type:"keyboard",action:"press",text:t});await L("inspect key-sequence",{pressKey:o.join(" ")},`key-sequence ${o.join(" ")}`),console.log(` pressed: ${o.join(", ")}`);break}case"keycode":{let o=a.slice(1);o.length===0&&(console.error(k("keycode","<code> [<code> ...]")),process.exit(1));let t=o.map(n=>({code:n,key:Go(n)})),s=t.filter(n=>!n.key);s.length>0&&(console.error(` unsupported keycode(s): ${s.map(n=>n.code).join(", ")}`),process.exit(1)),await Fe(d,"keycode");for(let n of t)await d.send({type:"keyboard",action:"press",text:n.key});await L("inspect keycode",{pressKey:t.map(n=>n.key).join(" ")},`keycode ${o.join(" ")}`),console.log(` pressed: ${o.join(", ")}`);break}case"dispatch":{let o=a[1];o||(console.error(k("dispatch","<char>")),process.exit(1)),await d.send({type:"keyboard",action:"dispatchKey",text:o}),await L("inspect dispatch",{dispatchKey:o},`dispatch ${JSON.stringify(o)}`),console.log(` dispatched: ${o}`);break}case"dismiss":{await d.send({type:"keyboard",action:"dismiss"}),await L("inspect dismiss",{hideKeyboard:!0},"dismiss keyboard"),console.log(" keyboard dismissed");break}case"double-tap":{let o=Number(a[1]),t=Number(a[2]),s=we(e);if(s){let m=await Me(d,s);m||(console.error(` not found: ${s.value}`),s.mode==="testid"&&Y("wait-selector-for-missing-testid",s.value),process.exit(1)),o=m.x,t=m.y}let n=a[3]?Number(a[3]):80;(!Number.isFinite(o)||!Number.isFinite(t)||!Number.isFinite(n))&&(console.error(k("double-tap","<x> <y> [gapMs] | --testid <id>")),process.exit(1));let i=Math.max(0,Math.round(n)),u=await d.send({type:"evaluate",code:`(async () => {
433
- const interact = window.__sootsimInteract
434
- if (interact?.doubleTap) {
435
- return {
436
- ok: !!(await interact.doubleTap(${o}, ${t}, ${i})),
437
- gapMs: ${i},
438
- }
439
- }
440
- if (!interact?.tap) {
441
- return { ok: false, reason: 'no interact.tap', gapMs: ${i} }
442
- }
443
- const first = await interact.tap(${o}, ${t})
444
- if (!first || first.hit === false) {
445
- return { ok: false, reason: 'first tap missed', gapMs: ${i}, first }
446
- }
447
- await new Promise((resolve) => setTimeout(resolve, ${i}))
448
- const second = await interact.tap(${o}, ${t})
449
- return {
450
- ok: !!second && second.hit !== false,
451
- gapMs: ${i},
452
- first,
453
- second,
454
- }
455
- })()`});u?.ok&&await L("inspect double-tap",{doubleTapAtCoords:{x:o,y:t,gapMs:i}},`double-tap @${o},${t}`),console.log(JSON.stringify(u,null,2));break}case"long-press":{let o=Number(a[1]),t=Number(a[2]),s=we(e),n=null;if(s){let f=await Me(d,s);f||(console.error(` not found: ${s.value}`),s.mode==="testid"&&Y("wait-selector-for-missing-testid",s.value),process.exit(1)),o=f.x,t=f.y,n={id:f.id??null,testID:f.testID??null,text:f.text??null,type:f.type??null}}let i=s?a[1]:a[3],u=i?Number(i):600;(!Number.isFinite(o)||!Number.isFinite(t)||!Number.isFinite(u))&&(console.error(k("long-press","<x> <y> [durationMs] | --testid <id>")),process.exit(1));let m=Math.max(0,Math.round(u)),b=await d.send({type:"longPress",x:o,y:t,durationMs:m,target:n});b?.ok&&await L("inspect long-press",{tapAtCoords:{x:o,y:t}},`long-press @${o},${t}`),console.log(JSON.stringify(b,null,2));break}case"touch":{let o=a[1],t=Number(a[2]),s=Number(a[3]),n=a[4]?Number(a[4]):999,i=o==="down"?"touchDown":o==="move"?"touchMove":o==="up"?"touchUp":o==="cancel"?"touchCancel":null;i||(console.error(k("touch","<down|move|up|cancel> <x> <y> [pointerId]")),process.exit(1)),o!=="cancel"&&(!Number.isFinite(t)||!Number.isFinite(s))&&(console.error(k("touch","<down|move|up|cancel> <x> <y> [pointerId]")),process.exit(1));let u=o==="down"?"tap":o==="move"?"move":null,m=u&&Number.isFinite(t)&&Number.isFinite(s)?`window.dispatchEvent(new CustomEvent('sootsim:agentAction', { detail: { type: '${u}', x: ${t}, y: ${s} } }));`:"",b=await d.send({type:"evaluate",code:`(async () => {
456
- ${m}
457
- const interact = window.__sootsimInteract
458
- if (!interact?.${i}) return { ok: false, reason: 'no interact.${i}' }
459
- const value = ${o==="cancel"?`await interact.${i}(${Math.max(1,Math.round(n))})`:`await interact.${i}(${t}, ${s}, ${Math.max(1,Math.round(n))})`}
460
- return { ok: !!value, value }
461
- })()`});b?.ok&&o!=="cancel"&&await L("inspect touch",{tapAtCoords:{x:t,y:s}},`touch ${o} @${t},${s}`),console.log(JSON.stringify(b,null,2));break}case"gesture":{let o=["scroll-up","scroll-down","scroll-left","scroll-right","swipe-from-left-edge","swipe-from-right-edge","swipe-from-top-edge","swipe-from-bottom-edge"],t=a[1],s=a[2]?Number(a[2]):220;(!t||!Number.isFinite(s))&&(console.error(k("gesture","<preset> [durationMs]")),console.error(` presets: ${o.join(", ")}`),process.exit(1)),o.includes(t)||(console.error(` unknown gesture preset: ${t}`),console.error(` presets: ${o.join(", ")}`),process.exit(1));let n=await d.send({type:"evaluate",code:`(async () => {
462
- const spec = globalThis.__sootsimDeviceSpec || {}
463
- return {
464
- width: spec.width || window.innerWidth || 393,
465
- height: spec.height || window.innerHeight || 852,
466
- statusBarHeight: spec.statusBarHeight || 0,
467
- homeIndicatorHeight: spec.homeIndicatorHeight || 0,
468
- }
469
- })()`}),i=Number(n?.width)||393,u=Number(n?.height)||852,m=Number(n?.statusBarHeight)||0,b=Number(n?.homeIndicatorHeight)||0,f=Math.round(i/2),w=Math.round(u/2),O=Math.max(24,m+18),q=Math.max(24,b+18),H=18,K=Math.min(220,Math.round(u*.24)),F=Math.min(180,Math.round(i*.32)),x=f,$=w,v=f,se=w;switch(t){case"scroll-up":$=w+Math.round(K/2),se=w-Math.round(K/2);break;case"scroll-down":$=w-Math.round(K/2),se=w+Math.round(K/2);break;case"scroll-left":x=f+Math.round(F/2),v=f-Math.round(F/2);break;case"scroll-right":x=f-Math.round(F/2),v=f+Math.round(F/2);break;case"swipe-from-left-edge":x=H,$=w,v=Math.min(i-H,H+F);break;case"swipe-from-right-edge":x=i-H,$=w,v=Math.max(H,i-H-F);break;case"swipe-from-top-edge":x=f,$=O,se=Math.min(u-q,O+K);break;case"swipe-from-bottom-edge":x=f,$=u-q,se=Math.max(O,u-q-K);break}let U=Math.max(8,Math.round(s/16)),R=Math.max(1,Math.round(s/U)),xe=await d.send({type:"evaluate",code:`(async () => {
470
- const interact = window.__sootsimInteract
471
- if (!interact?.drag) return { ok: false, reason: 'no interact.drag' }
472
- const value = await interact.drag(${x}, ${$}, ${v}, ${se}, ${U}, ${R})
473
- return { ok: !!value, value }
474
- })()`});xe?.ok&&await L("inspect gesture",{swipe:{start:`${x}, ${$}`,end:`${v}, ${se}`,duration:Math.max(1,Math.round(s))}},`gesture ${t}`),console.log(JSON.stringify({preset:t,from:{x,y:$},to:{x:v,y:se},result:xe},null,2));break}case"scroll":{let o=we(e),t=Xo(e),s=o?.mode==="testid"?o.value:t==null?a[1]:null,n=o||t!=null?1:2,i=Number(a[n]),u=Number(a[n+1]);(!s&&t==null||!Number.isFinite(i)||!Number.isFinite(u))&&(console.error(k("scroll","<id> <x> <y> | --testid <id> <x> <y> | --node-id <nodeId> <x> <y>")),process.exit(1));let m=await d.send({type:"evaluate",code:`(async () => {
475
- const t = window.__sootsimTest
476
- if (!t) return null
477
- const n = ${t!=null?`await t.inspectByNodeId(${JSON.stringify(t)})`:`await t.findByTestId(${JSON.stringify(s)})
478
- || await t.findById(${JSON.stringify(s)})`}
479
- if (!n || !n.absolutePosition || !n.layout) return null
480
- return {
481
- cx: n.absolutePosition.x + (n.layout.width || 0) / 2,
482
- cy: n.absolutePosition.y + (n.layout.height || 0) / 2,
483
- }
484
- })()`}),b=await G(d,"scrollTo",t!=null?{nodeId:t}:s,i,u,!1);if(b?.ok){let f=t!=null?`node ${t}`:`#${s}`;await L("inspect scroll",{scrollTo:{...t!=null?{nodeId:t}:{id:s},x:i,y:u}},`scroll ${f} -> ${i},${u}`)}console.log(JSON.stringify({...b,...m?{at:{x:m.cx,y:m.cy}}:{}},null,2));break}case"state":{let o=a[1];if(r==="get"&&!o){let s=await G(d,"getRuntimeState"),n=await d.send({type:"evaluate",code:Se});if(s&&typeof s=="object"&&s.diagnostics&&(s.diagnostics.errors=n?.errors??0,s.diagnostics.warnings=n?.warnings??0),s&&typeof s=="object"&&s.shell==null)try{let i=await ce(d);i&&(s.shell=i)}catch{}console.log(JSON.stringify(s,null,2));break}if(!o||o==="--help"||o==="-h"){console.log(`
485
- ${h("state")} \u2014 dump raw runtime state
486
-
487
- subcommands:
488
- shell dump shell transition/layout state
489
- worker dump render-worker host/animation state
490
- keyboard dump keyboard visibility, mode, and focused input
491
- ownership dump surface ownership + pointerOnSurface delivery stats
492
- node <id> dump raw node info by id or testID
493
- scroll <id> dump scroll metrics and runtime state
494
- scroll-hit <x> <y> dump the nearest scroll ancestor at coordinates
495
- hit <x> <y> dump the hit-test ancestry at coordinates
496
- gesture <x> <y> dump gesture routing/debug info at coordinates
497
-
498
- examples:
499
- ${h("state")} shell
500
- ${h("state")} worker
501
- ${h("state")} keyboard
502
- ${h("state")} ownership
503
- ${h("state")} node photos
504
- ${h("state")} scroll feed
505
- ${h("state")} scroll-hit 360 420
506
- ${h("state")} hit 200 720
507
- `);break}let t;switch(o){case"shell":t=await ce(d,500);break;case"worker":t=await Ce(d,"__sootsimRenderHost.queryStats");break;case"ownership":t=await d.send({type:"evaluate",code:`(() => {
508
- const h = window.__sootsimRenderHost
509
- if (!h || typeof h.getOwnershipSnapshot !== 'function') {
510
- return { error: 'getOwnershipSnapshot not available' }
511
- }
512
- return h.getOwnershipSnapshot()
513
- })()`});break;case"keyboard":t=await d.send({type:"evaluate",code:`(async () => {
514
- const kb = window.__sootsimKeyboard
515
- const test = window.__sootsimTest
516
- if (!kb) return { error: 'keyboard bridge not available' }
517
- const layout = typeof kb.getLayout === 'function' ? kb.getLayout() : null
518
- const secureTextEntry =
519
- !!layout?.spec?.secureTextEntry && layout?.spec?.keyboardType !== 'visible-password'
520
- const visible = kb.isVisible()
521
- const mode = kb.getMode()
522
- let focused = null
523
- if (test && typeof test.getFocusedNode === 'function') {
524
- try {
525
- focused = await test.getFocusedNode()
526
- } catch {}
527
- }
528
- return {
529
- visible,
530
- mode,
531
- layout,
532
- focusedInput: focused ? {
533
- nodeId: focused.nodeId ?? null,
534
- testID: focused.testID || null,
535
- id: focused.id || null,
536
- placeholder: focused.placeholder || null,
537
- secureTextEntry,
538
- text: secureTextEntry ? ${JSON.stringify(X)} : (focused.text || null),
539
- } : null,
540
- }
541
- })()`}),t=qe(t);break;case"node":{let s=a[2];s||(console.error(` usage: ${h("state")} node <id>`),process.exit(1)),t=await G(d,"findByTestId",s)||await G(d,"findById",s);break}case"scroll":{let s=a[2];s||(console.error(` usage: ${h("state")} scroll <id>`),process.exit(1)),t=await G(d,"getScrollState",s);break}case"scroll-hit":{let s=Number(a[2]),n=Number(a[3]);(!Number.isFinite(s)||!Number.isFinite(n))&&(console.error(` usage: ${h("state")} scroll-hit <x> <y>`),process.exit(1)),t=await G(d,"getScrollStateAt",s,n);break}case"hit":{let s=Number(a[2]),n=Number(a[3]);(!Number.isFinite(s)||!Number.isFinite(n))&&(console.error(` usage: ${h("state")} hit <x> <y>`),process.exit(1)),t=await G(d,"debugHitAt",s,n);break}case"gesture":{let s=Number(a[2]),n=Number(a[3]);(!Number.isFinite(s)||!Number.isFinite(n))&&(console.error(` usage: ${h("state")} gesture <x> <y>`),process.exit(1)),t=await G(d,"debugGestureAt",s,n);break}default:console.error(` unknown state subcommand: ${o}`),process.exit(1)}console.log(JSON.stringify(t,null,2));break}case"shell":{let o=a[1];if(!o||o==="--help"||o==="-h"){console.log(`
542
- ${h("shell")} \u2014 run built-in shell commands
543
-
544
- subcommands:
545
- launch <appId> [waitMs] [--clear-state]
546
- launch app and wait for settled shell state
547
- home [waitMs] go home and wait for settled shell state
548
- switcher [waitMs] open switcher and wait for settled shell state
549
- open-card <appId> [waitMs]
550
- open a specific switcher card and wait for app settle
551
- appearance <light|dark|auto|toggle>
552
- update simulator appearance
553
- lock toggle device lock state
554
- shake trigger the simulator shake gesture
555
-
556
- examples:
557
- ${h("shell")} launch photos
558
- ${h("shell")} launch rn --clear-state
559
- ${h("shell")} launch photos 1500
560
- ${h("shell")} home 500
561
- ${h("shell")} switcher 800
562
- ${h("shell")} open-card clock 800
563
- ${h("shell")} appearance dark
564
- ${h("shell")} lock
565
- `);break}let t=o==="launch"||o==="open-card"||o==="home"||o==="switcher",s=o==="launch"||o==="open-card"?a[3]:a[2],n=s?Number(s):350;t&&(!Number.isFinite(n)||n<0)&&(console.error(k("shell",o==="launch"||o==="open-card"?"<launch|open-card> <appId> [settleMs]":"<home|switcher> [settleMs]")),process.exit(1));let i=!1,u=!1,m=null,b=e.includes("--clear-state");if(o==="launch"){let f=a[2];f||(console.error(k("shell","launch <appId> [settleMs] [--clear-state]")),process.exit(1)),b&&await d.send({type:"evaluate",code:Ae(!0)}),i=!!await ge(d,"launchApp",n,f),{settled:u,state:m}=await Ne(d,Math.round(n),w=>!!w&&w.state==="app"&&w.activeApp===f&&w.showSwitcher===!1&&w.switcherPhase==="idle"&&typeof w.launchProgress=="number"&&w.launchProgress>=.98),i&&await L("inspect shell launch",b?{launchApp:{clearState:!0}}:{launchApp:{}},b?"launch app (clear state)":"launch app")}else if(o==="home")i=!!await ge(d,"goHome",n),{settled:u,state:m}=await Ne(d,Math.round(n),f=>!!f&&f.state==="home"&&f.activeApp==null&&f.showSwitcher===!1&&f.switcherPhase==="idle"&&typeof f.launchProgress=="number"&&f.launchProgress>=.98);else if(o==="switcher")i=!!await ge(d,"openSwitcher",n),{settled:u,state:m}=await Ne(d,Math.round(n),f=>!!f&&f.state==="app"&&f.showSwitcher===!0&&f.switcherPhase==="idle"&&typeof f.zoomLevel=="number"&&Math.abs(f.zoomLevel)<=.02&&typeof f.horizontalZoom=="number"&&Math.abs(f.horizontalZoom)<=.02),u&&(await ee(Ko),m=await ce(d));else if(o==="open-card"){let f=a[2];f||(console.error(k("shell","open-card <appId> [settleMs]")),process.exit(1)),i=!!await ge(d,"openSwitcherCard",n,f),{settled:u,state:m}=await Ne(d,Math.round(n),w=>!!w&&w.state==="app"&&w.activeApp===f&&w.showSwitcher===!1&&w.switcherPhase==="idle"&&typeof w.zoomLevel=="number"&&w.zoomLevel>=.98&&typeof w.horizontalZoom=="number"&&w.horizontalZoom>=.98),i&&await L("inspect shell open-card",{openSwitcherCard:{appId:f}},`open switcher card ${f}`)}else if(o==="appearance"){let f=a[2];(!f||!["light","dark","auto","toggle"].includes(f))&&(console.error(k("shell","appearance <light|dark|auto|toggle>")),process.exit(1));let w=await no(d,"appearance",f);if(i=!!w?.ok,m={appearance:w},i){let O=w?.applied??f;console.log(` appearance: ${O}`)}}else if(o==="lock"||o==="shake"){let f=await no(d,o);i=!!f?.ok,m={[o]:f}}else console.error(` unknown shell subcommand: ${o}`),process.exit(1);console.log(JSON.stringify({ok:i,settled:u,state:m},null,2));break}case"url":{await Kt(d,{args:c});break}case"reload":{let s=!1,n=!1;try{await d.send({type:"evaluate",code:"window.__sootsimConsole?.clear()"});let m=await d.send({type:"evaluate",code:`;(() => {
566
- const reloadExternalApp = window.SootSim?.bridges?.hotRemount?.reloadExternalApp
567
- if (typeof reloadExternalApp === 'function') {
568
- reloadExternalApp()
569
- return { kind: 'external-app' }
570
- }
571
- window.location.reload()
572
- return { kind: 'page' }
573
- })()`});n=!!m&&m.kind==="external-app",s=!0}catch{}console.log(" reloading...");let i=d,u=null;if(n)u=await ve(d,{timeoutMs:1e4,errorGraceMs:3e3});else{s&&await ee(300);let m=await $t(_,j,I,{timeoutMs:1e4,simIdSource:P});m?(i=m,u=await ve(m,{timeoutMs:1e4,errorGraceMs:3e3})):(console.log(" \u26A0 reload: bridge never reconnected within 10000ms"),i=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 if(u.source==="error-bail")console.log(` \u26A0 reload bailed after ${u.elapsedMs}ms: ${u.errors} console error(s), ready signal never fired`);else{let m=Zt(u);console.log(` \u26A0 reload timed out after ${u.elapsedMs}ms \u2014 ${m} (nodes: ${u.nodes}, targets: ${u.targets}, errors: ${u.errors})`)}if(i)try{let m=await fe(i,10);if(i!==d&&i.close(),Array.isArray(m)&&m.length>0){console.log(`
574
- \u26A0 ${m.length} error(s) during mount:
575
- `);for(let b of m){let f=b.args.map(w=>typeof w=="object"?JSON.stringify(w):w).join(" ");if(console.log(` ${f}`),b.stack){let w=b.stack.split(`
576
- `).slice(0,2);for(let O of w)console.log(` ${O.trim()}`)}}}}catch{}u&&!u.ready&&(process.exitCode=1);break}case"storage-clear":{if(await De(d),await d.send({type:"evaluate",code:Ae(!0)})!==!0){A(c)?E({cleared:!1,ready:!1,error:"external app reload bridge is unavailable"}):console.error(" storage clear failed: external app reload bridge is unavailable"),process.exitCode=1;break}let n=await ve(d,{timeoutMs:1e4,errorGraceMs:3e3});if(A(c)){E({cleared:!0,ready:n.ready,reload:n}),n.ready||(process.exitCode=1);break}if(n.ready){let i=n.source==="nodes-fallback"?" (no ready signal, node-count fallback)":"";console.log(` cleared tenant storage; ready in ${n.elapsedMs}ms: ${n.nodes} nodes${i}`)}else if(n.source==="error-bail")console.log(` \u26A0 storage clear reloaded but bailed after ${n.elapsedMs}ms: ${n.errors} console error(s), ready signal never fired`);else{let i=Zt(n);console.log(` \u26A0 storage clear reloaded but timed out after ${n.elapsedMs}ms \u2014 ${i} (nodes: ${n.nodes}, targets: ${n.targets}, errors: ${n.errors})`)}try{let i=await fe(d,10);if(Array.isArray(i)&&i.length>0){console.log(`
577
- \u26A0 ${i.length} error(s) during mount:
578
- `);for(let u of i){let m=u.args.map(b=>typeof b=="object"?JSON.stringify(b):b).join(" ");if(console.log(` ${m}`),u.stack){let b=u.stack.split(`
579
- `).slice(0,2);for(let f of b)console.log(` ${f.trim()}`)}}}}catch{}n.ready||(process.exitCode=1);break}case"eval":case"js":{let o=a.slice(1).join(" ");o||(console.error(k("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(` ${h("js")} SootSim.bridges.test.findByText("Sign in")`),console.error(` ${h("js")} SootSim.bridges.debug.snapshot("before")`),console.error(` ${h("js")} SootSim.bridges.keyboard.type("hello")`),console.error(` ${h("js")} SootSim.state.root.children.length`),process.exit(1));let t=o;t.startsWith("(async")||(t=`(async () => ${t})()`);let s=await d.send({type:"evaluate",code:t});console.log(JSON.stringify(s,null,2));let n=o.toLowerCase(),i=[];(n.includes("sootsim:gohome")||n.includes("gohome"))&&i.push("sootsim shell home"),(n.includes("sootsim:appswitcher")||n.includes("appswitcher"))&&i.push("sootsim shell switcher"),(n.includes("keyboard.isvisible")||n.includes("keyboard.getmode"))&&i.push("sootsim debug state keyboard"),n.includes("interact.tap")&&i.push("sootsim do tap <x> <y>"),n.includes("keyboard.type")&&i.push("sootsim do type <text>"),(n.includes("keyboard.press")||n.includes("keyboard.dispatchkey"))&&i.push("sootsim do key <name>"),n.includes("keyboard.dismiss")&&i.push("sootsim do dismiss"),n.includes("dumptree")&&i.push("sootsim get tree"),n.includes("dumpaccessibilitytree")&&i.push("sootsim get a11y"),n.includes("getnodecount")&&i.push("sootsim get count"),n.includes("findbytext")&&i.push("sootsim find <text>"),(n.includes("findbytestid")||n.includes("findbyid"))&&i.push("sootsim find --testid <id>"),n.includes("document.hidden")&&i.push("sootsim debug state keyboard (includes tab health)"),i.length>0&&Y("prefer-cli-over-eval",i);break}case"globals":{let o=await d.send({type:"evaluate",code:`(async () => {
580
- const globals = {}
581
-
582
- // test bridge (proxy in worker mode)
583
- const testMethods = [
584
- 'findById', 'findByTestId', 'findByText', 'findByLabel', 'findByRole',
585
- 'findAllByRole', 'findByA11yState', 'findAllByA11yState', 'findByHint',
586
- 'findPressable', 'getStyle', 'getLayout', 'getAbsolutePosition', 'isVisible',
587
- 'queryAll', 'dumpTree', 'dumpAccessibilityTree', 'getNodeCount', 'getShellState',
588
- 'getScrollState', 'getScrollStateAt', 'scrollTo', 'waitForTree',
589
- 'waitForScreenTransitions', 'debugByText', 'debugByTestId', 'debugHitAt',
590
- 'debugGestureAt'
591
- ]
592
- globals['test (\u2192 __sootsimTest)'] = testMethods
593
-
594
- // debug
595
- if (window.__sootsimDebug) {
596
- globals['debug (\u2192 __sootsimDebug)'] = Object.keys(window.__sootsimDebug)
597
- }
598
-
599
- // interact
600
- if (window.__sootsimInteract) {
601
- globals['interact (\u2192 __sootsimInteract)'] = Object.keys(window.__sootsimInteract)
602
- }
603
-
604
- // keyboard
605
- if (window.__sootsimKeyboard) {
606
- globals['keyboard (\u2192 __sootsimKeyboard)'] = Object.keys(window.__sootsimKeyboard)
607
- }
608
-
609
- // other globals
610
- globals['other'] = [
611
- 'root (\u2192 __sootsimRoot) - live node tree',
612
- 'render() (\u2192 __sootsimForceRender) - force re-render'
613
- ]
614
-
615
- return globals
616
- })()`});console.log(` sootsim JS API:
617
- `);for(let[t,s]of Object.entries(o)){console.log(` ${t}:`);for(let n of s)console.log(` .${n}`);console.log("")}console.log(` use: ${h("js")} <expression>`),console.log(` example: ${h("js")} test.findByText("Sign in")`);break}case"describe":{await Rt({bridge:d,args:e,positional:a});break}case"perf":{let o=a[1];if(!o||o==="--help"||o==="-h"){console.log(`
618
- ${h("perf")} \u2014 performance profiling
619
-
620
- subcommands:
621
- stats one-shot stats (zero overhead query)
622
- start begin recording frame times (also clears prior frames)
623
- stop stop recording and report results
624
- frames [n] get last N frame times (default: 50)
625
- worst [n] get the N slowest frames (default: 20)
626
- transition <e> profile a shell transition (goHome, appSwitcher, lockScreen)
627
-
628
- the read subcommands (stats, stop, frames, worst, transition) accept
629
- --json for a machine-readable payload on stdout.
630
-
631
- examples:
632
- ${h("perf")} stats
633
- ${h("perf")} start
634
- # ... interact with the app ...
635
- ${h("perf")} stop
636
- ${h("perf")} stop --json
637
- ${h("perf")} transition goHome
638
- `);break}switch(o){case"stats":{let t=await d.send({type:"evaluate",code:`(async () => {
639
- // worker mode (host exposes these)
640
- if (window.__sootsimPerfStats) {
641
- return await window.__sootsimPerfStats()
642
- }
643
- // main-thread mode
644
- const perf = window.__sootsimPerf
645
- const a11y = window.__sootsimA11y
646
- if (!perf && !a11y) return { error: 'no perf globals available' }
647
-
648
- const frameStats = perf?.getFrameStats?.() || {}
649
- const profile = a11y?.profile?.() || {}
650
- const nodeCount = await window.__sootsimTest?.getNodeCount?.() || 0
651
-
652
- return {
653
- frames: frameStats.frames || profile.syncCount || 0,
654
- avgMs: frameStats.totalMs && frameStats.frames
655
- ? (frameStats.totalMs / frameStats.frames).toFixed(2)
656
- : profile.avgSyncMs?.toFixed(2) || '?',
657
- maxMs: frameStats.maxMs?.toFixed(2) || profile.maxSyncMs?.toFixed(2) || '?',
658
- layoutMs: frameStats.layoutTotalMs?.toFixed(2) || '?',
659
- nodeCount,
660
- jankFrames: frameStats.recentFrames?.filter(f => f > 16.67).length || 0,
661
- recentCount: frameStats.recentFrames?.length || 0,
662
- }
663
- })()`});if(t.error&&(console.error(` error: ${t.error}`),process.exit(1)),A(c)){E(t);break}let s=t.avgMs==="?"&&t.maxMs==="?",n=t.avgMs!=="?"?(1e3/parseFloat(t.avgMs)).toFixed(1):"?";console.log(" perf stats:"),console.log(` frames: ${t.frames}`),console.log(` avg: ${t.avgMs}ms (${n} 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`),s&&process.stderr.write(`
664
- \u26A0 frame timings are zero \u2014 enable them first:
665
- sootsim debug perf start # begin recording
666
- # \u2026 interact / run a flow / wait for the transition \u2026
667
- sootsim debug perf stop # full report
668
- sootsim debug perf stats # one-shot snapshot
669
- `);break}case"start":{await d.send({type:"evaluate",code:`(async () => {
670
- // worker mode
671
- if (window.__sootsimPerfStart && window.__sootsimRenderHost) {
672
- const result = window.__sootsimPerfStart()
673
- window.${ne} = {
674
- active: true,
675
- mode: 'render-worker',
676
- lastSamples: [],
677
- startedAt: performance.now(),
678
- }
679
- return result
680
- }
681
- // main-thread mode
682
- window.__sootsimPerf?.enableFrameStats?.(true)
683
- window.__sootsimA11y?.resetProfile?.()
684
- window.${ne} = {
685
- active: true,
686
- mode: 'main-thread',
687
- lastSamples: [],
688
- startedAt: performance.now(),
689
- }
690
- return { started: true }
691
- })()`}),console.log(` profiling started \u2014 interact with the app, then run '${h("perf")} stop'`);break}case"stop":{let t=await d.send({type:"evaluate",code:`(async () => {
692
- // worker mode
693
- if (window.__sootsimRenderHost) {
694
- const session = window.${ne} || {}
695
- const priorSamples = session.active && Array.isArray(session.lastSamples)
696
- ? session.lastSamples
697
- : []
698
- const flushedSamples = await window.__sootsimRenderHost.flushSamples()
699
- const samples = priorSamples.concat(flushedSamples)
700
- window.${ne} = {
701
- ...session,
702
- active: false,
703
- mode: 'render-worker',
704
- lastSamples: samples,
705
- stoppedAt: performance.now(),
706
- }
707
-
708
- const frameTimes = samples.map(sample => sample[1])
709
- const layoutTimes = samples.map(sample => sample[2])
710
- const renderTimes = samples.map(sample => sample[3])
711
- const copyTimes = samples.map(sample => sample[4])
712
- const auxTimes = samples.map(sample => sample[5] || 0)
713
- const otherTimes = samples.map(sample => sample[6] || 0)
714
- const sorted = [...frameTimes].sort((a, b) => a - b)
715
- const total = frameTimes.reduce((a, b) => a + b, 0)
716
- const avg = frameTimes.length > 0 ? total / frameTimes.length : 0
717
- const max = Math.max(...frameTimes, 0)
718
- const layoutTotal = layoutTimes.reduce((a, b) => a + b, 0)
719
- const renderTotal = renderTimes.reduce((a, b) => a + b, 0)
720
- const copyTotal = copyTimes.reduce((a, b) => a + b, 0)
721
- const auxTotal = auxTimes.reduce((a, b) => a + b, 0)
722
- const otherTotal = otherTimes.reduce((a, b) => a + b, 0)
723
-
724
- return {
725
- mode: 'render-worker',
726
- frames: frameTimes.length,
727
- totalMs: total,
728
- avgMs: avg,
729
- maxMs: max,
730
- layoutTotalMs: layoutTotal,
731
- renderTotalMs: renderTotal,
732
- copyTotalMs: copyTotal,
733
- auxTotalMs: auxTotal,
734
- otherTotalMs: otherTotal,
735
- layoutAvgMs: frameTimes.length > 0 ? layoutTotal / frameTimes.length : 0,
736
- renderAvgMs: frameTimes.length > 0 ? renderTotal / frameTimes.length : 0,
737
- copyAvgMs: frameTimes.length > 0 ? copyTotal / frameTimes.length : 0,
738
- auxAvgMs: frameTimes.length > 0 ? auxTotal / frameTimes.length : 0,
739
- otherAvgMs: frameTimes.length > 0 ? otherTotal / frameTimes.length : 0,
740
- p50: sorted[Math.floor(sorted.length * 0.5)] || 0,
741
- p95: sorted[Math.floor(sorted.length * 0.95)] || 0,
742
- p99: sorted[Math.floor(sorted.length * 0.99)] || 0,
743
- jankFrames: frameTimes.filter((f) => f > 16.67).length,
744
- sampleCount: frameTimes.length,
745
- }
746
- }
747
- // main-thread mode
748
- const perf = window.__sootsimPerf
749
- const a11y = window.__sootsimA11y
750
- if (!perf && !a11y) return { error: 'no perf globals' }
751
-
752
- const frameStats = perf?.getFrameStats?.() || {}
753
- const profile = a11y?.profile?.() || {}
754
-
755
- // calculate distribution
756
- const recent = frameStats.recentFrames || []
757
- const sorted = [...recent].sort((a, b) => a - b)
758
- const p50 = sorted[Math.floor(sorted.length * 0.5)] || 0
759
- const p95 = sorted[Math.floor(sorted.length * 0.95)] || 0
760
- const p99 = sorted[Math.floor(sorted.length * 0.99)] || 0
761
-
762
- // disable collection
763
- perf?.disableFrameStats?.()
764
- window.${ne} = {
765
- ...(window.${ne} || {}),
766
- active: false,
767
- mode: 'main-thread',
768
- lastSamples: recent.map((ms, index) => [index, ms, 0, 0, 0, 0, 0]),
769
- stoppedAt: performance.now(),
770
- }
771
-
772
- return {
773
- mode: 'main-thread',
774
- frames: frameStats.frames || profile.syncCount || 0,
775
- totalMs: frameStats.totalMs || 0,
776
- avgMs: frameStats.totalMs && frameStats.frames
777
- ? frameStats.totalMs / frameStats.frames
778
- : profile.avgSyncMs || 0,
779
- maxMs: frameStats.maxMs || profile.maxSyncMs || 0,
780
- layoutTotalMs: frameStats.layoutTotalMs || 0,
781
- p50, p95, p99,
782
- jankFrames: recent.filter(f => f > 16.67).length,
783
- sampleCount: recent.length,
784
- }
785
- })()`});if(t.error&&(console.error(` error: ${t.error}`),process.exit(1)),A(c)){E(t);break}let s=t.avgMs>0?(1e3/t.avgMs).toFixed(1):"?",n=t.sampleCount>0?(t.jankFrames/t.sampleCount*100).toFixed(1):"0";console.log(` profiling stopped:
786
- `),console.log(` frames: ${t.frames}`),console.log(` total: ${t.totalMs.toFixed(1)}ms`),console.log(` avg: ${t.avgMs.toFixed(2)}ms (${s} 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 (${n}%) >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 s=await d.send({type:"evaluate",code:`(async () => {
787
- if (window.__sootsimRenderHost) {
788
- const session = window.${ne} || {}
789
- if (session.active) {
790
- const priorSamples = Array.isArray(session.lastSamples) ? session.lastSamples : []
791
- const flushedSamples = await window.__sootsimRenderHost.flushSamples()
792
- const samples = priorSamples.concat(flushedSamples)
793
- window.__sootsimRenderHost.startSampling()
794
- window.${ne} = {
795
- ...session,
796
- active: true,
797
- mode: 'render-worker',
798
- lastSamples: samples,
799
- lastFlushAt: performance.now(),
800
- }
801
- return {
802
- mode: 'render-worker',
803
- live: true,
804
- samples: samples.slice(-${t}),
805
- }
806
- }
807
- return {
808
- mode: 'render-worker',
809
- live: false,
810
- samples: (session.lastSamples || []).slice(-${t}),
811
- }
812
- }
813
- // main-thread mode
814
- const perf = window.__sootsimPerf
815
- if (!perf) return { error: 'no __sootsimPerf (try "perf start" first)' }
816
- const stats = perf.getFrameStats?.() || {}
817
- return {
818
- mode: 'main-thread',
819
- frames: (stats.recentFrames || []).slice(-${t}),
820
- }
821
- })()`});if(s.error&&(console.error(` error: ${s.error}`),process.exit(1)),A(c)){E(s);break}if(s.mode==="render-worker"){let i=Array.isArray(s.samples)?s.samples:[];if(i.length===0){console.log(` no frame data \u2014 run '${h("perf")} start' first`);break}console.log(` last ${i.length} sampled frames (ms):`),console.log(" total layout render copy aux other t+");for(let[u,m,b,f,w,O,q]of i)console.log(` ${m.toFixed(2).padStart(7)} ${b.toFixed(2).padStart(7)} ${f.toFixed(2).padStart(7)} ${w.toFixed(2).padStart(7)} ${O.toFixed(2).padStart(6)} ${q.toFixed(2).padStart(7)} ${String(u).padStart(5)}`);console.log(""),He(i.map(u=>u[1])),s.live&&console.log(" sampling continues");break}let n=Array.isArray(s.frames)?s.frames:Array.isArray(s)?s:[];if(n.length===0){console.log(` no frame data \u2014 run '${h("perf")} start' first`);break}console.log(` last ${n.length} frame times (ms):`),console.log(` ${n.map(i=>i.toFixed(2)).join(", ")}`),He(n);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 s=await d.send({type:"evaluate",code:`(async () => {
822
- if (window.__sootsimRenderHost) {
823
- const session = window.${ne} || {}
824
- if (session.active) {
825
- const priorSamples = Array.isArray(session.lastSamples) ? session.lastSamples : []
826
- const flushedSamples = await window.__sootsimRenderHost.flushSamples()
827
- const samples = priorSamples.concat(flushedSamples)
828
- window.__sootsimRenderHost.startSampling()
829
- window.${ne} = {
830
- ...session,
831
- active: true,
832
- mode: 'render-worker',
833
- lastSamples: samples,
834
- lastFlushAt: performance.now(),
835
- }
836
- return {
837
- mode: 'render-worker',
838
- live: true,
839
- samples: samples
840
- .slice()
841
- .sort((a, b) => b[1] - a[1])
842
- .slice(0, ${t}),
843
- }
844
- }
845
- const samples = Array.isArray(session.lastSamples) ? session.lastSamples : []
846
- return {
847
- mode: 'render-worker',
848
- live: false,
849
- samples: samples
850
- .slice()
851
- .sort((a, b) => b[1] - a[1])
852
- .slice(0, ${t}),
853
- }
854
- }
855
- const perf = window.__sootsimPerf
856
- if (!perf) return { error: 'no __sootsimPerf (try "perf start" first)' }
857
- const stats = perf.getFrameStats?.() || {}
858
- const recent = Array.isArray(stats.recentFrames) ? stats.recentFrames : []
859
- return {
860
- mode: 'main-thread',
861
- frames: recent.slice().sort((a, b) => b - a).slice(0, ${t}),
862
- }
863
- })()`});if(s.error&&(console.error(` error: ${s.error}`),process.exit(1)),A(c)){E(s);break}if(s.mode==="render-worker"){let i=Array.isArray(s.samples)?s.samples:[];if(i.length===0){console.log(` no frame data \u2014 run '${h("perf")} start' first`);break}console.log(` worst ${i.length} sampled frames (ms):`),console.log(" total layout render copy aux other t+");for(let[u,m,b,f,w,O,q]of i)console.log(` ${m.toFixed(2).padStart(7)} ${b.toFixed(2).padStart(7)} ${f.toFixed(2).padStart(7)} ${w.toFixed(2).padStart(7)} ${O.toFixed(2).padStart(6)} ${q.toFixed(2).padStart(7)} ${String(u).padStart(5)}`);s.live&&(console.log(""),console.log(" sampling continues"));break}let n=Array.isArray(s.frames)?s.frames:Array.isArray(s)?s:[];if(n.length===0){console.log(` no frame data \u2014 run '${h("perf")} start' first`);break}console.log(` worst ${n.length} frame times (ms):`),console.log(` ${n.map(i=>i.toFixed(2)).join(", ")}`);break}case"transition":{let t=a[2];if(!t||!["goHome","appSwitcher","lockScreen"].includes(t)){console.log(`
864
- ${h("perf")} transition <event> \u2014 profile a shell transition
865
-
866
- events:
867
- goHome swipe-to-home animation
868
- appSwitcher app switcher card animation
869
- lockScreen lock screen transition
870
-
871
- note: uses 600ms capture window \u2014 may need --timeout 10000 flag
872
-
873
- examples:
874
- ${h("perf")} transition goHome --timeout 10000
875
- ${h("perf")} transition appSwitcher
876
- `);break}let n=`sootsim:${t}`;Be(` profiling ${t} transition...`),Be(" (use --timeout 10000 if this times out)");let i=await d.send({type:"evaluate",code:`(async () => {
877
- // only supported in render-worker mode
878
- if (!window.__sootsimRenderHost) {
879
- return { error: 'transition profiling requires render-worker mode' }
880
- }
881
-
882
- // start sampling
883
- window.__sootsimRenderHost.startSampling()
884
-
885
- // give a frame for sampling to start
886
- await new Promise(r => requestAnimationFrame(() => r(undefined)))
887
-
888
- // dispatch the shell event
889
- window.dispatchEvent(new Event('${n}'))
890
-
891
- // wait 600ms for transition to complete (shell animations are ~300-500ms)
892
- // using fixed timing avoids complex animation-end detection
893
- await new Promise(r => setTimeout(r, 600))
894
-
895
- // flush samples and compute stats
896
- const samples = await window.__sootsimRenderHost.flushSamples()
897
- const frameTimes = samples.map(s => s[1])
898
- const layoutTimes = samples.map(s => s[2])
899
- const renderTimes = samples.map(s => s[3])
900
- const copyTimes = samples.map(s => s[4])
901
- const auxTimes = samples.map(s => s[5] || 0)
902
- const otherTimes = samples.map(s => s[6] || 0)
903
- const sorted = [...frameTimes].sort((a, b) => a - b)
904
- const total = frameTimes.reduce((a, b) => a + b, 0)
905
- const avg = frameTimes.length > 0 ? total / frameTimes.length : 0
906
- const max = Math.max(...frameTimes, 0)
907
- const layoutTotal = layoutTimes.reduce((a, b) => a + b, 0)
908
- const renderTotal = renderTimes.reduce((a, b) => a + b, 0)
909
- const copyTotal = copyTimes.reduce((a, b) => a + b, 0)
910
- const auxTotal = auxTimes.reduce((a, b) => a + b, 0)
911
- const otherTotal = otherTimes.reduce((a, b) => a + b, 0)
912
-
913
- return {
914
- event: '${t}',
915
- frames: frameTimes.length,
916
- totalMs: total,
917
- avgMs: avg,
918
- maxMs: max,
919
- layoutTotalMs: layoutTotal,
920
- renderTotalMs: renderTotal,
921
- copyTotalMs: copyTotal,
922
- auxTotalMs: auxTotal,
923
- otherTotalMs: otherTotal,
924
- layoutAvgMs: frameTimes.length > 0 ? layoutTotal / frameTimes.length : 0,
925
- renderAvgMs: frameTimes.length > 0 ? renderTotal / frameTimes.length : 0,
926
- copyAvgMs: frameTimes.length > 0 ? copyTotal / frameTimes.length : 0,
927
- auxAvgMs: frameTimes.length > 0 ? auxTotal / frameTimes.length : 0,
928
- otherAvgMs: frameTimes.length > 0 ? otherTotal / frameTimes.length : 0,
929
- p50: sorted[Math.floor(sorted.length * 0.5)] || 0,
930
- p95: sorted[Math.floor(sorted.length * 0.95)] || 0,
931
- p99: sorted[Math.floor(sorted.length * 0.99)] || 0,
932
- jankFrames: frameTimes.filter(f => f > 16.67).length,
933
- samples,
934
- }
935
- })()`});if(i.error&&(console.error(` error: ${i.error}`),process.exit(1)),A(c)){E(i);break}if(i.warning&&console.log(` warning: ${i.warning}`),i.frames===0){console.log(" no frames captured");break}let u=i.avgMs>0?(1e3/i.avgMs).toFixed(1):"?",m=i.frames>0?(i.jankFrames/i.frames*100).toFixed(1):"0";console.log(` ${t} transition profiled:
936
-
937
- frames: ${i.frames}
938
- total: ${i.totalMs.toFixed(1)}ms
939
- avg: ${i.avgMs.toFixed(2)}ms (${u} fps)
940
- max: ${i.maxMs.toFixed(2)}ms
941
-
942
- breakdown (avg per frame):
943
- layout: ${i.layoutAvgMs?.toFixed(2)||"?"}ms
944
- render: ${i.renderAvgMs?.toFixed(2)||"?"}ms
945
- copy: ${i.copyAvgMs?.toFixed(2)||"?"}ms
946
- aux: ${i.auxAvgMs?.toFixed(2)||"?"}ms
947
- other: ${i.otherAvgMs?.toFixed(2)||"?"}ms
948
-
949
- distribution (${i.frames} samples):
950
- p50: ${i.p50.toFixed(2)}ms
951
- p95: ${i.p95.toFixed(2)}ms
952
- p99: ${i.p99.toFixed(2)}ms
953
- jank: ${i.jankFrames} frames (${m}%) >16.67ms`),Array.isArray(i.samples)&&i.samples.length>0&&(console.log(""),He(i.samples.map(b=>b[1])));break}default:console.error(` unknown perf subcommand: ${o}`),console.error(" valid: stats, start, stop, frames, worst, transition"),/^--?reset$/.test(o)&&console.error(" note: 'perf start' already clears prior frames \u2014 no reset needed"),process.exit(1)}break}case"errors":{let o=a[1];if(o==="clear"){await De(d),A(c)?E({cleared:!0}):console.log(" error buffer cleared");break}let t=o?Number(o):20,s=await fe(d,t);if(A(c)){E(s);break}if(s.length===0){console.log(" no errors captured");break}console.log(` ${s.length} error(s):
954
- `);for(let n of s){let i=ie(n.timestamp),u=n.args.map(m=>typeof m=="object"?JSON.stringify(m):m).join(" ");if(console.log(` [${i}] ${u}`),n.stack){let m=n.stack.split(`
955
- `).slice(0,3);for(let b of m)console.log(` ${b.trim()}`)}}break}case"warnings":{let o=a[1]?Number(a[1]):20,t=await at(d,o);if(A(c)){E(t);break}if(t.length===0){console.log(" no warnings captured");break}console.log(` ${t.length} warning(s):
956
- `);for(let s of t){let n=ie(s.timestamp),i=s.args.map(u=>typeof u=="object"?JSON.stringify(u):u).join(" ");console.log(` [${n}] ${i}`)}break}case"animations":{let o=await G(d,"listAnimations")??[];if(e.includes("--json")){console.log(JSON.stringify(o,null,2));break}if(o.length===0){console.log(" no active animations");break}console.log(` ${o.length} active animation(s):
957
- `);for(let t of o){let s=String(t.kind).padEnd(6),n=`${Number(t.from).toFixed(2)}\u2192${Number(t.to).toFixed(2)}`,i=Number(t.current??0).toFixed(2),u=`${Math.round((t.progress??0)*100)}%`,m=`${Math.round(t.elapsedMs??0)}ms`,b=t.loop?" loop":"",f=t.layoutBound?" layout":"";console.log(` #${t.id} ${s} ${n.padEnd(14)} cur=${i.padEnd(7)} ${u.padStart(4)} ${m}${b}${f}`)}break}case"animation":{let o=a[1];(!o||o==="--help"||o==="-h")&&(console.error(` usage: ${h("animation")} <id>`),process.exit(1));let t=Number(o);Number.isFinite(t)||(console.error(` invalid id: ${o}`),process.exit(1));let s=await G(d,"getAnimation",t);console.log(JSON.stringify(s,null,2));break}case"stop-animation":{let o=a[1];(!o||o==="--help"||o==="-h")&&(console.error(` usage: ${h("stop-animation")} <id|all>`),process.exit(1));let t=o==="all"?"all":Number(o);t!=="all"&&!Number.isFinite(t)&&(console.error(` invalid id: ${o}`),process.exit(1));let s=await G(d,"stopAnimation",t);console.log(` stopped ${s??0} animation(s)`);break}case"requests":{let o=a[1];if(o==="clear"){await ct(d),A(c)?E({cleared:!0}):console.log(" request buffer cleared");break}let t=o==="all",s=t?a[2]:o,n=s?Number(s):20,i=await lt(d,{failed:!t,limit:n});if(A(c)){E(i);break}if(i.length===0){console.log(t?" no requests captured":" no failed requests captured");break}console.log(` ${i.length} ${t?"request(s)":"failed request(s)"}:
958
- `);for(let u of i){let m=ie(u.timestamp);console.log(` [${m}] ${oe(u)}`),u.responseBody?console.log(` ${u.responseBody}`):u.error&&console.log(` ${u.error}`)}break}case"network":{let o=a[1],t=null,s=null,n=!1,i=!1,u=1e3,m=!1,b=!1;for(let F=0;F<c.length;F++){let x=c[F];if(x==="--filter")t=c[F+1]??null,F++;else if(x==="--limit"){let $=Number(c[F+1]);Number.isFinite($)&&(s=$),F++}else if(x==="--threshold"){let $=Number(c[F+1]);Number.isFinite($)&&$>0&&(u=$),F++}else x==="--failed"?n=!0:x==="--slow"?i=!0:x==="--tail"||x==="-f"?m=!0:x==="--json"&&(b=!0)}if(o==="clear"){await d.send({type:"evaluate",code:'window.__sootsimObservability?.network.clear(); "cleared"'}),console.log(" network buffer cleared");break}if(o==="get"){let F=a[2];F||(console.error(" usage: sootsim network get <id>"),process.exit(1));let x=await d.send({type:"evaluate",code:`(() => {
959
- const obs = window.__sootsimObservability;
960
- if (!obs) return null;
961
- return obs.network.getSnapshot().find(e => e.id === ${JSON.stringify(F)}) || null;
962
- })()`});x||(console.error(` no entry with id ${F}`),process.exit(1)),b?console.log(JSON.stringify(x,null,2)):Wo(x);break}let f=s??(m?200:o?Number(o):20);Number.isFinite(f)||(console.error(` invalid limit: ${o} \u2014 \`network\` takes a numeric count (e.g. ${h("network")} 100).
963
- to target a specific sim, use \`--sim ${o}\` instead.`),process.exit(1));let w=async()=>{let F=await d.send({type:"evaluate",code:`(() => {
964
- const obs = window.__sootsimObservability;
965
- if (!obs) return { ok: false };
966
- return { ok: true, entries: obs.network.getSnapshot() };
967
- })()`});if(!F||!F.ok)throw new Error("observability bridge not installed \u2014 is the engine running?");return F.entries??[]},O=F=>{let x=F;if(n&&(x=x.filter($=>!!$.error||$.status!=null&&$.status>=400)),i&&(x=x.filter($=>$.durationMs!=null&&$.durationMs>=u)),t){let $=t.toLowerCase();x=x.filter(v=>(v.displayUrl||v.url).toLowerCase().includes($))}return i&&!m&&(x=[...x].sort(($,v)=>(v.durationMs??0)-($.durationMs??0))),x};if(!m){let F=await w(),x=O(F).slice(-f);if(b){console.log(JSON.stringify(x,null,2));break}if(x.length===0){F.length===0?console.log(" no network requests captured"):console.log(i?` no requests slower than ${u}ms (${F.length} total \u2014 try --threshold <ms>)`:" no matching requests");break}console.log(i?` ${x.length} request(s) slower than ${u}ms (sorted by duration desc):
968
- `:` ${x.length} request(s):
969
- `);for(let $ of x)eo($);break}console.log(` tailing network (ctrl-c to stop)...
970
- `);let q=new Set,H=!0,K=()=>{H=!1};process.on("SIGINT",K);try{for(;H;){let F=await w(),x=O(F);for(let $ of x)$.durationMs!=null&&(q.has($.id)||(q.add($.id),b?console.log(JSON.stringify($)):eo($)));await ee(250)}}finally{process.off("SIGINT",K)}break}case"logs":{let o=a[1],t=null,s=null,n=null,i=!1,u=!1,m=!1;for(let x=0;x<c.length;x++){let $=c[x];if($==="--filter")t=c[x+1]??null,x++;else if($==="--limit"){let v=Number(c[x+1]);Number.isFinite(v)&&(s=v),x++}else $==="--level"?(n=c[x+1]??null,x++):$==="--tail"||$==="-f"?i=!0:$==="--json"?u=!0:($==="--internal"||$==="--all")&&(m=!0)}let b=n?new Set(n.split(",").map(x=>x.trim()).filter(x=>x==="log"||x==="info"||x==="warn"||x==="error"||x==="debug")):null;if(o==="clear"){await ut(d),console.log(" log buffer cleared");break}let f=!u&&process.stdout.isTTY===!0,w=s??(i?500:o?Number(o):50);Number.isFinite(w)||(console.error(` invalid limit: ${o} \u2014 \`logs\` takes a numeric count (e.g. ${h("logs")} 100).
971
- to target a specific sim, use \`--sim ${o}\` instead.`),process.exit(1));let O=()=>dt(d),q=x=>mt(x,{level:b,filter:t,showInternal:m});if(!i){let x=await O(),$=q(x).slice(-w);if(u){console.log(JSON.stringify($,null,2));break}if($.length===0){console.log(x.length===0?" no logs captured":" no matching logs");break}console.log(` ${$.length} log(s):
972
- `);for(let v of $)oo(v,f);break}console.log(` tailing logs (ctrl-c to stop)...
973
- `);let H=new Set,K=!0,F=()=>{K=!1};process.on("SIGINT",F);try{for(;K;){let x=await O(),$=q(x);for(let v of $)H.has(v.id)||(H.add(v.id),u?console.log(JSON.stringify(v)):oo(v,f));await ee(250)}}finally{process.off("SIGINT",F)}break}default:console.error(` unknown subcommand: ${g}`),process.exit(1)}if(pe.has(g)&&!e.includes("--no-wait")&&process.env.SOOTSIM_NO_AUTO_WAIT!=="1"&&!await Xe(d,g)&&await S(d),!B.has(g)&&!A(c))try{await re(d)}catch{}}catch(o){let t=o instanceof Error?o.message:String(o);console.error(` ${g??"inspect"} failed: ${t}`);let s=/^no sim connected with id (.+)$/.exec(t),n=/^command timed out after (\d+)s$/.exec(t),i=t.startsWith("sim disconnected:")||t.startsWith("bridge never reconnected")||t.startsWith("could not connect to ws://");if(s)await Mt(d,_,s[1]);else if(/^no sim connected$/.test(t))Tt(_);else if(n)if(await Co(d)){let m=g??"describe";process.stderr.write(` the sim is still responsive \u2014 '${m}' just exceeded the ${n[1]}s command budget.
974
- the screen's node tree is large; narrow the query or raise the budget:
975
- sootsim ${m} --testid <id> # scope to one subtree
976
- sootsim find --testid <id> # targeted single-node lookup
977
- sootsim ${m} --timeout 60000 # raise per-command budget (ms)
978
- `)}else Qt();else if(i)Qt();else{try{await St(d)}catch{}try{await V({includeTail:!0})}catch{}try{await W({includeTail:!0})}catch{}}process.exit(1)}finally{d.close()}}export{Jn as runInspect};