rnxsim 0.1.313 → 0.1.314
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +11 -10
- package/README.md +5 -0
- package/cli/app-config.ts +65 -0
- package/cli/app-fonts.ts +408 -0
- package/cli/app-project.ts +231 -0
- package/cli/app-splash.ts +185 -0
- package/cli/app-state-reset.ts +24 -0
- package/cli/auth.ts +155 -0
- package/cli/bin.ts +594 -0
- package/cli/bridge-diagnostics.ts +226 -0
- package/cli/bridge-flow-runner.ts +2830 -0
- package/cli/browser-evals.ts +96 -0
- package/cli/commands/agent-wrapper.ts +986 -0
- package/cli/commands/agent.ts +423 -0
- package/cli/commands/app-fonts.ts +98 -0
- package/cli/commands/assert.ts +541 -0
- package/cli/commands/auth.ts +59 -0
- package/cli/commands/camera.ts +266 -0
- package/cli/commands/cleanup.ts +169 -0
- package/cli/commands/compat.ts +87 -0
- package/cli/commands/config.ts +32 -0
- package/cli/commands/control.ts +2142 -0
- package/cli/commands/cpu-profile.ts +269 -0
- package/cli/commands/daemon-mac-app.ts +169 -0
- package/cli/commands/daemon.ts +874 -0
- package/cli/commands/debug.ts +719 -0
- package/cli/commands/desktop.ts +39 -0
- package/cli/commands/detect.ts +197 -0
- package/cli/commands/detox.ts +385 -0
- package/cli/commands/device.ts +133 -0
- package/cli/commands/diagnose.ts +589 -0
- package/cli/commands/electron.ts +95 -0
- package/cli/commands/film.ts +379 -0
- package/cli/commands/flow.ts +1124 -0
- package/cli/commands/inspect/actions.ts +622 -0
- package/cli/commands/inspect/core.ts +2405 -0
- package/cli/commands/inspect/count.ts +17 -0
- package/cli/commands/inspect/describe.ts +192 -0
- package/cli/commands/inspect/env.ts +23 -0
- package/cli/commands/inspect/find.ts +171 -0
- package/cli/commands/inspect/get-layout.ts +39 -0
- package/cli/commands/inspect/keyboard.ts +52 -0
- package/cli/commands/inspect/list.ts +58 -0
- package/cli/commands/inspect/memory.ts +215 -0
- package/cli/commands/inspect/redaction.ts +39 -0
- package/cli/commands/inspect/resolve-target.ts +82 -0
- package/cli/commands/inspect/screens.ts +78 -0
- package/cli/commands/inspect/settle.ts +22 -0
- package/cli/commands/inspect/settling.ts +158 -0
- package/cli/commands/inspect/shared.ts +353 -0
- package/cli/commands/inspect/sleep.ts +14 -0
- package/cli/commands/inspect/tree.ts +32 -0
- package/cli/commands/inspect/url.ts +17 -0
- package/cli/commands/inspect/wait-event.ts +210 -0
- package/cli/commands/inspect/wait-idle.ts +24 -0
- package/cli/commands/inspect/wait-ready.ts +74 -0
- package/cli/commands/inspect/wait-selector.ts +54 -0
- package/cli/commands/inspect/wait.ts +31 -0
- package/cli/commands/inspect.ts +4519 -0
- package/cli/commands/install-desktop.ts +351 -0
- package/cli/commands/login.ts +331 -0
- package/cli/commands/logout.ts +31 -0
- package/cli/commands/maestro-generate.ts +361 -0
- package/cli/commands/maestro.ts +453 -0
- package/cli/commands/mode.ts +57 -0
- package/cli/commands/no-bridge-hint.ts +80 -0
- package/cli/commands/perf.ts +66 -0
- package/cli/commands/permissions.ts +203 -0
- package/cli/commands/profile.ts +108 -0
- package/cli/commands/react.ts +353 -0
- package/cli/commands/record.ts +1434 -0
- package/cli/commands/report-issue.ts +305 -0
- package/cli/commands/reset.ts +85 -0
- package/cli/commands/runtime.ts +351 -0
- package/cli/commands/screenshot-command.ts +106 -0
- package/cli/commands/screenshot-layers.ts +143 -0
- package/cli/commands/screenshot-mode.ts +37 -0
- package/cli/commands/screenshot.ts +488 -0
- package/cli/commands/screenshots-capture.ts +607 -0
- package/cli/commands/screenshots.ts +127 -0
- package/cli/commands/serve.ts +168 -0
- package/cli/commands/setup.ts +545 -0
- package/cli/commands/shell-boolean-mode.ts +81 -0
- package/cli/commands/skills.ts +467 -0
- package/cli/commands/slides.ts +361 -0
- package/cli/commands/state.ts +87 -0
- package/cli/commands/storage.ts +58 -0
- package/cli/commands/telemetry.ts +54 -0
- package/cli/commands/three-mode.ts +763 -0
- package/cli/commands/timeline.ts +122 -0
- package/cli/commands/upgrade.ts +208 -0
- package/cli/commands/upload.ts +1225 -0
- package/cli/commands/version.ts +54 -0
- package/cli/commands/what-happened.ts +327 -0
- package/cli/current-sim.ts +204 -0
- package/cli/desktop-companion.ts +300 -0
- package/cli/drivers/electron.ts +70 -0
- package/cli/drivers/index.ts +20 -0
- package/cli/drivers/playwright-provisioning.ts +180 -0
- package/cli/drivers/playwright.ts +698 -0
- package/cli/drivers/registry.ts +65 -0
- package/cli/drivers/types.ts +102 -0
- package/cli/flow-file.ts +142 -0
- package/cli/flow-live-status.ts +120 -0
- package/cli/flow-session.ts +187 -0
- package/cli/help.ts +80 -0
- package/cli/hidden-runtime-alias.ts +19 -0
- package/cli/hints.ts +216 -0
- package/cli/inspect-notice-state.ts +114 -0
- package/cli/maestro-js.ts +334 -0
- package/cli/open-url.ts +8 -0
- package/cli/parent-pid.ts +204 -0
- package/cli/parse-args.ts +211 -0
- package/cli/prompt.ts +51 -0
- package/cli/recording-access.ts +107 -0
- package/cli/registry.ts +1 -0
- package/cli/resolve-assets.ts +63 -0
- package/cli/run-registry.ts +226 -0
- package/cli/runtime-notes.ts +66 -0
- package/cli/runtime-summary.ts +25 -0
- package/cli/setup-repository.ts +187 -0
- package/cli/telemetry.ts +187 -0
- package/cli/ws-bridge.ts +798 -0
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-XZ2KTPCU.js → agent-7YBDCYMA.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-JJYYW2WH.js → agent-wrapper-2GHFBHCR.js} +2 -2
- package/dist-cli/chunks/{app-fonts-IXRNQG6B.js → app-fonts-RSNVPQSU.js} +2 -2
- package/dist-cli/chunks/{assert-54T5SK5F.js → assert-XCMX3XJX.js} +2 -2
- package/dist-cli/chunks/{auth-FI5UDI45.js → auth-B442HRAX.js} +2 -2
- package/dist-cli/chunks/{beta-JV6UKADW.js → beta-XJ55JK3M.js} +2 -2
- package/dist-cli/chunks/camera-UGYSSLIK.js +33 -0
- package/dist-cli/chunks/{chunk-3NV2NCNX.js → chunk-277AEQZX.js} +2 -2
- package/dist-cli/chunks/{chunk-2YR5BGA5.js → chunk-2JNSK774.js} +2 -2
- package/dist-cli/chunks/{chunk-RSZWCKNT.js → chunk-32WOTSTR.js} +3 -3
- package/dist-cli/chunks/{chunk-WEXDAC74.js → chunk-3S753SNQ.js} +2 -2
- package/dist-cli/chunks/{chunk-IJO63TDP.js → chunk-46ZOLOYA.js} +2 -2
- package/dist-cli/chunks/{chunk-WUSWBCWA.js → chunk-5L7ELDQL.js} +8 -9
- package/dist-cli/chunks/{chunk-WF3T4SVI.js → chunk-7OOPFSQS.js} +2 -2
- package/dist-cli/chunks/{chunk-TZFFR3SD.js → chunk-7SV3RPRW.js} +2 -2
- package/dist-cli/chunks/{chunk-WWZIXIRD.js → chunk-7ZC35MOU.js} +1 -1
- package/dist-cli/chunks/chunk-AMG5E6CC.js +9 -0
- package/dist-cli/chunks/{chunk-WINYQ44O.js → chunk-APWNH3A4.js} +1 -1
- package/dist-cli/chunks/chunk-B57XUKY3.js +4 -0
- package/dist-cli/chunks/{chunk-YDGQTMQL.js → chunk-C2NL26TD.js} +1 -1
- package/dist-cli/chunks/{chunk-NMF2ZMZQ.js → chunk-D2FNUWAB.js} +4 -4
- package/dist-cli/chunks/{chunk-YIFT42WN.js → chunk-E5T4XSJ3.js} +2 -2
- package/dist-cli/chunks/{chunk-46EUUFJ5.js → chunk-EAC34EQS.js} +1 -1
- package/dist-cli/chunks/{chunk-OVFJFXUD.js → chunk-EG32ML36.js} +2 -2
- package/dist-cli/chunks/{chunk-5YJCOWCH.js → chunk-FXUAC6D5.js} +1 -1
- package/dist-cli/chunks/chunk-GQSL4USA.js +6 -0
- package/dist-cli/chunks/{chunk-VFCMSYZK.js → chunk-HAXW27SS.js} +2 -2
- package/dist-cli/chunks/{chunk-2D2UPBBR.js → chunk-IZAHPAN6.js} +1 -1
- package/dist-cli/chunks/{chunk-7GN3LVWB.js → chunk-J62KM5TB.js} +2 -2
- package/dist-cli/chunks/{chunk-BTWORNNG.js → chunk-JEMCD5E4.js} +1 -1
- package/dist-cli/chunks/{chunk-DCEMHR2Y.js → chunk-JZS3Q37N.js} +2 -2
- package/dist-cli/chunks/{chunk-D4FFVGI5.js → chunk-KR4JON7D.js} +1 -1
- package/dist-cli/chunks/chunk-KWCYKQIQ.js +4 -0
- package/dist-cli/chunks/{chunk-VNQEB4L7.js → chunk-MCWPL644.js} +2 -2
- package/dist-cli/chunks/chunk-MJYK3N2I.js +4 -0
- package/dist-cli/chunks/{chunk-5TEF3ET3.js → chunk-O6TRIZNS.js} +2 -2
- package/dist-cli/chunks/{chunk-QKDWYITG.js → chunk-P7XL2E73.js} +3 -3
- package/dist-cli/chunks/{chunk-BBULZ7CG.js → chunk-PFQTUKQ4.js} +62 -87
- package/dist-cli/chunks/{chunk-GGRX24GF.js → chunk-PG5RZCTN.js} +2 -2
- package/dist-cli/chunks/{chunk-W6K4EFPH.js → chunk-QGRI2Z4M.js} +2 -2
- package/dist-cli/chunks/{chunk-ZMJD5GEC.js → chunk-QLXXE7GE.js} +1 -1
- package/dist-cli/chunks/{chunk-VZXWHRUZ.js → chunk-QOJJJWJE.js} +89 -133
- package/dist-cli/chunks/{chunk-OZSSI4WN.js → chunk-RWZY5427.js} +2 -2
- package/dist-cli/chunks/{chunk-UC6U3MML.js → chunk-RZKU2K3J.js} +2 -2
- package/dist-cli/chunks/{chunk-IJ5CAZZC.js → chunk-SBV4IK4H.js} +1 -1
- package/dist-cli/chunks/{chunk-DZS6WPUI.js → chunk-SGMVFFMK.js} +1 -1
- package/dist-cli/chunks/{chunk-OHAZNXLK.js → chunk-TAX4UT2N.js} +1 -1
- package/dist-cli/chunks/{chunk-5DHC6KHQ.js → chunk-TUOFAWXT.js} +1 -1
- package/dist-cli/chunks/{chunk-GASE6UBA.js → chunk-UHZLOHGP.js} +1 -1
- package/dist-cli/chunks/{chunk-F5ZRSS3C.js → chunk-VC7V76U3.js} +1 -1
- package/dist-cli/chunks/{chunk-RTN5C5RL.js → chunk-VQVMLW4U.js} +1 -1
- package/dist-cli/chunks/{chunk-WMIIKMGK.js → chunk-VUKKYPZN.js} +2 -2
- package/dist-cli/chunks/{chunk-5TPRP5QT.js → chunk-WGGRJDRE.js} +1 -1
- package/dist-cli/chunks/chunk-X6H76EKP.js +15 -0
- package/dist-cli/chunks/chunk-XLZ5FNRT.js +27 -0
- package/dist-cli/chunks/{chunk-HI5TFJWN.js → chunk-XULEACM4.js} +2 -2
- package/dist-cli/chunks/{chunk-MJRLLB4R.js → chunk-YFSDM7AX.js} +4 -4
- package/dist-cli/chunks/chunk-YWI3UEVX.js +5 -0
- package/dist-cli/chunks/{chunk-XEVZYVIW.js → chunk-ZBSJO4NB.js} +10 -9
- package/dist-cli/chunks/{cleanup-P27PA6JI.js → cleanup-EYLGCA6Z.js} +2 -2
- package/dist-cli/chunks/cli-version-LL2UGIHE.js +4 -0
- package/dist-cli/chunks/{compat-ZD65FED3.js → compat-TLJYHB4E.js} +2 -2
- package/dist-cli/chunks/{config-XMJRNM2A.js → config-4JWUOEXK.js} +2 -2
- package/dist-cli/chunks/{control-KMIQT3QP.js → control-HPAOYF4N.js} +2 -2
- package/dist-cli/chunks/daemon-OYLASXLE.js +4 -0
- package/dist-cli/chunks/{debug-PT4HOP7N.js → debug-4BKXF6KI.js} +5 -5
- package/dist-cli/chunks/{desktop-S3FG72AK.js → desktop-ZQ6ZD2S6.js} +3 -3
- package/dist-cli/chunks/{detox-B3D4IFCN.js → detox-WNPASSS3.js} +2 -2
- package/dist-cli/chunks/{device-XBNDSB2R.js → device-BXLXVG3V.js} +2 -2
- package/dist-cli/chunks/{diagnose-HMQXJE5N.js → diagnose-3QX7W5RW.js} +2 -2
- package/dist-cli/chunks/{disk-cleanup-BLCZ5BSZ.js → disk-cleanup-KSWKI7WB.js} +2 -2
- package/dist-cli/chunks/drivers-EPIEFF7P.js +4 -0
- package/dist-cli/chunks/{film-BJGTBYZB.js → film-GDMR33VO.js} +3 -3
- package/dist-cli/chunks/flow-NKMPBYCJ.js +4 -0
- package/dist-cli/chunks/help-OGCBHOKA.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-ANOYADHM.js → hidden-runtime-alias-S2GTDX3T.js} +2 -2
- package/dist-cli/chunks/home-paths-QRCDLTTV.js +4 -0
- package/dist-cli/chunks/inspect-FUYMOZPY.js +4 -0
- package/dist-cli/chunks/install-desktop-FR6YKW7Y.js +4 -0
- package/dist-cli/chunks/{login-FJ737MWG.js → login-IJAPUZHI.js} +4 -4
- package/dist-cli/chunks/{logout-ZCNMMHMY.js → logout-QMXDFU2X.js} +2 -2
- package/dist-cli/chunks/{maestro-SZTNKLDF.js → maestro-ZXU3YCVX.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-DCFAIZ4H.js → maestro-generate-PYB5QY7K.js} +3 -3
- package/dist-cli/chunks/{mode-GRMQCRXR.js → mode-WGUCL5FZ.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-W36EWFFB.js → optional-demo-registry-WH2O6H36.js} +2 -2
- package/dist-cli/chunks/{perf-QYBAAUZG.js → perf-TOD3UFAH.js} +2 -2
- package/dist-cli/chunks/{permissions-3QCQ6VF4.js → permissions-I5BRJGTB.js} +2 -2
- package/dist-cli/chunks/{record-QPPC2S4E.js → record-ZYL2FYSK.js} +3 -3
- package/dist-cli/chunks/{report-issue-7NMFP4HK.js → report-issue-TAI6DYZO.js} +2 -2
- package/dist-cli/chunks/reset-7YGYKQPQ.js +4 -0
- package/dist-cli/chunks/runtime-B4JO6QRI.js +4 -0
- package/dist-cli/chunks/{screenshot-command-67AECJFB.js → screenshot-command-7ANLODZY.js} +7 -7
- package/dist-cli/chunks/{screenshot-layers-ASWBYPJL.js → screenshot-layers-A7FYXSVU.js} +3 -3
- package/dist-cli/chunks/{screenshots-capture-PXA3HFQK.js → screenshots-capture-WT2ZY6CB.js} +2 -2
- package/dist-cli/chunks/serve-TG5WKA4V.js +44 -0
- package/dist-cli/chunks/{setup-7DWPMRSB.js → setup-PCWLD22V.js} +2 -2
- package/dist-cli/chunks/{skills-S3Y22TUA.js → skills-27ZHWCQS.js} +2 -2
- package/dist-cli/chunks/state-ZQVY46ZO.js +14 -0
- package/dist-cli/chunks/{storage-XUIMJWAJ.js → storage-OYC57C4X.js} +6 -6
- package/dist-cli/chunks/store-32HSPZHI.js +4 -0
- package/dist-cli/chunks/telemetry-PBJR7XHR.js +4 -0
- package/dist-cli/chunks/{timeline-TMPLQPSP.js → timeline-RAJJFQT6.js} +2 -2
- package/dist-cli/chunks/{upgrade-7HDSIM7K.js → upgrade-OEFNZRIP.js} +2 -2
- package/dist-cli/chunks/upload-LQJITLKK.js +4 -0
- package/dist-cli/chunks/version-NIXTY6PL.js +6 -0
- package/dist-cli/chunks/{web-DG3WBYD3.js → web-XBUTBVGR.js} +2 -2
- package/dist-cli/chunks/{what-happened-XFVUTZR7.js → what-happened-YNHRFUDX.js} +3 -3
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract.cjs +20 -0
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +67 -28
- package/dist-lib/host/bridge-host.cjs +140 -12
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +2815 -40
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +2549 -2061
- package/dist-lib/sdk.mjs +2543 -2061
- package/dist-lib/skills.cjs +480 -280
- package/dist-lib/vite.cjs +1 -1
- package/package.json +8 -2
- package/src/bridge-constants.ts +3 -4
- package/src/bridge-contract.ts +251 -0
- package/src/connect.ts +83 -0
- package/src/disk-cleanup.ts +30 -0
- package/src/home-paths.ts +81 -38
- package/src/host/bridge-host.ts +134 -6
- package/src/index.ts +27 -1
- package/src/sdk.ts +8 -0
- package/src/sim-client.ts +660 -0
- package/dist-cli/chunks/camera-VL73YIKP.js +0 -22
- package/dist-cli/chunks/chunk-4NPPOV2N.js +0 -5
- package/dist-cli/chunks/chunk-FSUYIVJ6.js +0 -9
- package/dist-cli/chunks/chunk-G2WW6L2C.js +0 -23
- package/dist-cli/chunks/chunk-KTHV3RUS.js +0 -26
- package/dist-cli/chunks/chunk-LF2ZVT7O.js +0 -6
- package/dist-cli/chunks/chunk-NFK7T35W.js +0 -4
- package/dist-cli/chunks/chunk-TIVZIMMW.js +0 -4
- package/dist-cli/chunks/cli-version-WWLPBDQ7.js +0 -4
- package/dist-cli/chunks/daemon-G2ME7NLB.js +0 -4
- package/dist-cli/chunks/drivers-LDECZGP2.js +0 -4
- package/dist-cli/chunks/flow-UEQNVTU7.js +0 -4
- package/dist-cli/chunks/help-T5FYSVGB.js +0 -4
- package/dist-cli/chunks/home-paths-GT3LFNOR.js +0 -4
- package/dist-cli/chunks/inspect-ZA6XF5LD.js +0 -4
- package/dist-cli/chunks/install-desktop-TIMUDHPL.js +0 -4
- package/dist-cli/chunks/runtime-XOAXMSTU.js +0 -4
- package/dist-cli/chunks/serve-BI2NBAXG.js +0 -44
- package/dist-cli/chunks/store-JTHEJLAZ.js +0 -4
- package/dist-cli/chunks/telemetry-ZYJGD2DB.js +0 -4
- package/dist-cli/chunks/upload-GMSZPWM6.js +0 -4
- package/dist-cli/chunks/version-HOCHZ37L.js +0 -6
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process'
|
|
2
|
+
import { readFileSync } from 'fs'
|
|
3
|
+
import { createRequire } from 'module'
|
|
4
|
+
|
|
5
|
+
// look up a process's parent pid. returns null if we can't read it
|
|
6
|
+
// (unsupported platform, process gone, permission denied).
|
|
7
|
+
//
|
|
8
|
+
// used by session-identity to walk one hop up from `process.ppid` — in a
|
|
9
|
+
// Claude Code Bash tool the shell's parent is the agent-level process,
|
|
10
|
+
// which is stable across invocations and distinct between agents.
|
|
11
|
+
|
|
12
|
+
type Lookup = (pid: number) => number | null
|
|
13
|
+
type CommLookup = (pid: number) => string | null
|
|
14
|
+
|
|
15
|
+
let cached: Lookup | null = null
|
|
16
|
+
let cachedComm: CommLookup | null = null
|
|
17
|
+
|
|
18
|
+
export function getParentPid(pid: number): number | null {
|
|
19
|
+
if (!cached) cached = build()
|
|
20
|
+
try {
|
|
21
|
+
return cached(pid)
|
|
22
|
+
} catch {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// short command name (comm) for a pid, or null when unreadable. used by the
|
|
28
|
+
// owner-pid walk to stop at effectively-immortal session hosts (tmux/screen
|
|
29
|
+
// servers and the Team Machine daemon), which must never become a sim owner.
|
|
30
|
+
export function getProcessComm(pid: number): string | null {
|
|
31
|
+
if (!cachedComm) cachedComm = buildComm()
|
|
32
|
+
try {
|
|
33
|
+
return cachedComm(pid)
|
|
34
|
+
} catch {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildComm(): CommLookup {
|
|
40
|
+
if (process.platform === 'linux') {
|
|
41
|
+
return (pid) => {
|
|
42
|
+
try {
|
|
43
|
+
return readFileSync(`/proc/${pid}/comm`, 'utf8').trim() || null
|
|
44
|
+
} catch {
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (process.platform === 'darwin') {
|
|
50
|
+
const ffi = tryBuildDarwinCommFfi()
|
|
51
|
+
if (ffi) return ffi
|
|
52
|
+
return (pid) => readProcessTable().get(pid)?.comm || null
|
|
53
|
+
}
|
|
54
|
+
return () => null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function build(): Lookup {
|
|
58
|
+
// on any platform, if the shell exports $PPID and it differs from the
|
|
59
|
+
// node-level process.ppid, it's the shell's own parent — one level up
|
|
60
|
+
// from where we need to be. this is the cheapest and most portable
|
|
61
|
+
// grand-ppid source since it needs no subprocess or FFI.
|
|
62
|
+
const envPpid = Number(process.env.PPID)
|
|
63
|
+
if (Number.isFinite(envPpid) && envPpid > 1 && envPpid !== process.ppid) {
|
|
64
|
+
return () => envPpid
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (process.platform === 'linux') return buildLinux()
|
|
68
|
+
if (process.platform === 'darwin') {
|
|
69
|
+
// on Bun: libproc via FFI is ~0.8µs/call. on Node: one `ps` snapshot of
|
|
70
|
+
// the whole tree (see readProcessTable).
|
|
71
|
+
const ffi = tryBuildDarwinFfi()
|
|
72
|
+
if (ffi) return ffi
|
|
73
|
+
return (pid) => {
|
|
74
|
+
const ppid = readProcessTable().get(pid)?.ppid
|
|
75
|
+
return ppid !== undefined && ppid > 0 ? ppid : null
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return () => null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// one snapshot of the whole process tree, shared by the ppid and comm lookups.
|
|
82
|
+
// macOS has no /proc and Node cannot call libproc, so each lookup used to shell
|
|
83
|
+
// out to `lsof -R -p <pid>` with a 2000ms budget — measured at 3.5s, 7.1s and
|
|
84
|
+
// 7.9s on a loaded box, so every hop of an ancestor walk timed out, the walk
|
|
85
|
+
// resolved to null, and `contrast ide open` launched a browser host with an
|
|
86
|
+
// empty owner pid that `ide close` could never attribute. `ps -eo pid,ppid,comm`
|
|
87
|
+
// answers for every process at once in ~0.1-0.9s. the short TTL keeps one walk
|
|
88
|
+
// on one snapshot while a long-lived caller still sees the tree change.
|
|
89
|
+
const PROCESS_TABLE_TTL_MS = 2000
|
|
90
|
+
let processTable: { readAt: number; rows: Map<number, { ppid: number; comm: string }> } =
|
|
91
|
+
{ readAt: 0, rows: new Map() }
|
|
92
|
+
|
|
93
|
+
function readProcessTable(): Map<number, { ppid: number; comm: string }> {
|
|
94
|
+
const now = Date.now()
|
|
95
|
+
if (processTable.readAt && now - processTable.readAt < PROCESS_TABLE_TTL_MS) {
|
|
96
|
+
return processTable.rows
|
|
97
|
+
}
|
|
98
|
+
const rows = new Map<number, { ppid: number; comm: string }>()
|
|
99
|
+
try {
|
|
100
|
+
const out = execFileSync('ps', ['-eo', 'pid=,ppid=,comm='], {
|
|
101
|
+
encoding: 'utf8',
|
|
102
|
+
timeout: 5000,
|
|
103
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
104
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
105
|
+
})
|
|
106
|
+
for (const line of out.split('\n')) {
|
|
107
|
+
const parsed = line.trim().match(/^(\d+)\s+(\d+)\s+(.*)$/)
|
|
108
|
+
if (!parsed) continue
|
|
109
|
+
// libproc reports pbi_comm — the executable's basename, truncated to
|
|
110
|
+
// MAXCOMLEN — and the immortal-host patterns are written against that
|
|
111
|
+
// shape, while `ps -o comm` prints the full path. normalize to one.
|
|
112
|
+
rows.set(Number(parsed[1]), {
|
|
113
|
+
ppid: Number(parsed[2]),
|
|
114
|
+
comm: (parsed[3].split('/').at(-1) ?? '').slice(0, PBI_COMM_LEN),
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// an unreadable table means unresolved ancestry, which callers already
|
|
119
|
+
// treat as "no defensible owner" and refuse on.
|
|
120
|
+
}
|
|
121
|
+
processTable = { readAt: now, rows }
|
|
122
|
+
return rows
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildLinux(): Lookup {
|
|
126
|
+
return (pid) => {
|
|
127
|
+
try {
|
|
128
|
+
const raw = readFileSync(`/proc/${pid}/stat`, 'utf8')
|
|
129
|
+
const end = raw.lastIndexOf(')')
|
|
130
|
+
if (end < 0) return null
|
|
131
|
+
const rest = raw
|
|
132
|
+
.slice(end + 1)
|
|
133
|
+
.trim()
|
|
134
|
+
.split(/\s+/)
|
|
135
|
+
const ppid = Number(rest[1])
|
|
136
|
+
return Number.isFinite(ppid) && ppid > 0 ? ppid : null
|
|
137
|
+
} catch {
|
|
138
|
+
return null
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// PROC_PIDTBSDINFO flavor for proc_pidinfo. struct proc_bsdinfo is 216 bytes;
|
|
144
|
+
// pbi_ppid is at offset 16 (after pbi_flags, pbi_status, pbi_xstatus, pbi_pid,
|
|
145
|
+
// each 4 bytes). source: <sys/proc_info.h> in the macOS SDK.
|
|
146
|
+
const PROC_PIDTBSDINFO = 3
|
|
147
|
+
const PROC_BSDINFO_SIZE = 216
|
|
148
|
+
const PBI_PPID_OFFSET = 16
|
|
149
|
+
// char pbi_comm[MAXCOMLEN] — after the 4-byte flags/status/xstatus/pid/ppid/
|
|
150
|
+
// uid/gid/ruid/rgid/svuid/svgid/rfu_1 fields. source: <sys/proc_info.h>.
|
|
151
|
+
const PBI_COMM_OFFSET = 48
|
|
152
|
+
const PBI_COMM_LEN = 16
|
|
153
|
+
|
|
154
|
+
function tryBuildDarwinFfi(): Lookup | null {
|
|
155
|
+
if (!process.versions?.bun) return null
|
|
156
|
+
try {
|
|
157
|
+
const r = createRequire(import.meta.url)
|
|
158
|
+
const { dlopen, FFIType } = r('bun:ffi')
|
|
159
|
+
const lib = dlopen('/usr/lib/libproc.dylib', {
|
|
160
|
+
proc_pidinfo: {
|
|
161
|
+
args: [FFIType.i32, FFIType.i32, FFIType.u64, FFIType.ptr, FFIType.i32],
|
|
162
|
+
returns: FFIType.i32,
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
const buf = Buffer.alloc(PROC_BSDINFO_SIZE)
|
|
166
|
+
return (pid) => {
|
|
167
|
+
const ret = Number(
|
|
168
|
+
lib.symbols.proc_pidinfo(pid, PROC_PIDTBSDINFO, 0n, buf, PROC_BSDINFO_SIZE),
|
|
169
|
+
)
|
|
170
|
+
if (ret <= 0) return null
|
|
171
|
+
const ppid = buf.readUInt32LE(PBI_PPID_OFFSET)
|
|
172
|
+
return ppid > 0 ? ppid : null
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
return null
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function tryBuildDarwinCommFfi(): CommLookup | null {
|
|
180
|
+
if (!process.versions?.bun) return null
|
|
181
|
+
try {
|
|
182
|
+
const r = createRequire(import.meta.url)
|
|
183
|
+
const { dlopen, FFIType } = r('bun:ffi')
|
|
184
|
+
const lib = dlopen('/usr/lib/libproc.dylib', {
|
|
185
|
+
proc_pidinfo: {
|
|
186
|
+
args: [FFIType.i32, FFIType.i32, FFIType.u64, FFIType.ptr, FFIType.i32],
|
|
187
|
+
returns: FFIType.i32,
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
const buf = Buffer.alloc(PROC_BSDINFO_SIZE)
|
|
191
|
+
return (pid) => {
|
|
192
|
+
const ret = Number(
|
|
193
|
+
lib.symbols.proc_pidinfo(pid, PROC_PIDTBSDINFO, 0n, buf, PROC_BSDINFO_SIZE),
|
|
194
|
+
)
|
|
195
|
+
if (ret <= 0) return null
|
|
196
|
+
const raw = buf.subarray(PBI_COMM_OFFSET, PBI_COMM_OFFSET + PBI_COMM_LEN)
|
|
197
|
+
const end = raw.indexOf(0)
|
|
198
|
+
const comm = raw.subarray(0, end < 0 ? raw.length : end).toString('utf8')
|
|
199
|
+
return comm || null
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
return null
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// shared CLI argument parser — handles global flags, commands, and per-command flags
|
|
2
|
+
|
|
3
|
+
export interface ParsedArgs {
|
|
4
|
+
command: string | null
|
|
5
|
+
commandArgs: string[]
|
|
6
|
+
globalFlags: Record<string, string | boolean | number>
|
|
7
|
+
help: boolean
|
|
8
|
+
version: boolean
|
|
9
|
+
verbose: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const GLOBAL_FLAGS: Record<
|
|
13
|
+
string,
|
|
14
|
+
{ type: 'string' | 'boolean' | 'number'; short?: string }
|
|
15
|
+
> = {
|
|
16
|
+
'--help': { type: 'boolean', short: '-h' },
|
|
17
|
+
'--version': { type: 'boolean', short: '-V' },
|
|
18
|
+
'--verbose': { type: 'boolean', short: '-v' },
|
|
19
|
+
'--port': { type: 'number', short: '-p' },
|
|
20
|
+
'--device': { type: 'string', short: '-d' },
|
|
21
|
+
'--theme': { type: 'string', short: '-t' },
|
|
22
|
+
'--headless': { type: 'boolean' },
|
|
23
|
+
'--driver': { type: 'string' },
|
|
24
|
+
'--sim': { type: 'string' },
|
|
25
|
+
// documented aliases for --sim (CLAUDE.md / skills / engine docs say
|
|
26
|
+
// `--session <id>`). normalized to the sim target in bin.ts.
|
|
27
|
+
'--session': { type: 'string' },
|
|
28
|
+
'--tab': { type: 'string' },
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// top-level verbs that route into runInspect. the surface is intentionally
|
|
32
|
+
// tiny — everything else lives under one of the grouping verbs:
|
|
33
|
+
// - `sootsim get <noun>` reads
|
|
34
|
+
// - `sootsim do <action>` writes
|
|
35
|
+
// - `sootsim debug <tool>` instrumentation (handled by runDebug)
|
|
36
|
+
// - `sootsim inspect <sub>` full legacy surface, all subcommands
|
|
37
|
+
// `describe` and `find` stay at root as the most common entrypoints.
|
|
38
|
+
// `list` stays at root because it's a sim-management read.
|
|
39
|
+
export const TOP_LEVEL_RUNTIME_COMMANDS = new Set([
|
|
40
|
+
'list',
|
|
41
|
+
'describe',
|
|
42
|
+
'find',
|
|
43
|
+
'get',
|
|
44
|
+
'do',
|
|
45
|
+
'wait',
|
|
46
|
+
'network',
|
|
47
|
+
'logs',
|
|
48
|
+
'shell',
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
// grouping verbs that take a sub-verb (`sootsim wait ready`,
|
|
52
|
+
// `sootsim do tap-id`). agents and humans very often type the hyphenated
|
|
53
|
+
// form (`sootsim wait-ready`, `sootsim do-tap-id`) — it reads as one
|
|
54
|
+
// command and matches the typed-tool names (rnx_wait_ready). bin.ts
|
|
55
|
+
// normalizes `<verb>-<rest>` → `<verb> <rest>` for these so the intuitive
|
|
56
|
+
// spelling just works instead of failing as "unknown command".
|
|
57
|
+
export const HYPHENATED_GROUPING_VERBS = new Set(['do', 'get', 'wait', 'shell', 'debug'])
|
|
58
|
+
|
|
59
|
+
// known commands — if first positional arg matches, it's a command
|
|
60
|
+
const COMMANDS = new Set([
|
|
61
|
+
'detox',
|
|
62
|
+
'maestro',
|
|
63
|
+
'record',
|
|
64
|
+
'film',
|
|
65
|
+
'storage',
|
|
66
|
+
'permissions',
|
|
67
|
+
'perf',
|
|
68
|
+
'screenshot',
|
|
69
|
+
'state',
|
|
70
|
+
'reset',
|
|
71
|
+
'camera',
|
|
72
|
+
'mode',
|
|
73
|
+
'inspect',
|
|
74
|
+
'debug',
|
|
75
|
+
'timeline',
|
|
76
|
+
'what-happened',
|
|
77
|
+
'shell',
|
|
78
|
+
'assert',
|
|
79
|
+
'device',
|
|
80
|
+
'open',
|
|
81
|
+
'list',
|
|
82
|
+
'use',
|
|
83
|
+
'claim',
|
|
84
|
+
'close',
|
|
85
|
+
'compat',
|
|
86
|
+
'report-issue',
|
|
87
|
+
'desktop',
|
|
88
|
+
'login',
|
|
89
|
+
'logout',
|
|
90
|
+
'auth',
|
|
91
|
+
'setup',
|
|
92
|
+
'serve',
|
|
93
|
+
'daemon',
|
|
94
|
+
'runtime',
|
|
95
|
+
'upgrade',
|
|
96
|
+
'update',
|
|
97
|
+
'version',
|
|
98
|
+
'skill',
|
|
99
|
+
'config',
|
|
100
|
+
'cleanup',
|
|
101
|
+
// stage config-plugin fonts into a build's asset manifest (run.sh build path)
|
|
102
|
+
'app-fonts',
|
|
103
|
+
// internal — not user-facing, spawned as a subprocess by electron main
|
|
104
|
+
// and by `sootsim agent start`.
|
|
105
|
+
'agent-wrapper',
|
|
106
|
+
// attached projects + local agent sessions.
|
|
107
|
+
'agent',
|
|
108
|
+
...TOP_LEVEL_RUNTIME_COMMANDS,
|
|
109
|
+
])
|
|
110
|
+
|
|
111
|
+
export function parseArgs(argv: string[]): ParsedArgs {
|
|
112
|
+
const args = argv.slice(2)
|
|
113
|
+
const result: ParsedArgs = {
|
|
114
|
+
command: null,
|
|
115
|
+
commandArgs: [],
|
|
116
|
+
globalFlags: {},
|
|
117
|
+
help: false,
|
|
118
|
+
version: false,
|
|
119
|
+
verbose: false,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let i = 0
|
|
123
|
+
|
|
124
|
+
// parse global flags and detect command
|
|
125
|
+
while (i < args.length) {
|
|
126
|
+
const arg = args[i]
|
|
127
|
+
|
|
128
|
+
// explicit separator — strip it, everything after is command args
|
|
129
|
+
if (arg === '--') {
|
|
130
|
+
result.commandArgs.push(...args.slice(i + 1))
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// check for global flag
|
|
135
|
+
const flagEntry = Object.entries(GLOBAL_FLAGS).find(
|
|
136
|
+
([name, def]) => name === arg || def.short === arg,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if (flagEntry) {
|
|
140
|
+
const [name, def] = flagEntry
|
|
141
|
+
const key = name.replace(/^--/, '')
|
|
142
|
+
if (def.type === 'boolean') {
|
|
143
|
+
result.globalFlags[key] = true
|
|
144
|
+
i++
|
|
145
|
+
} else {
|
|
146
|
+
const val = args[i + 1]
|
|
147
|
+
if (val === undefined || val.startsWith('-')) {
|
|
148
|
+
// missing value — treat as error, pass through
|
|
149
|
+
console.error(` warning: ${arg} requires a value`)
|
|
150
|
+
i++
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
if (def.type === 'number') {
|
|
154
|
+
const num = Number(val)
|
|
155
|
+
if (Number.isNaN(num)) {
|
|
156
|
+
console.error(` warning: ${arg} requires a number, got "${val}"`)
|
|
157
|
+
i += 2
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
result.globalFlags[key] = num
|
|
161
|
+
} else {
|
|
162
|
+
result.globalFlags[key] = val
|
|
163
|
+
}
|
|
164
|
+
i += 2
|
|
165
|
+
}
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// first non-flag arg — is it a command?
|
|
170
|
+
if (!result.command && !arg.startsWith('-')) {
|
|
171
|
+
// hyphenated grouping-verb spelling (`wait-ready`, `do-tap-id`):
|
|
172
|
+
// agents and humans type it constantly (it reads as one command and
|
|
173
|
+
// mirrors the typed tool names). split only the FIRST hyphen so
|
|
174
|
+
// multi-segment sub-verbs survive (`do-tap-id` → `do` `tap-id`).
|
|
175
|
+
// without this it fell through to the unrecognized branch as
|
|
176
|
+
// "unknown command: wait-ready" with empty stdout.
|
|
177
|
+
const dash = arg.indexOf('-')
|
|
178
|
+
if (dash > 0) {
|
|
179
|
+
const verb = arg.slice(0, dash)
|
|
180
|
+
const sub = arg.slice(dash + 1)
|
|
181
|
+
if (HYPHENATED_GROUPING_VERBS.has(verb) && sub.length > 0) {
|
|
182
|
+
result.command = verb
|
|
183
|
+
const rest = args.slice(i + 1)
|
|
184
|
+
result.commandArgs = [sub, ...(rest[0] === '--' ? rest.slice(1) : rest)]
|
|
185
|
+
break
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (COMMANDS.has(arg)) {
|
|
189
|
+
result.command = arg
|
|
190
|
+
const rest = args.slice(i + 1)
|
|
191
|
+
// strip leading -- separator if present
|
|
192
|
+
result.commandArgs = rest[0] === '--' ? rest.slice(1) : rest
|
|
193
|
+
break
|
|
194
|
+
}
|
|
195
|
+
// not a recognized command — leave it as command args so bin.ts can
|
|
196
|
+
// print a focused unknown-command message.
|
|
197
|
+
result.commandArgs = args.slice(i)
|
|
198
|
+
break
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// unrecognized flag — pass through to command
|
|
202
|
+
result.commandArgs.push(arg)
|
|
203
|
+
i++
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
result.help = !!result.globalFlags['help']
|
|
207
|
+
result.version = !!result.globalFlags['version']
|
|
208
|
+
result.verbose = !!result.globalFlags['verbose']
|
|
209
|
+
|
|
210
|
+
return result
|
|
211
|
+
}
|
package/cli/prompt.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// minimal readline prompts — no external dependencies
|
|
2
|
+
|
|
3
|
+
import { createInterface } from 'readline'
|
|
4
|
+
|
|
5
|
+
const rl = () =>
|
|
6
|
+
createInterface({
|
|
7
|
+
input: process.stdin,
|
|
8
|
+
output: process.stdout,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
function ask(question: string): Promise<string> {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
const r = rl()
|
|
14
|
+
r.question(question, (answer) => {
|
|
15
|
+
r.close()
|
|
16
|
+
resolve(answer.trim())
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function confirm(message: string, defaultYes = true): Promise<boolean> {
|
|
22
|
+
const hint = defaultYes ? '[Y/n]' : '[y/N]'
|
|
23
|
+
// non-interactive stdin (piped, /dev/null, closed) — readline's question
|
|
24
|
+
// callback would never fire, leaving the await unsettled and the process
|
|
25
|
+
// exiting 0 with a `Detected unsettled top-level await` warning. degrade
|
|
26
|
+
// to the default instead so scripted invocations behave predictably.
|
|
27
|
+
if (!process.stdin.isTTY) {
|
|
28
|
+
console.log(` ${message} ${hint} ${defaultYes ? 'y' : 'n'} (non-interactive)`)
|
|
29
|
+
return defaultYes
|
|
30
|
+
}
|
|
31
|
+
const answer = await ask(` ${message} ${hint} `)
|
|
32
|
+
if (answer === '') return defaultYes
|
|
33
|
+
return answer.toLowerCase().startsWith('y')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function select(message: string, options: string[]): Promise<number> {
|
|
37
|
+
console.log(`\n ${message}\n`)
|
|
38
|
+
for (let i = 0; i < options.length; i++) {
|
|
39
|
+
console.log(` ${i + 1}. ${options[i]}`)
|
|
40
|
+
}
|
|
41
|
+
console.log()
|
|
42
|
+
// non-interactive stdin — pick the first option rather than hanging.
|
|
43
|
+
if (!process.stdin.isTTY) {
|
|
44
|
+
console.log(` choose [1-${options.length}]: 1 (non-interactive)`)
|
|
45
|
+
return 0
|
|
46
|
+
}
|
|
47
|
+
const answer = await ask(` choose [1-${options.length}]: `)
|
|
48
|
+
const num = parseInt(answer, 10)
|
|
49
|
+
if (num >= 1 && num <= options.length) return num - 1
|
|
50
|
+
return 0 // default to first
|
|
51
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { readSharedDesktopAuthSession } from '../src/auth/shared-session'
|
|
2
|
+
import { authHeaderValue, resolveCliAuth, type CliAuth } from './auth'
|
|
3
|
+
import { resolveDefaultUploadOrigin } from './commands/upload'
|
|
4
|
+
|
|
5
|
+
type BillingSubscriptionSnapshot = {
|
|
6
|
+
plan?: 'free' | 'personal' | 'team'
|
|
7
|
+
entitlements?: {
|
|
8
|
+
desktopRecording?: boolean
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function billingOriginFor(auth: CliAuth, originOverride?: string): Promise<string> {
|
|
13
|
+
// an explicit override (e.g. --preview-origin https://contrast.localhost:3000 from
|
|
14
|
+
// `maestro test --preview`) wins over whatever origin the shared auth session
|
|
15
|
+
// happens to remember. this keeps local preview-upload flows pointed
|
|
16
|
+
// at the local billing endpoint even when the desktop session token
|
|
17
|
+
// was last minted against contrast.
|
|
18
|
+
if (originOverride) return originOverride.replace(/\/$/, '')
|
|
19
|
+
if (auth.kind === 'session' && auth.origin) {
|
|
20
|
+
return auth.origin.replace(/\/$/, '')
|
|
21
|
+
}
|
|
22
|
+
const shared = readSharedDesktopAuthSession()
|
|
23
|
+
if (shared?.origin) {
|
|
24
|
+
return shared.origin.replace(/\/$/, '')
|
|
25
|
+
}
|
|
26
|
+
// api-key auth (RNX_API_KEY) carries no origin.
|
|
27
|
+
// resolve it the same way the upload itself does — probe a local Contrast
|
|
28
|
+
// stack on :3000, else fall back to prod. this keeps the entitlement
|
|
29
|
+
// check pointed at the *same* origin the recording will upload to (the
|
|
30
|
+
// engine resolves loopback sims to the local :3000 stack); checking prod
|
|
31
|
+
// billing with a key minted against the local stack would 401 here even
|
|
32
|
+
// though the upload would have succeeded.
|
|
33
|
+
return (await resolveDefaultUploadOrigin()).replace(/\/$/, '')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface RecordingEntitlementOptions {
|
|
37
|
+
/** override the origin used to check billing. when the CLI is running
|
|
38
|
+
* against a local Contrast stack (e.g. `maestro test --preview --preview-origin
|
|
39
|
+
* https://contrast.localhost:3000`), the recording entitlement needs to hit the
|
|
40
|
+
* same origin that'll receive the upload — not the remote contrast
|
|
41
|
+
* origin stored in the shared desktop session. */
|
|
42
|
+
originOverride?: string
|
|
43
|
+
/** allow GitHub installation/action auth to record for pr preview uploads.
|
|
44
|
+
* this is intentionally opt-in so local `sootsim record` stays tied to
|
|
45
|
+
* the user's paid/trial desktop entitlement. */
|
|
46
|
+
allowGitHubAuth?: boolean
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function ensureCliRecordingEntitlement(
|
|
50
|
+
commandLabel: string,
|
|
51
|
+
opts: RecordingEntitlementOptions = {},
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
const auth = resolveCliAuth()
|
|
54
|
+
if (!auth) {
|
|
55
|
+
process.stderr.write(
|
|
56
|
+
`\n rnx ${commandLabel} needs auth before it can check recording access.\n\n` +
|
|
57
|
+
` pick one:\n` +
|
|
58
|
+
` • run \`rnxsim login\`\n` +
|
|
59
|
+
` • set RNX_API_KEY=sk_rnx_... (recommended for CI)\n\n`,
|
|
60
|
+
)
|
|
61
|
+
process.exit(1)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (auth.kind === 'github' && opts.allowGitHubAuth) return
|
|
65
|
+
|
|
66
|
+
const billingOrigin = await billingOriginFor(auth, opts.originOverride)
|
|
67
|
+
let res: Response
|
|
68
|
+
try {
|
|
69
|
+
res = await fetch(`${billingOrigin}/api/sootsim/billing/subscription`, {
|
|
70
|
+
headers: { authorization: authHeaderValue(auth) },
|
|
71
|
+
})
|
|
72
|
+
} catch (error) {
|
|
73
|
+
process.stderr.write(
|
|
74
|
+
`\n rnx ${commandLabel} couldn't verify recording access.\n` +
|
|
75
|
+
` ${error instanceof Error ? error.message : String(error)}\n\n`,
|
|
76
|
+
)
|
|
77
|
+
process.exit(1)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (res.status === 401) {
|
|
81
|
+
process.stderr.write(
|
|
82
|
+
`\n rnx ${commandLabel} couldn't verify recording access with the current auth against ${billingOrigin}.\n` +
|
|
83
|
+
` sign in again for that origin, or provide a valid RNX_API_KEY.\n\n`,
|
|
84
|
+
)
|
|
85
|
+
process.exit(1)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
const text = await res.text().catch(() => '')
|
|
90
|
+
process.stderr.write(
|
|
91
|
+
`\n rnx ${commandLabel} couldn't verify recording access (${res.status}).\n` +
|
|
92
|
+
` ${text || 'unexpected billing response'}\n\n`,
|
|
93
|
+
)
|
|
94
|
+
process.exit(1)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const data = (await res.json()) as BillingSubscriptionSnapshot
|
|
98
|
+
if (data.entitlements?.desktopRecording === true) return
|
|
99
|
+
|
|
100
|
+
const plan = data.plan ?? 'free'
|
|
101
|
+
process.stderr.write(
|
|
102
|
+
`\n rnx ${commandLabel} recording requires Personal, Team, or an active trial.\n` +
|
|
103
|
+
` current plan: ${plan}.\n` +
|
|
104
|
+
` free keeps basic 3d mode; the trial unlocks the full Personal surface.\n\n`,
|
|
105
|
+
)
|
|
106
|
+
process.exit(1)
|
|
107
|
+
}
|
package/cli/registry.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@sootsim/skills/cli'
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// resolve sootsim's bundled assets (fonts, icons, sounds)
|
|
2
|
+
// regardless of how the package was installed (source checkout, npm install, bunx).
|
|
3
|
+
//
|
|
4
|
+
// uses import.meta.url to derive the package root, then checks both
|
|
5
|
+
// the source layout (public/) and npm-installed layout (public/).
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync } from 'fs'
|
|
8
|
+
import { dirname, resolve } from 'path'
|
|
9
|
+
import { fileURLToPath } from 'url'
|
|
10
|
+
import { rnxPublicBrand } from '../src/public-brand'
|
|
11
|
+
|
|
12
|
+
// when bundled by esbuild, this file lives at dist-cli/bin.js
|
|
13
|
+
// when running from source, this file lives at cli/resolve-assets.ts
|
|
14
|
+
const thisDir = dirname(fileURLToPath(import.meta.url))
|
|
15
|
+
|
|
16
|
+
// walk up to find package root: look for package.json with name "sootsim"
|
|
17
|
+
function findPackageRoot(): string {
|
|
18
|
+
let dir = thisDir
|
|
19
|
+
for (let i = 0; i < 5; i++) {
|
|
20
|
+
try {
|
|
21
|
+
const pkgPath = resolve(dir, 'package.json')
|
|
22
|
+
if (existsSync(pkgPath)) {
|
|
23
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
24
|
+
if (pkg.name === rnxPublicBrand.packageName) return dir
|
|
25
|
+
}
|
|
26
|
+
} catch {}
|
|
27
|
+
dir = dirname(dir)
|
|
28
|
+
}
|
|
29
|
+
// fallback: assume one level up from this file
|
|
30
|
+
return dirname(thisDir)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const packageRoot = findPackageRoot()
|
|
34
|
+
|
|
35
|
+
export interface SootSimAssets {
|
|
36
|
+
fontsDir: string
|
|
37
|
+
iconsDir: string
|
|
38
|
+
soundsDir: string
|
|
39
|
+
packageRoot: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveAssets(): SootSimAssets {
|
|
43
|
+
// both source and npm-installed layouts use public/ at the package root
|
|
44
|
+
const publicDir = resolve(packageRoot, 'public')
|
|
45
|
+
|
|
46
|
+
const fontsDir = resolve(publicDir, 'fonts')
|
|
47
|
+
const iconsDir = resolve(publicDir, 'icons')
|
|
48
|
+
const soundsDir = resolve(publicDir, 'sounds')
|
|
49
|
+
|
|
50
|
+
if (!existsSync(fontsDir)) {
|
|
51
|
+
console.warn(`[rnx] warning: fonts directory not found at ${fontsDir}`)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
fontsDir,
|
|
56
|
+
iconsDir,
|
|
57
|
+
soundsDir,
|
|
58
|
+
packageRoot,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// re-export packageRoot for use in CLI commands that need SOOTSIM_ROOT
|
|
63
|
+
export { packageRoot }
|