rnxsim 0.1.313 → 0.1.315
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +11 -10
- package/README.md +5 -0
- package/cli/app-config.ts +65 -0
- package/cli/app-fonts.ts +408 -0
- package/cli/app-project.ts +231 -0
- package/cli/app-splash.ts +185 -0
- package/cli/app-state-reset.ts +24 -0
- package/cli/auth.ts +155 -0
- package/cli/bin.ts +594 -0
- package/cli/bridge-diagnostics.ts +226 -0
- package/cli/bridge-flow-runner.ts +2830 -0
- package/cli/browser-evals.ts +96 -0
- package/cli/commands/agent-wrapper.ts +986 -0
- package/cli/commands/agent.ts +423 -0
- package/cli/commands/app-fonts.ts +98 -0
- package/cli/commands/assert.ts +541 -0
- package/cli/commands/auth.ts +59 -0
- package/cli/commands/camera.ts +266 -0
- package/cli/commands/cleanup.ts +169 -0
- package/cli/commands/compat.ts +87 -0
- package/cli/commands/config.ts +32 -0
- package/cli/commands/control.ts +2142 -0
- package/cli/commands/cpu-profile.ts +269 -0
- package/cli/commands/daemon-mac-app.ts +169 -0
- package/cli/commands/daemon.ts +874 -0
- package/cli/commands/debug.ts +719 -0
- package/cli/commands/desktop.ts +39 -0
- package/cli/commands/detect.ts +197 -0
- package/cli/commands/detox.ts +385 -0
- package/cli/commands/device.ts +133 -0
- package/cli/commands/diagnose.ts +589 -0
- package/cli/commands/electron.ts +95 -0
- package/cli/commands/film.ts +379 -0
- package/cli/commands/flow.ts +1124 -0
- package/cli/commands/inspect/actions.ts +622 -0
- package/cli/commands/inspect/core.ts +2405 -0
- package/cli/commands/inspect/count.ts +17 -0
- package/cli/commands/inspect/describe.ts +192 -0
- package/cli/commands/inspect/env.ts +23 -0
- package/cli/commands/inspect/find.ts +171 -0
- package/cli/commands/inspect/get-layout.ts +39 -0
- package/cli/commands/inspect/keyboard.ts +52 -0
- package/cli/commands/inspect/list.ts +58 -0
- package/cli/commands/inspect/memory.ts +215 -0
- package/cli/commands/inspect/redaction.ts +39 -0
- package/cli/commands/inspect/resolve-target.ts +82 -0
- package/cli/commands/inspect/screens.ts +78 -0
- package/cli/commands/inspect/settle.ts +22 -0
- package/cli/commands/inspect/settling.ts +158 -0
- package/cli/commands/inspect/shared.ts +353 -0
- package/cli/commands/inspect/sleep.ts +14 -0
- package/cli/commands/inspect/tree.ts +32 -0
- package/cli/commands/inspect/url.ts +17 -0
- package/cli/commands/inspect/wait-event.ts +210 -0
- package/cli/commands/inspect/wait-idle.ts +24 -0
- package/cli/commands/inspect/wait-ready.ts +74 -0
- package/cli/commands/inspect/wait-selector.ts +54 -0
- package/cli/commands/inspect/wait.ts +31 -0
- package/cli/commands/inspect.ts +4519 -0
- package/cli/commands/install-desktop.ts +351 -0
- package/cli/commands/login.ts +331 -0
- package/cli/commands/logout.ts +31 -0
- package/cli/commands/maestro-generate.ts +361 -0
- package/cli/commands/maestro.ts +453 -0
- package/cli/commands/mode.ts +57 -0
- package/cli/commands/no-bridge-hint.ts +80 -0
- package/cli/commands/perf.ts +66 -0
- package/cli/commands/permissions.ts +203 -0
- package/cli/commands/profile.ts +108 -0
- package/cli/commands/react.ts +353 -0
- package/cli/commands/record.ts +1434 -0
- package/cli/commands/report-issue.ts +305 -0
- package/cli/commands/reset.ts +85 -0
- package/cli/commands/runtime.ts +351 -0
- package/cli/commands/screenshot-command.ts +106 -0
- package/cli/commands/screenshot-layers.ts +143 -0
- package/cli/commands/screenshot-mode.ts +37 -0
- package/cli/commands/screenshot.ts +488 -0
- package/cli/commands/screenshots-capture.ts +607 -0
- package/cli/commands/screenshots.ts +127 -0
- package/cli/commands/serve.ts +168 -0
- package/cli/commands/setup.ts +545 -0
- package/cli/commands/shell-boolean-mode.ts +81 -0
- package/cli/commands/skills.ts +467 -0
- package/cli/commands/slides.ts +361 -0
- package/cli/commands/state.ts +87 -0
- package/cli/commands/storage.ts +58 -0
- package/cli/commands/telemetry.ts +54 -0
- package/cli/commands/three-mode.ts +763 -0
- package/cli/commands/timeline.ts +122 -0
- package/cli/commands/upgrade.ts +208 -0
- package/cli/commands/upload.ts +1225 -0
- package/cli/commands/version.ts +54 -0
- package/cli/commands/what-happened.ts +327 -0
- package/cli/current-sim.ts +204 -0
- package/cli/desktop-companion.ts +300 -0
- package/cli/drivers/electron.ts +70 -0
- package/cli/drivers/index.ts +20 -0
- package/cli/drivers/playwright-provisioning.ts +180 -0
- package/cli/drivers/playwright.ts +698 -0
- package/cli/drivers/registry.ts +65 -0
- package/cli/drivers/types.ts +102 -0
- package/cli/flow-file.ts +142 -0
- package/cli/flow-live-status.ts +120 -0
- package/cli/flow-session.ts +187 -0
- package/cli/help.ts +80 -0
- package/cli/hidden-runtime-alias.ts +19 -0
- package/cli/hints.ts +216 -0
- package/cli/inspect-notice-state.ts +114 -0
- package/cli/maestro-js.ts +334 -0
- package/cli/open-url.ts +8 -0
- package/cli/parent-pid.ts +204 -0
- package/cli/parse-args.ts +211 -0
- package/cli/prompt.ts +51 -0
- package/cli/recording-access.ts +107 -0
- package/cli/registry.ts +1 -0
- package/cli/resolve-assets.ts +63 -0
- package/cli/run-registry.ts +226 -0
- package/cli/runtime-notes.ts +66 -0
- package/cli/runtime-summary.ts +25 -0
- package/cli/setup-repository.ts +187 -0
- package/cli/telemetry.ts +187 -0
- package/cli/ws-bridge.ts +798 -0
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-XZ2KTPCU.js → agent-CBU2WDKV.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-JJYYW2WH.js → agent-wrapper-UNB4R27L.js} +2 -2
- package/dist-cli/chunks/{app-fonts-IXRNQG6B.js → app-fonts-YMLRF3JD.js} +2 -2
- package/dist-cli/chunks/{assert-54T5SK5F.js → assert-5N6HUCL5.js} +2 -2
- package/dist-cli/chunks/{auth-FI5UDI45.js → auth-BHDR3A6G.js} +2 -2
- package/dist-cli/chunks/{beta-JV6UKADW.js → beta-LWUVCPYK.js} +2 -2
- package/dist-cli/chunks/camera-CNO6B2JZ.js +33 -0
- package/dist-cli/chunks/{chunk-ZMJD5GEC.js → chunk-25VCR44B.js} +1 -1
- package/dist-cli/chunks/{chunk-WUSWBCWA.js → chunk-26CSIR76.js} +8 -9
- package/dist-cli/chunks/{chunk-WINYQ44O.js → chunk-26WMWUH4.js} +1 -1
- package/dist-cli/chunks/{chunk-BTWORNNG.js → chunk-2JZ53D4A.js} +1 -1
- package/dist-cli/chunks/{chunk-DCEMHR2Y.js → chunk-2POODCTW.js} +2 -2
- package/dist-cli/chunks/{chunk-5TEF3ET3.js → chunk-4LTBK3ZN.js} +2 -2
- package/dist-cli/chunks/{chunk-NMF2ZMZQ.js → chunk-54MIRTFE.js} +4 -4
- package/dist-cli/chunks/{chunk-WEXDAC74.js → chunk-56H6EVO4.js} +2 -2
- package/dist-cli/chunks/{chunk-F5ZRSS3C.js → chunk-657SQG4D.js} +1 -1
- package/dist-cli/chunks/{chunk-IJ5CAZZC.js → chunk-6ZMRJPKI.js} +1 -1
- package/dist-cli/chunks/{chunk-W6K4EFPH.js → chunk-AOGGOC4V.js} +2 -2
- package/dist-cli/chunks/chunk-BPCDO7ST.js +15 -0
- package/dist-cli/chunks/{chunk-D4FFVGI5.js → chunk-C5O5CTXJ.js} +1 -1
- package/dist-cli/chunks/{chunk-RTN5C5RL.js → chunk-CMM64PPR.js} +1 -1
- package/dist-cli/chunks/{chunk-BBULZ7CG.js → chunk-D3M36D7O.js} +62 -87
- package/dist-cli/chunks/{chunk-5DHC6KHQ.js → chunk-DNCNFZ7V.js} +1 -1
- package/dist-cli/chunks/{chunk-YIFT42WN.js → chunk-FNTTIR2P.js} +2 -2
- package/dist-cli/chunks/chunk-H362IX2I.js +4 -0
- package/dist-cli/chunks/{chunk-IJO63TDP.js → chunk-J6HMTUZH.js} +2 -2
- package/dist-cli/chunks/{chunk-RSZWCKNT.js → chunk-JZ6VTZ5B.js} +3 -3
- package/dist-cli/chunks/{chunk-MJRLLB4R.js → chunk-KQSJVNFE.js} +4 -4
- package/dist-cli/chunks/chunk-L4JZKVCC.js +6 -0
- package/dist-cli/chunks/{chunk-VFCMSYZK.js → chunk-LP2BTEJ6.js} +2 -2
- package/dist-cli/chunks/{chunk-2YR5BGA5.js → chunk-LT6UC7I3.js} +2 -2
- package/dist-cli/chunks/{chunk-OVFJFXUD.js → chunk-MCKQH4AM.js} +2 -2
- package/dist-cli/chunks/{chunk-TZFFR3SD.js → chunk-MF7WUHQ3.js} +2 -2
- package/dist-cli/chunks/{chunk-46EUUFJ5.js → chunk-MSSY4EEZ.js} +1 -1
- package/dist-cli/chunks/{chunk-WMIIKMGK.js → chunk-N5ZCYP5F.js} +2 -2
- package/dist-cli/chunks/{chunk-3NV2NCNX.js → chunk-NCKAD3V7.js} +2 -2
- package/dist-cli/chunks/{chunk-VNQEB4L7.js → chunk-O2KRHVTU.js} +2 -2
- package/dist-cli/chunks/{chunk-OZSSI4WN.js → chunk-OF2HTH25.js} +2 -2
- package/dist-cli/chunks/{chunk-GGRX24GF.js → chunk-OXS2IWO2.js} +2 -2
- package/dist-cli/chunks/{chunk-WWZIXIRD.js → chunk-PVL7XSPN.js} +1 -1
- package/dist-cli/chunks/{chunk-DZS6WPUI.js → chunk-PVPAP3TV.js} +1 -1
- package/dist-cli/chunks/{chunk-5YJCOWCH.js → chunk-PXNAZYGV.js} +1 -1
- package/dist-cli/chunks/chunk-QR3HTFRB.js +5 -0
- package/dist-cli/chunks/{chunk-5TPRP5QT.js → chunk-QR7MWAWE.js} +1 -1
- package/dist-cli/chunks/chunk-RJIEZ2NY.js +4 -0
- package/dist-cli/chunks/{chunk-UC6U3MML.js → chunk-RVM4EZ4E.js} +2 -2
- package/dist-cli/chunks/{chunk-2D2UPBBR.js → chunk-SZKSFMWS.js} +1 -1
- package/dist-cli/chunks/{chunk-7GN3LVWB.js → chunk-TEL7G234.js} +2 -2
- package/dist-cli/chunks/{chunk-GASE6UBA.js → chunk-U2HNIHBY.js} +1 -1
- package/dist-cli/chunks/chunk-UROXJVY3.js +27 -0
- package/dist-cli/chunks/{chunk-HI5TFJWN.js → chunk-WM32MGTJ.js} +2 -2
- package/dist-cli/chunks/{chunk-VZXWHRUZ.js → chunk-WMZ7NJAO.js} +89 -133
- package/dist-cli/chunks/{chunk-XEVZYVIW.js → chunk-WUXCH6WN.js} +10 -9
- package/dist-cli/chunks/chunk-XOXGC3EH.js +9 -0
- package/dist-cli/chunks/chunk-XSSHENNY.js +4 -0
- package/dist-cli/chunks/{chunk-OHAZNXLK.js → chunk-XZ6O67AR.js} +1 -1
- package/dist-cli/chunks/{chunk-QKDWYITG.js → chunk-Y2MWQKOS.js} +3 -3
- package/dist-cli/chunks/{chunk-YDGQTMQL.js → chunk-YY24QI25.js} +1 -1
- package/dist-cli/chunks/{chunk-WF3T4SVI.js → chunk-ZWX6DDIS.js} +2 -2
- package/dist-cli/chunks/{cleanup-P27PA6JI.js → cleanup-F3NUJ3SL.js} +2 -2
- package/dist-cli/chunks/cli-version-XIW6O3FG.js +4 -0
- package/dist-cli/chunks/{compat-ZD65FED3.js → compat-AX2SEPTH.js} +2 -2
- package/dist-cli/chunks/{config-XMJRNM2A.js → config-4QQO5JVU.js} +2 -2
- package/dist-cli/chunks/{control-KMIQT3QP.js → control-OCKKSOWA.js} +2 -2
- package/dist-cli/chunks/daemon-N5VLMYKQ.js +4 -0
- package/dist-cli/chunks/{debug-PT4HOP7N.js → debug-T6MWVEVX.js} +5 -5
- package/dist-cli/chunks/{desktop-S3FG72AK.js → desktop-NCW7R7CB.js} +3 -3
- package/dist-cli/chunks/{detox-B3D4IFCN.js → detox-G6GITO2X.js} +2 -2
- package/dist-cli/chunks/{device-XBNDSB2R.js → device-7BTEJCTH.js} +2 -2
- package/dist-cli/chunks/{diagnose-HMQXJE5N.js → diagnose-SC5LACE3.js} +2 -2
- package/dist-cli/chunks/{disk-cleanup-BLCZ5BSZ.js → disk-cleanup-KK56QQZA.js} +2 -2
- package/dist-cli/chunks/drivers-NPGVBWMG.js +4 -0
- package/dist-cli/chunks/{film-BJGTBYZB.js → film-WGYEHGIA.js} +3 -3
- package/dist-cli/chunks/flow-KQBIBM3N.js +4 -0
- package/dist-cli/chunks/help-G7CAKI65.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-ANOYADHM.js → hidden-runtime-alias-CWTZWH3L.js} +2 -2
- package/dist-cli/chunks/home-paths-EBT4XHAS.js +4 -0
- package/dist-cli/chunks/inspect-CCPZXVDU.js +4 -0
- package/dist-cli/chunks/install-desktop-NZ3VGTQZ.js +4 -0
- package/dist-cli/chunks/{login-FJ737MWG.js → login-GSYOTGVQ.js} +4 -4
- package/dist-cli/chunks/{logout-ZCNMMHMY.js → logout-JPRIR7YK.js} +2 -2
- package/dist-cli/chunks/{maestro-SZTNKLDF.js → maestro-LXKDNO5M.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-DCFAIZ4H.js → maestro-generate-5ZIKOU53.js} +3 -3
- package/dist-cli/chunks/{mode-GRMQCRXR.js → mode-N3QHXCES.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-W36EWFFB.js → optional-demo-registry-XDH6X24N.js} +2 -2
- package/dist-cli/chunks/{perf-QYBAAUZG.js → perf-VOPL26FM.js} +2 -2
- package/dist-cli/chunks/{permissions-3QCQ6VF4.js → permissions-CZFKM4UD.js} +2 -2
- package/dist-cli/chunks/{record-QPPC2S4E.js → record-FODSOGMA.js} +3 -3
- package/dist-cli/chunks/{report-issue-7NMFP4HK.js → report-issue-FBPCZKOV.js} +2 -2
- package/dist-cli/chunks/reset-3H35GFO3.js +4 -0
- package/dist-cli/chunks/runtime-4OVFRXV5.js +4 -0
- package/dist-cli/chunks/{screenshot-command-67AECJFB.js → screenshot-command-7S3FYVO7.js} +7 -7
- package/dist-cli/chunks/{screenshot-layers-ASWBYPJL.js → screenshot-layers-CDYHPNC3.js} +3 -3
- package/dist-cli/chunks/{screenshots-capture-PXA3HFQK.js → screenshots-capture-EBIIY2YU.js} +2 -2
- package/dist-cli/chunks/serve-NELNSCZP.js +44 -0
- package/dist-cli/chunks/{setup-7DWPMRSB.js → setup-DF5ZDDIV.js} +2 -2
- package/dist-cli/chunks/{skills-S3Y22TUA.js → skills-E2QZF6WZ.js} +2 -2
- package/dist-cli/chunks/state-MBZDKZ7U.js +14 -0
- package/dist-cli/chunks/{storage-XUIMJWAJ.js → storage-J5BMNVXM.js} +6 -6
- package/dist-cli/chunks/store-U3DXC7HH.js +4 -0
- package/dist-cli/chunks/telemetry-XIPJVT5E.js +4 -0
- package/dist-cli/chunks/{timeline-TMPLQPSP.js → timeline-MC6N5BHY.js} +2 -2
- package/dist-cli/chunks/{upgrade-7HDSIM7K.js → upgrade-7UTU53DH.js} +2 -2
- package/dist-cli/chunks/upload-2M2KOTYI.js +4 -0
- package/dist-cli/chunks/version-VNACNYQK.js +6 -0
- package/dist-cli/chunks/{web-DG3WBYD3.js → web-MGJ2TUKR.js} +2 -2
- package/dist-cli/chunks/{what-happened-XFVUTZR7.js → what-happened-7DLKH3OD.js} +3 -3
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract.cjs +20 -0
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +67 -28
- package/dist-lib/host/bridge-host.cjs +140 -12
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +2815 -40
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +2549 -2061
- package/dist-lib/sdk.mjs +2543 -2061
- package/dist-lib/skills.cjs +480 -280
- package/dist-lib/vite.cjs +1 -1
- package/package.json +8 -2
- package/src/bridge-constants.ts +3 -4
- package/src/bridge-contract.ts +251 -0
- package/src/connect.ts +83 -0
- package/src/disk-cleanup.ts +30 -0
- package/src/home-paths.ts +81 -38
- package/src/host/bridge-host.ts +134 -6
- package/src/index.ts +27 -1
- package/src/sdk.ts +8 -0
- package/src/sim-client.ts +660 -0
- package/dist-cli/chunks/camera-VL73YIKP.js +0 -22
- package/dist-cli/chunks/chunk-4NPPOV2N.js +0 -5
- package/dist-cli/chunks/chunk-FSUYIVJ6.js +0 -9
- package/dist-cli/chunks/chunk-G2WW6L2C.js +0 -23
- package/dist-cli/chunks/chunk-KTHV3RUS.js +0 -26
- package/dist-cli/chunks/chunk-LF2ZVT7O.js +0 -6
- package/dist-cli/chunks/chunk-NFK7T35W.js +0 -4
- package/dist-cli/chunks/chunk-TIVZIMMW.js +0 -4
- package/dist-cli/chunks/cli-version-WWLPBDQ7.js +0 -4
- package/dist-cli/chunks/daemon-G2ME7NLB.js +0 -4
- package/dist-cli/chunks/drivers-LDECZGP2.js +0 -4
- package/dist-cli/chunks/flow-UEQNVTU7.js +0 -4
- package/dist-cli/chunks/help-T5FYSVGB.js +0 -4
- package/dist-cli/chunks/home-paths-GT3LFNOR.js +0 -4
- package/dist-cli/chunks/inspect-ZA6XF5LD.js +0 -4
- package/dist-cli/chunks/install-desktop-TIMUDHPL.js +0 -4
- package/dist-cli/chunks/runtime-XOAXMSTU.js +0 -4
- package/dist-cli/chunks/serve-BI2NBAXG.js +0 -44
- package/dist-cli/chunks/store-JTHEJLAZ.js +0 -4
- package/dist-cli/chunks/telemetry-ZYJGD2DB.js +0 -4
- package/dist-cli/chunks/upload-GMSZPWM6.js +0 -4
- package/dist-cli/chunks/version-HOCHZ37L.js +0 -6
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// `sootsim get count` — total SootSimNode count in the live tree.
|
|
2
|
+
|
|
3
|
+
import { inspectNodeCount } from './core'
|
|
4
|
+
import { printJson, wantsJson } from './shared'
|
|
5
|
+
import type { WsBridge } from '../../ws-bridge'
|
|
6
|
+
|
|
7
|
+
export async function runCountSubcommand(
|
|
8
|
+
bridge: WsBridge,
|
|
9
|
+
opts: { args: string[] } = { args: [] },
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
const result = await inspectNodeCount(bridge)
|
|
12
|
+
if (wantsJson(opts.args)) {
|
|
13
|
+
printJson(result)
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
console.log(` nodes: ${result.nodes}`)
|
|
17
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// `sootsim describe` — dump the visible-UI tree with layout + style annotations.
|
|
2
|
+
//
|
|
3
|
+
// see packages/sootsim-engine/src/test-bridge.ts for the actual dumpTree impl
|
|
4
|
+
// that powers this. the CLI-side job is just: optional preflight for in-flight
|
|
5
|
+
// screen transitions, call dumpTree with the right flags, and pretty-print.
|
|
6
|
+
|
|
7
|
+
import { maybeHint } from '../../hints'
|
|
8
|
+
import { inspectDescribe, type DescribeDumpOptions } from './core'
|
|
9
|
+
import {
|
|
10
|
+
maybeWaitForStartedScreenTransitions,
|
|
11
|
+
printJson,
|
|
12
|
+
sleep,
|
|
13
|
+
wantsJson,
|
|
14
|
+
} from './shared'
|
|
15
|
+
import type { WsBridge } from '../../ws-bridge'
|
|
16
|
+
|
|
17
|
+
export interface DescribeOptions {
|
|
18
|
+
bridge: WsBridge
|
|
19
|
+
args: string[]
|
|
20
|
+
positional: string[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function flagValue(args: string[], name: string): string | null {
|
|
24
|
+
const i = args.indexOf(name)
|
|
25
|
+
return i >= 0 && i + 1 < args.length ? args[i + 1] : null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function runDescribeSubcommand(opts: DescribeOptions): Promise<void> {
|
|
29
|
+
const { bridge, args, positional } = opts
|
|
30
|
+
const verbose = args.includes('--verbose') || args.includes('-v')
|
|
31
|
+
const jsonOut = wantsJson(args)
|
|
32
|
+
const verboseDescribeTransitions = verbose && !jsonOut
|
|
33
|
+
const watch = args.includes('--watch') || args.includes('-w')
|
|
34
|
+
const watchInterval = 1000
|
|
35
|
+
const compact = args.includes('--compact')
|
|
36
|
+
const hideXy = args.includes('--no-xy')
|
|
37
|
+
// when set, strip rows annotated `(clipped:left|right|top|bottom)` from the
|
|
38
|
+
// output. dumpTree marks anything that overflows the viewport so search
|
|
39
|
+
// context is preserved, but the rows balloon the output on screens like
|
|
40
|
+
// 3pc's profile (off-screen list items + edge-pulled chrome). this flag is
|
|
41
|
+
// a client-side filter so we don't need a roundtrip to dumpTree.
|
|
42
|
+
const hideClipped = args.includes('--no-clipped')
|
|
43
|
+
// by default describe hides nodes that sit fully behind a modal/sheet/popover
|
|
44
|
+
// scrim — they aren't reachable, and listing them as `(tap)` misleads agents
|
|
45
|
+
// into tapping content that's actually occluded. --include-occluded surfaces
|
|
46
|
+
// them annotated `(behind)`.
|
|
47
|
+
const includeOccluded = args.includes('--include-occluded')
|
|
48
|
+
|
|
49
|
+
const testIdLike = flagValue(args, '--testid-like')
|
|
50
|
+
const onlyGlob = flagValue(args, '--only')
|
|
51
|
+
const subtreeRoot = flagValue(args, '--subtree')
|
|
52
|
+
|
|
53
|
+
// bare positional still works — routes to --only if it looks globby,
|
|
54
|
+
// otherwise preserves the old substring-match behavior as `filter`. guard
|
|
55
|
+
// against a flag-looking token leaking in as a filter: an unrecognized
|
|
56
|
+
// `--flag` that the arg parser didn't strip must never become a text filter
|
|
57
|
+
// (that's how `describe --compact` used to match nothing).
|
|
58
|
+
const positionalFilter =
|
|
59
|
+
positional[1] && !positional[1].startsWith('-') ? positional[1] : undefined
|
|
60
|
+
const bareIsGlob = positionalFilter ? /[*?]/.test(positionalFilter) : false
|
|
61
|
+
const filter = !bareIsGlob && !onlyGlob ? positionalFilter : undefined
|
|
62
|
+
const effectiveOnlyGlob = onlyGlob ?? (bareIsGlob ? positionalFilter : undefined)
|
|
63
|
+
|
|
64
|
+
const runOnce = async () => {
|
|
65
|
+
await maybeWaitForStartedScreenTransitions(bridge, {
|
|
66
|
+
verbose: verboseDescribeTransitions,
|
|
67
|
+
})
|
|
68
|
+
const dumpOpts: DescribeDumpOptions = {
|
|
69
|
+
describe: true,
|
|
70
|
+
verbose,
|
|
71
|
+
filter: filter || '',
|
|
72
|
+
testIdLike: testIdLike || undefined,
|
|
73
|
+
onlyGlob: effectiveOnlyGlob || undefined,
|
|
74
|
+
subtreeRoot: subtreeRoot || undefined,
|
|
75
|
+
compact,
|
|
76
|
+
hideXy,
|
|
77
|
+
includeOccluded,
|
|
78
|
+
// no terminal to fit, so nothing gets clipped on the way out
|
|
79
|
+
fullText: jsonOut,
|
|
80
|
+
}
|
|
81
|
+
const result = await inspectDescribe(bridge, dumpOpts)
|
|
82
|
+
const tree = result?.tree
|
|
83
|
+
const shell = result?.shell
|
|
84
|
+
const keyboard = result?.keyboard
|
|
85
|
+
|
|
86
|
+
if (jsonOut) {
|
|
87
|
+
printJson({ shell, tree: tree ?? '', keyboard })
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// show shell state in header if available
|
|
92
|
+
if (shell && typeof shell === 'object') {
|
|
93
|
+
const shellParts = [
|
|
94
|
+
shell.state ? `state=${shell.state}` : null,
|
|
95
|
+
shell.activeApp ? `app=${shell.activeApp}` : null,
|
|
96
|
+
shell.showSwitcher ? 'switcher' : null,
|
|
97
|
+
shell.switcherPhase && shell.switcherPhase !== 'idle'
|
|
98
|
+
? `phase=${shell.switcherPhase}`
|
|
99
|
+
: null,
|
|
100
|
+
].filter(Boolean)
|
|
101
|
+
if (shellParts.length > 0) {
|
|
102
|
+
console.log(` shell: ${shellParts.join(' ')}`)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (typeof tree === 'string' && tree.startsWith('__SUBTREE_NOT_FOUND__:')) {
|
|
107
|
+
const missing = tree.slice('__SUBTREE_NOT_FOUND__:'.length)
|
|
108
|
+
console.log(` subtree root not found: ${missing}`)
|
|
109
|
+
maybeHint('subtree-root-not-found', missing)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!tree) {
|
|
114
|
+
const nodeCount = result?.nodeCount ?? 0
|
|
115
|
+
console.log(' no matching nodes found')
|
|
116
|
+
const anyFilterActive = filter || testIdLike || effectiveOnlyGlob || subtreeRoot
|
|
117
|
+
if (!anyFilterActive && nodeCount < 10) {
|
|
118
|
+
maybeHint('app-still-loading', nodeCount)
|
|
119
|
+
}
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let rendered = tree
|
|
124
|
+
let hiddenClippedCount = 0
|
|
125
|
+
if (hideClipped && typeof rendered === 'string') {
|
|
126
|
+
const lines = rendered.split('\n')
|
|
127
|
+
const kept: string[] = []
|
|
128
|
+
for (const line of lines) {
|
|
129
|
+
if (line.includes('(clipped:')) {
|
|
130
|
+
hiddenClippedCount += 1
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
kept.push(line)
|
|
134
|
+
}
|
|
135
|
+
rendered = kept.join('\n')
|
|
136
|
+
}
|
|
137
|
+
console.log(rendered)
|
|
138
|
+
if (hiddenClippedCount > 0) {
|
|
139
|
+
console.log(
|
|
140
|
+
` ${hiddenClippedCount} clipped row(s) hidden (omit --no-clipped to see)`,
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const anyFilterActive = filter || testIdLike || effectiveOnlyGlob || subtreeRoot
|
|
145
|
+
const searchFilterActive = filter || testIdLike || effectiveOnlyGlob
|
|
146
|
+
if (searchFilterActive && !watch) {
|
|
147
|
+
maybeHint('describe-filter-context')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// when no filter was active and the tree got long, nudge once per
|
|
151
|
+
// session toward the narrowing flags — this is exactly the case the
|
|
152
|
+
// user flagged where "which row is the swap sheet" got buried under
|
|
153
|
+
// 100+ lines of home content.
|
|
154
|
+
if (!anyFilterActive && !watch) {
|
|
155
|
+
const lineCount = tree.split('\n').length
|
|
156
|
+
if (lineCount >= 80) {
|
|
157
|
+
maybeHint('describe-use-filters')
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// keyboard summary — only when visible. rendered as a footer so it
|
|
162
|
+
// doesn't bury the tree on forms with non-trivial UI above the input.
|
|
163
|
+
if (keyboard && keyboard.visible) {
|
|
164
|
+
const spec = keyboard.spec
|
|
165
|
+
const kbParts = [
|
|
166
|
+
spec?.keyboardType ? `type=${spec.keyboardType}` : null,
|
|
167
|
+
spec?.returnKeyType && spec.returnKeyType !== 'default'
|
|
168
|
+
? `return=${spec.returnKeyType}`
|
|
169
|
+
: null,
|
|
170
|
+
keyboard.mode !== 'letters' ? `mode=${keyboard.mode}` : null,
|
|
171
|
+
keyboard.shifted ? 'shift' : null,
|
|
172
|
+
keyboard.capsLock ? 'caps' : null,
|
|
173
|
+
spec?.autoCapitalize && spec.autoCapitalize !== 'sentences'
|
|
174
|
+
? `autoCap=${spec.autoCapitalize}`
|
|
175
|
+
: null,
|
|
176
|
+
keyboard.accessoryBarId ? `accessory=${keyboard.accessoryBarId}` : null,
|
|
177
|
+
].filter(Boolean)
|
|
178
|
+
console.log(`\n keyboard: ${kbParts.join(' ') || 'visible'}`)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (watch) {
|
|
183
|
+
console.log(' watching... (Ctrl+C to stop)\n')
|
|
184
|
+
while (true) {
|
|
185
|
+
console.clear()
|
|
186
|
+
await runOnce()
|
|
187
|
+
await sleep(watchInterval)
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
await runOnce()
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// env vars that indicate the CLI is being driven by an agent/automation rather
|
|
2
|
+
// than a human at a terminal. when any is present, auto-waits get a longer
|
|
3
|
+
// budget and log more aggressively, because an agent can't see mid-animation
|
|
4
|
+
// flicker the way a human can.
|
|
5
|
+
const AGENT_ENV_VARS = [
|
|
6
|
+
'SOOTSIM_AGENT',
|
|
7
|
+
'TM_SESSION',
|
|
8
|
+
'CLAUDECODE',
|
|
9
|
+
'CLAUDE_CODE_ENTRYPOINT',
|
|
10
|
+
'CLAUDE_CODE_SESSION_ID',
|
|
11
|
+
'CODEX_THREAD_ID',
|
|
12
|
+
'CURSOR_TRACE_ID',
|
|
13
|
+
'AIDER_MODEL',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
export function isAgentEnv(): boolean {
|
|
17
|
+
if (process.env.SOOTSIM_AGENT === '0') return false
|
|
18
|
+
for (const name of AGENT_ENV_VARS) {
|
|
19
|
+
const v = process.env[name]
|
|
20
|
+
if (v && v.trim() && v !== '0') return true
|
|
21
|
+
}
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { maybeHint } from '../../hints'
|
|
2
|
+
import { inspectFind, rankInteractive, tapCommandForNode } from './core'
|
|
3
|
+
import { printJson, wantsJson } from './shared'
|
|
4
|
+
import type { WsBridge } from '../../ws-bridge'
|
|
5
|
+
|
|
6
|
+
// unified finder: one verb for text/testid/role/type/pressable/visible.
|
|
7
|
+
// --testid (not --id) keeps element lookup distinct from target selection.
|
|
8
|
+
// mode resolution + the evaluated JS live in inspect/core.ts (shared with the
|
|
9
|
+
// agent); this handler only parses flags and formats the result for the CLI.
|
|
10
|
+
export async function runFindSubcommand(opts: {
|
|
11
|
+
bridge: WsBridge
|
|
12
|
+
args: string[]
|
|
13
|
+
effectiveArgs: string[]
|
|
14
|
+
positional: string[]
|
|
15
|
+
inspectUsage: (name: string, tail: string) => string
|
|
16
|
+
}): Promise<void> {
|
|
17
|
+
const { bridge, args, effectiveArgs, positional, inspectUsage } = opts
|
|
18
|
+
|
|
19
|
+
const findFlag = (name: string): string | null => {
|
|
20
|
+
const i = effectiveArgs.indexOf(name)
|
|
21
|
+
return i >= 0 && i + 1 < effectiveArgs.length ? effectiveArgs[i + 1] : null
|
|
22
|
+
}
|
|
23
|
+
const hasFlag = (name: string): boolean => effectiveArgs.includes(name)
|
|
24
|
+
|
|
25
|
+
const testId = findFlag('--testid') || findFlag('--test-id')
|
|
26
|
+
const role = findFlag('--role')
|
|
27
|
+
const typeFlag = findFlag('--type')
|
|
28
|
+
const textFlag = findFlag('--text')
|
|
29
|
+
const pressable = hasFlag('--pressable')
|
|
30
|
+
const visible = hasFlag('--visible')
|
|
31
|
+
// ranked-tappable list — same data source as --pressable, but the
|
|
32
|
+
// output prioritises high-signal targets the agent likely wants to
|
|
33
|
+
// tap next (visible, has text or testID, has accessibilityLabel),
|
|
34
|
+
// and emits a copy-paste `sootsim do` command per row so the agent
|
|
35
|
+
// doesn't have to re-form a tap from raw fields.
|
|
36
|
+
const interactive = hasFlag('--interactive-targets') || hasFlag('--actions')
|
|
37
|
+
const bareText =
|
|
38
|
+
!testId && !role && !typeFlag && !textFlag && !pressable && !visible && !interactive
|
|
39
|
+
? positional[1]
|
|
40
|
+
: null
|
|
41
|
+
const textQuery = textFlag ?? bareText
|
|
42
|
+
|
|
43
|
+
const found = await inspectFind(bridge, {
|
|
44
|
+
testId,
|
|
45
|
+
role,
|
|
46
|
+
type: typeFlag,
|
|
47
|
+
text: textQuery,
|
|
48
|
+
pressable,
|
|
49
|
+
visible,
|
|
50
|
+
interactive,
|
|
51
|
+
})
|
|
52
|
+
if (!found) {
|
|
53
|
+
console.error(
|
|
54
|
+
inspectUsage(
|
|
55
|
+
'find',
|
|
56
|
+
'<text> | --text <t> | --testid <id> | --role <r> | --type <t> | --pressable | --visible | --interactive-targets',
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
const { mode, result } = found
|
|
62
|
+
const jsonOut = wantsJson(args)
|
|
63
|
+
// --verbose / --dump: emit full node JSON per result instead of just the
|
|
64
|
+
// one-line summary. the default summary is compact for scannability; when
|
|
65
|
+
// you already know the match and need to copy testID / layout / a11y
|
|
66
|
+
// props, --verbose saves the round-trip through `get node <id>`.
|
|
67
|
+
const verbose = args.includes('--verbose') || args.includes('--dump')
|
|
68
|
+
|
|
69
|
+
if (jsonOut) {
|
|
70
|
+
if (mode === 'interactive-targets' && Array.isArray(result)) {
|
|
71
|
+
// include the `tap` command per row when emitting JSON for agents
|
|
72
|
+
// — the human renderer does the same. tooling that drives sootsim
|
|
73
|
+
// can pick the highest-scored entry and execute its `tap` field.
|
|
74
|
+
printJson(rankInteractive(result).map((n) => ({ ...n, tap: tapCommandForNode(n) })))
|
|
75
|
+
} else {
|
|
76
|
+
printJson(result ?? null)
|
|
77
|
+
}
|
|
78
|
+
} else if (Array.isArray(result)) {
|
|
79
|
+
if (result.length === 0) {
|
|
80
|
+
console.log(` no ${mode} nodes found`)
|
|
81
|
+
const nodeCount = await bridge.send({
|
|
82
|
+
type: 'evaluate',
|
|
83
|
+
code: '(async () => (await window.__sootsimTest?.getNodeCount?.()) || 0)()',
|
|
84
|
+
})
|
|
85
|
+
if (typeof nodeCount === 'number' && nodeCount < 10) {
|
|
86
|
+
maybeHint('app-still-loading', nodeCount)
|
|
87
|
+
}
|
|
88
|
+
} else if (mode === 'interactive-targets') {
|
|
89
|
+
const ranked = rankInteractive(result)
|
|
90
|
+
console.log(
|
|
91
|
+
` found ${ranked.length} interactive target${ranked.length === 1 ? '' : 's'} (sorted by score):`,
|
|
92
|
+
)
|
|
93
|
+
for (const n of ranked.slice(0, 20)) {
|
|
94
|
+
const loc = n.absolutePosition
|
|
95
|
+
? `@(${Math.round(n.absolutePosition.x)},${Math.round(n.absolutePosition.y)})`
|
|
96
|
+
: ''
|
|
97
|
+
const size = n.layout
|
|
98
|
+
? `${Math.round(n.layout.width)}x${Math.round(n.layout.height)}`
|
|
99
|
+
: '?x?'
|
|
100
|
+
const text = n.text ? ` "${n.text.slice(0, 30)}"` : ''
|
|
101
|
+
const tid = n.testID ? ` #${n.testID}` : ''
|
|
102
|
+
const label = n.accessibilityLabel
|
|
103
|
+
? ` ⓘ"${String(n.accessibilityLabel).slice(0, 24)}"`
|
|
104
|
+
: ''
|
|
105
|
+
const roleName = n.accessibilityRole ? `[${n.accessibilityRole}]` : n.type
|
|
106
|
+
const tapCmd = tapCommandForNode(n)
|
|
107
|
+
console.log(` ${roleName}${text}${label}${tid} ${size} ${loc}`)
|
|
108
|
+
console.log(` → ${tapCmd}`)
|
|
109
|
+
if (verbose) console.log(indent(JSON.stringify(n, null, 2), ' '))
|
|
110
|
+
}
|
|
111
|
+
if (ranked.length > 20) console.log(` ... and ${ranked.length - 20} more`)
|
|
112
|
+
} else {
|
|
113
|
+
console.log(
|
|
114
|
+
` found ${result.length} node${result.length === 1 ? '' : 's'} (${mode}):`,
|
|
115
|
+
)
|
|
116
|
+
for (const n of result.slice(0, 20)) {
|
|
117
|
+
const loc = n.absolutePosition
|
|
118
|
+
? `@(${Math.round(n.absolutePosition.x)},${Math.round(n.absolutePosition.y)})`
|
|
119
|
+
: ''
|
|
120
|
+
const size = n.layout
|
|
121
|
+
? `${Math.round(n.layout.width)}x${Math.round(n.layout.height)}`
|
|
122
|
+
: '?x?'
|
|
123
|
+
const text = n.text ? ` "${n.text.slice(0, 30)}"` : ''
|
|
124
|
+
const tid = n.testID ? ` #${n.testID}` : ''
|
|
125
|
+
const tap = n.pressable ? ' (tap)' : ''
|
|
126
|
+
const roleName = n.accessibilityRole ? `[${n.accessibilityRole}]` : n.type
|
|
127
|
+
console.log(` ${roleName}${text}${tid} ${size} ${loc}${tap}`)
|
|
128
|
+
if (verbose) console.log(indent(JSON.stringify(n, null, 2), ' '))
|
|
129
|
+
}
|
|
130
|
+
if (result.length > 20) console.log(` ... and ${result.length - 20} more`)
|
|
131
|
+
}
|
|
132
|
+
} else if (result == null) {
|
|
133
|
+
const query = textQuery || testId || role || typeFlag || ''
|
|
134
|
+
console.log(` not found: ${query || mode}`)
|
|
135
|
+
if (testId) {
|
|
136
|
+
maybeHint('wait-selector-for-missing-testid', testId)
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
interface InspectNode {
|
|
140
|
+
type?: string
|
|
141
|
+
absolutePosition?: { x: number; y: number }
|
|
142
|
+
layout?: { width: number; height: number }
|
|
143
|
+
text?: string
|
|
144
|
+
testID?: string
|
|
145
|
+
pressable?: boolean
|
|
146
|
+
accessibilityRole?: string
|
|
147
|
+
}
|
|
148
|
+
const n = result as InspectNode
|
|
149
|
+
if (n.type && n.absolutePosition) {
|
|
150
|
+
const loc = `@(${Math.round(n.absolutePosition.x)},${Math.round(n.absolutePosition.y)})`
|
|
151
|
+
const size = n.layout
|
|
152
|
+
? `${Math.round(n.layout.width)}x${Math.round(n.layout.height)}`
|
|
153
|
+
: '?x?'
|
|
154
|
+
const text = n.text ? ` "${n.text.slice(0, 40)}"` : ''
|
|
155
|
+
const tid = n.testID ? ` #${n.testID}` : ''
|
|
156
|
+
const tap = n.pressable ? ' (tap)' : ''
|
|
157
|
+
const roleName = n.accessibilityRole ? `[${n.accessibilityRole}]` : n.type
|
|
158
|
+
console.log(` ${roleName}${text}${tid} ${size} ${loc}${tap}`)
|
|
159
|
+
if (verbose) console.log(indent(JSON.stringify(n, null, 2), ' '))
|
|
160
|
+
} else {
|
|
161
|
+
console.log(JSON.stringify(result, null, 2))
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function indent(text: string, prefix: string): string {
|
|
167
|
+
return text
|
|
168
|
+
.split('\n')
|
|
169
|
+
.map((line) => prefix + line)
|
|
170
|
+
.join('\n')
|
|
171
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// `sootsim get-layout` — bounding boxes for visible meaningful elements on the
|
|
2
|
+
// current screen. focused on the layout numbers a designer checks: on-screen
|
|
3
|
+
// box (x/y/w/h). pass --styling for computed borderRadius, per-side padding,
|
|
4
|
+
// fontSize, visual style fields, and native rendered text contrast. pass
|
|
5
|
+
// --filter <text> to narrow to the elements whose text/testID/role/selector
|
|
6
|
+
// match — fewer rows, full detail on each, which is how you read one area's
|
|
7
|
+
// styling without the whole screen.
|
|
8
|
+
//
|
|
9
|
+
// the extraction + formatting live in the shared `./core` kernel
|
|
10
|
+
// (inspectGetLayout / formatLayoutElements) so this CLI command and the agent's
|
|
11
|
+
// app_get_layout tool produce identical output. the CLI-side job is just:
|
|
12
|
+
// optional preflight for in-flight screen transitions, call the kernel, print.
|
|
13
|
+
|
|
14
|
+
import { formatLayoutElements, inspectGetLayout } from './core'
|
|
15
|
+
import { maybeWaitForStartedScreenTransitions, printJson, wantsJson } from './shared'
|
|
16
|
+
import type { WsBridge } from '../../ws-bridge'
|
|
17
|
+
|
|
18
|
+
export async function runGetLayoutSubcommand(opts: {
|
|
19
|
+
bridge: WsBridge
|
|
20
|
+
args: string[]
|
|
21
|
+
}): Promise<void> {
|
|
22
|
+
const { bridge, args } = opts
|
|
23
|
+
const jsonOut = wantsJson(args)
|
|
24
|
+
await maybeWaitForStartedScreenTransitions(bridge, { verbose: !jsonOut })
|
|
25
|
+
const styling = args.includes('--styling')
|
|
26
|
+
const filterIndex = args.indexOf('--filter')
|
|
27
|
+
const filter = filterIndex >= 0 ? args[filterIndex + 1] : undefined
|
|
28
|
+
const elements = await inspectGetLayout(bridge, { styling, filter })
|
|
29
|
+
if (jsonOut) {
|
|
30
|
+
printJson({ count: elements.length, elements })
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
console.log(
|
|
34
|
+
` layout (${elements.length} element${elements.length === 1 ? '' : 's'}):\n`,
|
|
35
|
+
)
|
|
36
|
+
// styling fields (including native rendered text contrast) are opt-in via
|
|
37
|
+
// --styling so the default inspect output stays scannable.
|
|
38
|
+
console.log(formatLayoutElements(elements, { styling }))
|
|
39
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// `sootsim get keyboard` — prints the current iOS keyboard's full state.
|
|
2
|
+
//
|
|
3
|
+
// shows visibility, keyboardType, returnKeyType, autoCapitalize policy,
|
|
4
|
+
// active QWERTY sub-mode, shift + capsLock, and the mounted accessory bar
|
|
5
|
+
// id (if any). reads from `__sootsimKeyboard.getLayout()`, which is
|
|
6
|
+
// populated by shell-worker `keyboard.layout` broadcasts.
|
|
7
|
+
|
|
8
|
+
import { inspectKeyboard } from './core'
|
|
9
|
+
import type { WsBridge } from '../../ws-bridge'
|
|
10
|
+
|
|
11
|
+
export async function runKeyboardSubcommand(
|
|
12
|
+
bridge: WsBridge,
|
|
13
|
+
opts: { json?: boolean } = {},
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const result = await inspectKeyboard(bridge)
|
|
16
|
+
|
|
17
|
+
if ('error' in result) {
|
|
18
|
+
console.error(result.error)
|
|
19
|
+
process.exit(1)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (opts.json) {
|
|
23
|
+
console.log(JSON.stringify(result, null, 2))
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { visible, spec, mode, shifted, capsLock, accessoryBarId } = result
|
|
28
|
+
const lines: string[] = []
|
|
29
|
+
lines.push(`keyboard: ${visible ? 'visible' : 'hidden'}`)
|
|
30
|
+
if (spec) {
|
|
31
|
+
lines.push(` type: ${spec.keyboardType}`)
|
|
32
|
+
lines.push(` returnKey: ${spec.returnKeyType}`)
|
|
33
|
+
lines.push(` autoCap: ${spec.autoCapitalize}`)
|
|
34
|
+
lines.push(` autoCorrect: ${spec.autoCorrect ? 'on' : 'off'}`)
|
|
35
|
+
lines.push(` appearance: ${spec.keyboardAppearance}`)
|
|
36
|
+
if (spec.secureTextEntry) lines.push(` secureTextEntry: true`)
|
|
37
|
+
if (spec.enablesReturnKeyAutomatically) {
|
|
38
|
+
lines.push(
|
|
39
|
+
` return: ${spec.currentTextIsEmpty ? 'disabled (empty)' : 'enabled'}`,
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
lines.push(` spec: <none> (shown via dev-tools with no TextInput)`)
|
|
44
|
+
}
|
|
45
|
+
lines.push(
|
|
46
|
+
` mode: ${mode}${shifted ? ' (shifted)' : ''}${capsLock ? ' (caps)' : ''}`,
|
|
47
|
+
)
|
|
48
|
+
if (accessoryBarId) {
|
|
49
|
+
lines.push(` accessoryBar: ${accessoryBarId}`)
|
|
50
|
+
}
|
|
51
|
+
console.log(lines.join('\n'))
|
|
52
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// `sootsim list` — connected sims, with the active one marked.
|
|
2
|
+
|
|
3
|
+
import { printConnectedSims } from '../control'
|
|
4
|
+
import { printJson, wantsJson } from './shared'
|
|
5
|
+
import type { WsBridge } from '../../ws-bridge'
|
|
6
|
+
|
|
7
|
+
export async function runListSubcommand(opts: {
|
|
8
|
+
bridge: WsBridge
|
|
9
|
+
simId: string | undefined
|
|
10
|
+
args: string[]
|
|
11
|
+
}): Promise<void> {
|
|
12
|
+
const sims = await opts.bridge.listSims()
|
|
13
|
+
|
|
14
|
+
// filter sims:
|
|
15
|
+
// --all show every sim including empty shells (default
|
|
16
|
+
// hides sims that have no bundle URL — they're
|
|
17
|
+
// usually idle host shells that overflow the
|
|
18
|
+
// list when several demos are open at once)
|
|
19
|
+
// --bundle <substring> only sims whose loaded url contains the substring
|
|
20
|
+
// --app-port <port> shorthand for `--bundle :<port>/`. NOT `--port`
|
|
21
|
+
// which is taken by the global bridge-port flag
|
|
22
|
+
// (see ws-bridge.ts parseBridgeCliArgs) — using
|
|
23
|
+
// --port here used to silently set the bridge
|
|
24
|
+
// port too, sending the WS connect to the wrong
|
|
25
|
+
// place and failing with "daemon is not running"
|
|
26
|
+
// --primary only the primary sim
|
|
27
|
+
const showAll = opts.args.includes('--all')
|
|
28
|
+
const bundleArg = opts.args.find((_, i) => opts.args[i - 1] === '--bundle')
|
|
29
|
+
const portArg = opts.args.find((_, i) => opts.args[i - 1] === '--app-port')
|
|
30
|
+
const primaryOnly = opts.args.includes('--primary')
|
|
31
|
+
const filtered = sims.filter((sim) => {
|
|
32
|
+
if (primaryOnly && !sim.isPrimary) return false
|
|
33
|
+
if (bundleArg && !(sim.url ?? '').includes(bundleArg)) return false
|
|
34
|
+
// a sim url looks like `http://localhost:5173/rn/8085?bundle=...` where
|
|
35
|
+
// `8085` is the metro/vite port the app is loaded from. the bundle url
|
|
36
|
+
// inside the `?bundle=` param is itself percent-encoded (`:8085/` ->
|
|
37
|
+
// `%3A8085%2F`), so the previous literal `:${port}/` substring check
|
|
38
|
+
// never matched. match the canonical `/rn/<port>` path segment instead.
|
|
39
|
+
if (portArg && !(sim.url ?? '').includes(`/rn/${portArg}`)) return false
|
|
40
|
+
if (!showAll && !bundleArg && !portArg && !primaryOnly) {
|
|
41
|
+
// hide host shells (no bundle loaded — only origin) unless they're
|
|
42
|
+
// the currently-selected sim
|
|
43
|
+
const hasBundle =
|
|
44
|
+
sim.url && (sim.url.includes('bundle=') || sim.url.includes('/index.bundle'))
|
|
45
|
+
if (!hasBundle && sim.id !== opts.simId) return false
|
|
46
|
+
}
|
|
47
|
+
return true
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
if (wantsJson(opts.args)) {
|
|
51
|
+
printJson(filtered.map((b) => ({ ...b, active: b.id === opts.simId })))
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
printConnectedSims(filtered, opts.simId)
|
|
55
|
+
if (filtered.length < sims.length && !wantsJson(opts.args)) {
|
|
56
|
+
console.log(` (${sims.length - filtered.length} more hidden — pass --all to show)`)
|
|
57
|
+
}
|
|
58
|
+
}
|