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,375 @@
1
+ // plain-language Maestro authoring for `rnx maestro generate`.
2
+
3
+ import { mkdtempSync, writeFileSync } from 'fs'
4
+ import { tmpdir } from 'os'
5
+ import { join } from 'path'
6
+ import yaml from 'yaml'
7
+ import { authHeaderValue, resolveCliAuth } from '../auth'
8
+ import { registerRun, stepSummaryFromTrace } from '../run-registry'
9
+ import { createBridgeFromParsed, parseBridgeCliArgs } from '../ws-bridge'
10
+ import {
11
+ findUnexpectedFlowArgs,
12
+ FLOW_ARG_VALUE_FLAGS,
13
+ FLOW_BRIDGE_ARG_OPTIONS,
14
+ getLastFlowPreviewUploadResult,
15
+ getLastFlowTraceSteps,
16
+ runFlowPlayback,
17
+ type UnexpectedFlowArg,
18
+ } from './flow'
19
+ import { inspectDescribe } from './inspect/core'
20
+ import { resolveDefaultUploadOrigin } from './upload'
21
+
22
+ const DEFAULT_TEST_MODEL = process.env.RNX_TEST_MODEL || 'deepseek-v4-flash'
23
+ const DESCRIBE_CONTEXT_LIMIT = 18_000
24
+ const LLM_TIMEOUT_MS = 180_000
25
+
26
+ function flagValue(args: string[], name: string): string | undefined {
27
+ const index = args.indexOf(name)
28
+ return index >= 0 ? args[index + 1] : undefined
29
+ }
30
+
31
+ function flagValueAny(args: string[], names: string[]): string | undefined {
32
+ for (const name of names) {
33
+ const value = flagValue(args, name)
34
+ if (value) return value
35
+ }
36
+ return undefined
37
+ }
38
+
39
+ // generate is a front-end for runFlowPlayback: it inspects the sim, asks the
40
+ // model for a flow, then hands the argv down. these are the only flags it reads
41
+ // itself; everything else belongs to the runner and is forwarded verbatim. this
42
+ // file used to keep its own allowlist of what to forward, which silently
43
+ // dropped --proof, --record, --profile, --screenshots, --out and --base-url
44
+ // while `maestro test` honoured all six.
45
+ const GENERATE_VALUE_FLAGS = ['--origin', '--preview-origin', '--public-origin']
46
+
47
+ const VALUE_FLAGS = new Set([...GENERATE_VALUE_FLAGS, ...FLOW_ARG_VALUE_FLAGS])
48
+
49
+ export function findPrompt(args: string[]): { prompt: string; index: number } | null {
50
+ for (let i = 0; i < args.length; i++) {
51
+ const arg = args[i]
52
+ if (!arg) continue
53
+ if (VALUE_FLAGS.has(arg)) {
54
+ i += 1
55
+ continue
56
+ }
57
+ if (arg.startsWith('-')) continue
58
+ return { prompt: arg, index: i }
59
+ }
60
+ return null
61
+ }
62
+
63
+ function stripPromptForBridge(args: string[], promptIndex: number): string[] {
64
+ return args.filter((_, index) => index !== promptIndex)
65
+ }
66
+
67
+ async function describeCurrentSim(args: string[], promptIndex: number): Promise<string> {
68
+ const bridgeArgs = stripPromptForBridge(args, promptIndex)
69
+ const parsedBridgeArgs = parseBridgeCliArgs(bridgeArgs, {
70
+ stripBooleanFlags: FLOW_BRIDGE_ARG_OPTIONS.stripBooleanFlags,
71
+ stripValueFlags: [
72
+ ...GENERATE_VALUE_FLAGS,
73
+ ...FLOW_BRIDGE_ARG_OPTIONS.stripValueFlags,
74
+ ],
75
+ })
76
+ const bridge = createBridgeFromParsed(parsedBridgeArgs)
77
+ try {
78
+ const describe = await inspectDescribe(bridge, {
79
+ describe: true,
80
+ verbose: false,
81
+ filter: '',
82
+ compact: true,
83
+ hideXy: true,
84
+ })
85
+ const parts = [
86
+ typeof describe.nodeCount === 'number' ? `nodes: ${describe.nodeCount}` : '',
87
+ describe.tree || '',
88
+ ].filter(Boolean)
89
+ const text = parts.join('\n')
90
+ return text.length > DESCRIBE_CONTEXT_LIMIT
91
+ ? text.slice(0, DESCRIBE_CONTEXT_LIMIT) + '\n[tree truncated]'
92
+ : text
93
+ } finally {
94
+ bridge.close()
95
+ }
96
+ }
97
+
98
+ const RNX_TEST_SYSTEM_PROMPT = `
99
+ You write Maestro-compatible rnx YAML flows from a user's plain-language test goal.
100
+ Return only JSON, no markdown, with this exact shape:
101
+ {"summary":"short human-readable result goal","steps":[{"waitFor":{"text":"...","timeout":10000}}]}
102
+
103
+ Use only these flow commands:
104
+ - waitFor: { "text"?: string, "id"?: string, "timeout"?: number }
105
+ - assertVisible: string or { "text"?: string, "id"?: string }
106
+ - assertNotVisible: string or { "text"?: string, "id"?: string }
107
+ - tapOn: string or { "text"?: string, "id"?: string, "index"?: number }
108
+ - inputText: string
109
+ - pressKey: string
110
+ - hideKeyboard: true
111
+ - swipe: { "direction": "UP" | "DOWN" | "LEFT" | "RIGHT", "duration"?: number }
112
+ - waitForAnimationToEnd: true
113
+ - takeScreenshot: "label"
114
+
115
+ Prefer ids/testIDs from the tree. Prefer visible text when no id exists.
116
+ Do not invent app-specific data that is not visible. Keep the flow short.
117
+ End with an assertion or wait that proves the user's goal.
118
+ `.trim()
119
+
120
+ function buildPromptRequest(prompt: string, describe: string): string {
121
+ return `
122
+ User goal:
123
+ ${prompt}
124
+
125
+ Current rnx visible tree:
126
+ ${describe || '[no tree available]'}
127
+
128
+ Generate the shortest replayable flow that verifies the goal from this current state.
129
+ `.trim()
130
+ }
131
+
132
+ async function generateFlowJson(args: {
133
+ prompt: string
134
+ describe: string
135
+ origin: string
136
+ authHeader: string
137
+ }): Promise<string> {
138
+ const res = await fetch(`${args.origin.replace(/\/$/, '')}/api/llm`, {
139
+ method: 'POST',
140
+ headers: {
141
+ 'content-type': 'application/json',
142
+ authorization: args.authHeader,
143
+ },
144
+ body: JSON.stringify({
145
+ modelId: DEFAULT_TEST_MODEL,
146
+ context: {
147
+ systemPrompt: RNX_TEST_SYSTEM_PROMPT,
148
+ messages: [
149
+ {
150
+ role: 'user',
151
+ content: buildPromptRequest(args.prompt, args.describe),
152
+ timestamp: Date.now(),
153
+ },
154
+ ],
155
+ },
156
+ options: { maxTokens: 1800, temperature: 0.2 },
157
+ threadId: null,
158
+ }),
159
+ signal: AbortSignal.timeout(LLM_TIMEOUT_MS),
160
+ })
161
+ if (!res.ok || !res.body) {
162
+ const text = await res.text().catch(() => '')
163
+ throw new Error(`/api/llm ${res.status}${text ? `: ${text}` : ''}`)
164
+ }
165
+
166
+ const reader = res.body.getReader()
167
+ const decoder = new TextDecoder()
168
+ const deltas: string[] = []
169
+ let buffer = ''
170
+ // /api/llm speaks pi-agent-core's proxy event protocol. text events carry
171
+ // visible deltas; transport diagnostics and keepalives are ignored here.
172
+ const handleLine = (line: string) => {
173
+ const trimmed = line.trim()
174
+ if (!trimmed) return
175
+ const event = JSON.parse(trimmed) as {
176
+ type?: string
177
+ delta?: string
178
+ errorMessage?: string
179
+ }
180
+ if (event.type === 'text_delta' && event.delta) {
181
+ deltas.push(event.delta)
182
+ } else if (event.type === 'error') {
183
+ throw new Error(event.errorMessage || 'llm error')
184
+ }
185
+ }
186
+
187
+ for (;;) {
188
+ const { value, done } = await reader.read()
189
+ if (done) break
190
+ buffer += decoder.decode(value, { stream: true })
191
+ let newline: number
192
+ while ((newline = buffer.indexOf('\n')) >= 0) {
193
+ handleLine(buffer.slice(0, newline))
194
+ buffer = buffer.slice(newline + 1)
195
+ }
196
+ }
197
+ buffer += decoder.decode()
198
+ if (buffer.trim()) handleLine(buffer)
199
+
200
+ const text = deltas.join('').trim()
201
+ if (!text) throw new Error('llm returned an empty test flow')
202
+ return text
203
+ }
204
+
205
+ function extractJson(text: string): unknown {
206
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)
207
+ const candidate = fenced
208
+ ? fenced[1]
209
+ : text.slice(text.indexOf('{'), text.lastIndexOf('}') + 1)
210
+ return JSON.parse(candidate)
211
+ }
212
+
213
+ function isRecord(value: unknown): value is Record<string, unknown> {
214
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value))
215
+ }
216
+
217
+ function parseGeneratedFlow(text: string): {
218
+ summary: string
219
+ steps: Record<string, unknown>[]
220
+ yamlText: string
221
+ } {
222
+ const parsed = extractJson(text)
223
+ if (!isRecord(parsed)) throw new Error('llm did not return a JSON object')
224
+ const summary =
225
+ typeof parsed.summary === 'string' && parsed.summary.trim()
226
+ ? parsed.summary.trim().slice(0, 500)
227
+ : 'rnx generated test'
228
+ const rawSteps = parsed.steps
229
+ if (!Array.isArray(rawSteps) || rawSteps.length === 0) {
230
+ throw new Error('llm returned no flow steps')
231
+ }
232
+ const steps = rawSteps.map((step, index) => {
233
+ if (!isRecord(step)) throw new Error(`flow step ${index + 1} is not an object`)
234
+ return step
235
+ })
236
+ return { summary, steps, yamlText: yaml.stringify(steps) }
237
+ }
238
+
239
+ export function buildFlowArgs(
240
+ args: string[],
241
+ promptIndex: number,
242
+ flowPath: string,
243
+ origin: string,
244
+ ) {
245
+ const out = [
246
+ flowPath,
247
+ '--preview',
248
+ '--preview-origin',
249
+ origin,
250
+ '--billing-kind',
251
+ 'test_run',
252
+ ]
253
+ for (let i = 0; i < args.length; i++) {
254
+ if (i === promptIndex) continue
255
+ const arg = args[i]
256
+ if (!arg) continue
257
+ if (arg === '--origin' || arg === '--preview-origin') {
258
+ i += 1
259
+ continue
260
+ }
261
+ if (arg === '--public-origin') {
262
+ const value = args[i + 1]
263
+ if (value) out.push('--preview-public-origin', value)
264
+ i += 1
265
+ continue
266
+ }
267
+ out.push(arg)
268
+ if (VALUE_FLAGS.has(arg)) {
269
+ const value = args[i + 1]
270
+ if (value !== undefined) out.push(value)
271
+ i += 1
272
+ }
273
+ }
274
+ return out
275
+ }
276
+
277
+ // an argument nothing reads is an argument that silently changes what the run
278
+ // does, and generate hands its argv to runFlowPlayback, which is the end of the
279
+ // line for argv. so whatever survives buildFlowArgs is checked against the
280
+ // runner's own vocabulary, before the sim inspection and the model call rather
281
+ // than after them.
282
+ export function findUnexpectedGenerateArgs(
283
+ args: string[],
284
+ promptIndex: number,
285
+ ): UnexpectedFlowArg[] {
286
+ const problems: UnexpectedFlowArg[] = []
287
+ for (let i = 0; i < args.length; i++) {
288
+ if (i === promptIndex) continue
289
+ const arg = args[i]
290
+ if (!arg) continue
291
+ // generate writes --billing-kind onto the flow argv itself and getFlag
292
+ // takes the first occurrence, so a caller's value loses to it in silence.
293
+ if (arg === '--billing-kind') {
294
+ problems.push({ arg, message: `${arg} is set by rnx maestro generate` })
295
+ i += 1
296
+ continue
297
+ }
298
+ if (!GENERATE_VALUE_FLAGS.includes(arg)) continue
299
+ const value = args[i + 1]
300
+ if (value === undefined || value.startsWith('-')) {
301
+ problems.push({ arg, message: `${arg} expects a value` })
302
+ }
303
+ i += 1
304
+ }
305
+ return [
306
+ ...problems,
307
+ ...findUnexpectedFlowArgs(buildFlowArgs(args, promptIndex, '<flow>', '<origin>')),
308
+ ]
309
+ }
310
+
311
+ export async function runMaestroGenerate(args: string[]): Promise<number> {
312
+ const promptArg = findPrompt(args)
313
+ if (!promptArg) {
314
+ console.error(' usage: rnx maestro generate "test goal"')
315
+ return 1
316
+ }
317
+ const { prompt, index: promptIndex } = promptArg
318
+ const unexpected = findUnexpectedGenerateArgs(args, promptIndex)
319
+ if (unexpected.length > 0) {
320
+ for (const problem of unexpected) {
321
+ console.error(` error: ${problem.message}`)
322
+ }
323
+ console.error('\n run `rnx maestro --help` for the flags this command accepts')
324
+ return 1
325
+ }
326
+ const cliAuth = resolveCliAuth()
327
+ if (!cliAuth || cliAuth.kind === 'github') {
328
+ console.error(' rnx maestro generate "<goal>" needs a login or RNX_API_KEY.')
329
+ console.error(' API keys must include preview_upload and llm scopes.')
330
+ return 1
331
+ }
332
+ const authHeader = authHeaderValue(cliAuth)
333
+ const origin = await resolveDefaultUploadOrigin(
334
+ flagValueAny(args, ['--origin', '--preview-origin']),
335
+ )
336
+ console.log(' inspecting current sim…')
337
+ const describe = await describeCurrentSim(args, promptIndex)
338
+ console.log(' generating Maestro flow…')
339
+ const generated = await generateFlowJson({ prompt, describe, origin, authHeader })
340
+ const flow = parseGeneratedFlow(generated)
341
+ const tempDir = mkdtempSync(join(tmpdir(), 'rnx-maestro-generate-'))
342
+ const flowPath = join(tempDir, 'flow.yaml')
343
+ writeFileSync(flowPath, flow.yamlText)
344
+ console.log(` flow: ${flowPath}`)
345
+
346
+ const startedAt = Date.now()
347
+ const flowArgs = buildFlowArgs(args, promptIndex, flowPath, origin)
348
+ const exitCode = await runFlowPlayback(flowArgs)
349
+ const upload = getLastFlowPreviewUploadResult()
350
+ const traceSteps = getLastFlowTraceSteps()
351
+ const failedStep = traceSteps.find((step) => step.status === 'failure')
352
+ const status = exitCode === 0 ? 'passed' : 'failed'
353
+ const run = await registerRun({
354
+ origin,
355
+ kind: 'maestro',
356
+ prompt,
357
+ summary: flow.summary,
358
+ status,
359
+ failureMessage:
360
+ status === 'failed' ? (failedStep?.error ?? 'flow playback failed') : null,
361
+ previewShareId: upload?.shareId ?? null,
362
+ owner: flagValue(args, '--owner') ?? null,
363
+ repo: flagValue(args, '--repo') ?? null,
364
+ durationMs: Date.now() - startedAt,
365
+ flowYamlSizeBytes: Buffer.byteLength(flow.yamlText, 'utf8'),
366
+ ...stepSummaryFromTrace(traceSteps),
367
+ auth: cliAuth,
368
+ })
369
+
370
+ console.log(`\n result: ${status}`)
371
+ if (run) console.log(` run: ${run.id}`)
372
+ if (upload?.previewUrl) console.log(` preview: ${upload.previewUrl}`)
373
+ if (run?.traceUrl) console.log(` replay: ${run.traceUrl}`)
374
+ return exitCode
375
+ }