peachsim 0.1.527

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +640 -0
  3. package/cli/app-config.ts +82 -0
  4. package/cli/app-fonts.ts +388 -0
  5. package/cli/app-project.ts +59 -0
  6. package/cli/app-splash.ts +171 -0
  7. package/cli/app-state-reset.ts +26 -0
  8. package/cli/auth.ts +184 -0
  9. package/cli/bin.ts +59 -0
  10. package/cli/bridge-diagnostics.ts +226 -0
  11. package/cli/bridge-flow-runner.ts +2806 -0
  12. package/cli/browser-backend.ts +44 -0
  13. package/cli/browser-evals.ts +96 -0
  14. package/cli/cli-update.ts +261 -0
  15. package/cli/cloud-client.ts +1530 -0
  16. package/cli/cloud-dispatch.ts +206 -0
  17. package/cli/cloud-session.ts +227 -0
  18. package/cli/command-contract.ts +221 -0
  19. package/cli/command-registry.ts +304 -0
  20. package/cli/commands/agent-wrapper.ts +1048 -0
  21. package/cli/commands/agent.ts +439 -0
  22. package/cli/commands/app-fonts.ts +99 -0
  23. package/cli/commands/assert.ts +542 -0
  24. package/cli/commands/auth.ts +71 -0
  25. package/cli/commands/box/bundle-plane.ts +71 -0
  26. package/cli/commands/box/checkout-plane.ts +256 -0
  27. package/cli/commands/box/plane-share.ts +208 -0
  28. package/cli/commands/box/rnx-command.ts +60 -0
  29. package/cli/commands/box/search-command.ts +157 -0
  30. package/cli/commands/box.ts +925 -0
  31. package/cli/commands/camera.ts +266 -0
  32. package/cli/commands/cleanup.ts +169 -0
  33. package/cli/commands/compat.ts +176 -0
  34. package/cli/commands/config.ts +34 -0
  35. package/cli/commands/control.ts +2154 -0
  36. package/cli/commands/cpu-profile.ts +941 -0
  37. package/cli/commands/daemon-mac-app.ts +165 -0
  38. package/cli/commands/daemon.ts +800 -0
  39. package/cli/commands/debug.ts +751 -0
  40. package/cli/commands/desktop.ts +39 -0
  41. package/cli/commands/detect.ts +201 -0
  42. package/cli/commands/detox.ts +481 -0
  43. package/cli/commands/device.ts +134 -0
  44. package/cli/commands/diagnose.ts +589 -0
  45. package/cli/commands/do-chain.ts +408 -0
  46. package/cli/commands/electron.ts +94 -0
  47. package/cli/commands/film.ts +379 -0
  48. package/cli/commands/flow.ts +1240 -0
  49. package/cli/commands/inspect/actions.ts +516 -0
  50. package/cli/commands/inspect/core.ts +2086 -0
  51. package/cli/commands/inspect/count.ts +17 -0
  52. package/cli/commands/inspect/describe.ts +119 -0
  53. package/cli/commands/inspect/env.ts +25 -0
  54. package/cli/commands/inspect/find.ts +169 -0
  55. package/cli/commands/inspect/get-layout.ts +53 -0
  56. package/cli/commands/inspect/keyboard.ts +54 -0
  57. package/cli/commands/inspect/list.ts +175 -0
  58. package/cli/commands/inspect/memory.ts +215 -0
  59. package/cli/commands/inspect/redaction.ts +39 -0
  60. package/cli/commands/inspect/resolve-target.ts +77 -0
  61. package/cli/commands/inspect/screens.ts +78 -0
  62. package/cli/commands/inspect/settle.ts +32 -0
  63. package/cli/commands/inspect/settling.ts +197 -0
  64. package/cli/commands/inspect/shared.ts +259 -0
  65. package/cli/commands/inspect/sleep.ts +15 -0
  66. package/cli/commands/inspect/tree.ts +23 -0
  67. package/cli/commands/inspect/url.ts +17 -0
  68. package/cli/commands/inspect/wait-event.ts +211 -0
  69. package/cli/commands/inspect/wait-idle.ts +25 -0
  70. package/cli/commands/inspect/wait-ready.ts +87 -0
  71. package/cli/commands/inspect/wait-selector.ts +59 -0
  72. package/cli/commands/inspect/wait.ts +32 -0
  73. package/cli/commands/inspect.ts +4738 -0
  74. package/cli/commands/install-cli.ts +275 -0
  75. package/cli/commands/install-desktop.ts +385 -0
  76. package/cli/commands/login.ts +357 -0
  77. package/cli/commands/logout.ts +24 -0
  78. package/cli/commands/maestro-generate.ts +375 -0
  79. package/cli/commands/maestro.ts +536 -0
  80. package/cli/commands/mode.ts +58 -0
  81. package/cli/commands/no-bridge-hint.ts +80 -0
  82. package/cli/commands/perf.ts +67 -0
  83. package/cli/commands/permissions.ts +203 -0
  84. package/cli/commands/platform.ts +410 -0
  85. package/cli/commands/preview.ts +103 -0
  86. package/cli/commands/profile.ts +106 -0
  87. package/cli/commands/react.ts +353 -0
  88. package/cli/commands/record.ts +1435 -0
  89. package/cli/commands/report-issue.ts +305 -0
  90. package/cli/commands/reset.ts +89 -0
  91. package/cli/commands/runtime.ts +348 -0
  92. package/cli/commands/screenshot-command.ts +106 -0
  93. package/cli/commands/screenshot-layers.ts +144 -0
  94. package/cli/commands/screenshot-mode.ts +38 -0
  95. package/cli/commands/screenshot.ts +492 -0
  96. package/cli/commands/screenshots-capture.ts +608 -0
  97. package/cli/commands/screenshots.ts +127 -0
  98. package/cli/commands/serve.ts +139 -0
  99. package/cli/commands/setup.ts +416 -0
  100. package/cli/commands/shell-boolean-mode.ts +82 -0
  101. package/cli/commands/skills.ts +468 -0
  102. package/cli/commands/slides.ts +357 -0
  103. package/cli/commands/state.ts +94 -0
  104. package/cli/commands/storage.ts +58 -0
  105. package/cli/commands/telemetry.ts +55 -0
  106. package/cli/commands/test.ts +535 -0
  107. package/cli/commands/three-mode.ts +771 -0
  108. package/cli/commands/timeline.ts +121 -0
  109. package/cli/commands/upgrade.ts +167 -0
  110. package/cli/commands/upload.ts +1203 -0
  111. package/cli/commands/version.ts +63 -0
  112. package/cli/commands/welcome.ts +17 -0
  113. package/cli/commands/what-happened.ts +328 -0
  114. package/cli/current-sim.ts +217 -0
  115. package/cli/desktop-companion.ts +315 -0
  116. package/cli/drivers/electron.ts +70 -0
  117. package/cli/drivers/index.ts +20 -0
  118. package/cli/drivers/playwright-provisioning.ts +184 -0
  119. package/cli/drivers/playwright-sim-host.ts +537 -0
  120. package/cli/drivers/playwright.ts +209 -0
  121. package/cli/drivers/registry.ts +65 -0
  122. package/cli/drivers/types.ts +102 -0
  123. package/cli/flow-export.ts +55 -0
  124. package/cli/flow-file.ts +142 -0
  125. package/cli/flow-live-status.ts +126 -0
  126. package/cli/flow-session.ts +140 -0
  127. package/cli/help-core.ts +143 -0
  128. package/cli/help.ts +44 -0
  129. package/cli/hidden-runtime-alias.ts +19 -0
  130. package/cli/hints.ts +214 -0
  131. package/cli/inspect-notice-state.ts +114 -0
  132. package/cli/internal-child.ts +176 -0
  133. package/cli/maestro-js.ts +323 -0
  134. package/cli/main.ts +704 -0
  135. package/cli/open-url.ts +8 -0
  136. package/cli/outbound-endpoints.ts +830 -0
  137. package/cli/parent-pid.ts +204 -0
  138. package/cli/parse-args.ts +189 -0
  139. package/cli/privacy.ts +53 -0
  140. package/cli/prompt.ts +56 -0
  141. package/cli/recording-access.ts +108 -0
  142. package/cli/registry.ts +2 -0
  143. package/cli/resolve-assets.ts +63 -0
  144. package/cli/rnx-in-process.ts +98 -0
  145. package/cli/run-registry.ts +226 -0
  146. package/cli/run-rnx-core.ts +148 -0
  147. package/cli/run-rnx.ts +137 -0
  148. package/cli/runtime-notes.ts +66 -0
  149. package/cli/runtime-summary.ts +25 -0
  150. package/cli/secure-file.ts +31 -0
  151. package/cli/self-invocation.ts +58 -0
  152. package/cli/send-to-box.ts +109 -0
  153. package/cli/setup-repository.ts +191 -0
  154. package/cli/shell-init.ts +98 -0
  155. package/cli/standalone.ts +4 -0
  156. package/cli/telemetry.ts +84 -0
  157. package/cli/ui.ts +68 -0
  158. package/cli/ws-bridge.ts +1020 -0
  159. package/detox/attempt-events.cjs +23 -0
  160. package/detox/colors.ts +54 -0
  161. package/detox/config-loader.ts +135 -0
  162. package/detox/element-types.ts +48 -0
  163. package/detox/expectations.ts +534 -0
  164. package/detox/gestures.ts +501 -0
  165. package/detox/index.ts +2694 -0
  166. package/detox/jest-preset.cjs +29 -0
  167. package/detox/jest-setup-after-env.cjs +22 -0
  168. package/detox/matchers.ts +30 -0
  169. package/detox/motion-change.cjs +109 -0
  170. package/detox/motion-change.d.cts +35 -0
  171. package/detox/navigation-lifecycle.ts +290 -0
  172. package/detox/navigation.ts +43 -0
  173. package/detox/proof-frame.cjs +114 -0
  174. package/detox/record-block-execution.cjs +78 -0
  175. package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
  176. package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
  177. package/detox/screenshots/color-test-rest-norngh.png +0 -0
  178. package/detox/screenshots/theme-blue-toggle.png +0 -0
  179. package/detox/screenshots/theme-blue.png +0 -0
  180. package/detox/screenshots/theme-red-toggle.png +0 -0
  181. package/detox/screenshots/theme-red.png +0 -0
  182. package/dist-lib/agent-daemon-client.cjs +502 -0
  183. package/dist-lib/agent-events.cjs +48 -0
  184. package/dist-lib/agent-identity.cjs +53 -0
  185. package/dist-lib/agent-sessions.cjs +759 -0
  186. package/dist-lib/attached-projects.cjs +526 -0
  187. package/dist-lib/auth/shared-session.cjs +208 -0
  188. package/dist-lib/backend-origin.cjs +88 -0
  189. package/dist-lib/beta.cjs +41 -0
  190. package/dist-lib/beta.mjs +13 -0
  191. package/dist-lib/bridge-constants.cjs +161 -0
  192. package/dist-lib/bridge-contract-input.cjs +451 -0
  193. package/dist-lib/bridge-contract-input.mjs +414 -0
  194. package/dist-lib/bridge-contract.cjs +130 -0
  195. package/dist-lib/bridge-contract.mjs +94 -0
  196. package/dist-lib/capture-contract.cjs +220 -0
  197. package/dist-lib/capture-contract.mjs +190 -0
  198. package/dist-lib/cli-constants.cjs +32 -0
  199. package/dist-lib/cloud-contract.cjs +194 -0
  200. package/dist-lib/cloud-contract.mjs +145 -0
  201. package/dist-lib/cloud.cjs +670 -0
  202. package/dist-lib/cloud.mjs +639 -0
  203. package/dist-lib/config.cjs +96 -0
  204. package/dist-lib/detox/index.cjs +3789 -0
  205. package/dist-lib/detox/jest-preset.cjs +29 -0
  206. package/dist-lib/dev-bundle-resolution.cjs +290 -0
  207. package/dist-lib/home-paths.cjs +638 -0
  208. package/dist-lib/host/bridge-host.cjs +10903 -0
  209. package/dist-lib/host/fetch-proxy-handler.cjs +421 -0
  210. package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
  211. package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
  212. package/dist-lib/host/replacement-module-handler.cjs +469 -0
  213. package/dist-lib/host/websocket-proxy.cjs +298 -0
  214. package/dist-lib/index.cjs +4268 -0
  215. package/dist-lib/jump-to-source-babel.cjs +93 -0
  216. package/dist-lib/jump-to-source-native.cjs +112 -0
  217. package/dist-lib/menu.cjs +646 -0
  218. package/dist-lib/menu.mjs +604 -0
  219. package/dist-lib/metro-fingerprint-registry.cjs +186 -0
  220. package/dist-lib/metro-fingerprint-registry.mjs +156 -0
  221. package/dist-lib/metro-production-bundle.cjs +329 -0
  222. package/dist-lib/metro-production-bundle.mjs +295 -0
  223. package/dist-lib/metro.cjs +904 -0
  224. package/dist-lib/profiles.cjs +246 -0
  225. package/dist-lib/public-brand.cjs +39 -0
  226. package/dist-lib/react-native-host-modules.cjs +51 -0
  227. package/dist-lib/react-native-host-modules.mjs +25 -0
  228. package/dist-lib/render-mode.cjs +54 -0
  229. package/dist-lib/scripts/dev-server-scanner.cjs +1232 -0
  230. package/dist-lib/sdk.cjs +2433 -0
  231. package/dist-lib/sdk.mjs +2336 -0
  232. package/dist-lib/skills.cjs +39132 -0
  233. package/dist-lib/swift-submit.cjs +92 -0
  234. package/dist-lib/swift-submit.mjs +65 -0
  235. package/dist-lib/swift.cjs +1646 -0
  236. package/dist-lib/vite.cjs +1591 -0
  237. package/package.json +290 -0
  238. package/scripts/dev-server-scanner.ts +1291 -0
  239. package/scripts/optional-demo-registry.ts +113 -0
  240. package/skills/contrast/SKILL.md +165 -0
  241. package/skills/rnx-debug/SKILL.md +433 -0
  242. package/skills/rnx-perf/SKILL.md +399 -0
  243. package/skills/rnx-setup/SKILL.md +172 -0
  244. package/skills/rnx-test/SKILL.md +321 -0
  245. package/skills/rnx-visual/SKILL.md +126 -0
  246. package/src/agent-daemon-client.ts +410 -0
  247. package/src/agent-events.ts +71 -0
  248. package/src/agent-identity.ts +23 -0
  249. package/src/agent-prompt.ts +71 -0
  250. package/src/agent-sessions.ts +580 -0
  251. package/src/app-url.ts +39 -0
  252. package/src/attached-projects.ts +539 -0
  253. package/src/auth/shared-session.ts +235 -0
  254. package/src/backend-origin.ts +70 -0
  255. package/src/beta.ts +19 -0
  256. package/src/bridge-constants.ts +175 -0
  257. package/src/bridge-contract-input.ts +502 -0
  258. package/src/bridge-contract.ts +558 -0
  259. package/src/browser-cache.ts +16 -0
  260. package/src/capture-contract.ts +587 -0
  261. package/src/cli-constants.ts +1 -0
  262. package/src/cli-version.ts +40 -0
  263. package/src/cloud-contract.ts +274 -0
  264. package/src/cloud.ts +762 -0
  265. package/src/codex-client.ts +234 -0
  266. package/src/config.ts +132 -0
  267. package/src/connect.ts +101 -0
  268. package/src/dev-bundle-resolution.ts +346 -0
  269. package/src/dev-server-open.ts +198 -0
  270. package/src/disk-cleanup.ts +540 -0
  271. package/src/flow-timeout-scale.ts +15 -0
  272. package/src/home-paths.ts +806 -0
  273. package/src/host/agent-host.ts +666 -0
  274. package/src/host/bridge-host.ts +2788 -0
  275. package/src/host/fetch-proxy-handler.ts +449 -0
  276. package/src/host/fetch-proxy-overrides.ts +51 -0
  277. package/src/host/open-url.ts +89 -0
  278. package/src/host/plane-host.ts +203 -0
  279. package/src/host/replacement-module-handler.ts +158 -0
  280. package/src/host/websocket-proxy.ts +315 -0
  281. package/src/index.ts +71 -0
  282. package/src/jump-to-source-babel.ts +135 -0
  283. package/src/jump-to-source-native.ts +102 -0
  284. package/src/menu.ts +777 -0
  285. package/src/metro-fingerprint-registry.ts +302 -0
  286. package/src/metro-plugin.ts +341 -0
  287. package/src/metro-production-bundle.ts +471 -0
  288. package/src/native-dev-bundle-url.ts +118 -0
  289. package/src/native-seam-manifest.ts +341 -0
  290. package/src/playwright-host-log.ts +16 -0
  291. package/src/plist.ts +27 -0
  292. package/src/poll-until.ts +123 -0
  293. package/src/profiles.ts +179 -0
  294. package/src/public-brand.ts +9 -0
  295. package/src/react-native-host-modules.ts +26 -0
  296. package/src/render-mode.ts +27 -0
  297. package/src/rolldown-plugin-swift.ts +240 -0
  298. package/src/runtime-assets.ts +233 -0
  299. package/src/runtime-delivery.ts +54 -0
  300. package/src/screenshots/compose.ts +422 -0
  301. package/src/screenshots/frame-compose.ts +514 -0
  302. package/src/screenshots/orchestrate.ts +256 -0
  303. package/src/screenshots/org-deck.ts +91 -0
  304. package/src/screenshots/registry.ts +58 -0
  305. package/src/screenshots/schema.ts +364 -0
  306. package/src/sdk.ts +15 -0
  307. package/src/settings-bundle.ts +418 -0
  308. package/src/shell-bridge-identity.ts +15 -0
  309. package/src/sim-client.ts +697 -0
  310. package/src/skills/builtin/a11y-review.ts +132 -0
  311. package/src/skills/builtin/compat-check.ts +71 -0
  312. package/src/skills/builtin/maestro-test.ts +121 -0
  313. package/src/skills/builtin/perf-profile.ts +90 -0
  314. package/src/skills/builtin/screenshot-all.ts +52 -0
  315. package/src/skills/builtin/visual-diff.ts +100 -0
  316. package/src/skills/registry.ts +107 -0
  317. package/src/skills/types.ts +41 -0
  318. package/src/swift-submit.ts +185 -0
  319. package/src/swift.ts +7 -0
  320. package/src/team-machine-prompt.ts +73 -0
  321. package/src/vite-plugin-one.ts +264 -0
  322. package/src/vite-plugin-swift.ts +620 -0
  323. package/src/vite-plugin.ts +1946 -0
  324. package/src/worklets-babel.ts +250 -0
@@ -0,0 +1,439 @@
1
+ // user-facing `rnx agent` command tree.
2
+ //
3
+ // session-lifecycle commands (start, prompt, end, watch) route through
4
+ // the bridge daemon — the daemon owns the single FIFO reader per
5
+ // session, so concurrent cli watches + electron UI subscribers no longer
6
+ // steal bytes from each other. the daemon auto-spawns on first use via
7
+ // `connectToDaemon`.
8
+ //
9
+ // disk-only inspection commands (projects, sessions, paths, transcript,
10
+ // attach, project show/remove) read the attached-projects JSON directly —
11
+ // no daemon needed, so `rnx agent projects` stays fast when no
12
+ // wrapper is live.
13
+
14
+ import fs from 'node:fs'
15
+ import path from 'node:path'
16
+ import {
17
+ AgentDaemonError,
18
+ connectToDaemon,
19
+ type AgentDaemonClient,
20
+ } from '../../src/agent-daemon-client'
21
+ import { parseAgentEventLine, type AgentEvent } from '../../src/agent-events'
22
+ import {
23
+ eventsFifoPath,
24
+ pidIsAlive,
25
+ promptFifoPath,
26
+ sessionDir,
27
+ transcriptPath,
28
+ type Provider,
29
+ } from '../../src/agent-sessions'
30
+ import {
31
+ costThisWeek,
32
+ deleteProject,
33
+ findProjectById,
34
+ getUserDataDir,
35
+ listProjects,
36
+ listSessions,
37
+ upsertProject,
38
+ type AgentSession,
39
+ type AttachedProject,
40
+ } from '../../src/attached-projects'
41
+ import { printCommandHelp } from '../help'
42
+ import { resolveDefaultBridgePort } from '../ws-bridge'
43
+
44
+ // the bridge this invocation talks to: `-p`, else the same dev-bridge or
45
+ // daemon pick every other command makes (PORT_OFFSET and SOOTSIM_BRIDGE_PORT
46
+ // included).
47
+ let daemonPort = 0
48
+
49
+ export async function runAgentCommand(
50
+ argv: string[],
51
+ opts: { port?: number } = {},
52
+ ): Promise<number> {
53
+ daemonPort = opts.port ?? resolveDefaultBridgePort()
54
+ const [sub, ...rest] = argv
55
+ try {
56
+ switch (sub) {
57
+ case undefined:
58
+ case '--help':
59
+ case '-h':
60
+ case 'help':
61
+ printHelp()
62
+ return 0
63
+ case 'attach':
64
+ return await cmdAttach(rest)
65
+ case 'projects':
66
+ return await cmdProjects()
67
+ case 'project':
68
+ return await cmdProject(rest)
69
+ case 'sessions':
70
+ return await cmdSessions(rest)
71
+ case 'start':
72
+ return await cmdStart(rest)
73
+ case 'prompt':
74
+ return await cmdPrompt(rest)
75
+ case 'watch':
76
+ return await cmdWatch(rest)
77
+ case 'transcript':
78
+ return await cmdTranscript(rest)
79
+ case 'end':
80
+ return await cmdEnd(rest)
81
+ case 'paths':
82
+ return await cmdPaths()
83
+ default:
84
+ process.stderr.write(`unknown agent subcommand: ${sub}\n`)
85
+ printHelp()
86
+ return 2
87
+ }
88
+ } catch (err) {
89
+ if (err instanceof AgentDaemonError) {
90
+ process.stderr.write(`${err.message}\n`)
91
+ return 1
92
+ }
93
+ throw err
94
+ }
95
+ }
96
+
97
+ /** open a daemon client for a single command. callers are responsible for
98
+ * closing the client when done so the CLI process exits promptly. */
99
+ async function withDaemon<T>(fn: (client: AgentDaemonClient) => Promise<T>): Promise<T> {
100
+ const client = await connectToDaemon({
101
+ port: daemonPort,
102
+ clientLabel: 'sootsim-agent-cli',
103
+ })
104
+ try {
105
+ return await fn(client)
106
+ } finally {
107
+ client.close()
108
+ }
109
+ }
110
+
111
+ function printHelp(): void {
112
+ // delegate to the meta-driven renderer so bare `rnx agent`, `--help`,
113
+ // and `unknown subcommand` paths all show the same rich help (header,
114
+ // options, examples) instead of a sparse hand-coded summary.
115
+ printCommandHelp('agent')
116
+ }
117
+
118
+ // --- arg parsing ---
119
+
120
+ function parseFlags(
121
+ argv: string[],
122
+ known: Record<string, 'value' | 'bool'>,
123
+ ): { flags: Record<string, string | boolean>; positional: string[] } {
124
+ const flags: Record<string, string | boolean> = {}
125
+ const positional: string[] = []
126
+ for (let i = 0; i < argv.length; i++) {
127
+ const arg = argv[i]
128
+ const kind = known[arg]
129
+ if (kind === 'value') {
130
+ flags[arg] = argv[i + 1] ?? ''
131
+ i++
132
+ } else if (kind === 'bool') {
133
+ flags[arg] = true
134
+ } else {
135
+ positional.push(arg)
136
+ }
137
+ }
138
+ return { flags, positional }
139
+ }
140
+
141
+ function resolveProvider(raw: unknown, fallback: Provider = 'codex'): Provider {
142
+ if (raw === 'codex' || raw === 'claude') return raw
143
+ return fallback
144
+ }
145
+
146
+ // --- subcommands ---
147
+
148
+ async function cmdAttach(argv: string[]): Promise<number> {
149
+ const { flags, positional } = parseFlags(argv, {
150
+ '--name': 'value',
151
+ '--provider': 'value',
152
+ })
153
+ const dir = positional[0]
154
+ if (!dir) {
155
+ process.stderr.write(
156
+ 'usage: rnx agent attach <dir> [--name X] [--provider codex|claude]\n',
157
+ )
158
+ return 2
159
+ }
160
+ const absolute = path.resolve(dir)
161
+ if (!fs.existsSync(absolute)) {
162
+ process.stderr.write(`directory does not exist: ${absolute}\n`)
163
+ return 1
164
+ }
165
+ const project = upsertProject({
166
+ cwd: absolute,
167
+ name: (flags['--name'] as string) || path.basename(absolute),
168
+ preferredProvider: resolveProvider(flags['--provider']),
169
+ sourceRoots: [absolute],
170
+ })
171
+ process.stdout.write(
172
+ `✓ attached "${project.name}" (id: ${project.id})\n` +
173
+ ` cwd: ${project.cwd}\n` +
174
+ ` framework: ${project.framework}\n` +
175
+ ` preferred provider: ${project.preferredProvider}\n`,
176
+ )
177
+ return 0
178
+ }
179
+
180
+ async function cmdProjects(): Promise<number> {
181
+ const projects = [...listProjects()].sort((a, b) => b.updatedAt - a.updatedAt)
182
+ if (projects.length === 0) {
183
+ process.stdout.write('no attached projects yet. try `rnx agent attach <dir>`.\n')
184
+ return 0
185
+ }
186
+ const header = `${'ID'.padEnd(18)} ${'NAME'.padEnd(20)} ${'FRAMEWORK'.padEnd(10)} ${'PROVIDER'.padEnd(8)} ${'7D COST'.padEnd(10)} CWD\n`
187
+ process.stdout.write(header)
188
+ for (const p of projects) {
189
+ const cost = costThisWeek(p)
190
+ const costCell = cost > 0 ? `$${cost.toFixed(2)}` : '—'
191
+ process.stdout.write(
192
+ `${p.id.padEnd(18)} ${truncate(p.name, 20).padEnd(20)} ${p.framework.padEnd(10)} ${p.preferredProvider.padEnd(8)} ${costCell.padEnd(10)} ${p.cwd}\n`,
193
+ )
194
+ }
195
+ return 0
196
+ }
197
+
198
+ async function cmdProject(argv: string[]): Promise<number> {
199
+ const [action, id] = argv
200
+ if (action === 'show') {
201
+ if (!id) {
202
+ process.stderr.write('usage: rnx agent project show <id>\n')
203
+ return 2
204
+ }
205
+ const project = findProjectById(id)
206
+ if (!project) {
207
+ process.stderr.write(`no project with id=${id}\n`)
208
+ return 1
209
+ }
210
+ process.stdout.write(JSON.stringify(project, null, 2) + '\n')
211
+ return 0
212
+ }
213
+ if (action === 'remove') {
214
+ if (!id) {
215
+ process.stderr.write('usage: rnx agent project remove <id>\n')
216
+ return 2
217
+ }
218
+ deleteProject(id)
219
+ process.stdout.write(`✓ removed project ${id}\n`)
220
+ return 0
221
+ }
222
+ process.stderr.write('usage: rnx agent project [show|remove] <id>\n')
223
+ return 2
224
+ }
225
+
226
+ async function cmdSessions(argv: string[]): Promise<number> {
227
+ const { flags } = parseFlags(argv, { '--project': 'value', '--all': 'bool' })
228
+ const projectId = (flags['--project'] as string) || undefined
229
+ const showAll = flags['--all'] === true
230
+ let sessions = listSessions(projectId)
231
+ if (!showAll) {
232
+ sessions = sessions.filter((s) => s.status !== 'ended')
233
+ }
234
+ sessions = sessions.sort((a, b) => b.lastSeenAt - a.lastSeenAt)
235
+ if (sessions.length === 0) {
236
+ process.stdout.write(
237
+ showAll ? 'no sessions.\n' : 'no active sessions. pass --all to include ended.\n',
238
+ )
239
+ return 0
240
+ }
241
+ const header = `${'SESSION'.padEnd(26)} ${'PROJECT'.padEnd(18)} ${'PROVIDER'.padEnd(8)} ${'STATUS'.padEnd(16)} ${'ALIVE'.padEnd(5)} LAST PROMPT\n`
242
+ process.stdout.write(header)
243
+ for (const s of sessions) {
244
+ const alive = pidIsAlive(s.wrapperPid, s.id) ? 'yes' : 'no'
245
+ const prompt = s.lastPrompt ? truncate(s.lastPrompt, 40) : ''
246
+ process.stdout.write(
247
+ `${shortId(s.id).padEnd(26)} ${shortId(s.projectId).padEnd(18)} ${s.provider.padEnd(8)} ${s.status.padEnd(16)} ${alive.padEnd(5)} ${prompt}\n`,
248
+ )
249
+ }
250
+ return 0
251
+ }
252
+
253
+ async function cmdStart(argv: string[]): Promise<number> {
254
+ const { flags, positional } = parseFlags(argv, {
255
+ '--provider': 'value',
256
+ '--codex-bin': 'value',
257
+ '--claude-bin': 'value',
258
+ '--fresh-thread': 'bool',
259
+ })
260
+ const projectId = positional[0]
261
+ if (!projectId) {
262
+ process.stderr.write(
263
+ 'usage: rnx agent start <projectId> [--provider codex|claude] [--fresh-thread]\n',
264
+ )
265
+ return 2
266
+ }
267
+ const result = await withDaemon((client) =>
268
+ client.startSession({
269
+ projectId,
270
+ provider:
271
+ resolveProvider(flags['--provider'], undefined as unknown as Provider) ||
272
+ undefined,
273
+ codexBin: (flags['--codex-bin'] as string) || undefined,
274
+ claudeBin: (flags['--claude-bin'] as string) || undefined,
275
+ freshThread: flags['--fresh-thread'] === true,
276
+ }),
277
+ )
278
+ const project = findProjectById(result.session.projectId)!
279
+ process.stdout.write(
280
+ `✓ session ${result.session.id} started\n` +
281
+ ` project: ${project.name} (${project.id})\n` +
282
+ ` provider: ${result.session.provider}\n` +
283
+ ` pid: ${result.wrapperPid}\n` +
284
+ ` prompt with: rnx agent prompt ${result.session.id} "<text>"\n` +
285
+ ` watch events: rnx agent watch ${result.session.id}\n`,
286
+ )
287
+ return 0
288
+ }
289
+
290
+ async function cmdPrompt(argv: string[]): Promise<number> {
291
+ const [sessionId, ...rest] = argv
292
+ const text = rest.join(' ')
293
+ if (!sessionId || !text) {
294
+ process.stderr.write('usage: rnx agent prompt <sessionId> "<text>"\n')
295
+ return 2
296
+ }
297
+ await withDaemon((client) => client.sendPrompt(sessionId, { text }))
298
+ process.stdout.write(`✓ prompt sent to ${sessionId}\n`)
299
+ return 0
300
+ }
301
+
302
+ async function cmdWatch(argv: string[]): Promise<number> {
303
+ const sessionId = argv[0]
304
+ if (!sessionId) {
305
+ process.stderr.write('usage: rnx agent watch <sessionId>\n')
306
+ return 2
307
+ }
308
+ process.stdout.write(`watching ${sessionId}\n---\n`)
309
+ // the daemon owns the FIFO reader. we stay subscribed until SIGINT —
310
+ // this is the one command that requires a persistent daemon client.
311
+ const client = await connectToDaemon({
312
+ port: daemonPort,
313
+ clientLabel: 'sootsim-agent-watch',
314
+ })
315
+ let resolveExit: (code: number) => void
316
+ const exit = new Promise<number>((resolve) => {
317
+ resolveExit = resolve
318
+ })
319
+ const offEvent = client.onAgentEvent(({ sessionId: sid, event }) => {
320
+ if (sid !== sessionId) return
321
+ process.stdout.write(formatEvent(event) + '\n')
322
+ })
323
+ const offDisconnect = client.onDisconnect(() => {
324
+ process.stderr.write('\n[rnx] daemon disconnected; exiting watch.\n')
325
+ resolveExit(1)
326
+ })
327
+ try {
328
+ await client.subscribeEvents(sessionId)
329
+ } catch (err) {
330
+ offEvent()
331
+ offDisconnect()
332
+ client.close()
333
+ throw err
334
+ }
335
+ process.on('SIGINT', () => {
336
+ offEvent()
337
+ offDisconnect()
338
+ void client.unsubscribeEvents(sessionId).catch(() => {})
339
+ client.close()
340
+ resolveExit(130)
341
+ })
342
+ return exit
343
+ }
344
+
345
+ function formatEvent(ev: AgentEvent): string {
346
+ const ts = fmtTs(ev.ts)
347
+ switch (ev.type) {
348
+ case 'ready':
349
+ return `[${ts}] ready ${ev.provider} @ ${ev.cwd}`
350
+ case 'prompt-received':
351
+ return `[${ts}] prompt "${truncate(ev.text, 120)}"`
352
+ case 'turn-started':
353
+ return `[${ts}] turn-started ${ev.turnId ?? ''}`
354
+ case 'turn-message':
355
+ return `[${ts}] turn-message ${ev.delta.replace(/\s+$/, '')}`
356
+ case 'turn-reasoning':
357
+ return `[${ts}] turn-reasoning ${truncate(ev.delta, 200)}`
358
+ case 'tool-call':
359
+ return `[${ts}] tool-call ${ev.name}`
360
+ case 'file-edited':
361
+ return `[${ts}] file-edited ${ev.kind.padEnd(6)} ${ev.path}`
362
+ case 'turn-completed':
363
+ return `[${ts}] turn-completed ${ev.durationMs}ms files=${ev.filesTouched.length}`
364
+ case 'approval-needed':
365
+ return `[${ts}] approval ${ev.kind}`
366
+ case 'error':
367
+ return `[${ts}] error ${ev.message}`
368
+ case 'exited':
369
+ return `[${ts}] exited code=${ev.code}`
370
+ case 'file-diff-delta':
371
+ return `[${ts}] file-diff-delta ${ev.path}`
372
+ case 'turn-plan':
373
+ return `[${ts}] turn-plan steps=${ev.steps.length}`
374
+ default:
375
+ return `[${ts}] ${(ev as { type: string }).type}`
376
+ }
377
+ }
378
+
379
+ async function cmdTranscript(argv: string[]): Promise<number> {
380
+ const sessionId = argv[0]
381
+ if (!sessionId) {
382
+ process.stderr.write('usage: rnx agent transcript <sessionId>\n')
383
+ return 2
384
+ }
385
+ const p = transcriptPath(sessionId)
386
+ if (!fs.existsSync(p)) {
387
+ process.stderr.write(`transcript not found: ${p}\n`)
388
+ return 1
389
+ }
390
+ process.stdout.write(fs.readFileSync(p, 'utf8'))
391
+ return 0
392
+ }
393
+
394
+ async function cmdEnd(argv: string[]): Promise<number> {
395
+ const sessionId = argv[0]
396
+ if (!sessionId) {
397
+ process.stderr.write('usage: rnx agent end <sessionId>\n')
398
+ return 2
399
+ }
400
+ await withDaemon((client) => client.endSession(sessionId))
401
+ process.stdout.write(`✓ ended session ${sessionId}\n`)
402
+ return 0
403
+ }
404
+
405
+ async function cmdPaths(): Promise<number> {
406
+ const dir = getUserDataDir()
407
+ process.stdout.write(
408
+ `userData: ${dir}\n` +
409
+ `state: ${path.join(dir, 'attached-projects.json')}\n` +
410
+ `sessions: ${path.join(dir, 'sessions')}\n` +
411
+ `transcripts: ${path.join(dir, 'transcripts')}\n` +
412
+ `locks: ${path.join(dir, 'locks')}\n`,
413
+ )
414
+ return 0
415
+ }
416
+
417
+ // --- utilities ---
418
+
419
+ function truncate(s: string, max: number): string {
420
+ if (s.length <= max) return s
421
+ return s.slice(0, max - 1) + '…'
422
+ }
423
+ function shortId(id: string): string {
424
+ return id.length > 14 ? `${id.slice(0, 14)}…` : id
425
+ }
426
+ function fmtTs(ts: number): string {
427
+ return new Date(ts).toISOString().replace('T', ' ').slice(0, 19)
428
+ }
429
+
430
+ // keep these importable for the legacy e2e script paths — the canonical
431
+ // source of truth is now `sootsim/agent-sessions`.
432
+ export const __internals = {
433
+ sessionDir,
434
+ promptFifoPath,
435
+ eventsFifoPath,
436
+ transcriptPath,
437
+ parseAgentEventLine,
438
+ }
439
+ export type { AgentSession, AttachedProject }
@@ -0,0 +1,99 @@
1
+ // rnx app-fonts stage — fetch the config-plugin fonts declared on an
2
+ // engine `?appFonts=` wire, gzip them, and merge asset descriptors into a
3
+ // build's asset manifest.
4
+ //
5
+ // the branch-build path (app/api/github/agent/run.sh) captures the metro
6
+ // __packager_asset graph itself, but config-plugin fonts (expo-font etc.) only
7
+ // live in the native binary — never the bundle — so that capture misses them.
8
+ // rather than reimplement font fetch/gzip/hash in bash, run.sh calls this once
9
+ // after the asset capture; the descriptors land in the same manifest the
10
+ // upload step already PUTs, keyed by sha256(url) so the rendered build resolves
11
+ // each font through /api/preview/share/fetch?url=<fontUrl> identically to the
12
+ // preview path (`rnx record upload`). both pipelines share fetchAppFontFiles.
13
+
14
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
15
+ import { join } from 'node:path'
16
+ import { gzipSync } from 'node:zlib'
17
+ import { fetchAppFontFiles } from '../app-fonts'
18
+ import { rnxExit } from '../run-rnx'
19
+
20
+ type ManifestEntry = {
21
+ url: string
22
+ urlhash: string
23
+ contentType: string
24
+ encoding: 'gzip'
25
+ sizeBytes: number
26
+ rawBytes: number
27
+ }
28
+
29
+ function flag(args: string[], name: string): string | undefined {
30
+ const idx = args.indexOf(`--${name}`)
31
+ if (idx !== -1 && idx + 1 < args.length) return args[idx + 1]
32
+ const inline = args.find((a) => a.startsWith(`--${name}=`))
33
+ return inline ? inline.slice(name.length + 3) : undefined
34
+ }
35
+
36
+ async function stage(args: string[]): Promise<void> {
37
+ const wire = flag(args, 'wire') || ''
38
+ const outDir = flag(args, 'out-dir')
39
+ const manifestPath = flag(args, 'manifest')
40
+ if (!outDir || !manifestPath) {
41
+ console.error(
42
+ ' usage: rnx app-fonts stage --wire <wire> --out-dir <dir> --manifest <path>',
43
+ )
44
+ rnxExit(1)
45
+ }
46
+ if (!wire.trim()) {
47
+ console.log('no app fonts declared — nothing to stage')
48
+ return
49
+ }
50
+
51
+ mkdirSync(outDir, { recursive: true })
52
+ const fonts = await fetchAppFontFiles(wire, {
53
+ onStaged: ({ url, byteLength }) =>
54
+ console.log(` staged app font: ${url} (${(byteLength / 1024).toFixed(1)} KiB)`),
55
+ onError: (url, err) =>
56
+ console.error(
57
+ ` warning: failed to fetch app font ${url}: ${err instanceof Error ? err.message : err}`,
58
+ ),
59
+ })
60
+
61
+ const descriptors: ManifestEntry[] = []
62
+ for (const font of fonts) {
63
+ const gz = gzipSync(font.bytes)
64
+ writeFileSync(join(outDir, font.urlhash), gz)
65
+ descriptors.push({
66
+ url: font.url,
67
+ urlhash: font.urlhash,
68
+ contentType: font.contentType,
69
+ encoding: 'gzip',
70
+ sizeBytes: gz.length,
71
+ rawBytes: font.bytes.byteLength,
72
+ })
73
+ }
74
+
75
+ let existing: ManifestEntry[] = []
76
+ if (existsSync(manifestPath)) {
77
+ try {
78
+ const parsed: unknown = JSON.parse(readFileSync(manifestPath, 'utf8'))
79
+ if (Array.isArray(parsed)) existing = parsed as ManifestEntry[]
80
+ } catch {}
81
+ }
82
+ const seen = new Set(existing.map((e) => e.urlhash))
83
+ const merged = [...existing, ...descriptors.filter((d) => !seen.has(d.urlhash))]
84
+ writeFileSync(manifestPath, JSON.stringify(merged))
85
+ console.log(
86
+ `staged ${descriptors.length} app font(s); manifest now has ${merged.length} file(s)`,
87
+ )
88
+ }
89
+
90
+ export async function runAppFonts(args: string[]): Promise<void> {
91
+ const sub = args[0]
92
+ if (sub === 'stage') {
93
+ await stage(args.slice(1))
94
+ return
95
+ }
96
+ console.error(` unknown app-fonts subcommand: ${sub ?? '(none)'}`)
97
+ console.error(' available: stage')
98
+ rnxExit(1)
99
+ }