rnxsim 0.1.366 → 0.1.367
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/README.md +14 -2
- package/cli/bin.ts +18 -0
- package/cli/bridge-flow-runner.ts +14 -151
- package/cli/commands/assert.ts +1 -1
- package/cli/commands/box/checkout-plane.ts +126 -0
- package/cli/commands/box/rnx-command.ts +68 -0
- package/cli/commands/box.ts +200 -0
- package/cli/commands/control.ts +12 -32
- package/cli/commands/do-chain.ts +412 -0
- package/cli/commands/flow.ts +0 -10
- package/cli/commands/inspect/core.ts +302 -725
- package/cli/commands/inspect/describe.ts +3 -76
- package/cli/commands/inspect/find.ts +3 -6
- package/cli/commands/inspect/get-layout.ts +14 -1
- package/cli/commands/inspect/resolve-target.ts +41 -47
- package/cli/commands/inspect/shared.ts +28 -123
- package/cli/commands/inspect/tree.ts +6 -15
- package/cli/commands/inspect/wait-ready.ts +2 -2
- package/cli/commands/inspect/wait-selector.ts +4 -8
- package/cli/commands/inspect.ts +23 -17
- package/cli/commands/install-desktop.ts +44 -7
- package/cli/commands/maestro-generate.ts +0 -2
- package/cli/commands/platform.ts +68 -0
- package/cli/commands/state.ts +5 -2
- package/cli/outbound-endpoints.ts +6 -1
- package/cli/parse-args.ts +13 -2
- package/cli/ws-bridge.ts +30 -2
- package/detox/index.ts +34 -17
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-SO6IOPRK.js → agent-EKKIAKP5.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-PMYCB2KO.js → agent-wrapper-MHLQUGA6.js} +2 -2
- package/dist-cli/chunks/{app-fonts-LULQGFY7.js → app-fonts-HN6ADRTQ.js} +2 -2
- package/dist-cli/chunks/{assert-WGNCD4ZU.js → assert-IMR6QCN6.js} +3 -3
- package/dist-cli/chunks/{auth-G7CCHH3M.js → auth-27LPMQNH.js} +2 -2
- package/dist-cli/chunks/{beta-N4LXL5TO.js → beta-6RXBB5AI.js} +2 -2
- package/dist-cli/chunks/box-DORLF3GP.js +1709 -0
- package/dist-cli/chunks/{camera-4JHCYBQS.js → camera-2G7JZ5DL.js} +2 -2
- package/dist-cli/chunks/{chunk-2UXU53MG.js → chunk-3QZJLNYY.js} +1 -1
- package/dist-cli/chunks/{chunk-YXXKSL6R.js → chunk-3VBVEDDA.js} +1 -1
- package/dist-cli/chunks/{chunk-ZJIS3CTW.js → chunk-4BWGLTKU.js} +2 -2
- package/dist-cli/chunks/chunk-4XSNO374.js +423 -0
- package/dist-cli/chunks/{chunk-RYIMSKDO.js → chunk-5DGMFUZG.js} +2 -2
- package/dist-cli/chunks/{chunk-M3BNCF7E.js → chunk-6DBS5EGX.js} +2 -2
- package/dist-cli/chunks/{chunk-ONORMKWX.js → chunk-6SO6I3D5.js} +2 -2
- package/dist-cli/chunks/chunk-75EFOPXE.js +32 -0
- package/dist-cli/chunks/{chunk-5NMCF5VF.js → chunk-A5AJFVOF.js} +1 -1
- package/dist-cli/chunks/{chunk-N3UC7HIX.js → chunk-AM2ODYAN.js} +1 -1
- package/dist-cli/chunks/{chunk-I6KIM6PN.js → chunk-AVOROGA3.js} +1 -1
- package/dist-cli/chunks/chunk-AXMMLO7D.js +130 -0
- package/dist-cli/chunks/{chunk-ORWP2E7R.js → chunk-BSEXEYNX.js} +2 -2
- package/dist-cli/chunks/{chunk-NQFJOCSF.js → chunk-CM6ZKZW7.js} +2 -2
- package/dist-cli/chunks/{chunk-ZMJ75EDG.js → chunk-DNVABCEC.js} +2 -2
- package/dist-cli/chunks/{chunk-2S7BKUP2.js → chunk-E33A4AIR.js} +2 -2
- package/dist-cli/chunks/{chunk-GXAB52K7.js → chunk-EFMOXYDD.js} +2 -2
- package/dist-cli/chunks/{chunk-TIAEYO6I.js → chunk-EV4P5UBP.js} +2 -2
- package/dist-cli/chunks/{chunk-JHXETDVC.js → chunk-G5IUIGGC.js} +1 -1
- package/dist-cli/chunks/chunk-GMLOCV3E.js +312 -0
- package/dist-cli/chunks/{chunk-ZLVRYXZ7.js → chunk-GRN2FHVW.js} +1 -1
- package/dist-cli/chunks/{chunk-A5FXX5PJ.js → chunk-HFFXPUEP.js} +1 -1
- package/dist-cli/chunks/chunk-HG6T4OOR.js +4 -0
- package/dist-cli/chunks/{chunk-OW5XHAA4.js → chunk-HNT2OXPZ.js} +1 -1
- package/dist-cli/chunks/chunk-IPEURNLI.js +24 -0
- package/dist-cli/chunks/chunk-IR44SFBA.js +5 -0
- package/dist-cli/chunks/chunk-JMSLXZFW.js +4 -0
- package/dist-cli/chunks/{chunk-EFR5DVNP.js → chunk-JPXQKLNH.js} +1 -1
- package/dist-cli/chunks/chunk-K3EC6JDE.js +4 -0
- package/dist-cli/chunks/{chunk-YGMEDJKJ.js → chunk-KHX65HAU.js} +2 -2
- package/dist-cli/chunks/{chunk-EQK5MJCU.js → chunk-LWOVKHRC.js} +2 -2
- package/dist-cli/chunks/{chunk-5OK3LCEJ.js → chunk-MQN5DWTI.js} +2 -2
- package/dist-cli/chunks/{chunk-TTASWLKM.js → chunk-N3BXQEUF.js} +1 -1
- package/dist-cli/chunks/{chunk-UCHW7ZHR.js → chunk-N5OSV26B.js} +2 -2
- package/dist-cli/chunks/chunk-PHNURIH3.js +4 -0
- package/dist-cli/chunks/{chunk-AFV32WZT.js → chunk-PP2KNVXP.js} +3 -3
- package/dist-cli/chunks/{chunk-XJU2QIRY.js → chunk-PROKNJVY.js} +2 -2
- package/dist-cli/chunks/chunk-QXLLY4IM.js +7 -0
- package/dist-cli/chunks/{chunk-HT6GFZOZ.js → chunk-RNQTOGM7.js} +1 -1
- package/dist-cli/chunks/{chunk-CN6XYYEA.js → chunk-RPNKKVBN.js} +27 -27
- package/dist-cli/chunks/{chunk-LN3RNW6X.js → chunk-RSNLI2NH.js} +2 -2
- package/dist-cli/chunks/{chunk-YCCCPFWT.js → chunk-RZGHV2WI.js} +2 -2
- package/dist-cli/chunks/chunk-SGGL3AFV.js +497 -0
- package/dist-cli/chunks/chunk-SH5ETJKG.js +4 -0
- package/dist-cli/chunks/{chunk-77ZHQJZR.js → chunk-T6J4YP56.js} +2 -2
- package/dist-cli/chunks/{chunk-MGDSJZ37.js → chunk-TWUE7WF5.js} +2 -2
- package/dist-cli/chunks/{chunk-SA6XVI55.js → chunk-U44VEQRN.js} +1 -1
- package/dist-cli/chunks/{chunk-IZKHOM37.js → chunk-URLVQHC3.js} +1 -1
- package/dist-cli/chunks/{chunk-GAJQSITE.js → chunk-VANZFTUE.js} +1 -1
- package/dist-cli/chunks/{chunk-EGAGZHO6.js → chunk-VB2YBH46.js} +1 -1
- package/dist-cli/chunks/{chunk-H23KWIKA.js → chunk-VFU2SJ7M.js} +2 -2
- package/dist-cli/chunks/{chunk-34IQM22N.js → chunk-VUBJ6FXU.js} +15 -15
- package/dist-cli/chunks/{chunk-NM2L2QV4.js → chunk-W5PATILQ.js} +2 -2
- package/dist-cli/chunks/chunk-WI5V4TYG.js +38 -0
- package/dist-cli/chunks/{chunk-6PL2CCGP.js → chunk-XTRHQAOV.js} +2 -2
- package/dist-cli/chunks/chunk-XWUTJLBM.js +27 -0
- package/dist-cli/chunks/{chunk-5DAIA77C.js → chunk-XYRUQQUR.js} +2 -2
- package/dist-cli/chunks/chunk-YBFB3RM7.js +4 -0
- package/dist-cli/chunks/chunk-YGQVQ2VG.js +4 -0
- package/dist-cli/chunks/{chunk-CTG7HBCA.js → chunk-YINACV32.js} +1 -1
- package/dist-cli/chunks/chunk-YV7ERO2V.js +5 -0
- package/dist-cli/chunks/{cleanup-KRIATKFH.js → cleanup-GVFHQ3KH.js} +2 -2
- package/dist-cli/chunks/cli-version-DPX5MA2M.js +4 -0
- package/dist-cli/chunks/{compat-6PTQ3QEA.js → compat-AYRTXYAM.js} +2 -2
- package/dist-cli/chunks/{config-ETGDUANO.js → config-K7LOOV62.js} +2 -2
- package/dist-cli/chunks/connect-H3CNY4IO.js +4 -0
- package/dist-cli/chunks/control-SMHZ2CF5.js +4 -0
- package/dist-cli/chunks/daemon-TEZUNI73.js +4 -0
- package/dist-cli/chunks/{debug-WIIB6ZKC.js → debug-ELGHG57T.js} +3 -3
- package/dist-cli/chunks/{desktop-TD7Q5LNB.js → desktop-4G7FK3MP.js} +3 -3
- package/dist-cli/chunks/{detox-7GNR33VM.js → detox-JKXGEJIZ.js} +2 -2
- package/dist-cli/chunks/{device-QOR3PGUF.js → device-IV3AR6KG.js} +2 -2
- package/dist-cli/chunks/{diagnose-RRCBLP6E.js → diagnose-3ERGF7EW.js} +2 -2
- package/dist-cli/chunks/{disk-cleanup-MJUGJPXT.js → disk-cleanup-ALFEVZ4B.js} +2 -2
- package/dist-cli/chunks/do-chain-K2B4OHM7.js +5 -0
- package/dist-cli/chunks/drivers-KJYXA3M3.js +4 -0
- package/dist-cli/chunks/env-2KF6IYZE.js +4 -0
- package/dist-cli/chunks/{film-D7Y64LID.js → film-UQXJFAD5.js} +3 -3
- package/dist-cli/chunks/flow-ASTUH56E.js +4 -0
- package/dist-cli/chunks/help-SNNYFELA.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-JGURVR2Z.js → hidden-runtime-alias-VJCWCFVO.js} +2 -2
- package/dist-cli/chunks/{home-paths-4ICFIGXO.js → home-paths-EGUZZ6J6.js} +2 -2
- package/dist-cli/chunks/inspect-XANAADQT.js +4 -0
- package/dist-cli/chunks/install-desktop-JKQA7UY6.js +4 -0
- package/dist-cli/chunks/{login-RU33QR2G.js → login-6KQGD7HK.js} +2 -2
- package/dist-cli/chunks/{logout-2IUG3Y5J.js → logout-2FCOVLBC.js} +2 -2
- package/dist-cli/chunks/{maestro-2MXTGG7I.js → maestro-HPSSEVJD.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-2PYIFGLR.js → maestro-generate-KQQ6JHMR.js} +5 -5
- package/dist-cli/chunks/{mode-3DAPG3HF.js → mode-SH7KRZET.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-BWSDJF2Z.js → optional-demo-registry-NAMCAVFX.js} +2 -2
- package/dist-cli/chunks/{perf-2TKPWB4N.js → perf-LWKAHXUL.js} +2 -2
- package/dist-cli/chunks/{permissions-5CSMHYNA.js → permissions-EZTKWTXV.js} +2 -2
- package/dist-cli/chunks/platform-KSOJIVN5.js +6 -0
- package/dist-cli/chunks/privacy-EJULWP7L.js +4 -0
- package/dist-cli/chunks/{record-I2HJCFP3.js → record-2IC7BAQN.js} +3 -3
- package/dist-cli/chunks/{report-issue-A4FZWBOC.js → report-issue-MMX6HOQO.js} +2 -2
- package/dist-cli/chunks/reset-PKFZA4NC.js +4 -0
- package/dist-cli/chunks/resolve-target-AFKU3BRE.js +4 -0
- package/dist-cli/chunks/runtime-XHWWPOIN.js +4 -0
- package/dist-cli/chunks/{screenshot-command-UFW5BFHP.js → screenshot-command-JYUXDVSA.js} +3 -3
- package/dist-cli/chunks/{screenshot-layers-5BIAL4YN.js → screenshot-layers-OGRY6AA3.js} +2 -2
- package/dist-cli/chunks/{screenshots-capture-EHYS3BV2.js → screenshots-capture-QHDN65XS.js} +2 -2
- package/dist-cli/chunks/{serve-O4FDM3AC.js → serve-W4BKAG4J.js} +3 -3
- package/dist-cli/chunks/setup-WJPMPSAC.js +4 -0
- package/dist-cli/chunks/sim-client-UOQGFBPO.js +4 -0
- package/dist-cli/chunks/{skills-ZK55SY32.js → skills-OPPXOD63.js} +2 -2
- package/dist-cli/chunks/state-TDAN7BOM.js +14 -0
- package/dist-cli/chunks/{storage-WP33ISEM.js → storage-CFB2YGLY.js} +2 -2
- package/dist-cli/chunks/store-4TEED4ZU.js +4 -0
- package/dist-cli/chunks/telemetry-S6N7CV6G.js +4 -0
- package/dist-cli/chunks/{timeline-DKGW5S4I.js → timeline-ASOOVP4C.js} +2 -2
- package/dist-cli/chunks/{upgrade-LL7G4B7G.js → upgrade-6MVJISHG.js} +2 -2
- package/dist-cli/chunks/upload-CBNIRFEW.js +4 -0
- package/dist-cli/chunks/{version-YLWR5G4F.js → version-5GS7CWRL.js} +2 -2
- package/dist-cli/chunks/{web-QELZ4CP6.js → web-QRRMYMV4.js} +2 -2
- package/dist-cli/chunks/welcome-33FXMAMB.js +5 -0
- package/dist-cli/chunks/{what-happened-MIOMZWM5.js → what-happened-GAZJGKXX.js} +2 -2
- package/dist-cli/chunks/ws-bridge-DIXUAPLQ.js +4 -0
- 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-input.cjs +336 -0
- package/dist-lib/bridge-contract-input.mjs +303 -0
- package/dist-lib/bridge-contract.cjs +29 -1
- package/dist-lib/bridge-contract.mjs +19 -0
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +32 -18
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +5 -1
- 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/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +214 -594
- 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-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.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/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +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 +273 -657
- package/dist-lib/sdk.mjs +271 -652
- package/dist-lib/skills.cjs +190 -287
- package/dist-lib/vite.cjs +1 -1
- package/package.json +9 -6
- package/skills/contrast/SKILL.md +3 -0
- package/skills/rnx-debug/SKILL.md +8 -0
- package/skills/rnx-test/SKILL.md +21 -0
- package/src/bridge-contract-input.ts +359 -0
- package/src/bridge-contract.ts +121 -1
- package/src/host/bridge-host.ts +7 -12
- package/src/index.ts +7 -0
- package/src/sim-client.ts +12 -3
- package/dist-cli/chunks/chunk-3X6IBUBX.js +0 -38
- package/dist-cli/chunks/chunk-7B37IZBN.js +0 -15
- package/dist-cli/chunks/chunk-DMPF2D24.js +0 -4
- package/dist-cli/chunks/chunk-DW37EVX5.js +0 -767
- package/dist-cli/chunks/chunk-DWFIH7CB.js +0 -32
- package/dist-cli/chunks/chunk-GUI3PDH2.js +0 -27
- package/dist-cli/chunks/chunk-KLF3K6ZZ.js +0 -4
- package/dist-cli/chunks/chunk-M5QCGLXR.js +0 -944
- package/dist-cli/chunks/chunk-M6SKQGAN.js +0 -5
- package/dist-cli/chunks/chunk-NAYLPOC5.js +0 -24
- package/dist-cli/chunks/chunk-VIND4SP3.js +0 -124
- package/dist-cli/chunks/chunk-XXNLYR64.js +0 -5
- package/dist-cli/chunks/chunk-ZFM24DOX.js +0 -4
- package/dist-cli/chunks/cli-version-SYGNHGFR.js +0 -4
- package/dist-cli/chunks/control-RBTNOLQM.js +0 -4
- package/dist-cli/chunks/daemon-N3SC5DK6.js +0 -4
- package/dist-cli/chunks/drivers-W6E3BZH4.js +0 -4
- package/dist-cli/chunks/flow-7QAE2V5Y.js +0 -4
- package/dist-cli/chunks/help-NLD6KWVG.js +0 -4
- package/dist-cli/chunks/inspect-C347BG52.js +0 -4
- package/dist-cli/chunks/install-desktop-YR6TWZYK.js +0 -4
- package/dist-cli/chunks/privacy-PWWC2BXO.js +0 -4
- package/dist-cli/chunks/reset-Y3PBPFYA.js +0 -4
- package/dist-cli/chunks/runtime-IC6NVVGA.js +0 -4
- package/dist-cli/chunks/setup-Y4QGSJBM.js +0 -4
- package/dist-cli/chunks/state-KFLAZNEY.js +0 -14
- package/dist-cli/chunks/store-VEE22CAY.js +0 -4
- package/dist-cli/chunks/telemetry-PZV4BISJ.js +0 -4
- package/dist-cli/chunks/upload-Y4C3YZDK.js +0 -4
- package/dist-cli/chunks/welcome-ZPKA5PZJ.js +0 -5
- package/dist-lib/mcp.cjs +0 -673
- package/dist-lib/mcp.mjs +0 -641
- package/src/mcp.ts +0 -791
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// `rnx box` — the box environment.
|
|
2
|
+
//
|
|
3
|
+
// bare `rnx box` is LOCAL and mounts the current checkout at /project. `rnx box
|
|
4
|
+
// create` provisions a durable cloud box. everything else in the namespace is
|
|
5
|
+
// cloud lifecycle. the cloud side is not built yet; those verbs fail by name
|
|
6
|
+
// and say what works instead, so nothing advertises a capability that always
|
|
7
|
+
// fails.
|
|
8
|
+
|
|
9
|
+
import path from 'node:path'
|
|
10
|
+
import { createInterface } from 'node:readline'
|
|
11
|
+
import { createHarnessShell, HarnessProjectFs, PROJECT_MOUNT } from '@contrast/harness'
|
|
12
|
+
import { rnxPublicBrand } from '../../src/public-brand'
|
|
13
|
+
import { findRepositoryPackageRoot } from '../setup-repository'
|
|
14
|
+
import { resolvePlacement } from '../ws-bridge'
|
|
15
|
+
import { CheckoutFilePlane, CheckoutRequiresGitError } from './box/checkout-plane'
|
|
16
|
+
import { createRnxCommand } from './box/rnx-command'
|
|
17
|
+
|
|
18
|
+
interface BoxCommandOptions {
|
|
19
|
+
port?: number
|
|
20
|
+
verbose?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const CLOUD_SUBCOMMANDS = new Set(['create', 'ls', 'connect', 'stop', 'delete'])
|
|
24
|
+
|
|
25
|
+
function usage(): void {
|
|
26
|
+
const rnx = rnxPublicBrand.commandName
|
|
27
|
+
console.log(`
|
|
28
|
+
${rnx} box — start a local box and enter its shell
|
|
29
|
+
|
|
30
|
+
usage:
|
|
31
|
+
${rnx} box start a local box over the current checkout
|
|
32
|
+
${rnx} box create create a cloud box and enter its shell
|
|
33
|
+
${rnx} box create --name=<n> create a named cloud box
|
|
34
|
+
|
|
35
|
+
a local box mounts this checkout at ${PROJECT_MOUNT}, gives you a shell with
|
|
36
|
+
ls, cat, find, rg, sed, pipes and redirects, and registers ${rnx} itself as a
|
|
37
|
+
shell command. writes go straight to the checkout — there is no second copy.
|
|
38
|
+
`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function runBox(
|
|
42
|
+
args: string[],
|
|
43
|
+
opts: BoxCommandOptions = {},
|
|
44
|
+
): Promise<number> {
|
|
45
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
46
|
+
usage()
|
|
47
|
+
return 0
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// a box inside a box would mount the same checkout twice and give the
|
|
51
|
+
// user two shells writing through one filesystem. refuse at the top rather
|
|
52
|
+
// than letting the second one start and quietly race the first.
|
|
53
|
+
const placement = resolvePlacement()
|
|
54
|
+
if (placement === 'local-box') {
|
|
55
|
+
console.error(
|
|
56
|
+
` ${rnxPublicBrand.commandName} box: you are already in a box.\n` +
|
|
57
|
+
` this shell is the box — run ${rnxPublicBrand.commandName} commands directly, or \`exit\` to leave.`,
|
|
58
|
+
)
|
|
59
|
+
return 1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const subcommand = args.find((arg) => !arg.startsWith('-'))
|
|
63
|
+
if (subcommand !== undefined) {
|
|
64
|
+
if (CLOUD_SUBCOMMANDS.has(subcommand)) {
|
|
65
|
+
console.error(
|
|
66
|
+
` ${rnxPublicBrand.commandName} box ${subcommand} needs a cloud box, which is not built yet.\n` +
|
|
67
|
+
` \`${rnxPublicBrand.commandName} box\` runs the same shell locally over this checkout.`,
|
|
68
|
+
)
|
|
69
|
+
return 1
|
|
70
|
+
}
|
|
71
|
+
console.error(
|
|
72
|
+
` unknown command: ${rnxPublicBrand.commandName} box ${subcommand}\n` +
|
|
73
|
+
` run \`${rnxPublicBrand.commandName} box --help\` to see the box surface.`,
|
|
74
|
+
)
|
|
75
|
+
return 1
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return runLocalBox(opts)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function runLocalBox(opts: BoxCommandOptions): Promise<number> {
|
|
82
|
+
// findRepositoryPackageRoot answers both halves of what a box needs: the
|
|
83
|
+
// nearest package.json, and only when it sits inside a git repository. git
|
|
84
|
+
// is what decides which files belong to the project, so a box without it
|
|
85
|
+
// would have to walk the whole directory tree — measured at 230k files on
|
|
86
|
+
// this repo against 15k from git.
|
|
87
|
+
const root = findRepositoryPackageRoot(process.cwd())
|
|
88
|
+
if (!root) {
|
|
89
|
+
console.error(
|
|
90
|
+
` ${rnxPublicBrand.commandName} box: no project here.\n` +
|
|
91
|
+
` the box needs a directory inside a git repository that has a\n` +
|
|
92
|
+
` package.json. it asks git which files belong to the project, so it\n` +
|
|
93
|
+
` never walks node_modules or build output. run \`git init\` if this is\n` +
|
|
94
|
+
` a new project.`,
|
|
95
|
+
)
|
|
96
|
+
return 1
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const plane = new CheckoutFilePlane(root)
|
|
100
|
+
try {
|
|
101
|
+
await plane.refresh()
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof CheckoutRequiresGitError) {
|
|
104
|
+
console.error(` ${rnxPublicBrand.commandName} box: ${error.message}`)
|
|
105
|
+
return 1
|
|
106
|
+
}
|
|
107
|
+
throw error
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const shell = createHarnessShell({
|
|
111
|
+
projectFs: new HarnessProjectFs(plane),
|
|
112
|
+
commands: [createRnxCommand({ root, shellCwd: () => shell.cwd })],
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const relative = path.relative(root, process.cwd())
|
|
116
|
+
console.log(` ...starting local box`)
|
|
117
|
+
console.log(` ...mounting ${root} at ${PROJECT_MOUNT}`)
|
|
118
|
+
console.log(` ...accessing shell\n`)
|
|
119
|
+
if (opts.verbose) {
|
|
120
|
+
console.log(` ${plane.index().size} project files indexed\n`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return repl(shell, plane, relative)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function repl(
|
|
127
|
+
shell: ReturnType<typeof createHarnessShell>,
|
|
128
|
+
plane: CheckoutFilePlane,
|
|
129
|
+
startRelative: string,
|
|
130
|
+
): Promise<number> {
|
|
131
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
132
|
+
|
|
133
|
+
// enter where the user was standing, so `rnx box` from a subdirectory does
|
|
134
|
+
// not silently teleport them to the project root.
|
|
135
|
+
if (startRelative && !startRelative.startsWith('..')) {
|
|
136
|
+
await shell.exec(`cd ${JSON.stringify(startRelative)}`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const prompt = () => {
|
|
140
|
+
rl.setPrompt(`${shell.cwd} > `)
|
|
141
|
+
rl.prompt()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// readline delivers a pasted block, or a piped script, as a burst of `line`
|
|
145
|
+
// events. run them one at a time in order — dropping the ones that arrive
|
|
146
|
+
// while a command is running would silently execute only the first line of
|
|
147
|
+
// a paste.
|
|
148
|
+
let queue: Promise<void> = Promise.resolve()
|
|
149
|
+
// stdin ended: stop printing prompts, but still finish what is already
|
|
150
|
+
// queued — piping a script in must run every line, not just the first.
|
|
151
|
+
let inputEnded = false
|
|
152
|
+
// the user typed `exit`: abandon anything still queued behind it.
|
|
153
|
+
let stopped = false
|
|
154
|
+
|
|
155
|
+
const run = async (command: string) => {
|
|
156
|
+
if (stopped) return
|
|
157
|
+
try {
|
|
158
|
+
// the checkout is shared with editors, agents, and the user's own
|
|
159
|
+
// terminal, so re-read the file set before every command rather than
|
|
160
|
+
// trusting a snapshot taken when the box started.
|
|
161
|
+
await plane.refresh()
|
|
162
|
+
const result = await shell.exec(command)
|
|
163
|
+
if (result.text && result.text !== '(no output)') console.log(result.text)
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
166
|
+
}
|
|
167
|
+
if (!inputEnded && !stopped) prompt()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return new Promise<number>((resolve) => {
|
|
171
|
+
prompt()
|
|
172
|
+
|
|
173
|
+
rl.on('line', (line) => {
|
|
174
|
+
const command = line.trim()
|
|
175
|
+
if (!command) {
|
|
176
|
+
queue = queue.then(() => {
|
|
177
|
+
if (!inputEnded && !stopped) prompt()
|
|
178
|
+
})
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
if (command === 'exit' || command === 'quit') {
|
|
182
|
+
queue = queue.then(() => {
|
|
183
|
+
stopped = true
|
|
184
|
+
rl.close()
|
|
185
|
+
})
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
queue = queue.then(() => run(command))
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
rl.on('close', () => {
|
|
192
|
+
inputEnded = true
|
|
193
|
+
// let any already-queued command finish before the process exits.
|
|
194
|
+
queue.then(() => {
|
|
195
|
+
console.log()
|
|
196
|
+
resolve(0)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
}
|
package/cli/commands/control.ts
CHANGED
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
worldForBridgePort,
|
|
67
67
|
type BridgeSimInfo,
|
|
68
68
|
} from '../ws-bridge'
|
|
69
|
-
import {
|
|
69
|
+
import { inspectDescribe, inspectWaitReady } from './inspect/core'
|
|
70
70
|
import { isAgentEnv } from './inspect/env'
|
|
71
71
|
import type { DriverId, DriverLaunchResult } from '../drivers/types'
|
|
72
72
|
|
|
@@ -1248,19 +1248,12 @@ async function maybeDescribeAfterOpen(wsPort: number, simId: string, args: strin
|
|
|
1248
1248
|
simIdSource: 'flag',
|
|
1249
1249
|
})
|
|
1250
1250
|
try {
|
|
1251
|
-
let readyProbe:
|
|
1251
|
+
let readyProbe: Awaited<ReturnType<typeof inspectWaitReady>> | null = null
|
|
1252
1252
|
try {
|
|
1253
|
-
readyProbe =
|
|
1254
|
-
type: 'evaluate',
|
|
1255
|
-
code: WAIT_READY_PROBE,
|
|
1256
|
-
})) as ReadyProbe | null
|
|
1253
|
+
readyProbe = await inspectWaitReady(bridge, 1)
|
|
1257
1254
|
} catch {}
|
|
1258
1255
|
if (readyProbe) {
|
|
1259
|
-
|
|
1260
|
-
readyProbe.flag === true &&
|
|
1261
|
-
!readyProbe.loadingText &&
|
|
1262
|
-
readyProbeHasContent(readyProbe)
|
|
1263
|
-
if (!readyEnough) {
|
|
1256
|
+
if (!readyProbe.ready) {
|
|
1264
1257
|
const reason = readyProbe.loadingText
|
|
1265
1258
|
? `still showing "${readyProbe.loadingText}"`
|
|
1266
1259
|
: readyProbe.flag !== true
|
|
@@ -1274,29 +1267,16 @@ async function maybeDescribeAfterOpen(wsPort: number, simId: string, args: strin
|
|
|
1274
1267
|
console.log(' before interacting, run: rnx wait ready --max-ms 120000')
|
|
1275
1268
|
}
|
|
1276
1269
|
}
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
return { tree, shell }
|
|
1286
|
-
})()`
|
|
1287
|
-
const result = (await bridge.send({ type: 'evaluate', code })) as {
|
|
1288
|
-
tree: string | null
|
|
1289
|
-
shell: { state?: string; activeApp?: string } | null
|
|
1290
|
-
} | null
|
|
1270
|
+
const result = await inspectDescribe(bridge, {
|
|
1271
|
+
describe: true,
|
|
1272
|
+
verbose: false,
|
|
1273
|
+
filter: '',
|
|
1274
|
+
compact: true,
|
|
1275
|
+
hideXy: false,
|
|
1276
|
+
maxDepth: 12,
|
|
1277
|
+
})
|
|
1291
1278
|
if (!result?.tree) return
|
|
1292
1279
|
console.log('')
|
|
1293
|
-
if (result.shell?.state) {
|
|
1294
|
-
const parts = [
|
|
1295
|
-
`state=${result.shell.state}`,
|
|
1296
|
-
result.shell.activeApp ? `app=${result.shell.activeApp}` : null,
|
|
1297
|
-
].filter(Boolean)
|
|
1298
|
-
console.log(` shell: ${parts.join(' ')}`)
|
|
1299
|
-
}
|
|
1300
1280
|
console.log(result.tree)
|
|
1301
1281
|
} finally {
|
|
1302
1282
|
bridge.close()
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { BridgeInputError } from '../../src/bridge-contract-input'
|
|
2
|
+
import type { SimPerformStep } from '../../src/sim-client'
|
|
3
|
+
|
|
4
|
+
interface TargetedDoubleTapAction {
|
|
5
|
+
type: 'targetedDoubleTap'
|
|
6
|
+
id: string
|
|
7
|
+
gapMs: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface TargetedLongPressAction {
|
|
11
|
+
type: 'targetedLongPress'
|
|
12
|
+
id: string
|
|
13
|
+
durationMs: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type DoChainAction =
|
|
17
|
+
| SimPerformStep
|
|
18
|
+
| TargetedDoubleTapAction
|
|
19
|
+
| TargetedLongPressAction
|
|
20
|
+
|
|
21
|
+
const DO_CHAIN_ACTIONS = new Set([
|
|
22
|
+
'tap',
|
|
23
|
+
'tap-id',
|
|
24
|
+
'tap-text',
|
|
25
|
+
'double-tap',
|
|
26
|
+
'long-press',
|
|
27
|
+
'type',
|
|
28
|
+
'type-into',
|
|
29
|
+
'key',
|
|
30
|
+
'key-sequence',
|
|
31
|
+
'dismiss',
|
|
32
|
+
'scroll',
|
|
33
|
+
'drag',
|
|
34
|
+
'swipe',
|
|
35
|
+
'pinch',
|
|
36
|
+
'touch',
|
|
37
|
+
'sleep',
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
export function hasDoChain(args: string[]): boolean {
|
|
41
|
+
return args.includes('--then')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function invalidAction(index: number, usage: string): never {
|
|
45
|
+
throw new BridgeInputError(
|
|
46
|
+
`do chain action ${index + 1} has invalid arguments; usage: rnx do ${usage}`,
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function finiteNumber(value: string | undefined, index: number, usage: string): number {
|
|
51
|
+
const parsed = Number(value)
|
|
52
|
+
if (value === undefined || !Number.isFinite(parsed)) invalidAction(index, usage)
|
|
53
|
+
return parsed
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function parseDoChainActions(args: string[]): DoChainAction[] {
|
|
57
|
+
const segments: string[][] = [[]]
|
|
58
|
+
for (const argument of args) {
|
|
59
|
+
if (argument !== '--then') {
|
|
60
|
+
segments[segments.length - 1].push(argument)
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
if (segments[segments.length - 1].length === 0) {
|
|
64
|
+
throw new BridgeInputError('do chain contains an empty action')
|
|
65
|
+
}
|
|
66
|
+
segments.push([])
|
|
67
|
+
}
|
|
68
|
+
if (segments.length < 2) {
|
|
69
|
+
throw new BridgeInputError('do chain needs at least two actions separated by --then')
|
|
70
|
+
}
|
|
71
|
+
if (segments[segments.length - 1].length === 0) {
|
|
72
|
+
throw new BridgeInputError('do chain cannot end with --then')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return segments.flatMap((segment, index): DoChainAction[] => {
|
|
76
|
+
const [action, ...values] = segment
|
|
77
|
+
if (!action) throw new BridgeInputError('do chain contains an empty action')
|
|
78
|
+
if (!DO_CHAIN_ACTIONS.has(action)) {
|
|
79
|
+
throw new BridgeInputError(
|
|
80
|
+
`do chain action ${index + 1} cannot batch ${JSON.stringify(action)}; run it as a separate rnx do command`,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
switch (action) {
|
|
85
|
+
case 'tap': {
|
|
86
|
+
if (values.length === 1 && !Number.isFinite(Number(values[0]))) {
|
|
87
|
+
return [{ type: 'tapId', id: values[0] }]
|
|
88
|
+
}
|
|
89
|
+
if (values.length !== 2) invalidAction(index, 'tap <testid> | <x> <y>')
|
|
90
|
+
return [
|
|
91
|
+
{
|
|
92
|
+
type: 'tap',
|
|
93
|
+
x: finiteNumber(values[0], index, 'tap <testid> | <x> <y>'),
|
|
94
|
+
y: finiteNumber(values[1], index, 'tap <testid> | <x> <y>'),
|
|
95
|
+
},
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case 'tap-id':
|
|
100
|
+
if (values.length !== 1) invalidAction(index, 'tap-id <id>')
|
|
101
|
+
return [{ type: 'tapId', id: values[0] }]
|
|
102
|
+
|
|
103
|
+
case 'tap-text': {
|
|
104
|
+
const text = values.join(' ')
|
|
105
|
+
if (!text) invalidAction(index, 'tap-text <text>')
|
|
106
|
+
return [{ type: 'tapText', text }]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
case 'double-tap': {
|
|
110
|
+
const usage = 'double-tap <testid> [gapMs] | <x> <y> [gapMs]'
|
|
111
|
+
if (values.length > 0 && !Number.isFinite(Number(values[0]))) {
|
|
112
|
+
if (values.length > 2) invalidAction(index, usage)
|
|
113
|
+
return [
|
|
114
|
+
{
|
|
115
|
+
type: 'targetedDoubleTap',
|
|
116
|
+
id: values[0],
|
|
117
|
+
gapMs:
|
|
118
|
+
values.length === 2
|
|
119
|
+
? Math.max(0, Math.round(finiteNumber(values[1], index, usage)))
|
|
120
|
+
: 80,
|
|
121
|
+
},
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
if (values.length < 2 || values.length > 3) invalidAction(index, usage)
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
type: 'doubleTap',
|
|
128
|
+
x: finiteNumber(values[0], index, usage),
|
|
129
|
+
y: finiteNumber(values[1], index, usage),
|
|
130
|
+
gapMs:
|
|
131
|
+
values.length === 3
|
|
132
|
+
? Math.max(0, Math.round(finiteNumber(values[2], index, usage)))
|
|
133
|
+
: 80,
|
|
134
|
+
},
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
case 'long-press': {
|
|
139
|
+
const usage = 'long-press <testid> [durationMs] | <x> <y> [durationMs]'
|
|
140
|
+
if (values.length > 0 && !Number.isFinite(Number(values[0]))) {
|
|
141
|
+
if (values.length > 2) invalidAction(index, usage)
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
type: 'targetedLongPress',
|
|
145
|
+
id: values[0],
|
|
146
|
+
durationMs:
|
|
147
|
+
values.length === 2
|
|
148
|
+
? Math.max(0, Math.round(finiteNumber(values[1], index, usage)))
|
|
149
|
+
: 600,
|
|
150
|
+
},
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
if (values.length < 2 || values.length > 3) invalidAction(index, usage)
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
type: 'longPress',
|
|
157
|
+
x: finiteNumber(values[0], index, usage),
|
|
158
|
+
y: finiteNumber(values[1], index, usage),
|
|
159
|
+
durationMs:
|
|
160
|
+
values.length === 3
|
|
161
|
+
? Math.max(0, Math.round(finiteNumber(values[2], index, usage)))
|
|
162
|
+
: 600,
|
|
163
|
+
},
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
case 'type': {
|
|
168
|
+
const text = values.join(' ')
|
|
169
|
+
if (!text) invalidAction(index, 'type <text>')
|
|
170
|
+
return [{ type: 'type', text }]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
case 'type-into': {
|
|
174
|
+
const text = values.slice(1).join(' ')
|
|
175
|
+
if (!values[0] || !text) invalidAction(index, 'type-into <id> <text>')
|
|
176
|
+
return [
|
|
177
|
+
{ type: 'tapId', id: values[0] },
|
|
178
|
+
{ type: 'type', text },
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
case 'key':
|
|
183
|
+
if (values.length !== 1) invalidAction(index, 'key <name>')
|
|
184
|
+
return [{ type: 'key', key: values[0] }]
|
|
185
|
+
|
|
186
|
+
case 'key-sequence':
|
|
187
|
+
if (values.length === 0) invalidAction(index, 'key-sequence <key> [<key> ...]')
|
|
188
|
+
return values.map((key) => ({ type: 'key', key }))
|
|
189
|
+
|
|
190
|
+
case 'dismiss':
|
|
191
|
+
if (values.length !== 0) invalidAction(index, 'dismiss')
|
|
192
|
+
return [{ type: 'dismissKeyboard' }]
|
|
193
|
+
|
|
194
|
+
case 'scroll': {
|
|
195
|
+
const usage = 'scroll <id> <x> <y>'
|
|
196
|
+
if (values.length !== 3) invalidAction(index, usage)
|
|
197
|
+
return [
|
|
198
|
+
{
|
|
199
|
+
type: 'scroll',
|
|
200
|
+
id: values[0],
|
|
201
|
+
x: finiteNumber(values[1], index, usage),
|
|
202
|
+
y: finiteNumber(values[2], index, usage),
|
|
203
|
+
animated: false,
|
|
204
|
+
},
|
|
205
|
+
]
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
case 'drag':
|
|
209
|
+
case 'swipe': {
|
|
210
|
+
const usage = `${action} <x1> <y1> <x2> <y2> [steps] [stepMs]`
|
|
211
|
+
if (values.length < 4 || values.length > 6) invalidAction(index, usage)
|
|
212
|
+
const defaultSteps = action === 'swipe' ? 10 : 12
|
|
213
|
+
const defaultStepMs = action === 'swipe' ? 8 : 16
|
|
214
|
+
return [
|
|
215
|
+
{
|
|
216
|
+
type: 'drag',
|
|
217
|
+
fromX: finiteNumber(values[0], index, usage),
|
|
218
|
+
fromY: finiteNumber(values[1], index, usage),
|
|
219
|
+
toX: finiteNumber(values[2], index, usage),
|
|
220
|
+
toY: finiteNumber(values[3], index, usage),
|
|
221
|
+
steps:
|
|
222
|
+
values.length >= 5
|
|
223
|
+
? Math.max(1, Math.round(finiteNumber(values[4], index, usage)))
|
|
224
|
+
: defaultSteps,
|
|
225
|
+
stepMs:
|
|
226
|
+
values.length === 6
|
|
227
|
+
? Math.max(0, Math.round(finiteNumber(values[5], index, usage)))
|
|
228
|
+
: defaultStepMs,
|
|
229
|
+
},
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
case 'pinch': {
|
|
234
|
+
const usage = "pinch <x1> <y1> <x2> <y2> <x1'> <y1'> <x2'> <y2'> [steps] [stepMs]"
|
|
235
|
+
if (values.length < 8 || values.length > 10) invalidAction(index, usage)
|
|
236
|
+
return [
|
|
237
|
+
{
|
|
238
|
+
type: 'pinch',
|
|
239
|
+
fromX1: finiteNumber(values[0], index, usage),
|
|
240
|
+
fromY1: finiteNumber(values[1], index, usage),
|
|
241
|
+
fromX2: finiteNumber(values[2], index, usage),
|
|
242
|
+
fromY2: finiteNumber(values[3], index, usage),
|
|
243
|
+
toX1: finiteNumber(values[4], index, usage),
|
|
244
|
+
toY1: finiteNumber(values[5], index, usage),
|
|
245
|
+
toX2: finiteNumber(values[6], index, usage),
|
|
246
|
+
toY2: finiteNumber(values[7], index, usage),
|
|
247
|
+
steps:
|
|
248
|
+
values.length >= 9
|
|
249
|
+
? Math.max(1, Math.round(finiteNumber(values[8], index, usage)))
|
|
250
|
+
: 12,
|
|
251
|
+
stepMs:
|
|
252
|
+
values.length === 10
|
|
253
|
+
? Math.max(0, Math.round(finiteNumber(values[9], index, usage)))
|
|
254
|
+
: 16,
|
|
255
|
+
},
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
case 'touch': {
|
|
260
|
+
const usage = 'touch <down|move|up|cancel> <x> <y> [pointerId]'
|
|
261
|
+
if (values.length < 3 || values.length > 4) invalidAction(index, usage)
|
|
262
|
+
const phase = values[0]
|
|
263
|
+
if (
|
|
264
|
+
phase !== 'down' &&
|
|
265
|
+
phase !== 'move' &&
|
|
266
|
+
phase !== 'up' &&
|
|
267
|
+
phase !== 'cancel'
|
|
268
|
+
) {
|
|
269
|
+
invalidAction(index, usage)
|
|
270
|
+
}
|
|
271
|
+
const pointerId =
|
|
272
|
+
values.length === 4
|
|
273
|
+
? Math.max(1, Math.round(finiteNumber(values[3], index, usage)))
|
|
274
|
+
: 999
|
|
275
|
+
if (phase === 'cancel') return [{ type: 'touchCancel', pointerId }]
|
|
276
|
+
return [
|
|
277
|
+
{
|
|
278
|
+
type:
|
|
279
|
+
phase === 'down' ? 'touchDown' : phase === 'move' ? 'touchMove' : 'touchUp',
|
|
280
|
+
x: finiteNumber(values[1], index, usage),
|
|
281
|
+
y: finiteNumber(values[2], index, usage),
|
|
282
|
+
pointerId,
|
|
283
|
+
},
|
|
284
|
+
]
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
case 'sleep': {
|
|
288
|
+
const usage = 'sleep [seconds]'
|
|
289
|
+
if (values.length > 1) invalidAction(index, usage)
|
|
290
|
+
const seconds = values.length === 1 ? finiteNumber(values[0], index, usage) : 0.5
|
|
291
|
+
if (seconds < 0) invalidAction(index, usage)
|
|
292
|
+
return [{ type: 'wait', ms: Math.round(seconds * 1000) }]
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
default:
|
|
296
|
+
throw new BridgeInputError(
|
|
297
|
+
`do chain action ${index + 1} cannot batch ${JSON.stringify(action)}`,
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export async function runDoChain(
|
|
304
|
+
args: string[],
|
|
305
|
+
opts: { port?: number } = {},
|
|
306
|
+
): Promise<number> {
|
|
307
|
+
const [connectModule, simClientModule, bridgeModule, envModule, targetModule] =
|
|
308
|
+
await Promise.all([
|
|
309
|
+
import('../../src/connect'),
|
|
310
|
+
import('../../src/sim-client'),
|
|
311
|
+
import('../ws-bridge'),
|
|
312
|
+
import('./inspect/env'),
|
|
313
|
+
import('./inspect/resolve-target'),
|
|
314
|
+
])
|
|
315
|
+
const { connect } = connectModule
|
|
316
|
+
const { SimPerformError } = simClientModule
|
|
317
|
+
const { checkSimHealth, parseBridgeCliArgs } = bridgeModule
|
|
318
|
+
const { isAgentEnv } = envModule
|
|
319
|
+
const { resolveTargetCoords } = targetModule
|
|
320
|
+
const wantsJson = args.includes('--json')
|
|
321
|
+
const skipSettle =
|
|
322
|
+
args.includes('--no-wait') || process.env.SOOTSIM_NO_AUTO_WAIT === '1'
|
|
323
|
+
const parsed = parseBridgeCliArgs(args, {
|
|
324
|
+
port: opts.port,
|
|
325
|
+
commandTimeoutMs: 30_000,
|
|
326
|
+
stripBooleanFlags: ['--json', '--no-wait'],
|
|
327
|
+
})
|
|
328
|
+
if (!Number.isFinite(parsed.commandTimeoutMs) || parsed.commandTimeoutMs <= 0) {
|
|
329
|
+
console.error(' do chain: --timeout must be a positive number')
|
|
330
|
+
return 1
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
let actions: DoChainAction[]
|
|
334
|
+
try {
|
|
335
|
+
actions = parseDoChainActions(parsed.positional)
|
|
336
|
+
} catch (error) {
|
|
337
|
+
console.error(` do chain: ${error instanceof Error ? error.message : error}`)
|
|
338
|
+
return 1
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let sim: Awaited<ReturnType<typeof connect>> | null = null
|
|
342
|
+
try {
|
|
343
|
+
sim = await connect({
|
|
344
|
+
port: parsed.wsPort,
|
|
345
|
+
sim: parsed.simId,
|
|
346
|
+
timeoutMs: parsed.commandTimeoutMs,
|
|
347
|
+
})
|
|
348
|
+
await checkSimHealth(sim)
|
|
349
|
+
|
|
350
|
+
const steps: SimPerformStep[] = []
|
|
351
|
+
for (const action of actions) {
|
|
352
|
+
if (action.type !== 'targetedDoubleTap' && action.type !== 'targetedLongPress') {
|
|
353
|
+
steps.push(action)
|
|
354
|
+
continue
|
|
355
|
+
}
|
|
356
|
+
const target = await resolveTargetCoords(sim, { mode: 'testid', value: action.id })
|
|
357
|
+
if (!target) {
|
|
358
|
+
throw new BridgeInputError(
|
|
359
|
+
`do chain could not find testID ${JSON.stringify(action.id)}`,
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
steps.push(
|
|
363
|
+
action.type === 'targetedDoubleTap'
|
|
364
|
+
? {
|
|
365
|
+
type: 'doubleTap',
|
|
366
|
+
x: target.x,
|
|
367
|
+
y: target.y,
|
|
368
|
+
gapMs: action.gapMs,
|
|
369
|
+
}
|
|
370
|
+
: {
|
|
371
|
+
type: 'longPress',
|
|
372
|
+
x: target.x,
|
|
373
|
+
y: target.y,
|
|
374
|
+
durationMs: action.durationMs,
|
|
375
|
+
},
|
|
376
|
+
)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const result = await sim.perform(steps, { timeoutMs: parsed.commandTimeoutMs })
|
|
380
|
+
if (!skipSettle) {
|
|
381
|
+
try {
|
|
382
|
+
const budgetMs = isAgentEnv() ? 400 : 200
|
|
383
|
+
const settled = await sim.settle({ maxMs: budgetMs })
|
|
384
|
+
if (!settled.settled) {
|
|
385
|
+
process.stderr.write(
|
|
386
|
+
` ⚠ auto-wait timed out after ${settled.elapsed ?? budgetMs}ms. next command may see mid-animation state. use \`rnx do settle\` for a longer wait.\n`,
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
} catch {}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (wantsJson) {
|
|
393
|
+
console.log(JSON.stringify(result))
|
|
394
|
+
} else {
|
|
395
|
+
console.log(
|
|
396
|
+
` completed ${result.completed} batched ${result.completed === 1 ? 'step' : 'steps'} in ${result.durationMs}ms`,
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
return 0
|
|
400
|
+
} catch (error) {
|
|
401
|
+
if (error instanceof SimPerformError && wantsJson) {
|
|
402
|
+
console.log(JSON.stringify(error.result))
|
|
403
|
+
} else {
|
|
404
|
+
console.error(
|
|
405
|
+
` do chain failed: ${error instanceof Error ? error.message : error}`,
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
return 1
|
|
409
|
+
} finally {
|
|
410
|
+
sim?.close()
|
|
411
|
+
}
|
|
412
|
+
}
|
package/cli/commands/flow.ts
CHANGED
|
@@ -456,16 +456,6 @@ flow extension:
|
|
|
456
456
|
})
|
|
457
457
|
}
|
|
458
458
|
if (!targetDevice) return false
|
|
459
|
-
const currentUrl = await bridge.send({
|
|
460
|
-
type: 'evaluate',
|
|
461
|
-
simId: driver.simId,
|
|
462
|
-
code: 'window.location.href',
|
|
463
|
-
})
|
|
464
|
-
if (typeof currentUrl === 'string' && currentUrl.length > 0) {
|
|
465
|
-
const nextUrl = new URL(currentUrl)
|
|
466
|
-
nextUrl.searchParams.set('device', targetDevice)
|
|
467
|
-
driver.setSimRouteHint(nextUrl.toString())
|
|
468
|
-
}
|
|
469
459
|
return Boolean(
|
|
470
460
|
await bridge.send({
|
|
471
461
|
type: 'call',
|