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/bin.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// peach CLI entry point. the full command and verb surface lives in the
|
|
4
4
|
// registry at `packages/peach-skills/src/cli/meta.ts` — help output,
|
|
5
5
|
// website docs, and agent skills all read from there. do not duplicate
|
|
6
|
-
// command listings in this file; run `
|
|
6
|
+
// command listings in this file; run `peach --help` for the canonical
|
|
7
7
|
// surface.
|
|
8
8
|
//
|
|
9
9
|
// this file only routes. the hidden entry points below each take over the
|
|
@@ -30,14 +30,14 @@ if (process.argv[2] === '__cloud-confirm') {
|
|
|
30
30
|
import('./cloud-session'),
|
|
31
31
|
])
|
|
32
32
|
const session = readCloudSession()
|
|
33
|
-
if (!session) throw new Error('
|
|
33
|
+
if (!session) throw new Error('Peach Cloud confirmation has no shell session')
|
|
34
34
|
// a box has no claim to keep alive: its simulator is held by the Box page
|
|
35
35
|
// for as long as the box runs, so a shell on a box confirms nothing.
|
|
36
36
|
if (session.service === 'sim') await confirmCloudSimulator(session)
|
|
37
37
|
process.exit(0)
|
|
38
38
|
} catch (error) {
|
|
39
39
|
process.stderr.write(
|
|
40
|
-
`
|
|
40
|
+
`Peach Cloud confirmation failed: ${
|
|
41
41
|
error instanceof Error ? error.message : String(error)
|
|
42
42
|
}\n`,
|
|
43
43
|
)
|
|
@@ -161,7 +161,7 @@ export interface MaestroStep {
|
|
|
161
161
|
launchApp?: any
|
|
162
162
|
// maestro canonical form: object with `file` XOR inline `commands`, plus
|
|
163
163
|
// optional `when` gate and `env` vars scoped to the sub-flow. legacy
|
|
164
|
-
//
|
|
164
|
+
// peach also accepts a bare path string.
|
|
165
165
|
runFlow?:
|
|
166
166
|
| string
|
|
167
167
|
| {
|
|
@@ -426,7 +426,7 @@ export class SootSimBridgeFlowRunner {
|
|
|
426
426
|
screenshotPathMode?: FlowScreenshotPathMode
|
|
427
427
|
// flow-wide default for screenshot layers — applied to every
|
|
428
428
|
// takeScreenshot step unless that step has its own `layers:`. set by
|
|
429
|
-
// `
|
|
429
|
+
// `peach maestro test --no-shell` to produce clean tenant-only captures
|
|
430
430
|
// (useful when a stuck shell overlay would otherwise pollute the
|
|
431
431
|
// demo asset set).
|
|
432
432
|
screenshotLayers?: 'full' | 'tenant' | 'shell'
|
|
@@ -530,8 +530,8 @@ export class SootSimBridgeFlowRunner {
|
|
|
530
530
|
if (status.bridgeError) {
|
|
531
531
|
throw new Error(
|
|
532
532
|
`target sim is not responding to the bridge (${status.bridgeError}). ` +
|
|
533
|
-
`it is likely a stale orphan; run \`
|
|
534
|
-
`with \`
|
|
533
|
+
`it is likely a stale orphan; run \`peach list\`, then close it ` +
|
|
534
|
+
`with \`peach close <id>\` or start a fresh one with \`peach open --new <port>\`.`,
|
|
535
535
|
)
|
|
536
536
|
}
|
|
537
537
|
throw new Error(`app not ready after ${timeout}ms: ${waitReadyReason(status)}`)
|
|
@@ -714,7 +714,7 @@ export class SootSimBridgeFlowRunner {
|
|
|
714
714
|
const extension = videoMime.includes('mp4') ? 'mp4' : 'webm'
|
|
715
715
|
const outputPath = path.join(
|
|
716
716
|
this.opts.recordingOutputDir,
|
|
717
|
-
`
|
|
717
|
+
`peach-${Date.now()}.${extension}`,
|
|
718
718
|
)
|
|
719
719
|
fs.writeFileSync(outputPath, Buffer.concat(chunks))
|
|
720
720
|
return outputPath
|
|
@@ -1089,7 +1089,7 @@ export class SootSimBridgeFlowRunner {
|
|
|
1089
1089
|
// single tap can land on a transitioning node and never grant focus —
|
|
1090
1090
|
// the keys then go nowhere and the field stays empty, silently breaking
|
|
1091
1091
|
// submit (the bluesky login-submit RCA, 2026-06-11). re-resolve + re-tap
|
|
1092
|
-
// until the input reports focused, the same gate
|
|
1092
|
+
// until the input reports focused, the same gate peach's `type-into`
|
|
1093
1093
|
// command uses. non-text-input taps keep the single-tap path.
|
|
1094
1094
|
const tapInputAttempts = element.isTextInput ? 4 : 1
|
|
1095
1095
|
for (let attempt = 0; attempt < tapInputAttempts; attempt++) {
|
|
@@ -1231,14 +1231,14 @@ export class SootSimBridgeFlowRunner {
|
|
|
1231
1231
|
|
|
1232
1232
|
async inputText(text: string) {
|
|
1233
1233
|
// real maestro types into the currently-focused field, and its driver's
|
|
1234
|
-
// tap synchronously focuses it. in
|
|
1234
|
+
// tap synchronously focuses it. in peach, focus propagates async across
|
|
1235
1235
|
// the tenant→shell worker boundary (tap → focusTextInput → keyboard show),
|
|
1236
1236
|
// so a preceding `tapOn` on a text input may not have landed focus by the
|
|
1237
1237
|
// time we dispatch keystrokes — especially right after a re-render storm
|
|
1238
1238
|
// (e.g. bluesky dismissing the custom-server dialog before tapping the
|
|
1239
1239
|
// username field). without a focused input the keys go nowhere and the
|
|
1240
1240
|
// field stays empty, silently breaking submit. wait for a focused text
|
|
1241
|
-
// input first — the same focus gate
|
|
1241
|
+
// input first — the same focus gate peach's own `type-into` uses (see
|
|
1242
1242
|
// the 2026-04-17 bluesky tap-routing regression in commands/inspect.ts).
|
|
1243
1243
|
if (!(await this.waitForFocusedTextInput())) {
|
|
1244
1244
|
throw new Error(
|
|
@@ -1610,8 +1610,8 @@ export class SootSimBridgeFlowRunner {
|
|
|
1610
1610
|
)
|
|
1611
1611
|
if (typeof tree === 'string') write('tree.txt', tree)
|
|
1612
1612
|
|
|
1613
|
-
// 6. console buffer + failed requests (captured by
|
|
1614
|
-
// hook / request recorder; same shape as `
|
|
1613
|
+
// 6. console buffer + failed requests (captured by peach's native-globals
|
|
1614
|
+
// hook / request recorder; same shape as `peach get errors` etc.).
|
|
1615
1615
|
const consoleBuf = await safeEval<{
|
|
1616
1616
|
errors: unknown[]
|
|
1617
1617
|
warnings: unknown[]
|
|
@@ -2171,11 +2171,11 @@ export class SootSimBridgeFlowRunner {
|
|
|
2171
2171
|
await this.rearmCaptureAfterRuntimeReload()
|
|
2172
2172
|
}
|
|
2173
2173
|
|
|
2174
|
-
// maestro clearKeychain: there is no real keychain in
|
|
2174
|
+
// maestro clearKeychain: there is no real keychain in peach's browser
|
|
2175
2175
|
// sandbox. log loudly so the author notices, then continue — matching the
|
|
2176
2176
|
// "loud warning, not silent skip" policy in the plan.
|
|
2177
2177
|
clearKeychain() {
|
|
2178
|
-
console.warn('[flow] clearKeychain:
|
|
2178
|
+
console.warn('[flow] clearKeychain: peach has no keychain surface — no-op (warning)')
|
|
2179
2179
|
}
|
|
2180
2180
|
|
|
2181
2181
|
// maestro copyTextFrom: find an element and capture its text for use in
|
|
@@ -2263,7 +2263,7 @@ export class SootSimBridgeFlowRunner {
|
|
|
2263
2263
|
return !(await this.isElementVisible(opts))
|
|
2264
2264
|
}
|
|
2265
2265
|
if (when.platform !== undefined) {
|
|
2266
|
-
//
|
|
2266
|
+
// peach emulates iOS — match against the context's platform the way
|
|
2267
2267
|
// upstream compares against cachedDeviceInfo.platform. keeps
|
|
2268
2268
|
// cross-platform maestro flows running without stripping guards.
|
|
2269
2269
|
return when.platform.toLowerCase() === this.js.maestro.platform.toLowerCase()
|
package/cli/browser-evals.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// browser-side evaluator snippets for CLI inspect commands.
|
|
2
2
|
//
|
|
3
|
-
// the
|
|
4
|
-
//
|
|
3
|
+
// the peach CLI ships some commands that need to observe the live
|
|
4
|
+
// peach tree or its rendered pixels. we send these strings over the peach host's
|
|
5
5
|
// `evaluate` handler.
|
|
6
6
|
//
|
|
7
7
|
// cropping happens once, in the capture module (`bridges.screenshot`),
|
package/cli/cli-update.ts
CHANGED
|
@@ -130,7 +130,7 @@ function envFlagIsOn(value: string | undefined): boolean {
|
|
|
130
130
|
export function automaticCliUpdateChecksEnabled(
|
|
131
131
|
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
132
132
|
): boolean {
|
|
133
|
-
return !envFlagIsOff(env.
|
|
133
|
+
return !envFlagIsOff(env.PEACH_UPDATE_CHECK)
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
export function shouldUseAutomaticCliUpdateCheck(
|
|
@@ -146,7 +146,7 @@ export function shouldUseAutomaticCliUpdateCheck(
|
|
|
146
146
|
!automaticCliUpdateChecksEnabled(env) ||
|
|
147
147
|
envFlagIsOn(env.CI) ||
|
|
148
148
|
envFlagIsOn(env.GITHUB_ACTIONS) ||
|
|
149
|
-
envFlagIsOn(env.
|
|
149
|
+
envFlagIsOn(env.PEACH_NO_PROMPT) ||
|
|
150
150
|
isAgentEnv(env) ||
|
|
151
151
|
invocation.commandArgs.includes('--json') ||
|
|
152
152
|
invocation.commandArgs.includes('--ci') ||
|
|
@@ -212,14 +212,14 @@ export async function fetchCliReleaseFile(
|
|
|
212
212
|
})
|
|
213
213
|
} catch (error) {
|
|
214
214
|
throw new Error(
|
|
215
|
-
`could not reach the
|
|
215
|
+
`could not reach the peach update service: ${
|
|
216
216
|
error instanceof Error ? error.message : String(error)
|
|
217
217
|
}`,
|
|
218
218
|
)
|
|
219
219
|
}
|
|
220
220
|
if (!response.ok) {
|
|
221
221
|
throw new Error(
|
|
222
|
-
`
|
|
222
|
+
`peach update download failed: ${response.status} ${response.statusText}`,
|
|
223
223
|
)
|
|
224
224
|
}
|
|
225
225
|
return Buffer.from(await response.arrayBuffer())
|
|
@@ -242,7 +242,7 @@ export async function refreshCliUpdateCache(
|
|
|
242
242
|
.trim()
|
|
243
243
|
if (!CLI_VERSION_PATTERN.test(latestVersion)) {
|
|
244
244
|
throw new Error(
|
|
245
|
-
`
|
|
245
|
+
`peach update service returned an invalid version: ${latestVersion || '(empty)'}`,
|
|
246
246
|
)
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -256,6 +256,6 @@ export async function refreshCliUpdateCache(
|
|
|
256
256
|
}
|
|
257
257
|
writeState(next)
|
|
258
258
|
const update = availability(next)
|
|
259
|
-
if (!update) throw new Error('
|
|
259
|
+
if (!update) throw new Error('peach update cache did not retain the latest version')
|
|
260
260
|
return update
|
|
261
261
|
}
|
package/cli/cloud-client.ts
CHANGED
|
@@ -67,8 +67,8 @@ export const DEFAULT_RNX_CLOUD_ORIGIN = rnxPublicBrand.origin
|
|
|
67
67
|
export const MAX_RNX_CLOUD_BUNDLE_BYTES = RNX_CLOUD_MAX_ARTIFACT_BYTES
|
|
68
68
|
|
|
69
69
|
// every message this module writes names the command through this constant.
|
|
70
|
-
const REMOTE_COMMAND = '
|
|
71
|
-
const PREVIEW_COMMAND = '
|
|
70
|
+
const REMOTE_COMMAND = 'peach remote'
|
|
71
|
+
const PREVIEW_COMMAND = 'peach preview'
|
|
72
72
|
|
|
73
73
|
export class UnsupportedRemoteHostError extends Error {
|
|
74
74
|
readonly code = 'unsupported_host' as const
|
|
@@ -78,7 +78,7 @@ export class UnsupportedRemoteHostError extends Error {
|
|
|
78
78
|
constructor(platform: string, host: string, message?: string) {
|
|
79
79
|
super(
|
|
80
80
|
message ??
|
|
81
|
-
`
|
|
81
|
+
`peach remote ${platform} does not support --${host}: peachsim/cloud only creates iOS simulators`,
|
|
82
82
|
)
|
|
83
83
|
this.name = 'UnsupportedRemoteHostError'
|
|
84
84
|
this.platform = platform
|
|
@@ -147,7 +147,7 @@ function failureSentence(text: string): string | null {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// the account service says what it needs in its own words; the CLI says what
|
|
150
|
-
// the person can do about it, which is choose the account at `
|
|
150
|
+
// the person can do about it, which is choose the account at `peach login`.
|
|
151
151
|
const ACCOUNT_CHOICE_REFUSALS = new Set([
|
|
152
152
|
'accountId is required',
|
|
153
153
|
'you do not have access to this account',
|
|
@@ -165,7 +165,7 @@ function responseError(status: number, text: string): Error {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
function resolveCloudOrigin(value?: string): string {
|
|
168
|
-
const configured = value?.trim() || process.env.
|
|
168
|
+
const configured = value?.trim() || process.env.PEACH_CLOUD_ORIGIN?.trim()
|
|
169
169
|
const source = configured || DEFAULT_RNX_CLOUD_ORIGIN
|
|
170
170
|
return normalizeCloudOrigin(source)
|
|
171
171
|
}
|
|
@@ -715,18 +715,18 @@ export async function produceCloudBoxArtifact(
|
|
|
715
715
|
): Promise<CloudBoxArtifactProduction> {
|
|
716
716
|
const prepared = await embedBundleAssets(
|
|
717
717
|
bundlePath,
|
|
718
|
-
'
|
|
718
|
+
'peach box create',
|
|
719
719
|
'allow-placeholders',
|
|
720
720
|
)
|
|
721
721
|
const warnings: string[] = []
|
|
722
722
|
if (prepared.search.unconfirmed && prepared.dest) {
|
|
723
723
|
warnings.push(
|
|
724
|
-
`
|
|
724
|
+
`peach box create read this bundle's assets from ${prepared.dest.directory} on layout alone: every asset declares a scale that directory does not hold, so not one of Metro's hashes could be reproduced. Confirm these are the images this bundle was built against.`,
|
|
725
725
|
)
|
|
726
726
|
}
|
|
727
727
|
if (prepared.substituted.length > 0) {
|
|
728
728
|
warnings.push(
|
|
729
|
-
`
|
|
729
|
+
`peach box create embedded a placeholder image for ${prepared.substituted.length} asset(s) no scale of which is in ${prepared.search.searched.join(', ') || 'any asset dest directory'}: ${prepared.substituted.join(', ')}. Rebuild with \`react-native bundle --assets-dest\` to ship the real images.`,
|
|
730
730
|
)
|
|
731
731
|
}
|
|
732
732
|
return {
|
|
@@ -1020,7 +1020,7 @@ export async function createCloudBox(options: CreateCloudBoxOptions): Promise<{
|
|
|
1020
1020
|
}
|
|
1021
1021
|
}
|
|
1022
1022
|
|
|
1023
|
-
// one customer plane: the sim-service instance API. every `
|
|
1023
|
+
// one customer plane: the sim-service instance API. every `peach remote`
|
|
1024
1024
|
// simulator is created, listed, read, and deleted here; the box stays the
|
|
1025
1025
|
// host behind it and is never the addressed object.
|
|
1026
1026
|
|
|
@@ -1083,7 +1083,7 @@ function readSimCreateResponse(
|
|
|
1083
1083
|
}
|
|
1084
1084
|
}
|
|
1085
1085
|
|
|
1086
|
-
// `
|
|
1086
|
+
// `peach remote ios|android <bundle>` creates one simulator through the
|
|
1087
1087
|
// instance API and waits for its app to answer ready. both platforms run
|
|
1088
1088
|
// this same typed path: an android create is refused by the service with a
|
|
1089
1089
|
// typed 400, never by a cli-side branch.
|
|
@@ -1098,7 +1098,7 @@ export async function createRemoteSim(options: CreateRemoteSimOptions): Promise<
|
|
|
1098
1098
|
const apiOrigin = resolveCloudOrigin(options.apiOrigin)
|
|
1099
1099
|
const artifact = await produceCloudArtifact(options.bundlePath)
|
|
1100
1100
|
const query = new URLSearchParams({
|
|
1101
|
-
labels: `
|
|
1101
|
+
labels: `peach.remote=1,peach.platform=${options.platform},peach.bundle=${artifact.sha256.slice(0, 12)}`,
|
|
1102
1102
|
wait: 'true',
|
|
1103
1103
|
})
|
|
1104
1104
|
if (options.reuse) query.set('reuseIfExists', 'true')
|
|
@@ -1210,14 +1210,14 @@ function readSimView(value: unknown): RnxCloudSimView {
|
|
|
1210
1210
|
}
|
|
1211
1211
|
}
|
|
1212
1212
|
|
|
1213
|
-
// `
|
|
1213
|
+
// `peach remote list`: the simulators this account runs that `peach remote` made.
|
|
1214
1214
|
export async function listRemoteSims(options: {
|
|
1215
1215
|
apiOrigin?: string
|
|
1216
1216
|
authorization: string
|
|
1217
1217
|
accountId?: string | null
|
|
1218
1218
|
}): Promise<RnxCloudSimView[]> {
|
|
1219
1219
|
const apiOrigin = resolveCloudOrigin(options.apiOrigin)
|
|
1220
|
-
const selector = new URLSearchParams({ labelSelector: '
|
|
1220
|
+
const selector = new URLSearchParams({ labelSelector: 'peach.remote=1' })
|
|
1221
1221
|
const headers: Record<string, string> = { authorization: options.authorization }
|
|
1222
1222
|
if (options.accountId) headers['x-rnx-account-id'] = options.accountId
|
|
1223
1223
|
let response: Response
|
|
@@ -1289,7 +1289,7 @@ export async function getRemoteSim(options: {
|
|
|
1289
1289
|
}
|
|
1290
1290
|
}
|
|
1291
1291
|
|
|
1292
|
-
// `
|
|
1292
|
+
// `peach remote stop <id>`: delete one simulator by id. missing is null so the
|
|
1293
1293
|
// caller can name what was not there; anything else refused throws.
|
|
1294
1294
|
export async function deleteRemoteSim(options: {
|
|
1295
1295
|
apiOrigin?: string
|
|
@@ -1536,7 +1536,7 @@ class CloudBoxBridge implements WsBridge {
|
|
|
1536
1536
|
}
|
|
1537
1537
|
|
|
1538
1538
|
closeSim(): Promise<never> {
|
|
1539
|
-
return Promise.reject(new Error('close a box simulator with
|
|
1539
|
+
return Promise.reject(new Error('close a box simulator with peach box stop'))
|
|
1540
1540
|
}
|
|
1541
1541
|
|
|
1542
1542
|
claim(): Promise<BridgeClaimResult> {
|
|
@@ -1612,7 +1612,7 @@ export interface BoxDisplayGrant {
|
|
|
1612
1612
|
expectedPeer: unknown
|
|
1613
1613
|
}
|
|
1614
1614
|
|
|
1615
|
-
// how long `
|
|
1615
|
+
// how long `peach remote` waits for a fresh box's page to attach its simulator
|
|
1616
1616
|
// before giving up on the watch url. the page boots the bundle after the
|
|
1617
1617
|
// browser is up, and a large bundle takes a while to parse.
|
|
1618
1618
|
const BOX_DISPLAY_GRANT_WAIT_MS = 180_000
|
package/cli/cloud-dispatch.ts
CHANGED
|
@@ -119,7 +119,7 @@ function refuseCloudInvocation(
|
|
|
119
119
|
): CloudBoundaryResult {
|
|
120
120
|
const verb = firstVerb(positional)
|
|
121
121
|
const name = verb ? `${command} ${verb}` : command
|
|
122
|
-
console.error(`
|
|
122
|
+
console.error(` peach is not available for a remote simulator`)
|
|
123
123
|
return { handled: true, code: 1 }
|
|
124
124
|
}
|
|
125
125
|
|
|
@@ -183,7 +183,7 @@ export async function dispatchCloudBoundary({
|
|
|
183
183
|
if (session.service === 'box') {
|
|
184
184
|
if (command === 'claim') {
|
|
185
185
|
console.error(
|
|
186
|
-
`
|
|
186
|
+
` peach is not available for a box; its simulator is held by the Box page. use peach box stop to end the box.`,
|
|
187
187
|
)
|
|
188
188
|
return { handled: true, code: 1 }
|
|
189
189
|
}
|
package/cli/cloud-session.ts
CHANGED
|
@@ -2,15 +2,15 @@ import { fstatSync, writeSync } from 'node:fs'
|
|
|
2
2
|
import { isLoopbackHost } from '../src/backend-origin.ts'
|
|
3
3
|
|
|
4
4
|
const CLOUD_SESSION_VERSION = 3 as const
|
|
5
|
-
const SHELL_UPDATE_FD_ENV = '
|
|
5
|
+
const SHELL_UPDATE_FD_ENV = 'PEACH_SHELL_UPDATE_FD'
|
|
6
6
|
const MAX_ENCODED_SESSION_BYTES = 32 * 1024
|
|
7
7
|
|
|
8
|
-
export const
|
|
8
|
+
export const PEACH_CLOUD_SESSION_ENV = 'PEACH_CLOUD_SESSION'
|
|
9
9
|
export const RNX_SHELL_UPDATE_PREFIX = '__RNX_SHELL_UPDATE_V1__'
|
|
10
10
|
export const RNX_SHELL_UPDATE_SET_PREFIX = `${RNX_SHELL_UPDATE_PREFIX}set:`
|
|
11
11
|
export const RNX_SHELL_UPDATE_UNSET = `${RNX_SHELL_UPDATE_PREFIX}unset`
|
|
12
12
|
|
|
13
|
-
// the shell's remote target. two services answer
|
|
13
|
+
// the shell's remote target. two services answer peach commands and they are
|
|
14
14
|
// addressed differently, so the session names which one it is rather than
|
|
15
15
|
// leaving a reader to infer it from which fields happen to be filled in.
|
|
16
16
|
export type CloudSession = CloudSimSession | CloudBoxSession
|
|
@@ -163,7 +163,7 @@ export function parseCloudSession(encoded: string): CloudSession {
|
|
|
163
163
|
value.length > MAX_ENCODED_SESSION_BYTES ||
|
|
164
164
|
!/^[A-Za-z0-9_-]+$/.test(value)
|
|
165
165
|
) {
|
|
166
|
-
throw new Error(`${
|
|
166
|
+
throw new Error(`${PEACH_CLOUD_SESSION_ENV} is invalid`)
|
|
167
167
|
}
|
|
168
168
|
let parsed: unknown
|
|
169
169
|
try {
|
|
@@ -173,14 +173,14 @@ export function parseCloudSession(encoded: string): CloudSession {
|
|
|
173
173
|
}
|
|
174
174
|
parsed = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes))
|
|
175
175
|
} catch {
|
|
176
|
-
throw new Error(`${
|
|
176
|
+
throw new Error(`${PEACH_CLOUD_SESSION_ENV} is invalid`)
|
|
177
177
|
}
|
|
178
178
|
if (!isRecord(parsed) || parsed.version !== CLOUD_SESSION_VERSION) {
|
|
179
|
-
throw new Error(`${
|
|
179
|
+
throw new Error(`${PEACH_CLOUD_SESSION_ENV} is invalid`)
|
|
180
180
|
}
|
|
181
181
|
const apiOrigin = parsed.apiOrigin
|
|
182
182
|
if (typeof apiOrigin !== 'string') {
|
|
183
|
-
throw new Error(`${
|
|
183
|
+
throw new Error(`${PEACH_CLOUD_SESSION_ENV} is invalid`)
|
|
184
184
|
}
|
|
185
185
|
const platform =
|
|
186
186
|
parsed.platform === 'ios' || parsed.platform === 'android'
|
|
@@ -234,14 +234,14 @@ export function parseCloudSession(encoded: string): CloudSession {
|
|
|
234
234
|
boxToken,
|
|
235
235
|
})
|
|
236
236
|
} catch {
|
|
237
|
-
throw new Error(`${
|
|
237
|
+
throw new Error(`${PEACH_CLOUD_SESSION_ENV} is invalid`)
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
|
|
241
241
|
export function readCloudSession(
|
|
242
242
|
environment: Readonly<Record<string, string | undefined>> = process.env,
|
|
243
243
|
): CloudSession | null {
|
|
244
|
-
const encoded = environment[
|
|
244
|
+
const encoded = environment[PEACH_CLOUD_SESSION_ENV]
|
|
245
245
|
return encoded?.trim() ? parseCloudSession(encoded) : null
|
|
246
246
|
}
|
|
247
247
|
|
|
@@ -252,12 +252,12 @@ function shellUpdateFd(
|
|
|
252
252
|
if (!source) return null
|
|
253
253
|
const descriptor = Number(source)
|
|
254
254
|
if (!Number.isSafeInteger(descriptor) || descriptor < 3) {
|
|
255
|
-
throw new Error('
|
|
255
|
+
throw new Error('Peach shell integration provided an invalid update channel')
|
|
256
256
|
}
|
|
257
257
|
try {
|
|
258
258
|
fstatSync(descriptor)
|
|
259
259
|
} catch {
|
|
260
|
-
throw new Error('
|
|
260
|
+
throw new Error('Peach shell integration update channel is unavailable')
|
|
261
261
|
}
|
|
262
262
|
return descriptor
|
|
263
263
|
}
|
|
@@ -266,7 +266,7 @@ export function requireCloudSessionShellUpdate(): number {
|
|
|
266
266
|
const descriptor = shellUpdateFd()
|
|
267
267
|
if (descriptor === null) {
|
|
268
268
|
throw new Error(
|
|
269
|
-
'
|
|
269
|
+
'Peach shell integration is required so this remote simulator stays connected to the current shell; restart the shell after installing peach',
|
|
270
270
|
)
|
|
271
271
|
}
|
|
272
272
|
return descriptor
|
package/cli/cloud-test-tunnel.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// peach test --cloud live-dev-stack publisher (tunnel CLI half).
|
|
2
2
|
//
|
|
3
3
|
// when --app names a live packager (http loopback) instead of a prebuilt
|
|
4
4
|
// bundle, the submitter publishes that stack through a token-gated
|
|
@@ -188,7 +188,7 @@ async function ensureCloudflared(): Promise<boolean> {
|
|
|
188
188
|
const osMap: Record<string, string> = { linux: 'linux', darwin: 'darwin' }
|
|
189
189
|
const os = osMap[process.platform]
|
|
190
190
|
if (!arch || !os) return false
|
|
191
|
-
const dest = join(tmpdir(), '
|
|
191
|
+
const dest = join(tmpdir(), 'peach-cloudflared')
|
|
192
192
|
const binary = join(dest, 'cloudflared')
|
|
193
193
|
if (existsSync(binary)) {
|
|
194
194
|
process.env.PATH = `${dest}:${process.env.PATH ?? ''}`
|
|
@@ -317,10 +317,10 @@ export async function publishLiveDevStack(args: {
|
|
|
317
317
|
if (!(await ensureCloudflared())) {
|
|
318
318
|
closeProxy()
|
|
319
319
|
throw new Error(
|
|
320
|
-
'cloudflared is required for live dev stacks (
|
|
320
|
+
'cloudflared is required for live dev stacks (peach test --cloud with http://localhost:<port>). install it or check your network.',
|
|
321
321
|
)
|
|
322
322
|
}
|
|
323
|
-
const logFile = join(tmpdir(), `
|
|
323
|
+
const logFile = join(tmpdir(), `peach-test-tunnel-${Date.now()}-${proxyPort}.log`)
|
|
324
324
|
const tunnel = await launchQuickTunnel(
|
|
325
325
|
`http://127.0.0.1:${proxyPort}`,
|
|
326
326
|
hostHeader,
|
package/cli/command-registry.ts
CHANGED
|
@@ -169,14 +169,14 @@ export function getRnxCommandAvailability(
|
|
|
169
169
|
: {
|
|
170
170
|
available: false,
|
|
171
171
|
reason:
|
|
172
|
-
'
|
|
172
|
+
'peach web is available in the browser backend, where it selects the existing web preview.',
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
if (!registration.browserFamily) {
|
|
177
177
|
return {
|
|
178
178
|
available: false,
|
|
179
|
-
reason: `
|
|
179
|
+
reason: `peach ${command} is unavailable in the browser backend; it requires the node backend.`,
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -184,7 +184,7 @@ export function getRnxCommandAvailability(
|
|
|
184
184
|
if (command === 'shell' && subcommand && subcommand !== 'appearance') {
|
|
185
185
|
return {
|
|
186
186
|
available: false,
|
|
187
|
-
reason: `
|
|
187
|
+
reason: `peach shell ${subcommand} is unavailable in the browser backend; Factory exposes no semantic operation for it.`,
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
if (
|
|
@@ -196,14 +196,14 @@ export function getRnxCommandAvailability(
|
|
|
196
196
|
) {
|
|
197
197
|
return {
|
|
198
198
|
available: false,
|
|
199
|
-
reason: '
|
|
199
|
+
reason: 'peach shell appearance supports light or dark in the browser backend.',
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
if (command === 'storage' && subcommand === 'profile') {
|
|
203
203
|
return {
|
|
204
204
|
available: false,
|
|
205
205
|
reason:
|
|
206
|
-
'
|
|
206
|
+
'peach storage profile is unavailable in the browser backend; use peach storage clear for the selected preview.',
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
const screenshotSubcommand = command === 'screenshot' ? subcommand : null
|
|
@@ -213,14 +213,14 @@ export function getRnxCommandAvailability(
|
|
|
213
213
|
) {
|
|
214
214
|
return {
|
|
215
215
|
available: false,
|
|
216
|
-
reason: `
|
|
216
|
+
reason: `peach screenshot ${screenshotSubcommand} is unavailable in the browser backend; it requires the node backend.`,
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
if (command === 'timeline' && subcommand === 'dump') {
|
|
220
220
|
return {
|
|
221
221
|
available: false,
|
|
222
222
|
reason:
|
|
223
|
-
'
|
|
223
|
+
'peach timeline dump is unavailable in the browser backend because it writes to the host filesystem.',
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
// agent-wrapper — long-running bridge process between
|
|
1
|
+
// agent-wrapper — long-running bridge process between peach desktop and a
|
|
2
2
|
// local coding agent (codex / claude).
|
|
3
3
|
//
|
|
4
|
-
// shipped as an internal
|
|
4
|
+
// shipped as an internal peach CLI subcommand so it survives `bun build
|
|
5
5
|
// --compile` bundling. callers spawn it as:
|
|
6
6
|
//
|
|
7
|
-
//
|
|
7
|
+
// peach agent-wrapper \
|
|
8
8
|
// --session-id <id> --project-id <id> --provider codex \
|
|
9
9
|
// --cwd <path> --prompt-in <fifo> --events-out <fifo> \
|
|
10
10
|
// [--transcript <file>]
|
|
@@ -148,7 +148,7 @@ export async function runAgentWrapper(argv: string[]): Promise<number> {
|
|
|
148
148
|
!args.eventsOut
|
|
149
149
|
) {
|
|
150
150
|
process.stderr.write(
|
|
151
|
-
'usage:
|
|
151
|
+
'usage: peach agent-wrapper --session-id <id> --project-id <id>\n' +
|
|
152
152
|
' --provider codex|claude --cwd <path>\n' +
|
|
153
153
|
' --prompt-in <fifo> --events-out <fifo>\n' +
|
|
154
154
|
' [--transcript <file>]\n' +
|
|
@@ -246,7 +246,7 @@ export async function runAgentWrapper(argv: string[]): Promise<number> {
|
|
|
246
246
|
bin: resolvedAgentBin,
|
|
247
247
|
cwd,
|
|
248
248
|
env: {
|
|
249
|
-
|
|
249
|
+
PEACH_CLI_IDENTITY: sessionId,
|
|
250
250
|
SOOTSIM_PROJECT_ID: projectId,
|
|
251
251
|
},
|
|
252
252
|
})
|
|
@@ -419,7 +419,7 @@ export async function runAgentWrapper(argv: string[]): Promise<number> {
|
|
|
419
419
|
}
|
|
420
420
|
|
|
421
421
|
// open prompt.in with O_RDWR so our own write end keeps the FIFO alive
|
|
422
|
-
// across external writer open/close cycles. `
|
|
422
|
+
// across external writer open/close cycles. `peach agent prompt` opens,
|
|
423
423
|
// writes one line, closes — without a self-writer the reader would EOF
|
|
424
424
|
// after the first prompt and subsequent prompts would be lost.
|
|
425
425
|
const promptFd = openSync(promptIn, fsConstants.O_RDWR)
|
|
@@ -889,7 +889,7 @@ function buildClaudeTurnRunner(deps: ClaudeTurnDeps): TurnRunner {
|
|
|
889
889
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
890
890
|
env: {
|
|
891
891
|
...claudeEnv,
|
|
892
|
-
|
|
892
|
+
PEACH_CLI_IDENTITY: sessionId,
|
|
893
893
|
SOOTSIM_PROJECT_ID: projectId,
|
|
894
894
|
},
|
|
895
895
|
},
|