rnxsim 0.1.370 → 0.1.372
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/cli/bin.ts +2 -2
- package/cli/commands/box/checkout-plane.ts +65 -14
- package/cli/commands/box/cloud-box.ts +294 -0
- package/cli/commands/box/plane-share.ts +208 -0
- package/cli/commands/box/rnx-command.ts +2 -2
- package/cli/commands/box/search-command.ts +157 -0
- package/cli/commands/box.ts +269 -13
- package/cli/commands/control.ts +6 -1
- package/cli/outbound-endpoints.ts +37 -1
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-3R4424LW.js → agent-RTAKJ5UM.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-4PPWZWSY.js → agent-wrapper-JWRDI26D.js} +2 -2
- package/dist-cli/chunks/{app-fonts-KYXB6CXQ.js → app-fonts-RKO3N4PB.js} +2 -2
- package/dist-cli/chunks/{assert-7EZOWGYQ.js → assert-Y5LXPWC6.js} +2 -2
- package/dist-cli/chunks/{auth-L7M36H6C.js → auth-4SPTRWTM.js} +2 -2
- package/dist-cli/chunks/{beta-FKAF6YWJ.js → beta-KYS22QPY.js} +2 -2
- package/dist-cli/chunks/{box-5Y5VBMHV.js → box-7CK72ZPF.js} +617 -596
- package/dist-cli/chunks/{camera-XHF2RPFG.js → camera-RUL426MQ.js} +2 -2
- package/dist-cli/chunks/{chunk-EQ3OE2AV.js → chunk-2MA3Q44D.js} +1 -1
- package/dist-cli/chunks/{chunk-DHYTRCIP.js → chunk-2OA2WE7G.js} +2 -2
- package/dist-cli/chunks/{chunk-DMHSD6YJ.js → chunk-3C6LTIDV.js} +2 -2
- package/dist-cli/chunks/{chunk-NR2V2ONN.js → chunk-4UMODU6C.js} +2 -2
- package/dist-cli/chunks/{chunk-Z2VBSNFI.js → chunk-5Q6ZEZFF.js} +1 -1
- package/dist-cli/chunks/{chunk-LUOHRBJG.js → chunk-7FYOZQBM.js} +1 -1
- package/dist-cli/chunks/{chunk-Q4MRJ6PO.js → chunk-BRRWRBW7.js} +2 -2
- package/dist-cli/chunks/chunk-CEQXZ3GA.js +11 -0
- package/dist-cli/chunks/{chunk-DUXHYPBP.js → chunk-CZ2FOZVC.js} +2 -2
- package/dist-cli/chunks/{chunk-CMVRTZ5G.js → chunk-D5AT7UT5.js} +2 -2
- package/dist-cli/chunks/{chunk-D2GZASRV.js → chunk-DQOQQFMP.js} +1 -1
- package/dist-cli/chunks/{chunk-3UXXYJVD.js → chunk-EKSSAC4L.js} +1 -1
- package/dist-cli/chunks/{chunk-5BR33NAZ.js → chunk-EY5OHL2M.js} +1 -1
- package/dist-cli/chunks/{chunk-FK4JSTZ3.js → chunk-FOW2LPM7.js} +2 -2
- package/dist-cli/chunks/{chunk-WFQHT4QD.js → chunk-G5G7PQKO.js} +2 -2
- package/dist-cli/chunks/{chunk-WPV4H5S4.js → chunk-GPYG2L7R.js} +2 -2
- package/dist-cli/chunks/{chunk-3SOPYG7S.js → chunk-H2SKPUSI.js} +2 -2
- package/dist-cli/chunks/{chunk-6A6L3VJR.js → chunk-H567W7JT.js} +2 -2
- package/dist-cli/chunks/{chunk-PAUEZO6F.js → chunk-HDGZAVH3.js} +2 -2
- package/dist-cli/chunks/{chunk-AALKIAUZ.js → chunk-I37LRPZ3.js} +1 -1
- package/dist-cli/chunks/{chunk-JZLDHPB7.js → chunk-I7LRVN5M.js} +2 -2
- package/dist-cli/chunks/{chunk-3BG6UCQY.js → chunk-IX25FFF3.js} +11 -11
- package/dist-cli/chunks/{chunk-72S5FCBY.js → chunk-J2TJRPDB.js} +2 -2
- package/dist-cli/chunks/{chunk-RRX52O37.js → chunk-JPRHJMUJ.js} +1 -1
- package/dist-cli/chunks/{chunk-BMIJZNKD.js → chunk-K4TGUDY4.js} +2 -2
- package/dist-cli/chunks/{chunk-7C7TJYZ3.js → chunk-KSE5XGYU.js} +2 -2
- package/dist-cli/chunks/{chunk-OJZHECGH.js → chunk-KVVNCIJA.js} +1 -1
- package/dist-cli/chunks/{chunk-LXPHL6FO.js → chunk-KWLUOLYQ.js} +2 -2
- package/dist-cli/chunks/{chunk-C7BA5CCM.js → chunk-KXAE64HF.js} +2 -2
- package/dist-cli/chunks/{chunk-EWSWOWXK.js → chunk-L5BEWXQZ.js} +2 -2
- package/dist-cli/chunks/{chunk-D24EOJ4C.js → chunk-LAQKTHRM.js} +2 -2
- package/dist-cli/chunks/{chunk-IDLFVRPN.js → chunk-LWLHVF6Y.js} +1 -1
- package/dist-cli/chunks/{chunk-EOIBLKZO.js → chunk-MKHW3JNL.js} +2 -2
- package/dist-cli/chunks/{chunk-64JAEJ6N.js → chunk-MSM2E74F.js} +1 -1
- package/dist-cli/chunks/{chunk-DXV37CJN.js → chunk-NBEOA72C.js} +1 -1
- package/dist-cli/chunks/{chunk-H5X5FJ63.js → chunk-NLMDMNO6.js} +2 -2
- package/dist-cli/chunks/{chunk-6W4HIWC7.js → chunk-NT43X5LI.js} +2 -2
- package/dist-cli/chunks/{chunk-PNOENVMJ.js → chunk-NWPYM54M.js} +1 -1
- package/dist-cli/chunks/{chunk-O6QLKSAM.js → chunk-OLHHB6LD.js} +2 -2
- package/dist-cli/chunks/{chunk-MDCY532E.js → chunk-P6K6XFWE.js} +2 -2
- package/dist-cli/chunks/{chunk-LIMWG54N.js → chunk-PETP6LVI.js} +1 -1
- package/dist-cli/chunks/{chunk-YIZGNT7A.js → chunk-PKY76S57.js} +1 -1
- package/dist-cli/chunks/chunk-POR7R3TP.js +4 -0
- package/dist-cli/chunks/chunk-PUH6RQBJ.js +7 -0
- package/dist-cli/chunks/{chunk-NJMU5BN6.js → chunk-Q5WPPOFV.js} +1 -1
- package/dist-cli/chunks/{chunk-HQQTS2CB.js → chunk-Q7JSU5DI.js} +3 -3
- package/dist-cli/chunks/{chunk-U63T6LIQ.js → chunk-QA2PTSOC.js} +1 -1
- package/dist-cli/chunks/{chunk-2FOGXVOK.js → chunk-R7HMFLXI.js} +2 -2
- package/dist-cli/chunks/{chunk-FA75F34Z.js → chunk-RXKZJAGY.js} +1 -1
- package/dist-cli/chunks/{chunk-5RKRVS5A.js → chunk-SC7PGULZ.js} +2 -2
- package/dist-cli/chunks/{chunk-UFVAAWGZ.js → chunk-SLTEYNAE.js} +1 -1
- package/dist-cli/chunks/{chunk-6GPS7U4X.js → chunk-SSQCKJQZ.js} +2 -2
- package/dist-cli/chunks/{chunk-ARMXC3C3.js → chunk-SZX6ARJX.js} +3 -3
- package/dist-cli/chunks/{chunk-6S5LJV2O.js → chunk-U3XYGLXG.js} +2 -2
- package/dist-cli/chunks/chunk-UA5WQCBU.js +7 -0
- package/dist-cli/chunks/{chunk-VVQCZ42C.js → chunk-UVKX6FQ4.js} +1 -1
- package/dist-cli/chunks/{chunk-Z3DENGXW.js → chunk-VQQXUXXF.js} +2 -2
- package/dist-cli/chunks/{chunk-I52NT6VG.js → chunk-WR7LRCYW.js} +1 -1
- package/dist-cli/chunks/{chunk-HEKHLQ3B.js → chunk-XA4YU3A4.js} +3 -3
- package/dist-cli/chunks/{chunk-GRO74J2X.js → chunk-YEGWC2OY.js} +1 -1
- package/dist-cli/chunks/{chunk-AVJBT5LF.js → chunk-YWTBN7SD.js} +1 -1
- package/dist-cli/chunks/{chunk-PIZXOXBL.js → chunk-ZRQKEKHX.js} +2 -2
- package/dist-cli/chunks/{chunk-X6WQ4YWE.js → chunk-ZTZTRD5R.js} +1 -1
- package/dist-cli/chunks/{cleanup-MXMMJ7JW.js → cleanup-7ATBNZHC.js} +2 -2
- package/dist-cli/chunks/cli-version-D2IDQO7X.js +4 -0
- package/dist-cli/chunks/{compat-E6KE5KDI.js → compat-4ZC6ZMMA.js} +2 -2
- package/dist-cli/chunks/{config-QSLLVZDS.js → config-EOBODQ32.js} +2 -2
- package/dist-cli/chunks/{connect-QE4VU6GB.js → connect-ZQ72CZ7O.js} +2 -2
- package/dist-cli/chunks/control-FKPKLOE2.js +4 -0
- package/dist-cli/chunks/daemon-WFHWS3JU.js +4 -0
- package/dist-cli/chunks/{debug-5FG3MBJS.js → debug-7Z325GNU.js} +3 -3
- package/dist-cli/chunks/{desktop-5EZX7NX5.js → desktop-CR7ALX7H.js} +3 -3
- package/dist-cli/chunks/{detox-WJGQP5TM.js → detox-RZ37736M.js} +2 -2
- package/dist-cli/chunks/{device-D7LKCAMS.js → device-TA3SQP6K.js} +2 -2
- package/dist-cli/chunks/{diagnose-A3W6ZTGA.js → diagnose-JFKFKR3S.js} +2 -2
- package/dist-cli/chunks/disk-cleanup-HZM7VM4S.js +4 -0
- package/dist-cli/chunks/{do-chain-RUJNRPPF.js → do-chain-MTT65YNX.js} +2 -2
- package/dist-cli/chunks/drivers-BXEXWRVJ.js +4 -0
- package/dist-cli/chunks/{env-U67SQVTV.js → env-SFI24PLS.js} +2 -2
- package/dist-cli/chunks/{film-XC6UAQ3V.js → film-GPHSJLIH.js} +3 -3
- package/dist-cli/chunks/flow-FBCU3TZN.js +4 -0
- package/dist-cli/chunks/help-6MLROWYO.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-7S2TNZXG.js → hidden-runtime-alias-7Z6PYH53.js} +2 -2
- package/dist-cli/chunks/{home-paths-LMO5LB4F.js → home-paths-MEGVA6ET.js} +2 -2
- package/dist-cli/chunks/inspect-EWT5SXBD.js +4 -0
- package/dist-cli/chunks/install-desktop-IFBUH7MH.js +4 -0
- package/dist-cli/chunks/{login-JIU42S4C.js → login-EHUZYRAM.js} +2 -2
- package/dist-cli/chunks/{logout-JTAWQXZO.js → logout-WPGT62N6.js} +2 -2
- package/dist-cli/chunks/{maestro-TYBQYOGB.js → maestro-4HVDLCTI.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-YLAGWVKE.js → maestro-generate-VBS2EZMM.js} +3 -3
- package/dist-cli/chunks/{mode-DV24ZRZ6.js → mode-MGTS4QTF.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-JZH66VIM.js → optional-demo-registry-F3FQT5WT.js} +2 -2
- package/dist-cli/chunks/{perf-LXXJ4EAX.js → perf-47R3OEI7.js} +2 -2
- package/dist-cli/chunks/{permissions-KAOVZUPW.js → permissions-ZOLFZWR2.js} +2 -2
- package/dist-cli/chunks/{platform-KW35446W.js → platform-C4E26ZBZ.js} +3 -3
- package/dist-cli/chunks/privacy-WXGGG4ZI.js +4 -0
- package/dist-cli/chunks/{record-EK3ETFHZ.js → record-OUXWCWUF.js} +3 -3
- package/dist-cli/chunks/{report-issue-NW5PCAZF.js → report-issue-LJJXONXQ.js} +2 -2
- package/dist-cli/chunks/reset-ZZYLPETS.js +4 -0
- package/dist-cli/chunks/resolve-target-MVZZR2JW.js +4 -0
- package/dist-cli/chunks/runtime-DEC5AF3M.js +4 -0
- package/dist-cli/chunks/{screenshot-command-TQSPTFT2.js → screenshot-command-CUHDOS5D.js} +3 -3
- package/dist-cli/chunks/{screenshot-layers-IZP3L3AQ.js → screenshot-layers-DVWQVKT6.js} +2 -2
- package/dist-cli/chunks/{screenshots-capture-K7LJOVAE.js → screenshots-capture-GWIUJ3A6.js} +2 -2
- package/dist-cli/chunks/{serve-MMX4WXXG.js → serve-TX5OKGJM.js} +19 -19
- package/dist-cli/chunks/setup-X7LLGW67.js +4 -0
- package/dist-cli/chunks/{sim-client-KLAYIFOC.js → sim-client-W4WZOZ3P.js} +2 -2
- package/dist-cli/chunks/{skills-N4D3POIS.js → skills-TDFQAZ6A.js} +2 -2
- package/dist-cli/chunks/{state-ES6TFQZF.js → state-P2Z25OLQ.js} +2 -2
- package/dist-cli/chunks/{storage-52DR5V2C.js → storage-DWEOFXEB.js} +2 -2
- package/dist-cli/chunks/store-TKBLMYCV.js +4 -0
- package/dist-cli/chunks/telemetry-E3CBKJMD.js +4 -0
- package/dist-cli/chunks/{timeline-NRVNB5JR.js → timeline-55XNA4GX.js} +2 -2
- package/dist-cli/chunks/{upgrade-LNED5YQ2.js → upgrade-3XRCVDVC.js} +2 -2
- package/dist-cli/chunks/upload-QB77GSMQ.js +4 -0
- package/dist-cli/chunks/{version-VEF2DYGG.js → version-WTYGLXBJ.js} +2 -2
- package/dist-cli/chunks/{web-CGDE6S73.js → web-ZWRY3VHM.js} +2 -2
- package/dist-cli/chunks/welcome-7ATM5EGX.js +5 -0
- package/dist-cli/chunks/{what-happened-FSIDWL32.js → what-happened-Q3IGIH4K.js} +2 -2
- package/dist-cli/chunks/{ws-bridge-QXFQLRE7.js → ws-bridge-KCPZSBBX.js} +2 -2
- 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 +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- 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 +69 -34
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +430 -106
- 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 +1 -1
- 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 +149 -93
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +679 -307
- package/dist-lib/vite.cjs +1 -1
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +275 -92
- package/src/dev-bundle-resolution.ts +123 -40
- package/src/disk-cleanup.ts +44 -0
- package/src/host/bridge-host.ts +11 -0
- package/src/host/plane-host.ts +203 -0
- package/dist-cli/chunks/chunk-GJRLPJQL.js +0 -6
- package/dist-cli/chunks/chunk-NTC46LSA.js +0 -4
- package/dist-cli/chunks/chunk-OTFTMGAL.js +0 -7
- package/dist-cli/chunks/cli-version-MLS55UTY.js +0 -4
- package/dist-cli/chunks/control-XD5F6K5T.js +0 -4
- package/dist-cli/chunks/daemon-UN5SE2JO.js +0 -4
- package/dist-cli/chunks/disk-cleanup-BDRHX5ZG.js +0 -4
- package/dist-cli/chunks/drivers-3NJ5RL6X.js +0 -4
- package/dist-cli/chunks/flow-FHZZD3P6.js +0 -4
- package/dist-cli/chunks/help-BYYDUSXF.js +0 -4
- package/dist-cli/chunks/inspect-RQCTWDCC.js +0 -4
- package/dist-cli/chunks/install-desktop-GOWXCQUE.js +0 -4
- package/dist-cli/chunks/privacy-MIFE72CG.js +0 -4
- package/dist-cli/chunks/reset-RZ4O5AFJ.js +0 -4
- package/dist-cli/chunks/resolve-target-7R5BNYMM.js +0 -4
- package/dist-cli/chunks/runtime-MZHLSRS6.js +0 -4
- package/dist-cli/chunks/setup-YFP3KEQZ.js +0 -4
- package/dist-cli/chunks/store-HOL45WCI.js +0 -4
- package/dist-cli/chunks/telemetry-BV6OTYPF.js +0 -4
- package/dist-cli/chunks/upload-RXKHFIBU.js +0 -4
- package/dist-cli/chunks/welcome-FT2AMUWW.js +0 -5
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// `rg` inside a local box, run by git rather than by a JavaScript matcher.
|
|
2
|
+
//
|
|
3
|
+
// just-bash implements rg in JavaScript and reads every file body through the
|
|
4
|
+
// plane, so one search of this repo is ~15,000 plane reads. MEASURED in the box
|
|
5
|
+
// shell: 14,093ms and two lines of output, which are its abort message. It does
|
|
6
|
+
// not return results at all, and searching is the most common thing an agent
|
|
7
|
+
// does, so that is the box being unusable on a real project.
|
|
8
|
+
//
|
|
9
|
+
// WHY GIT GREP AND NOT RIPGREP. The first version of this shelled out to `rg`,
|
|
10
|
+
// which turned CI red: the Linux runners have no ripgrep binary, and a box that
|
|
11
|
+
// depends on whatever happens to be installed on the host is not a box. Git is
|
|
12
|
+
// different in kind, not degree: `CheckoutFilePlane` already REFUSES to open a
|
|
13
|
+
// project that is not a git repository and runs `git ls-files` before every
|
|
14
|
+
// single command, so git is a hard, already-enforced requirement. Searching
|
|
15
|
+
// with it adds no dependency and cannot be absent where the box already runs.
|
|
16
|
+
//
|
|
17
|
+
// It also makes the file set correct BY CONSTRUCTION rather than by agreement.
|
|
18
|
+
// The box indexes `git ls-files -co --exclude-standard`, tracked AND
|
|
19
|
+
// untracked-not-ignored, and `git grep --untracked` applies those same rules
|
|
20
|
+
// from the same tool. RAN against a checkout built to expose the difference:
|
|
21
|
+
// tracked, untracked-not-ignored, a dotfile and a dotdir file all match, and
|
|
22
|
+
// gitignored trees do not. Ripgrep needed the plane's path list piped into it
|
|
23
|
+
// to get there, because plain `rg .` silently misses dotfiles and `rg --hidden`
|
|
24
|
+
// searches .git/**.
|
|
25
|
+
//
|
|
26
|
+
// MEASURED on this repo, 14,949 indexed files: 391ms p50 against ripgrep's
|
|
27
|
+
// 260ms and just-bash's 14,093ms. Giving up 130ms to remove a host dependency
|
|
28
|
+
// and an entire class of "works on my machine" is the right trade.
|
|
29
|
+
//
|
|
30
|
+
// It also removes the ARG_MAX chunking the ripgrep version needed. Git walks
|
|
31
|
+
// its own index, so there is one process and one exit code rather than several
|
|
32
|
+
// whose "no match here" had to be merged.
|
|
33
|
+
|
|
34
|
+
import { spawn } from 'node:child_process'
|
|
35
|
+
import { defineCommand, type CustomCommand } from '@contrast/harness/shell'
|
|
36
|
+
import { PROJECT_MOUNT } from '@contrast/harness/shell'
|
|
37
|
+
|
|
38
|
+
// generous next to a 414ms p95: this bounds a wedged child, it does not cut off
|
|
39
|
+
// a large search.
|
|
40
|
+
const SEARCH_DEADLINE_MS = 20_000
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* THE DEADLINE SETTLES THIS PROMISE ITSELF rather than only killing the child.
|
|
44
|
+
* A spawned child's answer has been observed going missing while the process
|
|
45
|
+
* stayed alive, with no one able to say which event never arrived, so nothing
|
|
46
|
+
* that waits on a child event can be trusted to settle. A timer in this process
|
|
47
|
+
* cannot be missed, so it is the authority and the kill is a side effect.
|
|
48
|
+
* See docs/testing.md.
|
|
49
|
+
*/
|
|
50
|
+
function runGitGrep(args: string[], cwd: string) {
|
|
51
|
+
return new Promise<{ stdout: string; stderr: string; exitCode: number }>((resolve) => {
|
|
52
|
+
const child = spawn('git', args, {
|
|
53
|
+
cwd,
|
|
54
|
+
env: process.env,
|
|
55
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
56
|
+
})
|
|
57
|
+
let stdout = ''
|
|
58
|
+
let stderr = ''
|
|
59
|
+
let settled = false
|
|
60
|
+
const finish = (exitCode: number) => {
|
|
61
|
+
if (settled) return
|
|
62
|
+
settled = true
|
|
63
|
+
clearTimeout(timer)
|
|
64
|
+
resolve({ stdout, stderr, exitCode })
|
|
65
|
+
}
|
|
66
|
+
const timer = setTimeout(() => {
|
|
67
|
+
try {
|
|
68
|
+
child.kill('SIGKILL')
|
|
69
|
+
} catch {}
|
|
70
|
+
stderr += `rg: search timed out after ${SEARCH_DEADLINE_MS}ms\n`
|
|
71
|
+
finish(2)
|
|
72
|
+
}, SEARCH_DEADLINE_MS)
|
|
73
|
+
|
|
74
|
+
child.stdout.on('data', (chunk) => {
|
|
75
|
+
stdout += String(chunk)
|
|
76
|
+
})
|
|
77
|
+
child.stderr.on('data', (chunk) => {
|
|
78
|
+
stderr += String(chunk)
|
|
79
|
+
})
|
|
80
|
+
child.once('error', (error) => {
|
|
81
|
+
stderr += `rg: ${error.message}\n`
|
|
82
|
+
finish(2)
|
|
83
|
+
})
|
|
84
|
+
child.once('close', (code) => finish(code ?? 0))
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// the rg flags worth honouring, mapped onto git grep's spelling. anything else
|
|
89
|
+
// is REFUSED BY NAME rather than dropped, because a search that silently
|
|
90
|
+
// ignores `-i` returns a confidently wrong answer.
|
|
91
|
+
const FLAG_MAP: Record<string, string> = {
|
|
92
|
+
'-i': '-i',
|
|
93
|
+
'--ignore-case': '-i',
|
|
94
|
+
'-l': '-l',
|
|
95
|
+
'--files-with-matches': '-l',
|
|
96
|
+
'-w': '-w',
|
|
97
|
+
'--word-regexp': '-w',
|
|
98
|
+
'-c': '-c',
|
|
99
|
+
'--count': '-c',
|
|
100
|
+
'-v': '-v',
|
|
101
|
+
'--invert-match': '-v',
|
|
102
|
+
'-F': '-F',
|
|
103
|
+
'--fixed-strings': '-F',
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function createSearchCommand(options: { root: string }): CustomCommand {
|
|
107
|
+
return defineCommand('rg', async (args) => {
|
|
108
|
+
const flags: string[] = []
|
|
109
|
+
const bare: string[] = []
|
|
110
|
+
for (const arg of args) {
|
|
111
|
+
if (!arg.startsWith('-')) {
|
|
112
|
+
bare.push(arg)
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
const mapped = FLAG_MAP[arg]
|
|
116
|
+
if (!mapped) {
|
|
117
|
+
return {
|
|
118
|
+
stdout: '',
|
|
119
|
+
stderr: `rg: this box does not support ${arg}\n`,
|
|
120
|
+
exitCode: 2,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
flags.push(mapped)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const pattern = bare.shift()
|
|
127
|
+
if (pattern === undefined) {
|
|
128
|
+
return { stdout: '', stderr: 'rg: no pattern given\n', exitCode: 2 }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// paths are project-relative to git; strip the shell's mount prefix so
|
|
132
|
+
// `rg x /project/src` and `rg x src` mean the same thing.
|
|
133
|
+
const scopes = bare.map((p) =>
|
|
134
|
+
p.startsWith(`${PROJECT_MOUNT}/`) ? p.slice(PROJECT_MOUNT.length + 1) : p,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
// --untracked is what makes this the plane's set rather than git's tracked
|
|
138
|
+
// set. -E for extended regex, the closest of git's grammars to ripgrep's.
|
|
139
|
+
// -e so a pattern beginning with a dash is a pattern, not a flag.
|
|
140
|
+
const result = await runGitGrep(
|
|
141
|
+
[
|
|
142
|
+
'grep',
|
|
143
|
+
'--no-color',
|
|
144
|
+
'-n',
|
|
145
|
+
'-I',
|
|
146
|
+
'--untracked',
|
|
147
|
+
'-E',
|
|
148
|
+
...flags,
|
|
149
|
+
'-e',
|
|
150
|
+
pattern,
|
|
151
|
+
...(scopes.length > 0 ? ['--', ...scopes] : []),
|
|
152
|
+
],
|
|
153
|
+
options.root,
|
|
154
|
+
)
|
|
155
|
+
return result
|
|
156
|
+
})
|
|
157
|
+
}
|
package/cli/commands/box.ts
CHANGED
|
@@ -8,19 +8,41 @@
|
|
|
8
8
|
|
|
9
9
|
import path from 'node:path'
|
|
10
10
|
import { createInterface } from 'node:readline'
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
createHarnessShell,
|
|
13
|
+
HarnessProjectFs,
|
|
14
|
+
PROJECT_MOUNT,
|
|
15
|
+
} from '@contrast/harness/shell'
|
|
12
16
|
import { rnxPublicBrand } from '../../src/public-brand'
|
|
17
|
+
import { authHeaderOrExit } from '../auth'
|
|
13
18
|
import { findRepositoryPackageRoot } from '../setup-repository'
|
|
14
19
|
import { resolvePlacement } from '../ws-bridge'
|
|
15
20
|
import { CheckoutFilePlane, CheckoutRequiresGitError } from './box/checkout-plane'
|
|
21
|
+
import {
|
|
22
|
+
CloudBoxError,
|
|
23
|
+
connectCloudBox,
|
|
24
|
+
createCloudBox,
|
|
25
|
+
heartbeatSession,
|
|
26
|
+
listCloudBoxes,
|
|
27
|
+
stopCloudBox,
|
|
28
|
+
} from './box/cloud-box'
|
|
29
|
+
import { sharePlane } from './box/plane-share'
|
|
16
30
|
import { createRnxCommand } from './box/rnx-command'
|
|
31
|
+
import { createSearchCommand } from './box/search-command'
|
|
32
|
+
import type { HarnessShellResult } from '@contrast/harness/shell'
|
|
17
33
|
|
|
18
34
|
interface BoxCommandOptions {
|
|
19
35
|
port?: number
|
|
20
36
|
verbose?: boolean
|
|
21
37
|
}
|
|
22
38
|
|
|
23
|
-
const CLOUD_SUBCOMMANDS = new Set(['
|
|
39
|
+
const CLOUD_SUBCOMMANDS = new Set(['connect'])
|
|
40
|
+
|
|
41
|
+
// no default. a cloud box service has to be deployed before one can exist, and
|
|
42
|
+
// defaulting to an origin that answers nothing would advertise a capability
|
|
43
|
+
// that always fails.
|
|
44
|
+
const ENDPOINT_ENV = 'RNX_BOX_ENDPOINT'
|
|
45
|
+
const API_ORIGIN_ENV = 'RNX_API_ORIGIN'
|
|
24
46
|
|
|
25
47
|
function usage(): void {
|
|
26
48
|
const rnx = rnxPublicBrand.commandName
|
|
@@ -31,6 +53,12 @@ function usage(): void {
|
|
|
31
53
|
${rnx} box start a local box over the current checkout
|
|
32
54
|
${rnx} box create create a cloud box and enter its shell
|
|
33
55
|
${rnx} box create --name=<n> create a named cloud box
|
|
56
|
+
${rnx} box create --endpoint=<url>
|
|
57
|
+
the box service to create it in, or set
|
|
58
|
+
${ENDPOINT_ENV}
|
|
59
|
+
${rnx} box ls cloud boxes running on this account
|
|
60
|
+
${rnx} box stop --name=<n> stop a cloud box and its billing
|
|
61
|
+
${rnx} box delete --name=<n> stop it and wipe its files
|
|
34
62
|
|
|
35
63
|
a local box mounts this checkout at ${PROJECT_MOUNT}, gives you a shell with
|
|
36
64
|
ls, cat, find, rg, sed, pipes and redirects, and registers ${rnx} itself as a
|
|
@@ -60,6 +88,11 @@ export async function runBox(
|
|
|
60
88
|
}
|
|
61
89
|
|
|
62
90
|
const subcommand = args.find((arg) => !arg.startsWith('-'))
|
|
91
|
+
if (subcommand === 'create') return runCloudBoxCreate(args, opts)
|
|
92
|
+
if (subcommand === 'stop' || subcommand === 'delete') {
|
|
93
|
+
return runCloudBoxStop(args, subcommand === 'delete')
|
|
94
|
+
}
|
|
95
|
+
if (subcommand === 'ls') return runCloudBoxList(args)
|
|
63
96
|
if (subcommand !== undefined) {
|
|
64
97
|
if (CLOUD_SUBCOMMANDS.has(subcommand)) {
|
|
65
98
|
console.error(
|
|
@@ -78,6 +111,177 @@ export async function runBox(
|
|
|
78
111
|
return runLocalBox(opts)
|
|
79
112
|
}
|
|
80
113
|
|
|
114
|
+
function flag(args: string[], name: string): string | undefined {
|
|
115
|
+
const inline = args.find((arg) => arg.startsWith(`--${name}=`))
|
|
116
|
+
if (inline) return inline.slice(name.length + 3)
|
|
117
|
+
const index = args.indexOf(`--${name}`)
|
|
118
|
+
if (index >= 0 && args[index + 1] && !args[index + 1].startsWith('-')) {
|
|
119
|
+
return args[index + 1]
|
|
120
|
+
}
|
|
121
|
+
return undefined
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function apiOrigin(args: string[]): string {
|
|
125
|
+
return (
|
|
126
|
+
flag(args, 'api-origin') ??
|
|
127
|
+
process.env[API_ORIGIN_ENV]?.trim() ??
|
|
128
|
+
rnxPublicBrand.origin
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function requireEndpoint(args: string[], label: string): string | null {
|
|
133
|
+
const endpoint = flag(args, 'endpoint') ?? process.env[ENDPOINT_ENV]?.trim()
|
|
134
|
+
if (endpoint) return endpoint
|
|
135
|
+
const rnx = rnxPublicBrand.commandName
|
|
136
|
+
console.error(
|
|
137
|
+
` ${rnx} box ${label}: no cloud box service to talk to.\n` +
|
|
138
|
+
` set ${ENDPOINT_ENV} or pass --endpoint=<url>.`,
|
|
139
|
+
)
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function runCloudBoxStop(args: string[], purge: boolean): Promise<number> {
|
|
144
|
+
const rnx = rnxPublicBrand.commandName
|
|
145
|
+
const label = purge ? 'delete' : 'stop'
|
|
146
|
+
const endpoint = requireEndpoint(args, label)
|
|
147
|
+
if (!endpoint) return 1
|
|
148
|
+
const name =
|
|
149
|
+
flag(args, 'name') ?? path.basename(findRepositoryPackageRoot(process.cwd()) ?? '')
|
|
150
|
+
if (!name) {
|
|
151
|
+
console.error(` ${rnx} box ${label}: which box? pass --name=<n>.`)
|
|
152
|
+
return 1
|
|
153
|
+
}
|
|
154
|
+
const { header } = authHeaderOrExit(`box ${label}`)
|
|
155
|
+
try {
|
|
156
|
+
const result = await stopCloudBox({
|
|
157
|
+
endpoint,
|
|
158
|
+
name,
|
|
159
|
+
apiKey: header.replace(/^Bearer /, ''),
|
|
160
|
+
purge,
|
|
161
|
+
})
|
|
162
|
+
console.log(
|
|
163
|
+
purge
|
|
164
|
+
? ` deleted ${name}, freeing ${Number(result.freedBytes ?? 0)} bytes`
|
|
165
|
+
: ` stopped ${name}`,
|
|
166
|
+
)
|
|
167
|
+
return 0
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (error instanceof CloudBoxError) {
|
|
170
|
+
console.error(` ${rnx} box ${label}: ${error.message}`)
|
|
171
|
+
return 1
|
|
172
|
+
}
|
|
173
|
+
throw error
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function runCloudBoxList(args: string[]): Promise<number> {
|
|
178
|
+
const rnx = rnxPublicBrand.commandName
|
|
179
|
+
const { header } = authHeaderOrExit('box ls')
|
|
180
|
+
try {
|
|
181
|
+
const boxes = await listCloudBoxes({
|
|
182
|
+
apiOrigin: apiOrigin(args),
|
|
183
|
+
apiKey: header.replace(/^Bearer /, ''),
|
|
184
|
+
})
|
|
185
|
+
if (boxes.length === 0) {
|
|
186
|
+
console.log(` no cloud boxes running on this account`)
|
|
187
|
+
return 0
|
|
188
|
+
}
|
|
189
|
+
for (const box of boxes) {
|
|
190
|
+
const minutes = Math.round(box.meteredMs / 60_000)
|
|
191
|
+
console.log(` ${box.name ?? box.id} running ${minutes}m since ${box.startedAt}`)
|
|
192
|
+
}
|
|
193
|
+
return 0
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (error instanceof CloudBoxError) {
|
|
196
|
+
console.error(` ${rnx} box ls: ${error.message}`)
|
|
197
|
+
return 1
|
|
198
|
+
}
|
|
199
|
+
throw error
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function runCloudBoxCreate(
|
|
204
|
+
args: string[],
|
|
205
|
+
opts: BoxCommandOptions,
|
|
206
|
+
): Promise<number> {
|
|
207
|
+
const rnx = rnxPublicBrand.commandName
|
|
208
|
+
const endpoint = flag(args, 'endpoint') ?? process.env[ENDPOINT_ENV]?.trim()
|
|
209
|
+
if (!endpoint) {
|
|
210
|
+
console.error(
|
|
211
|
+
` ${rnx} box create: no cloud box service to create a box in.\n` +
|
|
212
|
+
` set ${ENDPOINT_ENV} or pass --endpoint=<url>. \`${rnx} box\` runs the\n` +
|
|
213
|
+
` same shell locally over this checkout.`,
|
|
214
|
+
)
|
|
215
|
+
return 1
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const root = findRepositoryPackageRoot(process.cwd())
|
|
219
|
+
if (!root) {
|
|
220
|
+
console.error(
|
|
221
|
+
` ${rnx} box create: no project here. a box is created from a checkout,\n` +
|
|
222
|
+
` so run this inside a git repository that has a package.json.`,
|
|
223
|
+
)
|
|
224
|
+
return 1
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const plane = new CheckoutFilePlane(root)
|
|
228
|
+
try {
|
|
229
|
+
await plane.refresh()
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (error instanceof CheckoutRequiresGitError) {
|
|
232
|
+
console.error(` ${rnx} box create: ${error.message}`)
|
|
233
|
+
return 1
|
|
234
|
+
}
|
|
235
|
+
throw error
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const name = flag(args, 'name') ?? path.basename(root)
|
|
239
|
+
// the ACCOUNT's key. the box service forwards it to /v1/rnx/sessions, which
|
|
240
|
+
// is what authenticates this caller, names the account, and starts the
|
|
241
|
+
// session the box is billed against.
|
|
242
|
+
const { header } = authHeaderOrExit('box create')
|
|
243
|
+
const apiKey = header.replace(/^Bearer /, '')
|
|
244
|
+
|
|
245
|
+
console.log(` ...creating cloud box ${name}`)
|
|
246
|
+
let session: Awaited<ReturnType<typeof connectCloudBox>> | null = null
|
|
247
|
+
let heartbeat: { stop: () => void } | null = null
|
|
248
|
+
try {
|
|
249
|
+
const created = await createCloudBox({
|
|
250
|
+
endpoint,
|
|
251
|
+
name,
|
|
252
|
+
apiKey,
|
|
253
|
+
plane,
|
|
254
|
+
onProgress: opts.verbose
|
|
255
|
+
? (seeded, total) => console.log(` ...seeded ${seeded}/${total} files`)
|
|
256
|
+
: undefined,
|
|
257
|
+
})
|
|
258
|
+
console.log(` ...seeded ${created.files} project files`)
|
|
259
|
+
// shown once and never stored: this process does not own a credential
|
|
260
|
+
// store, and inventing one here would be a second place secrets live.
|
|
261
|
+
console.log(` ...box token (shown once): ${created.token}`)
|
|
262
|
+
console.log(` ...accessing shell\n`)
|
|
263
|
+
session = await connectCloudBox({ endpoint, name, token: created.token })
|
|
264
|
+
// bank the wall clock while the shell is open. the box holds no account
|
|
265
|
+
// credential, so the client that is using it is what keeps the session
|
|
266
|
+
// alive; when this process dies the sweep closes it at the last heartbeat.
|
|
267
|
+
heartbeat = heartbeatSession({
|
|
268
|
+
apiOrigin: apiOrigin(args),
|
|
269
|
+
apiKey,
|
|
270
|
+
sessionId: created.sessionId,
|
|
271
|
+
})
|
|
272
|
+
return await repl(session, '')
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (error instanceof CloudBoxError) {
|
|
275
|
+
console.error(` ${rnx} box create: ${error.message}`)
|
|
276
|
+
return 1
|
|
277
|
+
}
|
|
278
|
+
throw error
|
|
279
|
+
} finally {
|
|
280
|
+
heartbeat?.stop()
|
|
281
|
+
session?.close()
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
81
285
|
async function runLocalBox(opts: BoxCommandOptions): Promise<number> {
|
|
82
286
|
// findRepositoryPackageRoot answers both halves of what a box needs: the
|
|
83
287
|
// nearest package.json, and only when it sits inside a git repository. git
|
|
@@ -109,35 +313,90 @@ async function runLocalBox(opts: BoxCommandOptions): Promise<number> {
|
|
|
109
313
|
|
|
110
314
|
const shell = createHarnessShell({
|
|
111
315
|
projectFs: new HarnessProjectFs(plane),
|
|
112
|
-
commands: [
|
|
316
|
+
commands: [
|
|
317
|
+
createRnxCommand({ root, shellCwd: () => shell.cwd }),
|
|
318
|
+
// overrides just-bash's JavaScript rg, which reads every file body
|
|
319
|
+
// through the plane. see search-command.ts for the numbers.
|
|
320
|
+
createSearchCommand({ root }),
|
|
321
|
+
],
|
|
113
322
|
})
|
|
114
323
|
|
|
115
324
|
const relative = path.relative(root, process.cwd())
|
|
116
325
|
console.log(` ...starting local box`)
|
|
117
326
|
console.log(` ...mounting ${root} at ${PROJECT_MOUNT}`)
|
|
327
|
+
|
|
328
|
+
// authorize this checkout for the bridge daemon so a browser tab can reach
|
|
329
|
+
// this box's plane. deliberately NOT awaited: a box must not pay for a
|
|
330
|
+
// bridge that may not be there, and an older daemon never answers at all.
|
|
331
|
+
const shared = sharePlane(root, { port: opts.port })
|
|
332
|
+
// two boxes over one checkout is worth SAYING, not refusing. a person with
|
|
333
|
+
// two terminals in one project is doing something reasonable, and the plane
|
|
334
|
+
// re-reads the file list before every command, so neither box works from a
|
|
335
|
+
// stale one. what it does break is an agent mid-run, whose file set changes
|
|
336
|
+
// underneath it because something else is writing the tree. that is the wave
|
|
337
|
+
// case, and the wave's own isolation check is where a refusal belongs; this
|
|
338
|
+
// reports the fact so that check has something to refuse on.
|
|
339
|
+
void shared.settled.then((outcome) => {
|
|
340
|
+
if ('declined' in outcome) {
|
|
341
|
+
if (opts.verbose) console.log(` ...plane not shared: ${outcome.declined}`)
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
if (outcome.holders > 1) {
|
|
345
|
+
console.error(
|
|
346
|
+
`\n ${rnxPublicBrand.commandName} box: ${outcome.holders - 1} other box${
|
|
347
|
+
outcome.holders > 2 ? 'es are' : ' is'
|
|
348
|
+
} already open on this checkout.\n` +
|
|
349
|
+
` writes from either land on the same files. that is fine for a person\n` +
|
|
350
|
+
` with two terminals, and not fine for an agent, whose file set will\n` +
|
|
351
|
+
` change underneath it mid-run.\n`,
|
|
352
|
+
)
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
if (opts.verbose) {
|
|
356
|
+
console.log(` ...plane shared on the bridge (port ${outcome.port})`)
|
|
357
|
+
}
|
|
358
|
+
})
|
|
359
|
+
|
|
118
360
|
console.log(` ...accessing shell\n`)
|
|
119
361
|
if (opts.verbose) {
|
|
120
362
|
console.log(` ${plane.index().size} project files indexed\n`)
|
|
121
363
|
}
|
|
122
364
|
|
|
123
|
-
|
|
365
|
+
try {
|
|
366
|
+
// the checkout is shared with editors, agents, and the user's own terminal,
|
|
367
|
+
// so re-read the file set before every command rather than trusting a
|
|
368
|
+
// snapshot taken when the box started. a cloud box has no equivalent: its
|
|
369
|
+
// store is the box, and the box refreshes on its own side.
|
|
370
|
+
return await repl(shell, relative, () => plane.refresh())
|
|
371
|
+
} finally {
|
|
372
|
+
shared?.close()
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// the REPL drives whatever holds the shell. locally that is an in-process
|
|
377
|
+
// HarnessShell over this checkout; for a cloud box it is a socket to the box,
|
|
378
|
+
// which holds both the files and the shell. same loop, because the only
|
|
379
|
+
// difference is where exec runs.
|
|
380
|
+
interface BoxSession {
|
|
381
|
+
readonly cwd: string
|
|
382
|
+
exec(command: string): Promise<HarnessShellResult>
|
|
124
383
|
}
|
|
125
384
|
|
|
126
385
|
async function repl(
|
|
127
|
-
|
|
128
|
-
plane: CheckoutFilePlane,
|
|
386
|
+
session: BoxSession,
|
|
129
387
|
startRelative: string,
|
|
388
|
+
beforeCommand?: () => Promise<void>,
|
|
130
389
|
): Promise<number> {
|
|
131
390
|
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
132
391
|
|
|
133
392
|
// enter where the user was standing, so `rnx box` from a subdirectory does
|
|
134
393
|
// not silently teleport them to the project root.
|
|
135
394
|
if (startRelative && !startRelative.startsWith('..')) {
|
|
136
|
-
await
|
|
395
|
+
await session.exec(`cd ${JSON.stringify(startRelative)}`)
|
|
137
396
|
}
|
|
138
397
|
|
|
139
398
|
const prompt = () => {
|
|
140
|
-
rl.setPrompt(`${
|
|
399
|
+
rl.setPrompt(`${session.cwd} > `)
|
|
141
400
|
rl.prompt()
|
|
142
401
|
}
|
|
143
402
|
|
|
@@ -155,11 +414,8 @@ async function repl(
|
|
|
155
414
|
const run = async (command: string) => {
|
|
156
415
|
if (stopped) return
|
|
157
416
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
// trusting a snapshot taken when the box started.
|
|
161
|
-
await plane.refresh()
|
|
162
|
-
const result = await shell.exec(command)
|
|
417
|
+
await beforeCommand?.()
|
|
418
|
+
const result = await session.exec(command)
|
|
163
419
|
if (result.text && result.text !== '(no output)') console.log(result.text)
|
|
164
420
|
} catch (error) {
|
|
165
421
|
console.error(error instanceof Error ? error.message : String(error))
|
package/cli/commands/control.ts
CHANGED
|
@@ -388,7 +388,12 @@ async function resolveConnectionInputForCli(target: string): Promise<ResolvedDev
|
|
|
388
388
|
|
|
389
389
|
if (registeredPort && registeredPort > 0) {
|
|
390
390
|
const discovered = await probePort(registeredPort)
|
|
391
|
-
|
|
391
|
+
// a provisional bundle URL means the scanner's fast manifest probe got no
|
|
392
|
+
// answer and fell back to the generic `/index.bundle`. that is enough to
|
|
393
|
+
// LIST the server and not enough to open it, so hand the question to
|
|
394
|
+
// `resolveConnectionInput`, which asks the manifest again with a budget
|
|
395
|
+
// that fits a real dev server and returns the entry point the app names.
|
|
396
|
+
if (discovered && !discovered.bundleUrlProvisional) {
|
|
392
397
|
return {
|
|
393
398
|
bundleUrl: discovered.bundleUrl,
|
|
394
399
|
port: discovered.port,
|
|
@@ -82,6 +82,18 @@ export const OUTBOUND_ENDPOINTS: Record<string, OutboundEndpointDeclaration> = {
|
|
|
82
82
|
preConsent: false,
|
|
83
83
|
payload: 'preview objects selected by the user for upload',
|
|
84
84
|
},
|
|
85
|
+
// its own entry, deliberately not folded into contrast_user_action. this is
|
|
86
|
+
// the only egress that sends the CONTENTS of the user's project somewhere,
|
|
87
|
+
// and to an operator-chosen host rather than a Contrast one, so it must not
|
|
88
|
+
// be readable as "the usual Contrast call".
|
|
89
|
+
rnx_cloud_box: {
|
|
90
|
+
category: 'explicit-user-action',
|
|
91
|
+
destination:
|
|
92
|
+
'the RNX cloud box service named by --endpoint or RNX_BOX_ENDPOINT; no default, so this call cannot happen unless the user names a host',
|
|
93
|
+
preConsent: false,
|
|
94
|
+
payload:
|
|
95
|
+
'every file git reports for the current checkout, then shell commands and file reads and writes for as long as the box shell is open',
|
|
96
|
+
},
|
|
85
97
|
}
|
|
86
98
|
|
|
87
99
|
export interface OutboundCallDeclaration {
|
|
@@ -93,6 +105,30 @@ export interface OutboundCallDeclaration {
|
|
|
93
105
|
// a new fetch, websocket, node HTTP request, or TCP connection has no entry and
|
|
94
106
|
// fails the package test until its destination and consent behavior are declared.
|
|
95
107
|
export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> = {
|
|
108
|
+
// `rnx box create` uploads the checkout to the box service and then talks to
|
|
109
|
+
// it. the destination is whatever the user named; there is no default.
|
|
110
|
+
'packages/sootsim/cli/commands/box/cloud-box.ts :: fetch :: endpointOf(base, name, verb)':
|
|
111
|
+
{ category: 'rnx_cloud_box', count: 1 },
|
|
112
|
+
'packages/sootsim/cli/commands/box/cloud-box.ts :: websocket :: url': {
|
|
113
|
+
category: 'rnx_cloud_box',
|
|
114
|
+
count: 1,
|
|
115
|
+
},
|
|
116
|
+
// the account's own session meter: starting, listing and heartbeating the
|
|
117
|
+
// session a cloud box is billed against. no project content.
|
|
118
|
+
"packages/sootsim/cli/commands/box/cloud-box.ts :: fetch :: `${options.apiOrigin.replace(/\\/+$/, '')}/v1/rnx/sessions`":
|
|
119
|
+
{ category: 'contrast_user_action', count: 1 },
|
|
120
|
+
'packages/sootsim/cli/commands/box/cloud-box.ts :: fetch :: url': {
|
|
121
|
+
category: 'contrast_user_action',
|
|
122
|
+
count: 1,
|
|
123
|
+
},
|
|
124
|
+
// a LOCAL box authorizing its own checkout with the loopback bridge daemon.
|
|
125
|
+
// this one refuses to guess a port, so it cannot reach anything but a bridge
|
|
126
|
+
// the user's own session is running; see the note on ws-bridge.ts below
|
|
127
|
+
// about splitting these if bridgeWsUrl ever returns a remote endpoint.
|
|
128
|
+
'packages/sootsim/cli/commands/box/plane-share.ts :: websocket :: bridgeWsUrl(port)': {
|
|
129
|
+
category: 'local_development',
|
|
130
|
+
count: 2,
|
|
131
|
+
},
|
|
96
132
|
'packages/contrast-runtime-delivery/src/index.ts :: fetch :: url': {
|
|
97
133
|
category: 'runtime_delivery',
|
|
98
134
|
count: 2,
|
|
@@ -101,7 +137,7 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
|
|
|
101
137
|
category: 'local_development',
|
|
102
138
|
count: 1,
|
|
103
139
|
},
|
|
104
|
-
'packages/sootsim/scripts/dev-server-scanner.ts :: http.request :: { hostname, port, path, method,
|
|
140
|
+
'packages/sootsim/scripts/dev-server-scanner.ts :: http.request :: { hostname, port, path, method, headers }':
|
|
105
141
|
{
|
|
106
142
|
category: 'local_development',
|
|
107
143
|
count: 1,
|
package/dist-cli/bin.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/*! rnx v0.1.
|
|
2
|
+
/*! rnx v0.1.372 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
3
3
|
import { createRequire as __sootsimCreateRequire } from 'node:module'
|
|
4
4
|
var require = __sootsimCreateRequire(import.meta.url)
|
|
5
|
-
import{a as g,b as f}from"./chunks/chunk-
|
|
5
|
+
import{a as g,b as f}from"./chunks/chunk-PKY76S57.js";import{a as h}from"./chunks/chunk-YWTBN7SD.js";import{a as m}from"./chunks/chunk-MSM2E74F.js";import"./chunks/chunk-WR7LRCYW.js";function k(a){let t=a instanceof Error?a.message:String(a);(/^command timed out after \d+s$/.test(t)||t.startsWith("sim disconnected:")||t.startsWith("bridge never reconnected")||t.startsWith("multiple sims are connected:")||t.startsWith("saved sim ")||t.startsWith("no sim connected with id ")||t.startsWith("could not connect to ws://")||t.startsWith("rnx bridge daemon is not running")||t.startsWith("rnx bridge lockfile is fresh")||t.startsWith("rnx bridge exited before becoming ready")||t.startsWith(`${m.commandName} open: requested bridge port`)||t.startsWith("background daemon setup is unavailable")||t.startsWith("rnx bridge did not start within"))&&!process.env.SOOTSIM_VERBOSE&&(process.stderr.write(` ${t}
|
|
6
6
|
`),process.exit(1)),a instanceof Error&&a.stack?process.stderr.write(`${a.stack}
|
|
7
7
|
`):process.stderr.write(`${t}
|
|
8
8
|
`),process.exit(1)}process.on("unhandledRejection",k);process.on("uncaughtException",k);var v=typeof __SOOTSIM_STANDALONE__<"u"&&__SOOTSIM_STANDALONE__,C=new Set(["desktop"]);function $(a){!v||!C.has(a)||(process.stderr.write(` ${m.commandName} ${a} isn't available in the standalone binary \u2014
|
|
9
9
|
it needs vite / electron / playwright from a project's node_modules.
|
|
10
10
|
run the npm package instead:
|
|
11
11
|
${m.commandName} ${a}
|
|
12
|
-
`),process.exit(1))}var e=f(process.argv);if(!e.command&&e.commandArgs.length>0){let{resolveHiddenRuntimeAlias:a}=await import("./chunks/hidden-runtime-alias-
|
|
13
|
-
`)}var b=!1;async function S(){if(!b&&(b=!0,!(e.command==="cleanup"||e.command==="serve"||e.command==="agent-wrapper"||e.command==="daemon"&&e.commandArgs[0]==="uninstall")))try{let{
|
|
12
|
+
`),process.exit(1))}var e=f(process.argv);if(!e.command&&e.commandArgs.length>0){let{resolveHiddenRuntimeAlias:a}=await import("./chunks/hidden-runtime-alias-7Z6PYH53.js");e=a(e)}var _=e.command==="cleanup"&&e.commandArgs.length===1&&e.commandArgs[0]==="--automatic-worker",x=!e.command&&e.commandArgs.length===0;if(!_&&!x&&!e.help&&!e.version&&e.command!=="config"&&e.command!=="setup"&&e.command!=="telemetry"){let{ensurePrivacyChoice:a}=await import("./chunks/privacy-WXGGG4ZI.js");await a({prompt:!0})}if(e.command!=="serve"&&e.command!=="daemon"){let{consumeRuntimeUpgradeNotice:a}=await import("./chunks/home-paths-MEGVA6ET.js"),t=a();t&&process.stderr.write(` ${m.name} engine upgraded to v${t.to}`+(t.from?` (from v${t.from})`:"")+` \xB7 what's new: ${m.origin}/changelog
|
|
13
|
+
`)}var b=!1;async function S(){if(!b&&(b=!0,!(e.command==="cleanup"||e.command==="serve"||e.command==="agent-wrapper"||e.command==="daemon"&&e.commandArgs[0]==="uninstall")))try{let{shouldSkipAutomaticSootsimCleanup:a}=await import("./chunks/disk-cleanup-HZM7VM4S.js");if(a())return;let{spawn:t}=await import("node:child_process"),r=["cleanup","--automatic-worker"],d=process.argv[1],p=v?t(process.execPath,r,{detached:!0,stdio:["ignore","ignore","inherit"],env:process.env}):d?t(process.execPath,[d,...r],{detached:!0,stdio:["ignore","ignore","inherit"],env:process.env}):t(m.commandName,r,{detached:!0,stdio:["ignore","ignore","inherit"],env:process.env});p.once("error",w=>{process.stderr.write(` ${m.name} automatic cleanup will retry on the next run: ${w.message}
|
|
14
14
|
`)}),p.unref()}catch(a){process.stderr.write(` ${m.name} automatic cleanup will retry on the next run: ${a instanceof Error?a.message:String(a)}
|
|
15
|
-
`)}}async function n(a){let{flushCliTelemetry:t}=await import("./chunks/telemetry-
|
|
15
|
+
`)}}async function n(a){let{flushCliTelemetry:t}=await import("./chunks/telemetry-E3CBKJMD.js");await t(),await S(),process.exit(a)}if(e.version){let{getCliVersion:a}=await import("./chunks/cli-version-D2IDQO7X.js"),{IS_BETA:t,BETA_LABEL:r}=await import("./chunks/beta-KYS22QPY.js"),d=t?` \xB7 ${r}`:"";console.log(`${m.commandName} v${a()}${d}`);let{readActiveRuntime:p}=await import("./chunks/home-paths-MEGVA6ET.js"),w=p();console.log(w?`runtime v${w}`:"runtime not installed"),await n(0)}if(e.help&&!e.command){let{printHelp:a}=await import("./chunks/help-6MLROWYO.js");a(),await n(0)}var o=e.globalFlags.port,c=e.verbose,u=e.globalFlags.device,l=e.globalFlags.theme,E=e.globalFlags.driver,O=e.globalFlags.headless===!0,A=e.globalFlags.sim??e.globalFlags.session??e.globalFlags.tab,i=A?["--sim",A,...e.commandArgs]:e.commandArgs;if(u||l){let{settingsStore:a}=await import("./chunks/store-TKBLMYCV.js"),t={};u&&(t.deviceModel=u),l&&(t.colorScheme=l),a.apply(t)}if(!e.command&&e.commandArgs.length===0){let{runWelcome:a}=await import("./chunks/welcome-7ATM5EGX.js");await n(await a())}var s=e.command??"";if(!s){if(e.commandArgs.length>0){let t=e.commandArgs.find(r=>!r.startsWith("-"))??e.commandArgs[0];console.error(` unknown command: ${t}`),console.error(` run \`${m.commandName} --help\` to see the full surface.`),await n(1)}let{printHelp:a}=await import("./chunks/help-6MLROWYO.js");a(),await n(0)}if(e.help||e.commandArgs.includes("--help")||e.commandArgs.includes("-h")){if(s==="skill"){let{runSkill:r}=await import("./chunks/skills-TDFQAZ6A.js");await r(e.commandArgs),await n(0)}if(s==="state"){let{runState:r}=await import("./chunks/state-P2Z25OLQ.js");await n(await r(i,{port:o}))}if(s==="reset"){let{runReset:r}=await import("./chunks/reset-ZZYLPETS.js");await n(await r(i,{port:o}))}let a=s==="do"||s==="get"||s==="debug"||s==="shell"||s==="perf"||s==="wait",t=e.commandArgs.find(r=>!r.startsWith("-"));if(!(s==="shell"||s==="perf"))if(a&&!t){let{printGroupHelp:r}=await import("./chunks/help-6MLROWYO.js");r(s)&&await n(0)}else{let{printCommandHelp:r}=await import("./chunks/help-6MLROWYO.js");r(a&&t?t:s,{prefer:a&&t?"verb":"command",group:a&&t?s:void 0}),await n(0)}}$(s);try{if(g.has(s)){if(s==="do"){let{hasDoChain:t,runDoChain:r}=await import("./chunks/do-chain-MTT65YNX.js");t(i)&&await n(await r(i,{port:o}))}let{runInspect:a}=await import("./chunks/inspect-EWT5SXBD.js");await a([s,...i],{port:o,verbose:c})}else switch(s){case"assert":{let{runAssert:a}=await import("./chunks/assert-Y5LXPWC6.js");await a(e.commandArgs);break}case"detox":{let{runDetox:a}=await import("./chunks/detox-RZ37736M.js");await a(e.commandArgs,{port:o,verbose:c});break}case"maestro":{let{runMaestro:a}=await import("./chunks/maestro-4HVDLCTI.js"),t=await a(i,{port:o,verbose:c});await n(typeof t=="number"?t:0)}case"record":{let{runRecord:a}=await import("./chunks/record-OUXWCWUF.js");await a(i,{port:o,verbose:c});break}case"film":{let{runFilm:a}=await import("./chunks/film-GPHSJLIH.js"),t=await a(i,{port:o,verbose:c});await n(typeof t=="number"?t:0)}case"storage":{let{runStorage:a}=await import("./chunks/storage-DWEOFXEB.js"),t=await a(i,{port:o,verbose:c});await n(typeof t=="number"?t:0)}case"state":{let{runState:a}=await import("./chunks/state-P2Z25OLQ.js"),t=await a(i,{port:o});await n(t)}case"reset":{let{runReset:a}=await import("./chunks/reset-ZZYLPETS.js"),t=await a(i,{port:o});await n(t)}case"perf":{let{runPerf:a}=await import("./chunks/perf-47R3OEI7.js"),t=await a(i,{port:o,verbose:c});await n(typeof t=="number"?t:0)}case"screenshot":{let{runScreenshotCommand:a}=await import("./chunks/screenshot-command-CUHDOS5D.js"),t=await a(i,{port:o,verbose:c});await n(typeof t=="number"?t:0)}case"camera":{let{runCamera:a}=await import("./chunks/camera-RUL426MQ.js"),t=await a(i,{port:o});await n(typeof t=="number"?t:0)}case"mode":{let{runMode:a}=await import("./chunks/mode-MGTS4QTF.js");await a(i,{port:o,verbose:c});break}case"permissions":{let{runPermissions:a}=await import("./chunks/permissions-ZOLFZWR2.js"),t=await a(i,{port:o,verbose:c});await n(t)}case"debug":{let{runDebug:a}=await import("./chunks/debug-7Z325GNU.js");await a(i,{port:o,verbose:c});break}case"timeline":{let{runTimeline:a}=await import("./chunks/timeline-55XNA4GX.js");await a(i,{port:o,verbose:c});break}case"what-happened":{let{runWhatHappened:a}=await import("./chunks/what-happened-Q3IGIH4K.js");await a(i,{port:o,verbose:c});break}case"open":{let{runOpenCommand:a}=await import("./chunks/control-FKPKLOE2.js");await a(i,{port:o});break}case"ios":case"android":{let{runPlatformCommand:a}=await import("./chunks/platform-C4E26ZBZ.js"),t=await a(s,i,{port:o});await n(typeof t=="number"?t:0)}case"box":{let{runBox:a}=await import("./chunks/box-7CK72ZPF.js");await n(await a(i,{port:o,verbose:c}))}case"use":{let{runUseCommand:a}=await import("./chunks/control-FKPKLOE2.js");await a(i,{port:o});break}case"claim":{let{runClaimCommand:a}=await import("./chunks/control-FKPKLOE2.js");await a(i,{port:o});break}case"close":{let{runCloseCommand:a}=await import("./chunks/control-FKPKLOE2.js");await a(i,{port:o});break}case"device":{let{runDeviceCommand:a}=await import("./chunks/device-TA3SQP6K.js");await a(i,{port:o});break}case"compat":{let{runCompat:a}=await import("./chunks/compat-4ZC6ZMMA.js");await a(e.commandArgs);break}case"report-issue":{let{runReportIssue:a}=await import("./chunks/report-issue-LJJXONXQ.js"),t=await a(e.commandArgs);await n(t)}case"desktop":{let{runDesktop:a}=await import("./chunks/desktop-CR7ALX7H.js");await a(e.commandArgs,{port:o,device:u});break}case"login":{let{runLogin:a}=await import("./chunks/login-EHUZYRAM.js");await a(e.commandArgs);break}case"logout":{let{runLogout:a}=await import("./chunks/logout-WPGT62N6.js");await a();break}case"auth":{let{runAuth:a}=await import("./chunks/auth-4SPTRWTM.js");await a(e.commandArgs);break}case"setup":{let{runSetup:a}=await import("./chunks/setup-X7LLGW67.js");await a(e.commandArgs);break}case"serve":{let{runServe:a}=await import("./chunks/serve-TX5OKGJM.js");await a(e.commandArgs,{port:o});break}case"daemon":{let{runDaemon:a}=await import("./chunks/daemon-WFHWS3JU.js");await a(e.commandArgs,{port:o});break}case"runtime":{let{runRuntime:a}=await import("./chunks/runtime-DEC5AF3M.js");await a(e.commandArgs);break}case"upgrade":{let{runUpgrade:a}=await import("./chunks/upgrade-3XRCVDVC.js");await a(e.commandArgs);break}case"version":{let{runVersion:a}=await import("./chunks/version-WTYGLXBJ.js");await a(e.commandArgs);break}case"agent":{let{runAgentCommand:a}=await import("./chunks/agent-RTAKJ5UM.js"),t=await a(e.commandArgs);await n(t)}case"agent-wrapper":{let{runAgentWrapper:a}=await import("./chunks/agent-wrapper-JWRDI26D.js"),t=await a(e.commandArgs);await n(t)}case"skill":{let{runSkill:a}=await import("./chunks/skills-TDFQAZ6A.js");await a(e.commandArgs);break}case"app-fonts":{let{runAppFonts:a}=await import("./chunks/app-fonts-RKO3N4PB.js");await a(e.commandArgs);break}case"config":{let{runConfig:a}=await import("./chunks/config-EOBODQ32.js");await a(e.commandArgs);break}case"cleanup":{let{runCleanup:a}=await import("./chunks/cleanup-7ATBNZHC.js"),t=await a(e.commandArgs);await n(t)}default:console.error(` unknown command: ${s}`),console.error(` run \`${m.commandName} --help\` to see the full surface.`),await n(1)}}catch(a){throw a instanceof h&&await n(a.code),a}await S();
|