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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: peach-test
|
|
3
3
|
description: Run automated tests against your React Native app under Peach with Maestro YAML or Detox/Jest
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -9,10 +9,10 @@ Peach has two test runners. Maestro owns every YAML test, whether it was
|
|
|
9
9
|
hand-authored, generated from a goal, or recorded from live CLI actions.
|
|
10
10
|
Detox runs existing imperative Jest suites through the Peach driver.
|
|
11
11
|
|
|
12
|
-
> needs a connected, pinned sim. if `
|
|
13
|
-
> `/
|
|
12
|
+
> needs a connected, pinned sim. if `peach describe` errors, load
|
|
13
|
+
> `/peach-setup` first.
|
|
14
14
|
|
|
15
|
-
Set `
|
|
15
|
+
Set `PEACH_NO_OPEN=1` before starting an app dev server for a Detox,
|
|
16
16
|
Maestro, or iOS-only pass. Apps can opt into a named Electron window through
|
|
17
17
|
the official Vite or Metro plugin, and test-only work must not open that window.
|
|
18
18
|
|
|
@@ -37,15 +37,15 @@ Do not invent a third runner for how a Maestro flow was authored.
|
|
|
37
37
|
implementation and author flows against it.
|
|
38
38
|
- **pin a sim before running a suite.** parallel CI jobs colliding
|
|
39
39
|
on the default bridge port is the #1 flake source. pass `--port` in
|
|
40
|
-
CI and `
|
|
40
|
+
CI and `peach use <id>` locally.
|
|
41
41
|
- **every flow ends with an assert, not a screenshot.** a screenshot
|
|
42
42
|
passes as long as the flow didn't throw. `assertVisible` (or
|
|
43
43
|
`expect(...).toBeVisible()`) actually verifies the happy path.
|
|
44
44
|
- **don't run a 40-step "smoke everything" flow.** when it fails, you
|
|
45
45
|
can't tell what regressed. split into `login.yaml`, `compose.yaml`,
|
|
46
46
|
`settings.yaml`, chain with `runFlow`.
|
|
47
|
-
- **inside an `
|
|
48
|
-
chain, use `
|
|
47
|
+
- **inside an `peach do` chain, use `sleep` only for gesture timing.** after the
|
|
48
|
+
chain, use `peach wait` for app state so automation advances on a real condition.
|
|
49
49
|
|
|
50
50
|
## you're done when
|
|
51
51
|
|
|
@@ -62,8 +62,8 @@ batch. use a chain for a short repro, form fill, or timing-sensitive gesture
|
|
|
62
62
|
that does not belong in a saved test:
|
|
63
63
|
|
|
64
64
|
```sh
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
peach do tap email --then type user@example.com --then tap submit
|
|
66
|
+
peach wait selector dashboard
|
|
67
67
|
```
|
|
68
68
|
|
|
69
69
|
the batch stops on the first failure. add `--json` for per-action results.
|
|
@@ -79,23 +79,23 @@ Write declarative Maestro YAML. The same command runs existing suites,
|
|
|
79
79
|
generates tests, records live drafts, profiles, and uploads replayable results.
|
|
80
80
|
|
|
81
81
|
```sh
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
peach maestro test .maestro/login.yaml # run one flow
|
|
83
|
+
peach maestro test .maestro/login.yaml --record # record video
|
|
84
|
+
peach maestro test .maestro/login.yaml --profile # include perf stats
|
|
85
|
+
peach maestro test .maestro/ # run the suite
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
Author from a live interaction:
|
|
89
89
|
|
|
90
90
|
```sh
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
peach maestro start # begin a draft
|
|
92
|
+
peach do tap-id loginButton # drive the app via the CLI
|
|
93
|
+
peach maestro keep # save the step
|
|
94
|
+
peach do type "user@example.com"
|
|
95
|
+
peach maestro keep
|
|
96
|
+
peach do tap-id submit
|
|
97
|
+
peach maestro keep
|
|
98
|
+
peach maestro end --output .maestro/login.yaml --validate
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
the recorder emits minimal YAML — no coordinate dumps, no `sleep` noise —
|
|
@@ -129,7 +129,7 @@ exemplar flow:
|
|
|
129
129
|
run with env interpolation:
|
|
130
130
|
|
|
131
131
|
```sh
|
|
132
|
-
|
|
132
|
+
peach maestro --env USERNAME=alice --env PASSWORD=hunter2 \
|
|
133
133
|
test .maestro/login.yaml \
|
|
134
134
|
--record --profile
|
|
135
135
|
```
|
|
@@ -144,7 +144,7 @@ CLI's `wait idle` with a generous budget. use it before any
|
|
|
144
144
|
Generate and immediately run a Maestro flow from a plain-language goal:
|
|
145
145
|
|
|
146
146
|
```sh
|
|
147
|
-
|
|
147
|
+
peach maestro generate "log in with the demo account and verify the dashboard"
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
Generation writes the same deterministic Maestro artifact, uploads its replay,
|
|
@@ -161,19 +161,19 @@ command change:
|
|
|
161
161
|
maestro test .maestro/login.yaml
|
|
162
162
|
|
|
163
163
|
# after
|
|
164
|
-
|
|
164
|
+
peach maestro test .maestro/login.yaml
|
|
165
165
|
```
|
|
166
166
|
|
|
167
167
|
no test rewrites, no detox config, no simulator. the runtime auto-launches
|
|
168
168
|
a Peach shell if one isn't running.
|
|
169
169
|
|
|
170
170
|
```sh
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
peach maestro # auto-discover .maestro/ or maestro/
|
|
172
|
+
peach maestro test .maestro/login.yaml
|
|
173
|
+
peach maestro test .maestro/ # every flow in a directory
|
|
174
|
+
peach maestro init # scaffold a starter flow
|
|
175
|
+
peach maestro --list-compat # supported / partial / unsupported
|
|
176
|
+
peach maestro --env USERNAME=alice test .maestro/login.yaml
|
|
177
177
|
```
|
|
178
178
|
|
|
179
179
|
### supported verbs
|
|
@@ -211,7 +211,7 @@ these throw `unsupported maestro verb: X`:
|
|
|
211
211
|
### troubleshooting
|
|
212
212
|
|
|
213
213
|
- **"no maestro flows found"** — pass an explicit path
|
|
214
|
-
(`
|
|
214
|
+
(`peach maestro test path/to/flow.yaml`) or run `peach maestro
|
|
215
215
|
init` to scaffold one.
|
|
216
216
|
- **"missing environment variable for flow placeholder: FOO"** — pass
|
|
217
217
|
`--env FOO=bar` or export it. pick one mechanism per repo and document
|
|
@@ -229,7 +229,7 @@ an existing detox suite runs against Peach with **one line** of jest
|
|
|
229
229
|
config and zero test changes.
|
|
230
230
|
|
|
231
231
|
`import { by, device, element, expect, waitFor } from 'detox'` is
|
|
232
|
-
rewritten by jest to
|
|
232
|
+
rewritten by jest to a peach-backed driver that drives the real shell
|
|
233
233
|
over playwright. matchers, assertions, and the `waitFor` API match the
|
|
234
234
|
upstream detox surface.
|
|
235
235
|
|
|
@@ -259,14 +259,14 @@ works until your first hung shell.
|
|
|
259
259
|
### commands
|
|
260
260
|
|
|
261
261
|
```sh
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
262
|
+
peach detox # run e2e/, test/e2e/, or detox/
|
|
263
|
+
peach detox --config my.cjs # use a specific jest config
|
|
264
|
+
peach detox --watch # jest watch mode
|
|
265
|
+
peach detox -t "login" # --testNamePattern
|
|
266
|
+
peach detox --headed # keep the shell window visible
|
|
267
|
+
peach detox --no-launch # don't auto-launch a shell
|
|
268
|
+
peach detox --port 5173 # explicit bridge port for CI
|
|
269
|
+
peach detox init # scaffold config + e2e/example.test.ts
|
|
270
270
|
```
|
|
271
271
|
|
|
272
272
|
### compat surface
|
|
@@ -292,12 +292,12 @@ rnx detox init # scaffold config + e2e/example.test.ts
|
|
|
292
292
|
### troubleshooting
|
|
293
293
|
|
|
294
294
|
- **"no Peach shell reachable"** — start one yourself
|
|
295
|
-
(`
|
|
295
|
+
(`peach open <port>`) or pass `--port <n>`.
|
|
296
296
|
- **"jest cannot find module 'detox'"** — the preset isn't wired. confirm
|
|
297
297
|
`preset: 'peachsim/detox'` is in the jest config jest
|
|
298
298
|
actually loaded (`--config` overrides package.json).
|
|
299
299
|
- **matchers fail on elements that exist** — canvas has no DOM. use
|
|
300
|
-
`by.id` (testID); `
|
|
300
|
+
`by.id` (testID); `peach describe` shows what the shell sees.
|
|
301
301
|
- **passes locally, flakes in CI** — the shell boots asynchronously.
|
|
302
302
|
bump jest `testTimeout` to 10–15 s and add a `waitFor(element(...))
|
|
303
303
|
.toBeVisible().withTimeout(10000)` before the first assertion.
|
|
@@ -315,7 +315,7 @@ rnx detox init # scaffold config + e2e/example.test.ts
|
|
|
315
315
|
the first assertion, pin the bridge port, and run with `--video` so
|
|
316
316
|
failing Maestro runs land a recording next to the output.
|
|
317
317
|
- **a flake reproduces but resists fixing** — don't paper over with
|
|
318
|
-
`sleep`. open `/
|
|
319
|
-
the timeline (`
|
|
318
|
+
`sleep`. open `/peach-debug` against the same sim, capture
|
|
319
|
+
the timeline (`peach timeline start` → reproduce → `peach
|
|
320
320
|
what-happened`), and look for the missing animation-completion or
|
|
321
321
|
the unresolved fetch that's racing the assertion.
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: peach-visual
|
|
3
3
|
description: Pixel-level rendering comparison between Peach and native iOS — fonts, gradients, shadows, image fidelity
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Peach visual fidelity
|
|
7
7
|
|
|
8
8
|
pixel-level rendering fidelity: does the Peach canvas match native iOS
|
|
9
|
-
for text, gradients, shadows, blur, and image rendering? the `
|
|
10
|
-
CLI gives you two primitives — **canvas capture** (`
|
|
11
|
-
and **structural diff** (`
|
|
9
|
+
for text, gradients, shadows, blur, and image rendering? the `peach`
|
|
10
|
+
CLI gives you two primitives — **canvas capture** (`peach screenshot`)
|
|
11
|
+
and **structural diff** (`peach debug snapshot`/`diff`). pixel
|
|
12
12
|
comparison itself is "capture both sides, diff the PNGs with a standard
|
|
13
13
|
image tool" — there is no single oracle-diff command, and that's fine:
|
|
14
14
|
the capture step is the hard part and it's fully reproducible.
|
|
15
15
|
|
|
16
|
-
> needs a connected, pinned sim. if `
|
|
17
|
-
> load `/
|
|
16
|
+
> needs a connected, pinned sim. if `peach describe` errors,
|
|
17
|
+
> load `/peach-setup` first.
|
|
18
18
|
|
|
19
19
|
## route in
|
|
20
20
|
|
|
21
21
|
- structural change ("which nodes / which props changed?") → load
|
|
22
|
-
`/
|
|
22
|
+
`/peach-debug`, debugging branch — `debug snapshot` + `debug
|
|
23
23
|
diff` is faster, deterministic, and tells you *why*.
|
|
24
24
|
- visual change (color, gradient, shadow, font metric, blur, image)? →
|
|
25
25
|
continue here.
|
|
@@ -28,36 +28,36 @@ the capture step is the hard part and it's fully reproducible.
|
|
|
28
28
|
|
|
29
29
|
## anti-patterns (read first)
|
|
30
30
|
|
|
31
|
-
- **run structural diff before pixel work.** `
|
|
32
|
-
`debug diff` (in `/
|
|
31
|
+
- **run structural diff before pixel work.** `peach debug snapshot` /
|
|
32
|
+
`debug diff` (in `/peach-debug`) is cheaper, deterministic, and
|
|
33
33
|
points at the responsible nodes. only when it's empty (or the change is
|
|
34
34
|
pure rendering) does a pixel comparison add information.
|
|
35
35
|
- **don't single-screenshot animated surfaces.** a gradient
|
|
36
|
-
mid-animation never matches. gate the capture behind `
|
|
36
|
+
mid-animation never matches. gate the capture behind `peach wait
|
|
37
37
|
idle` first.
|
|
38
38
|
- **a > 5% pixel threshold hides regressions.** ~1% is the right
|
|
39
39
|
ballpark for catching real font / shadow / gradient drift without
|
|
40
40
|
flagging anti-alias noise. go higher only with a written reason.
|
|
41
41
|
- **pin device and theme.** capture both sides on the same simulated
|
|
42
42
|
device and appearance. capture each appearance separately with
|
|
43
|
-
`
|
|
43
|
+
`peach screenshot` after setting it on both simulators.
|
|
44
44
|
- **commit baselines alongside the code change that produced them.**
|
|
45
45
|
baseline churn without code churn means someone captured noise.
|
|
46
46
|
|
|
47
|
-
## capturing — `
|
|
47
|
+
## capturing — `peach screenshot`
|
|
48
48
|
|
|
49
49
|
fast, no extra setup, works on any loaded app:
|
|
50
50
|
|
|
51
51
|
```sh
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
peach screenshot --output app.png # full canvas
|
|
53
|
+
peach screenshot --area 0,200,393,400 # crop to a logical rect
|
|
54
|
+
peach screenshot --id loginButton -o btn.png # crop to a node by testID
|
|
55
|
+
peach screenshot --text "Sign in" # crop to a node by text
|
|
56
|
+
peach screenshot --no-shell -o tenant.png # tenant surface only, no iOS chrome
|
|
57
|
+
peach screenshot --shell-only # only the simulated iOS chrome
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
`
|
|
60
|
+
`peach screenshot` captures the current screen as a PNG. navigate to each
|
|
61
61
|
screen you want to inspect and capture it with a distinct output filename.
|
|
62
62
|
|
|
63
63
|
## comparing pixels
|
|
@@ -67,9 +67,9 @@ capture both sides to PNGs, then diff with any standard image tool.
|
|
|
67
67
|
**Peach vs Peach (regression across your own commits):**
|
|
68
68
|
|
|
69
69
|
```sh
|
|
70
|
-
|
|
70
|
+
peach screenshot -o before.png # baseline, before your change
|
|
71
71
|
# … make the rendering change, let the sim hot-reload …
|
|
72
|
-
|
|
72
|
+
peach screenshot -o after.png # after your change
|
|
73
73
|
npx pixelmatch before.png after.png diff.png 0.1 # writes diff.png
|
|
74
74
|
# or: magick compare before.png after.png diff.png
|
|
75
75
|
```
|
|
@@ -80,9 +80,9 @@ npx pixelmatch before.png after.png diff.png 0.1 # writes diff.png
|
|
|
80
80
|
# native side: lifecycle and UI use xcodebuildmcp with one claimed simulator
|
|
81
81
|
# lossless capture names that simulator explicitly; never use the booted alias
|
|
82
82
|
xcrun simctl io <udid> screenshot --type=png native.png
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
npx pixelmatch native.png
|
|
83
|
+
# peach side: same screen, same appearance
|
|
84
|
+
peach screenshot -o peach.png
|
|
85
|
+
npx pixelmatch native.png peach.png diff.png 0.1
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
`xcodebuildmcp simulator screenshot` may return an optimized JPEG even when the
|
|
@@ -97,7 +97,7 @@ under unrelated chrome.
|
|
|
97
97
|
## typical workflow
|
|
98
98
|
|
|
99
99
|
1. structural diff first — confirm the change is genuinely visual.
|
|
100
|
-
2. capture the affected screen with `
|
|
100
|
+
2. capture the affected screen with `peach screenshot` (crop to the
|
|
101
101
|
element if you can).
|
|
102
102
|
3. diff against the baseline (previous commit, or a native capture).
|
|
103
103
|
4. iterate the rendering until the diff is under threshold.
|
|
@@ -114,7 +114,7 @@ under unrelated chrome.
|
|
|
114
114
|
## recovery — common failure modes
|
|
115
115
|
|
|
116
116
|
- **a single icon/glyph diffs** — font-load race or image not yet
|
|
117
|
-
cached. recapture after `
|
|
117
|
+
cached. recapture after `peach wait idle`, or navigate away and back
|
|
118
118
|
to seed the cache.
|
|
119
119
|
- **everything diffs by a constant offset** — you captured the two sides
|
|
120
120
|
on different devices or appearances. pin both, recapture.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// node-side client for the daemon's agent:* ws protocol.
|
|
2
2
|
//
|
|
3
3
|
// used by electron main (translates IPC → daemon) and by the CLI
|
|
4
|
-
// `
|
|
4
|
+
// `peach agent …` subcommands. the browser shell has its own shim that
|
|
5
5
|
// uses window.WebSocket; this file is node-only.
|
|
6
6
|
//
|
|
7
7
|
// single long-lived connection per client. if the daemon goes away, the
|
|
@@ -105,7 +105,7 @@ export class AgentDaemonClient {
|
|
|
105
105
|
this.ws.off('open', onOpen)
|
|
106
106
|
reject(
|
|
107
107
|
new AgentDaemonError(
|
|
108
|
-
`could not connect to
|
|
108
|
+
`could not connect to peach daemon on port ${this.port}: ${err.message}`,
|
|
109
109
|
'NO_DAEMON',
|
|
110
110
|
),
|
|
111
111
|
)
|
|
@@ -361,7 +361,7 @@ export async function ensureDaemonRunning(
|
|
|
361
361
|
}
|
|
362
362
|
if (shouldSkipPersistentDaemon()) {
|
|
363
363
|
throw new AgentDaemonError(
|
|
364
|
-
`no
|
|
364
|
+
`no peach bridge on port ${port}. run \`peach daemon install\` to enable the faster background service, or \`peach serve\` in another shell.`,
|
|
365
365
|
'DEV_HOST_REFUSED',
|
|
366
366
|
)
|
|
367
367
|
}
|
|
@@ -385,8 +385,8 @@ export async function ensureDaemonRunning(
|
|
|
385
385
|
await new Promise((r) => setTimeout(r, 100))
|
|
386
386
|
}
|
|
387
387
|
throw new AgentDaemonError(
|
|
388
|
-
`spawned
|
|
389
|
-
`run \`
|
|
388
|
+
`spawned peach daemon on port ${port} but it did not come up in time. ` +
|
|
389
|
+
`run \`peach serve\` manually to diagnose.`,
|
|
390
390
|
'SPAWN_TIMEOUT',
|
|
391
391
|
)
|
|
392
392
|
}
|
package/src/agent-events.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// wire format emitted by the agent-wrapper CLI subcommand on the events.out
|
|
2
2
|
// FIFO. every line on the FIFO is exactly one JSON object — parse line-by-line.
|
|
3
3
|
//
|
|
4
|
-
// this file is the source of truth for the
|
|
4
|
+
// this file is the source of truth for the peach ↔ agent channel. it's
|
|
5
5
|
// imported by electron main (cockpit bridge), the CLI watch/transcript tools,
|
|
6
6
|
// and the wrapper implementation itself, so it must stay runtime-safe (no
|
|
7
7
|
// node-specific or electron-specific imports).
|
package/src/agent-identity.ts
CHANGED
|
@@ -6,7 +6,7 @@ export function agentSessionIdFromCliIdentity(identity: string | null): string |
|
|
|
6
6
|
if (colon < 0) return identity.trim() || null
|
|
7
7
|
const source = identity.slice(0, colon)
|
|
8
8
|
const value = identity.slice(colon + 1).trim()
|
|
9
|
-
return source === '
|
|
9
|
+
return source === 'PEACH_CLI_IDENTITY' && value ? value : null
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export function teamMachineSessionIdFromCliIdentity(
|
package/src/agent-sessions.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// session lifecycle shared between the CLI (`
|
|
2
|
-
// main (IPC handlers). both contexts spawn the same `
|
|
1
|
+
// session lifecycle shared between the CLI (`peach agent ...`) and electron
|
|
2
|
+
// main (IPC handlers). both contexts spawn the same `peach agent-wrapper`
|
|
3
3
|
// subcommand, mutate the same AttachedProjects store, and subscribe to the
|
|
4
4
|
// same events.out FIFO.
|
|
5
5
|
//
|
|
@@ -67,10 +67,10 @@ export function pidIsAlive(pid: number | undefined, sessionId?: string): boolean
|
|
|
67
67
|
|
|
68
68
|
// --- invocation resolution ---
|
|
69
69
|
|
|
70
|
-
/** locate the
|
|
70
|
+
/** locate the peach CLI so this process can run a subcommand of it. callers
|
|
71
71
|
* range from the CLI itself to electron main and the vite-plugin daemon.
|
|
72
72
|
* priority:
|
|
73
|
-
* 1.
|
|
73
|
+
* 1. PEACH_BIN env var (explicit override)
|
|
74
74
|
* 2. this process already IS the CLI — `rnxSelfInvocation` owns that answer
|
|
75
75
|
* 3. electron prod: bundled binary under process.resourcesPath/bin/
|
|
76
76
|
* 4. workspace build artifacts (dist-bin/<binary> or dist-cli/bin.js)
|
|
@@ -80,8 +80,8 @@ export function pidIsAlive(pid: number | undefined, sessionId?: string): boolean
|
|
|
80
80
|
* 5. CLI dev (argv[1] points at the entry script): re-use argv[0] + argv[1]
|
|
81
81
|
*/
|
|
82
82
|
export function resolveSootsimInvocation(): RnxSelfInvocation {
|
|
83
|
-
if (process.env.
|
|
84
|
-
return { executable: process.env.
|
|
83
|
+
if (process.env.PEACH_BIN) {
|
|
84
|
+
return { executable: process.env.PEACH_BIN, prefixArgs: [] }
|
|
85
85
|
}
|
|
86
86
|
// the compiled standalone binary cannot be found through argv: bun reports
|
|
87
87
|
// argv[0] as the literal string "bun" and argv[1] as a path inside its
|
|
@@ -121,11 +121,11 @@ export function resolveSootsimInvocation(): RnxSelfInvocation {
|
|
|
121
121
|
return { executable: process.argv[0], prefixArgs: [argv1] }
|
|
122
122
|
}
|
|
123
123
|
// anything else — a bin shim such as `…/node_modules/.bin/vite`, or no
|
|
124
|
-
// argv[1] at all — names no
|
|
124
|
+
// argv[1] at all — names no peach entry script, so say so instead of
|
|
125
125
|
// spawning the host runtime with no arguments.
|
|
126
126
|
throw new Error(
|
|
127
|
-
'
|
|
128
|
-
'or build the workspace CLI via `bun run --cwd packages/
|
|
127
|
+
'peach CLI not found. set PEACH_BIN to the path of the peach binary, ' +
|
|
128
|
+
'or build the workspace CLI via `bun run --cwd packages/peachsim build:cli`.',
|
|
129
129
|
)
|
|
130
130
|
}
|
|
131
131
|
|
|
@@ -133,7 +133,7 @@ function tryWorkspaceSootsim(): RnxSelfInvocation | null {
|
|
|
133
133
|
try {
|
|
134
134
|
const sootsimDir = resolveSootsimPackageDir()
|
|
135
135
|
if (!sootsimDir) return null
|
|
136
|
-
const binaryName = `
|
|
136
|
+
const binaryName = `peach-${
|
|
137
137
|
process.platform === 'win32' ? 'windows' : process.platform
|
|
138
138
|
}-${process.arch}${process.platform === 'win32' ? '.exe' : ''}`
|
|
139
139
|
const distBinary = path.join(sootsimDir, 'dist-bin', binaryName)
|
|
@@ -147,8 +147,8 @@ function tryWorkspaceSootsim(): RnxSelfInvocation | null {
|
|
|
147
147
|
const buildMtime = fs.statSync(distBin).mtimeMs
|
|
148
148
|
if (buildMtime < srcMtime) {
|
|
149
149
|
console.warn(
|
|
150
|
-
`[
|
|
151
|
-
`rebuild with \`bun run --cwd packages/
|
|
150
|
+
`[peach] dist-cli/bin.js is older than agent-wrapper.ts — ` +
|
|
151
|
+
`rebuild with \`bun run --cwd packages/peachsim build:cli\`.`,
|
|
152
152
|
)
|
|
153
153
|
}
|
|
154
154
|
}
|
|
@@ -342,7 +342,7 @@ export async function startSession(opts: StartSessionOpts): Promise<StartSession
|
|
|
342
342
|
'ALREADY_RUNNING',
|
|
343
343
|
`session already running for project=${project.id} provider=${provider} ` +
|
|
344
344
|
`(session ${existingLive.id}, pid ${existingLive.wrapperPid}). ` +
|
|
345
|
-
'end it first with `
|
|
345
|
+
'end it first with `peach agent end <sessionId>`.',
|
|
346
346
|
)
|
|
347
347
|
}
|
|
348
348
|
|
package/src/attached-projects.ts
CHANGED
|
@@ -114,7 +114,7 @@ function userDataDir(): string {
|
|
|
114
114
|
return electronUserDataDir()
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
/** exposed for CLI status printing so `
|
|
117
|
+
/** exposed for CLI status printing so `peach agent projects` can show the
|
|
118
118
|
* user which file it's reading from. */
|
|
119
119
|
export function getUserDataDir(): string {
|
|
120
120
|
return userDataDir()
|
|
@@ -38,7 +38,7 @@ export type SharedDesktopAuthSession = {
|
|
|
38
38
|
validatedAt?: string
|
|
39
39
|
/**
|
|
40
40
|
* the account this sign-in bills cloud work to. a session token names the
|
|
41
|
-
* person, not an account, so the CLI chooses one at `
|
|
41
|
+
* person, not an account, so the CLI chooses one at `peach login` and names it
|
|
42
42
|
* on every request that opens a paid session.
|
|
43
43
|
*/
|
|
44
44
|
accountId?: string
|
package/src/bridge-constants.ts
CHANGED
|
@@ -127,16 +127,16 @@ export function resolveSootsimWebSocketProxyUrl(input: {
|
|
|
127
127
|
}): string {
|
|
128
128
|
const pageUrl = parseHrefUrl(input.href)
|
|
129
129
|
if (!pageUrl || (pageUrl.protocol !== 'http:' && pageUrl.protocol !== 'https:')) {
|
|
130
|
-
throw new Error(`invalid
|
|
130
|
+
throw new Error(`invalid peach websocket proxy page URL: ${input.href}`)
|
|
131
131
|
}
|
|
132
132
|
let targetUrl: URL
|
|
133
133
|
try {
|
|
134
134
|
targetUrl = new URL(input.targetUrl)
|
|
135
135
|
} catch {
|
|
136
|
-
throw new Error(`invalid
|
|
136
|
+
throw new Error(`invalid peach websocket proxy target URL: ${input.targetUrl}`)
|
|
137
137
|
}
|
|
138
138
|
if (targetUrl.protocol !== 'ws:' && targetUrl.protocol !== 'wss:') {
|
|
139
|
-
throw new Error(`invalid
|
|
139
|
+
throw new Error(`invalid peach websocket proxy target URL: ${input.targetUrl}`)
|
|
140
140
|
}
|
|
141
141
|
const proxyUrl = new URL('/__websocket-proxy', pageUrl)
|
|
142
142
|
proxyUrl.protocol = pageUrl.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SIM_LONG_PRESS_MAX_MS } from './bridge-contract'
|
|
2
2
|
// untrusted JSON -> bridge-contract argument. one owner for that conversion, so
|
|
3
|
-
// every transport in front of a sim (the
|
|
3
|
+
// every transport in front of a sim (the Peach Cloud sim service's HTTP and
|
|
4
4
|
// WebSocket routes) validates its request bodies here rather than growing a
|
|
5
5
|
// second reader for the same shapes.
|
|
6
6
|
import type {
|
package/src/bridge-contract.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// the wire contract for the
|
|
1
|
+
// the wire contract for the peach ws bridge. the engine implements it in
|
|
2
2
|
// sootsim-engine/src/mcp/ws-bridge.ts; the peachsim cli client, the SimClient
|
|
3
3
|
// sdk, and the flow runner speak it. it is the single owner of these shapes,
|
|
4
4
|
// and neither side redeclares them.
|
|
@@ -367,7 +367,7 @@ export const SEMANTIC_READY_CONTENT_NODE_FLOOR = 100
|
|
|
367
367
|
export const SEMANTIC_READY_NODE_STABLE_MS = 750
|
|
368
368
|
export const SEMANTIC_READY_POLL_MS = 150
|
|
369
369
|
|
|
370
|
-
export const PUBLIC_NO_NETWORK_MARKER = 'public
|
|
370
|
+
export const PUBLIC_NO_NETWORK_MARKER = 'public Peach Cloud simulators have no network'
|
|
371
371
|
export const HEADLESS_TENANT_FETCH_HUNG_MARKER = 'headless tenant fetch hung'
|
|
372
372
|
|
|
373
373
|
export function readyProbeHasContent(
|
|
@@ -424,7 +424,7 @@ export interface SimStateOptions {
|
|
|
424
424
|
route?: boolean
|
|
425
425
|
errors?: boolean
|
|
426
426
|
// include the bounded guest console tail. omitted by the ordinary state
|
|
427
|
-
// command so one-shot state remains compact; `
|
|
427
|
+
// command so one-shot state remains compact; `peach logs` requests it.
|
|
428
428
|
logs?: boolean
|
|
429
429
|
// tree depth, matching the standalone `tree` command. default 5.
|
|
430
430
|
depth?: number
|
package/src/cli-version.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
//
|
|
1
|
+
// peach cli version, resolved once and cached. standalone builds embed it so
|
|
2
2
|
// lazy command chunks do not depend on an npm package layout at runtime.
|
|
3
3
|
|
|
4
4
|
import { readFileSync } from 'node:fs'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
6
|
import { rnxPublicBrand } from './public-brand.ts'
|
|
7
7
|
|
|
8
|
-
declare const
|
|
8
|
+
declare const __PEACH_CLI_VERSION__: string
|
|
9
9
|
|
|
10
10
|
let cached: string | null = null
|
|
11
11
|
|
|
12
12
|
export function getCliVersion(): string {
|
|
13
13
|
if (cached != null) return cached
|
|
14
14
|
if (
|
|
15
|
-
typeof
|
|
16
|
-
typeof
|
|
17
|
-
|
|
15
|
+
typeof __PEACH_CLI_VERSION__ !== 'undefined' &&
|
|
16
|
+
typeof __PEACH_CLI_VERSION__ === 'string' &&
|
|
17
|
+
__PEACH_CLI_VERSION__
|
|
18
18
|
) {
|
|
19
|
-
cached =
|
|
19
|
+
cached = __PEACH_CLI_VERSION__
|
|
20
20
|
return cached
|
|
21
21
|
}
|
|
22
22
|
// prefer package resolution (works for npm/global installs); fall back to
|