peachsim 0.1.571 → 0.1.573
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 +1 -1
- package/README.md +106 -106
- package/cli/app-config.ts +1 -1
- package/cli/app-fonts.ts +4 -4
- package/cli/auth.ts +11 -11
- package/cli/bin.ts +4 -4
- package/cli/bridge-flow-runner.ts +13 -13
- package/cli/browser-evals.ts +2 -2
- package/cli/cli-update.ts +6 -6
- package/cli/cloud-client.ts +16 -16
- package/cli/cloud-dispatch.ts +2 -2
- package/cli/cloud-session.ts +12 -12
- package/cli/cloud-test-tunnel.ts +4 -4
- package/cli/command-registry.ts +7 -7
- package/cli/commands/agent-wrapper.ts +7 -7
- package/cli/commands/agent.ts +16 -16
- package/cli/commands/app-fonts.ts +3 -3
- package/cli/commands/assert.ts +10 -10
- package/cli/commands/auth.ts +5 -5
- package/cli/commands/box/checkout-plane.ts +1 -1
- package/cli/commands/box/plane-share.ts +2 -2
- package/cli/commands/box/rnx-command.ts +5 -5
- package/cli/commands/box.ts +38 -38
- package/cli/commands/camera.ts +13 -13
- package/cli/commands/cleanup.ts +13 -13
- package/cli/commands/compat.ts +7 -7
- package/cli/commands/config.ts +5 -5
- package/cli/commands/control.ts +63 -63
- package/cli/commands/cpu-profile.ts +11 -11
- package/cli/commands/daemon-mac-app.ts +10 -10
- package/cli/commands/daemon.ts +29 -27
- package/cli/commands/debug.ts +35 -35
- package/cli/commands/desktop.ts +3 -3
- package/cli/commands/detox.ts +33 -33
- package/cli/commands/device.ts +9 -9
- package/cli/commands/diagnose.ts +17 -17
- package/cli/commands/do-chain.ts +4 -4
- package/cli/commands/electron.ts +9 -9
- package/cli/commands/film.ts +9 -9
- package/cli/commands/flow.ts +49 -49
- package/cli/commands/inspect/core.ts +12 -12
- package/cli/commands/inspect/count.ts +1 -1
- package/cli/commands/inspect/describe.ts +1 -1
- package/cli/commands/inspect/env.ts +2 -2
- package/cli/commands/inspect/find.ts +2 -2
- package/cli/commands/inspect/get-layout.ts +2 -2
- package/cli/commands/inspect/keyboard.ts +1 -1
- package/cli/commands/inspect/list.ts +4 -4
- package/cli/commands/inspect/memory.ts +1 -1
- package/cli/commands/inspect/resolve-target.ts +1 -1
- package/cli/commands/inspect/screens.ts +1 -1
- package/cli/commands/inspect/shared.ts +1 -1
- package/cli/commands/inspect/tree.ts +1 -1
- package/cli/commands/inspect/url.ts +1 -1
- package/cli/commands/inspect/wait-event.ts +5 -5
- package/cli/commands/inspect.ts +82 -82
- package/cli/commands/install-cli.ts +16 -16
- package/cli/commands/install-desktop.ts +26 -26
- package/cli/commands/login.ts +11 -11
- package/cli/commands/maestro-generate.ts +10 -10
- package/cli/commands/maestro.ts +77 -67
- package/cli/commands/mode.ts +4 -4
- package/cli/commands/no-bridge-hint.ts +12 -12
- package/cli/commands/perf.ts +11 -11
- package/cli/commands/permissions.ts +5 -5
- package/cli/commands/platform.ts +21 -21
- package/cli/commands/preview.ts +5 -5
- package/cli/commands/profile.ts +12 -12
- package/cli/commands/react.ts +15 -15
- package/cli/commands/record.ts +31 -31
- package/cli/commands/report-issue.ts +4 -4
- package/cli/commands/reset.ts +2 -2
- package/cli/commands/runtime.ts +23 -23
- package/cli/commands/screenshot-command.ts +2 -2
- package/cli/commands/screenshot-layers.ts +5 -5
- package/cli/commands/screenshot-mode.ts +7 -7
- package/cli/commands/screenshot.ts +24 -24
- package/cli/commands/screenshots-capture.ts +13 -13
- package/cli/commands/screenshots.ts +6 -6
- package/cli/commands/serve.ts +12 -12
- package/cli/commands/setup.ts +25 -25
- package/cli/commands/skills.ts +30 -23
- package/cli/commands/slides.ts +20 -20
- package/cli/commands/state.ts +2 -2
- package/cli/commands/storage.ts +3 -3
- package/cli/commands/telemetry.ts +2 -2
- package/cli/commands/test.ts +15 -15
- package/cli/commands/three-mode.ts +17 -17
- package/cli/commands/timeline.ts +12 -12
- package/cli/commands/upgrade.ts +11 -11
- package/cli/commands/upload.ts +26 -26
- package/cli/commands/version.ts +7 -7
- package/cli/commands/what-happened.ts +20 -20
- package/cli/current-sim.ts +3 -3
- package/cli/desktop-companion.ts +16 -16
- package/cli/drivers/electron.ts +4 -4
- package/cli/drivers/playwright-sim-host.ts +7 -7
- package/cli/drivers/playwright.ts +4 -4
- package/cli/drivers/registry.ts +1 -1
- package/cli/drivers/types.ts +5 -5
- package/cli/flow-export.ts +1 -1
- package/cli/flow-file.ts +5 -5
- package/cli/flow-session.ts +1 -1
- package/cli/help-core.ts +13 -13
- package/cli/hints.ts +14 -14
- package/cli/inspect-notice-state.ts +1 -1
- package/cli/internal-child.ts +2 -2
- package/cli/maestro-js.ts +4 -4
- package/cli/maestro-report.ts +1 -1
- package/cli/main.ts +14 -14
- package/cli/outbound-endpoints.ts +30 -30
- package/cli/parse-args.ts +7 -7
- package/cli/privacy.ts +1 -1
- package/cli/recording-access.ts +10 -10
- package/cli/resolve-assets.ts +2 -2
- package/cli/rnx-in-process.ts +5 -5
- package/cli/run-registry.ts +1 -1
- package/cli/run-rnx-core.ts +3 -3
- package/cli/run-rnx.ts +1 -1
- package/cli/runtime-notes.ts +1 -1
- package/cli/self-invocation.ts +1 -1
- package/cli/send-to-box.ts +1 -1
- package/cli/shell-init.ts +40 -40
- package/cli/ui.ts +1 -1
- package/cli/ws-bridge.ts +22 -22
- package/detox/config-loader.ts +1 -1
- package/detox/index.ts +28 -28
- package/detox/navigation.ts +2 -2
- package/dist-lib/agent-daemon-client.cjs +12 -12
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +2 -2
- package/dist-lib/agent-sessions.cjs +12 -12
- package/dist-lib/attached-projects.cjs +4 -4
- package/dist-lib/auth/shared-session.cjs +4 -4
- 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 +4 -4
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +2 -2
- package/dist-lib/bridge-contract.mjs +2 -2
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +42 -42
- package/dist-lib/cloud.mjs +42 -42
- package/dist-lib/config.cjs +2 -2
- package/dist-lib/detox/index.cjs +29 -29
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +12 -12
- package/dist-lib/host/bridge-host.cjs +72 -71
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +6 -6
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +48 -48
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/menu.cjs +5 -5
- package/dist-lib/menu.mjs +5 -5
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +15 -15
- package/dist-lib/profiles.cjs +4 -4
- package/dist-lib/public-brand.cjs +3 -3
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +2 -1
- package/dist-lib/sdk.cjs +9 -9
- package/dist-lib/sdk.mjs +9 -9
- package/dist-lib/skills.cjs +416 -415
- package/dist-lib/swift-compile.cjs +1 -1
- package/dist-lib/swift-compile.mjs +1 -1
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite.cjs +24 -24
- package/package.json +1 -1
- package/scripts/dev-server-scanner.ts +4 -3
- package/skills/contrast/SKILL.md +75 -75
- package/skills/{rnx-debug → peach-debug}/SKILL.md +90 -90
- package/skills/{rnx-perf → peach-perf}/SKILL.md +13 -13
- package/skills/{rnx-setup → peach-setup}/SKILL.md +60 -60
- package/skills/{rnx-test → peach-test}/SKILL.md +44 -44
- package/skills/{rnx-visual → peach-visual}/SKILL.md +26 -26
- package/src/agent-daemon-client.ts +5 -5
- package/src/agent-events.ts +1 -1
- package/src/agent-identity.ts +1 -1
- package/src/agent-sessions.ts +13 -13
- package/src/attached-projects.ts +1 -1
- package/src/auth/shared-session.ts +1 -1
- package/src/bridge-constants.ts +3 -3
- package/src/bridge-contract-input.ts +1 -1
- package/src/bridge-contract.ts +3 -3
- package/src/cli-version.ts +6 -6
- package/src/cloud.ts +40 -40
- package/src/config.ts +1 -1
- package/src/connect.ts +6 -6
- package/src/dev-server-open.ts +8 -8
- package/src/disk-cleanup.ts +3 -3
- package/src/home-paths.ts +26 -26
- package/src/host/agent-host.ts +3 -3
- package/src/host/bridge-host.ts +33 -33
- package/src/host/fetch-proxy-handler.ts +1 -1
- package/src/host/plane-host.ts +2 -2
- package/src/host/replacement-module-handler.ts +2 -2
- package/src/menu.ts +2 -2
- package/src/metro-plugin.ts +7 -7
- package/src/metro-production-bundle.ts +2 -2
- package/src/native-dev-bundle-url.ts +2 -2
- package/src/playwright-host-log.ts +1 -1
- package/src/public-brand.ts +2 -2
- package/src/react-native-host-modules.ts +1 -1
- package/src/rolldown-plugin-swift.ts +3 -3
- package/src/runtime-assets.ts +11 -11
- package/src/runtime-delivery.ts +6 -6
- package/src/screenshots/orchestrate.ts +1 -1
- package/src/screenshots/org-deck.ts +1 -1
- package/src/screenshots/schema.ts +2 -2
- package/src/sim-client.ts +1 -1
- package/src/skills/builtin/compat-check.ts +2 -2
- package/src/vite-plugin-one.ts +8 -8
- package/src/vite-plugin-swift.ts +3 -3
- package/src/vite-plugin.ts +6 -19
package/cli/commands/diagnose.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// peach get diagnosis — composed read commands for "what just went wrong?"
|
|
2
2
|
//
|
|
3
3
|
// the demo-friendly compact diagnostic surface called out in
|
|
4
4
|
// plans/expensify-sootsim-agent-device-demo.md. one command, one round
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
// sed/grep workflow in both speed and information density.
|
|
7
7
|
//
|
|
8
8
|
// usage:
|
|
9
|
-
//
|
|
10
|
-
//
|
|
9
|
+
// peach get diagnosis [--since 90s] [--include react,network,console,screen,fetch,alert]
|
|
10
|
+
// peach get diagnosis --json
|
|
11
11
|
//
|
|
12
12
|
// the output is grouped by source (timeline, network, react, console)
|
|
13
13
|
// rather than chronological. for chronological order use
|
|
14
|
-
// `
|
|
14
|
+
// `peach what-happened` directly. `recent` is the only subcommand
|
|
15
15
|
// today; `diagnose <something-else>` may be added later (e.g. boot,
|
|
16
16
|
// scrolling-jank, idle-burn).
|
|
17
17
|
//
|
|
@@ -101,10 +101,10 @@ const DEFAULT_INCLUDES = new Set(['timeline', 'network', 'react', 'console', 'sc
|
|
|
101
101
|
|
|
102
102
|
function printHelp(): void {
|
|
103
103
|
console.log(`
|
|
104
|
-
|
|
104
|
+
peach get diagnosis — compact "what just went wrong?" report
|
|
105
105
|
|
|
106
106
|
usage:
|
|
107
|
-
|
|
107
|
+
peach get diagnosis [--since <duration>] [--include <csv>] [--json]
|
|
108
108
|
|
|
109
109
|
options:
|
|
110
110
|
--since <d> absolute window for timeline + network (e.g. 90s, 2m, 500ms; default 90s)
|
|
@@ -115,14 +115,14 @@ options:
|
|
|
115
115
|
--json emit a single structured JSON document
|
|
116
116
|
|
|
117
117
|
the diagnose report runs every dimension in parallel against the same
|
|
118
|
-
sim and returns a grouped summary. use \`
|
|
119
|
-
for chronological events, \`
|
|
120
|
-
\`
|
|
118
|
+
sim and returns a grouped summary. use \`peach what-happened\`
|
|
119
|
+
for chronological events, \`peach network\` for full request inspection,
|
|
120
|
+
\`peach perf react summary\` for the React tables on their own.
|
|
121
121
|
|
|
122
122
|
examples:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
peach get diagnosis
|
|
124
|
+
peach get diagnosis --since 30s --include network,react
|
|
125
|
+
peach get diagnosis --json > /tmp/diagnosis.json
|
|
126
126
|
`)
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -315,7 +315,7 @@ function formatNetworkRow(entry: NetworkEntry): string {
|
|
|
315
315
|
const dur = entry.durationMs != null ? `${entry.durationMs.toFixed(0)}ms` : '...'
|
|
316
316
|
const url = entry.displayUrl ?? entry.url
|
|
317
317
|
// shorten long urls to 96 chars in the diagnose report — full
|
|
318
|
-
// detail is one `
|
|
318
|
+
// detail is one `peach network get <id>` away.
|
|
319
319
|
const shortUrl = url.length > 96 ? `${url.slice(0, 92)}…` : url
|
|
320
320
|
return ` ${status.padStart(3)} ${method} ${dur.padStart(7)} ${shortUrl} (${entry.id})`
|
|
321
321
|
}
|
|
@@ -371,7 +371,7 @@ export async function runDiagnose(
|
|
|
371
371
|
const sub = parsed.positional[0] ?? 'recent'
|
|
372
372
|
if (sub !== 'recent') {
|
|
373
373
|
console.error(` unknown subcommand: ${sub}`)
|
|
374
|
-
console.error(' did you mean:
|
|
374
|
+
console.error(' did you mean: peach get diagnosis ?')
|
|
375
375
|
return 1
|
|
376
376
|
}
|
|
377
377
|
|
|
@@ -567,7 +567,7 @@ export async function runDiagnose(
|
|
|
567
567
|
const identityKey = getCliIdentityKey()
|
|
568
568
|
if (identityKey) {
|
|
569
569
|
console.log(
|
|
570
|
-
`\n next:
|
|
570
|
+
`\n next: peach what-happened (cursor advances) · peach network get <id> · peach perf react why <fiber-id>`,
|
|
571
571
|
)
|
|
572
572
|
}
|
|
573
573
|
return 0
|
|
@@ -575,10 +575,10 @@ export async function runDiagnose(
|
|
|
575
575
|
// a bridge call rejecting (most commonly "no sim connected") must not
|
|
576
576
|
// escape as a raw stack trace with a 0 exit code — `get diagnosis` is the
|
|
577
577
|
// "what just went wrong" command; failing it should read cleanly and
|
|
578
|
-
// exit non-zero so `
|
|
578
|
+
// exit non-zero so `peach get diagnosis && …` chains behave.
|
|
579
579
|
const msg = err instanceof Error ? err.message : String(err)
|
|
580
580
|
if (/no sim connected/i.test(msg)) {
|
|
581
|
-
console.error(' no sim connected — open one first:
|
|
581
|
+
console.error(' no sim connected — open one first: peach open <metroPort>')
|
|
582
582
|
} else {
|
|
583
583
|
console.error(` diagnose failed: ${msg}`)
|
|
584
584
|
}
|
package/cli/commands/do-chain.ts
CHANGED
|
@@ -43,7 +43,7 @@ export function hasDoChain(args: string[]): boolean {
|
|
|
43
43
|
|
|
44
44
|
function invalidAction(index: number, usage: string): never {
|
|
45
45
|
throw new BridgeInputError(
|
|
46
|
-
`do chain action ${index + 1} has invalid arguments; usage:
|
|
46
|
+
`do chain action ${index + 1} has invalid arguments; usage: peach do ${usage}`,
|
|
47
47
|
)
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -74,7 +74,7 @@ export function parseDoChainActions(args: string[]): DoChainAction[] {
|
|
|
74
74
|
if (!action) throw new BridgeInputError('do chain contains an empty action')
|
|
75
75
|
if (!DO_CHAIN_ACTIONS.has(action)) {
|
|
76
76
|
throw new BridgeInputError(
|
|
77
|
-
`do chain action ${index + 1} cannot batch ${JSON.stringify(action)}; run it as a separate
|
|
77
|
+
`do chain action ${index + 1} cannot batch ${JSON.stringify(action)}; run it as a separate peach do command`,
|
|
78
78
|
)
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -315,7 +315,7 @@ export async function runDoChain(
|
|
|
315
315
|
const { isAgentEnv } = envModule
|
|
316
316
|
const { resolveTargetCoords } = targetModule
|
|
317
317
|
const wantsJson = args.includes('--json')
|
|
318
|
-
const skipSettle = args.includes('--no-wait') || process.env.
|
|
318
|
+
const skipSettle = args.includes('--no-wait') || process.env.PEACH_NO_AUTO_WAIT === '1'
|
|
319
319
|
const parsed = parseBridgeCliArgs(args, {
|
|
320
320
|
port: opts.port,
|
|
321
321
|
commandTimeoutMs: 30_000,
|
|
@@ -379,7 +379,7 @@ export async function runDoChain(
|
|
|
379
379
|
const settled = await sim.settle({ maxMs: budgetMs })
|
|
380
380
|
if (!settled.settled) {
|
|
381
381
|
process.stderr.write(
|
|
382
|
-
` ⚠ auto-wait timed out after ${settled.elapsed ?? budgetMs}ms. next command may see mid-animation state. use \`
|
|
382
|
+
` ⚠ auto-wait timed out after ${settled.elapsed ?? budgetMs}ms. next command may see mid-animation state. use \`peach do settle\` for a longer wait.\n`,
|
|
383
383
|
)
|
|
384
384
|
}
|
|
385
385
|
} catch {}
|
package/cli/commands/electron.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// peach desktop — launch the installed desktop companion via the
|
|
2
2
|
// electron driver. the driver wraps findDesktopCompanion +
|
|
3
3
|
// launchDesktopCompanion, so this command is a thin CLI surface that
|
|
4
4
|
// parses options, prompts for install, and delegates everything else.
|
|
@@ -39,10 +39,10 @@ export async function resolveElectronLaunchUrl(
|
|
|
39
39
|
export async function runElectron(args: string[], opts: ElectronOptions) {
|
|
40
40
|
if (args.includes('--help') || args.includes('-h')) {
|
|
41
41
|
console.log(`
|
|
42
|
-
|
|
42
|
+
peach desktop — launch the desktop companion
|
|
43
43
|
|
|
44
44
|
usage:
|
|
45
|
-
|
|
45
|
+
peach desktop [options]
|
|
46
46
|
|
|
47
47
|
options:
|
|
48
48
|
--port <number> connect to running dev server on this port
|
|
@@ -50,9 +50,9 @@ options:
|
|
|
50
50
|
--ephemeral launch with a temporary isolated storage profile
|
|
51
51
|
|
|
52
52
|
examples:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
peach desktop
|
|
54
|
+
peach desktop --port 5173
|
|
55
|
+
peach desktop --profile qa
|
|
56
56
|
`)
|
|
57
57
|
rnxExit(0)
|
|
58
58
|
}
|
|
@@ -60,7 +60,7 @@ examples:
|
|
|
60
60
|
const profileArg = args.find((_, i) => args[i - 1] === '--profile')
|
|
61
61
|
const ephemeralProfile = args.includes('--ephemeral')
|
|
62
62
|
if (profileArg && ephemeralProfile) {
|
|
63
|
-
console.error('
|
|
63
|
+
console.error(' peach desktop: --profile cannot be combined with --ephemeral')
|
|
64
64
|
rnxExit(1)
|
|
65
65
|
}
|
|
66
66
|
const profileId = profileArg ? ensureProfile(profileArg).id : undefined
|
|
@@ -68,8 +68,8 @@ examples:
|
|
|
68
68
|
if (!install) {
|
|
69
69
|
printMissingCompanionHint()
|
|
70
70
|
const canPrompt =
|
|
71
|
-
process.stdin.isTTY && process.env.CI !== '1' && process.env.
|
|
72
|
-
if (canPrompt && (await confirm('run
|
|
71
|
+
process.stdin.isTTY && process.env.CI !== '1' && process.env.PEACH_NO_PROMPT !== '1'
|
|
72
|
+
if (canPrompt && (await confirm('run peach desktop install now?', true))) {
|
|
73
73
|
console.log()
|
|
74
74
|
const { runInstallDesktop } = await import('./install-desktop')
|
|
75
75
|
await runInstallDesktop(['--yes'])
|
package/cli/commands/film.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// peach film — record a flow on the flat canvas, then re-play that
|
|
2
2
|
// recording on the 3d device stage under a camera cinematic and capture the
|
|
3
3
|
// stage as video.
|
|
4
4
|
//
|
|
@@ -68,10 +68,10 @@ async function evalInPage<T>(bridge: WsBridge, code: string, timeoutMs = 30_000)
|
|
|
68
68
|
export async function runFilm(args: string[], opts: FilmOptions): Promise<number> {
|
|
69
69
|
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
70
70
|
console.log(`
|
|
71
|
-
|
|
71
|
+
peach film — record a flow flat, replay it on the 3d stage, save a clip
|
|
72
72
|
|
|
73
73
|
usage:
|
|
74
|
-
|
|
74
|
+
peach film <flow.yaml> [options]
|
|
75
75
|
|
|
76
76
|
how it works:
|
|
77
77
|
1. the flow runs at full speed on the flat canvas while the engine
|
|
@@ -82,7 +82,7 @@ how it works:
|
|
|
82
82
|
|
|
83
83
|
options:
|
|
84
84
|
--out <path> output file (.webm, or .mp4 when ffmpeg is
|
|
85
|
-
installed). default: ~/Desktop/
|
|
85
|
+
installed). default: ~/Desktop/peach-films/<flow>.webm
|
|
86
86
|
--script <id> camera script to animate during playback
|
|
87
87
|
(default: hero-arc)
|
|
88
88
|
--fps <n> stage capture framerate (default: 30)
|
|
@@ -90,16 +90,16 @@ options:
|
|
|
90
90
|
--keep-three leave 3d mode + the recording editor on afterwards
|
|
91
91
|
--sim <id> target a specific connected sim
|
|
92
92
|
|
|
93
|
-
stage decor (same flags as \`
|
|
93
|
+
stage decor (same flags as \`peach mode three configure\`):
|
|
94
94
|
--background <id> · --gradient <stops> · --gradient-angle <deg>
|
|
95
95
|
--colorway <id> · --room on|off · --focus on|off · --focus-amount <id>
|
|
96
96
|
--focus-range <id> · --environment on|off · --env-shape/spread/size/color
|
|
97
97
|
--env-glow on|off · --env-plastic on|off
|
|
98
98
|
|
|
99
99
|
examples:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
peach film flows/feed-scroll.yaml
|
|
101
|
+
peach film flows/swap.yaml --script hero-arc --background gradient-tide --out ~/Desktop/swap-3d.mp4
|
|
102
|
+
peach film flows/login.yaml --colorway deep-blue --environment on --env-shape pane --room on
|
|
103
103
|
`)
|
|
104
104
|
return 0
|
|
105
105
|
}
|
|
@@ -116,7 +116,7 @@ examples:
|
|
|
116
116
|
const flowName = flowPath.replace(/^.*\//, '').replace(/\.ya?ml$/, '')
|
|
117
117
|
const outArg = flagValue(args, '--out')
|
|
118
118
|
const outPath = resolve(
|
|
119
|
-
outArg ?? join(homedir(), 'Desktop', '
|
|
119
|
+
outArg ?? join(homedir(), 'Desktop', 'peach-films', `${flowName}.webm`),
|
|
120
120
|
)
|
|
121
121
|
const wantsMp4 = extname(outPath).toLowerCase() === '.mp4'
|
|
122
122
|
if (wantsMp4 && spawnSync('ffmpeg', ['-version']).status !== 0) {
|
package/cli/commands/flow.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// shared Maestro YAML playback and live-authoring implementation.
|
|
2
|
-
// discovers
|
|
2
|
+
// discovers a peach target when needed, then reuses the shared bridge action layer
|
|
3
3
|
|
|
4
4
|
import * as fs from 'fs'
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto'
|
|
@@ -127,7 +127,7 @@ export function findUnexpectedFlowArgs(args: string[]): UnexpectedFlowArg[] {
|
|
|
127
127
|
if (globalTakesValue !== undefined) {
|
|
128
128
|
return {
|
|
129
129
|
arg,
|
|
130
|
-
message: `${arg} is a global
|
|
130
|
+
message: `${arg} is a global peach flag and goes before the command: peach ${arg}${
|
|
131
131
|
globalTakesValue ? ' <value>' : ''
|
|
132
132
|
} <command>`,
|
|
133
133
|
}
|
|
@@ -192,7 +192,7 @@ export async function resolveAppFrontmatterTarget(
|
|
|
192
192
|
return { target: trimmed }
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
// discover
|
|
195
|
+
// discover a peach shell URL — prefer patched servers, fall back to any match.
|
|
196
196
|
// exported so `detox.ts` and `maestro.ts` can reuse the same discovery logic.
|
|
197
197
|
export async function discoverSootsimUrl(): Promise<string | null> {
|
|
198
198
|
const servers = await scanDevServers()
|
|
@@ -208,21 +208,21 @@ export async function runFlowPlayback(args: string[]): Promise<number> {
|
|
|
208
208
|
lastFlowTraceSteps = []
|
|
209
209
|
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
210
210
|
console.log(`
|
|
211
|
-
|
|
211
|
+
peach maestro — author and run Maestro YAML flows
|
|
212
212
|
|
|
213
213
|
usage:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
214
|
+
peach maestro test <flow.yaml> [options]
|
|
215
|
+
peach maestro load <flow.yaml> [options]
|
|
216
|
+
peach maestro start
|
|
217
|
+
peach maestro keep
|
|
218
|
+
peach maestro end [--output <path>] [--validate] [--video]
|
|
219
|
+
peach maestro validate <path>
|
|
220
220
|
|
|
221
221
|
options:
|
|
222
222
|
--dry-run, --no-run load flow into the runner idle without executing steps
|
|
223
223
|
--record record a webm artifact while the flow runs
|
|
224
224
|
--profile capture perf stats while the flow runs
|
|
225
|
-
--out <dir> output directory (default: /tmp/
|
|
225
|
+
--out <dir> output directory (default: /tmp/peach-recordings)
|
|
226
226
|
--device <model> override the flow frontmatter device for this run
|
|
227
227
|
--theme <scheme> override the flow frontmatter theme (light or dark) for
|
|
228
228
|
this run
|
|
@@ -233,7 +233,7 @@ options:
|
|
|
233
233
|
--url <url> load this target before running the flow
|
|
234
234
|
--base-url <url> run the flow against the shell at this URL instead of
|
|
235
235
|
the discovered one, and poll that shell's own bridge.
|
|
236
|
-
errors when no live
|
|
236
|
+
errors when no live peach world serves it, so a pinned run
|
|
237
237
|
can never quietly measure the default shell instead
|
|
238
238
|
--screenshot-paths <mode>
|
|
239
239
|
screenshot path mode: dir (default) or flow
|
|
@@ -287,15 +287,15 @@ default runtime:
|
|
|
287
287
|
recording and profiling both run on the same bridge-backed path
|
|
288
288
|
|
|
289
289
|
examples:
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
290
|
+
peach maestro test login.yaml
|
|
291
|
+
peach maestro test demo.yaml --url 8081 --record --slow 500 --out ./recordings
|
|
292
|
+
peach maestro test demo.yaml --sim a2
|
|
293
|
+
peach maestro start
|
|
294
|
+
peach maestro keep
|
|
295
|
+
peach maestro end --output ./.maestro/draft.yaml
|
|
296
|
+
peach maestro end --output ./.maestro/draft.yaml --validate
|
|
297
|
+
peach maestro end --output ./.maestro/draft.yaml --video
|
|
298
|
+
peach maestro validate ./.maestro/draft.yaml
|
|
299
299
|
|
|
300
300
|
flow extension:
|
|
301
301
|
takeScreenshot: hero
|
|
@@ -311,7 +311,7 @@ flow extension:
|
|
|
311
311
|
for (const problem of unexpected) {
|
|
312
312
|
console.error(` error: ${problem.message}`)
|
|
313
313
|
}
|
|
314
|
-
console.error('\n run `
|
|
314
|
+
console.error('\n run `peach maestro --help` for the flags this command accepts')
|
|
315
315
|
rnxExit(1)
|
|
316
316
|
}
|
|
317
317
|
|
|
@@ -331,14 +331,14 @@ flow extension:
|
|
|
331
331
|
// three identical frames on the first try instead of hunting for them.
|
|
332
332
|
const proofMode = args.includes('--proof')
|
|
333
333
|
const openInNewSim = args.includes('--new') || headed
|
|
334
|
-
const outDir = getFlag('--out') || '/tmp/
|
|
334
|
+
const outDir = getFlag('--out') || '/tmp/peach-recordings'
|
|
335
335
|
// when --screenshots isn't explicitly set, default to the --out directory.
|
|
336
336
|
// this matches what most users expect: one flag controls the whole output
|
|
337
337
|
// bundle (recording + screenshots) for a demo run.
|
|
338
|
-
// previously, screenshots silently landed in /tmp/
|
|
338
|
+
// previously, screenshots silently landed in /tmp/peach-flow even when
|
|
339
339
|
// --out was set, requiring a second --screenshots flag to redirect them.
|
|
340
340
|
const screenshotDir =
|
|
341
|
-
getFlag('--screenshots') || (getFlag('--out') ? outDir : '/tmp/
|
|
341
|
+
getFlag('--screenshots') || (getFlag('--out') ? outDir : '/tmp/peach-flow')
|
|
342
342
|
const screenshotPathMode =
|
|
343
343
|
getFlag('--screenshot-paths') === 'flow' ? 'flow' : ('dir' as const)
|
|
344
344
|
// --no-shell makes every takeScreenshot in the flow capture tenant-only
|
|
@@ -355,7 +355,7 @@ flow extension:
|
|
|
355
355
|
const deviceOverride = getFlag('--device')?.trim() || ''
|
|
356
356
|
// --device and --theme are also global flags, but a global sets the settings
|
|
357
357
|
// store inside THIS process and the sim renders in the shell's browser page,
|
|
358
|
-
// so `
|
|
358
|
+
// so `peach --theme light maestro test` reaches nothing. both have to be read
|
|
359
359
|
// here and pushed over the bridge, and they are read the same way so a caller
|
|
360
360
|
// that gets one right cannot get the other silently ignored.
|
|
361
361
|
const themeOverride = getFlag('--theme')?.trim() || ''
|
|
@@ -467,7 +467,7 @@ flow extension:
|
|
|
467
467
|
const mode = wantElectron ? 'desktop companion' : 'bridge'
|
|
468
468
|
const appLabel = targetApp ? ` | target: ${targetApp}` : ''
|
|
469
469
|
console.log(`
|
|
470
|
-
|
|
470
|
+
peach maestro — ${path.basename(flowPath)}
|
|
471
471
|
${steps.length} steps | ${mode}${appLabel}${record ? ' | recording' : ''}${profile ? ' | profiling' : ''}${slow ? ` | ${slow}ms delay` : ''}
|
|
472
472
|
`)
|
|
473
473
|
|
|
@@ -508,7 +508,7 @@ flow extension:
|
|
|
508
508
|
) {
|
|
509
509
|
const discovered = await discoverSootsimUrl()
|
|
510
510
|
if (!discovered) {
|
|
511
|
-
console.error(' error: no current sim and no
|
|
511
|
+
console.error(' error: no current sim and no peach target found')
|
|
512
512
|
rnxExit(1)
|
|
513
513
|
}
|
|
514
514
|
openTarget = discovered
|
|
@@ -566,7 +566,7 @@ flow extension:
|
|
|
566
566
|
: readCurrentSimId() || resolvedBridgeArgs.simId
|
|
567
567
|
// carry the source through so the target-sim notice reads `via --sim`
|
|
568
568
|
// (not the misleading `primary fallback — no sim pinned`) when the flow
|
|
569
|
-
// was actually pinned with --sim or a `
|
|
569
|
+
// was actually pinned with --sim or a `peach use` selection.
|
|
570
570
|
const pinnedSimIdSource: typeof resolvedBridgeArgs.simIdSource =
|
|
571
571
|
resolvedBridgeArgs.simIdSource === 'flag' ? 'flag' : pinnedSimId ? 'saved' : 'none'
|
|
572
572
|
const bridge = createBridge(resolvedBridgeArgs.wsPort, {
|
|
@@ -580,10 +580,10 @@ flow extension:
|
|
|
580
580
|
})
|
|
581
581
|
let driver: SootSimBridgeFlowRunner
|
|
582
582
|
const applySimSettings = async () => {
|
|
583
|
-
const statusBarTime = frontmatter.env?.
|
|
583
|
+
const statusBarTime = frontmatter.env?.PEACH_STATUS_BAR_TIME
|
|
584
584
|
if (statusBarTime) {
|
|
585
585
|
if (!/^(?:[1-9]|1[0-2]):[0-5][0-9]$/.test(statusBarTime)) {
|
|
586
|
-
throw new Error('
|
|
586
|
+
throw new Error('PEACH_STATUS_BAR_TIME must be a 12-hour time such as 9:41')
|
|
587
587
|
}
|
|
588
588
|
await bridge.send({
|
|
589
589
|
type: 'evaluate',
|
|
@@ -623,7 +623,7 @@ flow extension:
|
|
|
623
623
|
billingOriginOverride: preview ? previewOrigin : undefined,
|
|
624
624
|
// any recording reached via a github installation token is a ci /
|
|
625
625
|
// pr-preview run, so allow it for preview uploads. local flow recording
|
|
626
|
-
// writes a file and matches `
|
|
626
|
+
// writes a file and matches `peach record --mode video`.
|
|
627
627
|
allowGitHubRecording: preview,
|
|
628
628
|
// a test_run bills its recording from balance at finalize (preflighted
|
|
629
629
|
// at upload init), not from a plan entitlement: the plan gate would
|
|
@@ -698,8 +698,8 @@ flow extension:
|
|
|
698
698
|
// here is redundant and triggers a sim-id rotation on the reload that
|
|
699
699
|
// routes the flow to a stale sim, hanging step 1 ("made no progress …
|
|
700
700
|
// bridge or bundle probably hung"). one reset is enough — the bot
|
|
701
|
-
// signals it did one via
|
|
702
|
-
if (preview && !startsWithLaunchApp && !process.env.
|
|
701
|
+
// signals it did one via PEACH_PREVIEW_RESET_DONE.
|
|
702
|
+
if (preview && !startsWithLaunchApp && !process.env.PEACH_PREVIEW_RESET_DONE) {
|
|
703
703
|
await driver.launchApp({ clearState: true })
|
|
704
704
|
}
|
|
705
705
|
if (dryRun) {
|
|
@@ -863,9 +863,9 @@ flow extension:
|
|
|
863
863
|
)
|
|
864
864
|
}
|
|
865
865
|
await printBridgeFailureDiagnostics(bridge, {
|
|
866
|
-
errorsCommand: `
|
|
867
|
-
warningsCommand: `
|
|
868
|
-
requestsCommand: `
|
|
866
|
+
errorsCommand: `peach get errors 5${driver.simId ? ` --sim ${driver.simId}` : ''}`,
|
|
867
|
+
warningsCommand: `peach get warnings 5${driver.simId ? ` --sim ${driver.simId}` : ''}`,
|
|
868
|
+
requestsCommand: `peach get requests 5${driver.simId ? ` --sim ${driver.simId}` : ''}`,
|
|
869
869
|
})
|
|
870
870
|
exitCode = 1
|
|
871
871
|
}
|
|
@@ -992,7 +992,7 @@ flow extension:
|
|
|
992
992
|
} else if (driver.simId && driver.simId !== pinnedSimId) {
|
|
993
993
|
// `launchApp clearState` rotates the sim id mid-flow (the shell reconnects
|
|
994
994
|
// under a fresh id). the runner tracks that in-memory, but a directory run
|
|
995
|
-
// (`
|
|
995
|
+
// (`peach maestro test <dir>`) spawns the next flow fresh — it re-reads
|
|
996
996
|
// the saved sim id, which would still point at the pre-rotation sim and
|
|
997
997
|
// fail with "sim not responding / stale orphan". persist the rotated id so
|
|
998
998
|
// the next flow reuses the live sim. only when we're NOT closing it.
|
|
@@ -1044,7 +1044,7 @@ async function prepareEventsFile(events: Array<Record<string, any>>): Promise<st
|
|
|
1044
1044
|
const lines =
|
|
1045
1045
|
events.map((e) => JSON.stringify(e)).join('\n') + (events.length ? '\n' : '')
|
|
1046
1046
|
const gz = gzipSync(Buffer.from(lines, 'utf8'))
|
|
1047
|
-
const outPath = path.join(tmpdir(), `
|
|
1047
|
+
const outPath = path.join(tmpdir(), `peach-events-${Date.now()}.jsonl.gz`)
|
|
1048
1048
|
fs.writeFileSync(outPath, gz)
|
|
1049
1049
|
console.log(` events: ${events.length} written to ${outPath} (${gz.length} bytes gz)`)
|
|
1050
1050
|
return outPath
|
|
@@ -1075,7 +1075,7 @@ async function prepareFlowStepTimelineFile(
|
|
|
1075
1075
|
}))
|
|
1076
1076
|
const lines = events.map((event) => JSON.stringify(event)).join('\n') + '\n'
|
|
1077
1077
|
const gz = gzipSync(Buffer.from(lines, 'utf8'))
|
|
1078
|
-
const outPath = path.join(tmpdir(), `
|
|
1078
|
+
const outPath = path.join(tmpdir(), `peach-flow-steps-${Date.now()}.jsonl.gz`)
|
|
1079
1079
|
fs.writeFileSync(outPath, gz)
|
|
1080
1080
|
console.log(
|
|
1081
1081
|
` flow trace: ${events.length} step events written to ${outPath} (${gz.length} bytes gz)`,
|
|
@@ -1102,14 +1102,14 @@ async function prepareFlowSnapshotManifest(
|
|
|
1102
1102
|
: [],
|
|
1103
1103
|
)
|
|
1104
1104
|
if (snapshots.length === 0) return null
|
|
1105
|
-
const outPath = path.join(tmpdir(), `
|
|
1105
|
+
const outPath = path.join(tmpdir(), `peach-flow-snapshots-${Date.now()}.json`)
|
|
1106
1106
|
fs.writeFileSync(outPath, JSON.stringify(snapshots))
|
|
1107
1107
|
console.log(` flow snapshots: ${snapshots.length} written to ${outPath}`)
|
|
1108
1108
|
return outPath
|
|
1109
1109
|
}
|
|
1110
1110
|
|
|
1111
1111
|
async function prepareFrameStatsFile(frameStats: unknown): Promise<string> {
|
|
1112
|
-
const outPath = path.join(tmpdir(), `
|
|
1112
|
+
const outPath = path.join(tmpdir(), `peach-frame-stats-${Date.now()}.json`)
|
|
1113
1113
|
fs.writeFileSync(outPath, JSON.stringify(frameStats))
|
|
1114
1114
|
console.log(` frame stats: written to ${outPath}`)
|
|
1115
1115
|
return outPath
|
|
@@ -1131,7 +1131,7 @@ async function runFlowSession(args: string[]) {
|
|
|
1131
1131
|
case 'keep': {
|
|
1132
1132
|
const result = keepFlowCandidate()
|
|
1133
1133
|
if (!result.active) {
|
|
1134
|
-
console.error(' no active flow draft — run `
|
|
1134
|
+
console.error(' no active flow draft — run `peach maestro start` first')
|
|
1135
1135
|
rnxExit(1)
|
|
1136
1136
|
}
|
|
1137
1137
|
if (!result.kept) {
|
|
@@ -1160,14 +1160,14 @@ async function runFlowSession(args: string[]) {
|
|
|
1160
1160
|
outputPath || (validate ? createDraftValidationPath() : undefined),
|
|
1161
1161
|
)
|
|
1162
1162
|
if (!result.active) {
|
|
1163
|
-
console.error(' no active flow draft — run `
|
|
1163
|
+
console.error(' no active flow draft — run `peach maestro start` first')
|
|
1164
1164
|
rnxExit(1)
|
|
1165
1165
|
}
|
|
1166
1166
|
if (!result.valid) {
|
|
1167
1167
|
console.error(' flow draft is not valid:')
|
|
1168
1168
|
for (const issue of result.issues) console.error(` - ${issue}`)
|
|
1169
1169
|
console.error(
|
|
1170
|
-
' draft preserved — keep at least one real interaction or run `
|
|
1170
|
+
' draft preserved — keep at least one real interaction or run `peach maestro start` to reset',
|
|
1171
1171
|
)
|
|
1172
1172
|
rnxExit(1)
|
|
1173
1173
|
}
|
|
@@ -1202,7 +1202,7 @@ async function runFlowSession(args: string[]) {
|
|
|
1202
1202
|
)
|
|
1203
1203
|
if (result.outputPath) {
|
|
1204
1204
|
console.log(` saved: ${result.outputPath}`)
|
|
1205
|
-
console.log(` next:
|
|
1205
|
+
console.log(` next: peach maestro test ${result.outputPath} --record`)
|
|
1206
1206
|
return
|
|
1207
1207
|
}
|
|
1208
1208
|
console.log('')
|
|
@@ -1213,7 +1213,7 @@ async function runFlowSession(args: string[]) {
|
|
|
1213
1213
|
case 'validate': {
|
|
1214
1214
|
const flowPath = args[1]
|
|
1215
1215
|
if (!flowPath || flowPath.startsWith('-')) {
|
|
1216
|
-
console.error(' usage:
|
|
1216
|
+
console.error(' usage: peach maestro validate <path>')
|
|
1217
1217
|
rnxExit(1)
|
|
1218
1218
|
}
|
|
1219
1219
|
const issues = validateFlowFile(flowPath)
|
|
@@ -1230,7 +1230,7 @@ async function runFlowSession(args: string[]) {
|
|
|
1230
1230
|
|
|
1231
1231
|
// the subcommand / flow-file path is the first positional arg, but a
|
|
1232
1232
|
// `--sim <id>` pair can sit in front of it: bin.ts prepends the global
|
|
1233
|
-
// `--sim` to every command's args, and `
|
|
1233
|
+
// `--sim` to every command's args, and `peach maestro --sim a2 start` is a
|
|
1234
1234
|
// documented form. hoist a leading `--sim <value>` to the end so `args[0]`
|
|
1235
1235
|
// is the real subcommand again (downstream getFlag / parseBridgeCliArgs
|
|
1236
1236
|
// still find `--sim` wherever it lands). without this, `--sim 6a5 flow
|
|
@@ -1268,7 +1268,7 @@ function printFlowProfile(result: SootSimFlowProfileResult) {
|
|
|
1268
1268
|
printShellPerfReport(result)
|
|
1269
1269
|
}
|
|
1270
1270
|
function createDraftValidationPath() {
|
|
1271
|
-
return path.join(tmpdir(), `
|
|
1271
|
+
return path.join(tmpdir(), `peach-flow-draft-${Date.now()}.yaml`)
|
|
1272
1272
|
}
|
|
1273
1273
|
|
|
1274
1274
|
function buildFlowValidationArgs(args: string[], outputPath: string | null) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// transport-agnostic
|
|
1
|
+
// transport-agnostic peach inspect-verb kernels.
|
|
2
2
|
//
|
|
3
3
|
// each function here sends the smallest bridge command its verb needs and
|
|
4
4
|
// returns *structured data* — no arg parsing or stdout. the
|
|
@@ -54,7 +54,7 @@ export function resolveMaxMsFlag(args: string[], fallbackMs: number): number {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// the minimal bridge an inspect kernel needs: a JSON command channel into a
|
|
57
|
-
//
|
|
57
|
+
// peach instance.
|
|
58
58
|
export interface InspectBridge {
|
|
59
59
|
readonly plane?: 'local' | 'cloud'
|
|
60
60
|
send(cmd: WsBridgeCommand, opts?: { timeoutMs?: number }): Promise<any>
|
|
@@ -289,7 +289,7 @@ export async function inspectDescribe(
|
|
|
289
289
|
// fields with `styling:true` / `--styling`.
|
|
290
290
|
//
|
|
291
291
|
// the SAME LayoutElement shape is produced by both preview surfaces:
|
|
292
|
-
// - native/
|
|
292
|
+
// - native/peach — `LAYOUT_GET_NATIVE_EVAL` reads the engine node tree via
|
|
293
293
|
// __sootsimTest.listInspectable() (the source `describe` uses), where each
|
|
294
294
|
// SootSimInspectInfo already carries absolute layout and optional style /
|
|
295
295
|
// box-model data.
|
|
@@ -662,7 +662,7 @@ export function layoutGetNativeEval(opts: LayoutExtractionOptions = {}): string
|
|
|
662
662
|
|
|
663
663
|
const screenshot = window.SootSim && window.SootSim.bridges && window.SootSim.bridges.screenshot
|
|
664
664
|
if (typeof screenshot !== 'function') {
|
|
665
|
-
throw new Error('styled native layout requires the
|
|
665
|
+
throw new Error('styled native layout requires the peach capture bridge')
|
|
666
666
|
}
|
|
667
667
|
const device = list.find((node) => node && node.device)?.device || {}
|
|
668
668
|
const width = Math.round(Number(device.width) || window.innerWidth || 0)
|
|
@@ -707,7 +707,7 @@ export function layoutGetNativeEval(opts: LayoutExtractionOptions = {}): string
|
|
|
707
707
|
export const LAYOUT_GET_NATIVE_EVAL = layoutGetNativeEval()
|
|
708
708
|
|
|
709
709
|
// `get-layout` (native) — bounding boxes for visible meaningful elements on the
|
|
710
|
-
//
|
|
710
|
+
// peach/native preview. transport-agnostic: the CLI runs it over its WS
|
|
711
711
|
// bridge, the agent runs it over its in-process preview bridge.
|
|
712
712
|
export async function inspectGetLayout(
|
|
713
713
|
bridge: InspectBridge,
|
|
@@ -981,16 +981,16 @@ export function scoreInteractive(n: InteractiveNode): number {
|
|
|
981
981
|
return score
|
|
982
982
|
}
|
|
983
983
|
|
|
984
|
-
// the copy-paste `
|
|
984
|
+
// the copy-paste `peach do` tap command for an interactive node.
|
|
985
985
|
export function tapCommandForNode(n: InteractiveNode): string {
|
|
986
|
-
if (n.testID) return `
|
|
986
|
+
if (n.testID) return `peach do tap-id ${shellEscape(n.testID)}`
|
|
987
987
|
const text = typeof n.text === 'string' ? n.text.trim() : ''
|
|
988
|
-
if (text.length > 0 && text.length <= 80) return `
|
|
988
|
+
if (text.length > 0 && text.length <= 80) return `peach do tap-text ${shellEscape(text)}`
|
|
989
989
|
const x =
|
|
990
990
|
Math.round(((n.absolutePosition?.x ?? 0) + (n.layout?.width ?? 0) / 2) * 10) / 10
|
|
991
991
|
const y =
|
|
992
992
|
Math.round(((n.absolutePosition?.y ?? 0) + (n.layout?.height ?? 0) / 2) * 10) / 10
|
|
993
|
-
return `
|
|
993
|
+
return `peach do tap ${x} ${y}`
|
|
994
994
|
}
|
|
995
995
|
|
|
996
996
|
function shellEscape(value: string): string {
|
|
@@ -1045,7 +1045,7 @@ export function waitReadyReason(
|
|
|
1045
1045
|
) {
|
|
1046
1046
|
return status.bridgeError
|
|
1047
1047
|
}
|
|
1048
|
-
return `sim unreachable: ${status.bridgeError} — the target tab is closed or no sim is connected (check \`
|
|
1048
|
+
return `sim unreachable: ${status.bridgeError} — the target tab is closed or no sim is connected (check \`peach list\`, reopen with \`peach open\`)`
|
|
1049
1049
|
}
|
|
1050
1050
|
const waitingOnGuestFetch =
|
|
1051
1051
|
status.externalReady === false && /https?:\/\//.test(status.externalStatus)
|
|
@@ -1417,7 +1417,7 @@ export async function inspectLogs(bridge: InspectBridge): Promise<LogEntry[]> {
|
|
|
1417
1417
|
return [
|
|
1418
1418
|
{
|
|
1419
1419
|
id: `cloud-${ts}-${index}`,
|
|
1420
|
-
source: '
|
|
1420
|
+
source: 'peach-cloud',
|
|
1421
1421
|
level,
|
|
1422
1422
|
ts,
|
|
1423
1423
|
args: [text],
|
|
@@ -1477,7 +1477,7 @@ export function filterLogEntries(
|
|
|
1477
1477
|
out = out.filter((e) => {
|
|
1478
1478
|
const first = e.args[0]
|
|
1479
1479
|
return !(
|
|
1480
|
-
typeof first === 'string' && /^\[(?:
|
|
1480
|
+
typeof first === 'string' && /^\[(?:peach|sootsim)(?:\s[^\]]*)?\]/.test(first)
|
|
1481
1481
|
)
|
|
1482
1482
|
})
|
|
1483
1483
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// budget and log more aggressively, because an agent can't see mid-animation
|
|
4
4
|
// flicker the way a human can.
|
|
5
5
|
const AGENT_ENV_VARS = [
|
|
6
|
-
'
|
|
6
|
+
'PEACH_AGENT',
|
|
7
7
|
'TM_SESSION',
|
|
8
8
|
'CLAUDECODE',
|
|
9
9
|
'CLAUDE_CODE_ENTRYPOINT',
|
|
@@ -16,7 +16,7 @@ const AGENT_ENV_VARS = [
|
|
|
16
16
|
export function isAgentEnv(
|
|
17
17
|
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
18
18
|
): boolean {
|
|
19
|
-
if (env.
|
|
19
|
+
if (env.PEACH_AGENT === '0') return false
|
|
20
20
|
for (const name of AGENT_ENV_VARS) {
|
|
21
21
|
const v = env[name]
|
|
22
22
|
if (v && v.trim() && v !== '0') return true
|