@avasapp/agent-bridge 0.1.0
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 +21 -0
- package/README.md +159 -0
- package/dist/adapters/expo-router.cjs +144 -0
- package/dist/adapters/expo-router.d.cts +38 -0
- package/dist/adapters/react-native-mmkv.cjs +102 -0
- package/dist/adapters/react-native-mmkv.d.cts +15 -0
- package/dist/adapters/tanstack-query.cjs +161 -0
- package/dist/adapters/tanstack-query.d.cts +10 -0
- package/dist/adapters/zustand.cjs +97 -0
- package/dist/adapters/zustand.d.cts +15 -0
- package/dist/chunk-UEWFQWCY.js +575 -0
- package/dist/cli.js +657 -0
- package/dist/client/index.cjs +568 -0
- package/dist/client/index.d.ts +112 -0
- package/dist/client/index.js +14 -0
- package/dist/expo/index.cjs +62 -0
- package/dist/expo/index.d.cts +9 -0
- package/dist/network/index.cjs +568 -0
- package/dist/network/index.d.cts +85 -0
- package/dist/noop/expo-router.cjs +26 -0
- package/dist/noop/expo.cjs +27 -0
- package/dist/noop/index.cjs +36 -0
- package/dist/noop/network.cjs +34 -0
- package/dist/noop/react-native-mmkv.cjs +26 -0
- package/dist/noop/tanstack-query.cjs +26 -0
- package/dist/noop/zustand.cjs +26 -0
- package/dist/runtime/index.cjs +933 -0
- package/dist/runtime/index.d.cts +69 -0
- package/dist/types-C6DUUHnB.d.cts +76 -0
- package/entries/expo-router.cjs +9 -0
- package/entries/expo.cjs +9 -0
- package/entries/index.cjs +9 -0
- package/entries/network.cjs +9 -0
- package/entries/react-native-mmkv.cjs +9 -0
- package/entries/tanstack-query.cjs +9 -0
- package/entries/zustand.cjs +9 -0
- package/package.json +120 -0
- package/skills/agent-bridge/SKILL.md +89 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { a as Transport, A as AgentBridgeOptions } from '../types-C6DUUHnB.cjs';
|
|
2
|
+
export { C as CallMessage, D as DeviceInfo, L as LogEntry, R as RUNTIME_MARKER, b as ResultMessage, c as ToolDefinition, d as ToolFn, e as ToolInfo, T as Tools } from '../types-C6DUUHnB.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Works in any React Native app served by Metro. The client evaluates one fixed
|
|
6
|
+
* entry point with an ASCII-only JSON payload, and the app pushes the result
|
|
7
|
+
* back through a CDP binding (Hermes can't `awaitPromise` RN's promises).
|
|
8
|
+
*/
|
|
9
|
+
declare function cdpTransport(): Transport;
|
|
10
|
+
|
|
11
|
+
type SettleResult = {
|
|
12
|
+
commits: number;
|
|
13
|
+
ms: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Resolves once React has committed the renders queued so far: after a tick
|
|
17
|
+
* (TanStack Query notifies on a timeout), then until a frame passes with no
|
|
18
|
+
* commit and nothing queued. Returns right away when nothing was queued, and
|
|
19
|
+
* gives up after `maxMs` (default 500).
|
|
20
|
+
*/
|
|
21
|
+
declare function settle(options?: {
|
|
22
|
+
maxMs?: number;
|
|
23
|
+
}): Promise<SettleResult>;
|
|
24
|
+
|
|
25
|
+
type Rect = {
|
|
26
|
+
x: number;
|
|
27
|
+
y: number;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type ElementKind = 'button' | 'input' | 'text' | 'view';
|
|
33
|
+
type ScreenElement = {
|
|
34
|
+
kind: ElementKind;
|
|
35
|
+
text?: string;
|
|
36
|
+
testID?: string;
|
|
37
|
+
label?: string;
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
value?: string;
|
|
40
|
+
editable?: boolean;
|
|
41
|
+
role?: string;
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
rect: Rect | null;
|
|
44
|
+
/** Only in `screen.snapshot({ all: true })`. */
|
|
45
|
+
onScreen?: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* What to act on. A string tries testID, then label, then placeholder, then
|
|
50
|
+
* visible text (exact, then substring). An object must match every field it
|
|
51
|
+
* sets; `index` picks one of several matches.
|
|
52
|
+
*/
|
|
53
|
+
type Target = string | {
|
|
54
|
+
testID?: string;
|
|
55
|
+
label?: string;
|
|
56
|
+
placeholder?: string;
|
|
57
|
+
text?: string;
|
|
58
|
+
index?: number;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Starts the bridge outside React. Returns a function that stops it. */
|
|
62
|
+
declare function startAgentBridge(options?: AgentBridgeOptions): () => void;
|
|
63
|
+
/**
|
|
64
|
+
* Exposes `tools` to coding agents while mounted. Tools are read on every call,
|
|
65
|
+
* so passing a fresh object each render is fine. Transports start once.
|
|
66
|
+
*/
|
|
67
|
+
declare function useAgentBridge(options?: AgentBridgeOptions): void;
|
|
68
|
+
|
|
69
|
+
export { AgentBridgeOptions, type ScreenElement, type SettleResult, type Target, Transport, cdpTransport, settle, startAgentBridge, useAgentBridge };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** `agent-bridge assert-absent` fails when a bundle contains this string. */
|
|
2
|
+
declare const RUNTIME_MARKER = "@avasapp/agent-bridge/runtime";
|
|
3
|
+
type CallMessage = {
|
|
4
|
+
id: string;
|
|
5
|
+
tool: string;
|
|
6
|
+
args?: unknown[];
|
|
7
|
+
/** Device to run on. Expo's socket broadcasts, so every app sees every call. */
|
|
8
|
+
to?: string;
|
|
9
|
+
};
|
|
10
|
+
/** An error or warning the app logged, threw or left unhandled. */
|
|
11
|
+
type LogEntry = {
|
|
12
|
+
level: 'error' | 'warn';
|
|
13
|
+
message: string;
|
|
14
|
+
/** First lines of the stack, when there was an Error. */
|
|
15
|
+
stack?: string;
|
|
16
|
+
/** Date.now() in the app. */
|
|
17
|
+
at: number;
|
|
18
|
+
/** The tool that was running when it was logged. */
|
|
19
|
+
during?: string;
|
|
20
|
+
/** Otherwise, the last tool that had finished. */
|
|
21
|
+
after?: string;
|
|
22
|
+
};
|
|
23
|
+
/** `logs`: errors the app recorded since its previous reply. Old clients ignore it. */
|
|
24
|
+
type ResultMessage = ({
|
|
25
|
+
id: string;
|
|
26
|
+
from: string;
|
|
27
|
+
ok: true;
|
|
28
|
+
value: unknown;
|
|
29
|
+
ms: number;
|
|
30
|
+
} | {
|
|
31
|
+
id: string;
|
|
32
|
+
from: string;
|
|
33
|
+
ok: false;
|
|
34
|
+
error: string;
|
|
35
|
+
ms: number;
|
|
36
|
+
}) & {
|
|
37
|
+
logs?: LogEntry[];
|
|
38
|
+
};
|
|
39
|
+
type ToolInfo = {
|
|
40
|
+
name: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
};
|
|
43
|
+
type DeviceInfo = {
|
|
44
|
+
deviceId: string;
|
|
45
|
+
name: string;
|
|
46
|
+
platform: string;
|
|
47
|
+
protocol: number;
|
|
48
|
+
tools: ToolInfo[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type ToolFn = (...args: any[]) => unknown;
|
|
52
|
+
type ToolDefinition = ToolFn | {
|
|
53
|
+
description?: string;
|
|
54
|
+
run: ToolFn;
|
|
55
|
+
};
|
|
56
|
+
/** Tools by name. Namespace them with a dot, e.g. `query.pin`. */
|
|
57
|
+
type Tools = Record<string, ToolDefinition>;
|
|
58
|
+
type TransportContext = {
|
|
59
|
+
info: () => DeviceInfo;
|
|
60
|
+
dispatch: (call: CallMessage) => Promise<ResultMessage>;
|
|
61
|
+
};
|
|
62
|
+
type Transport = {
|
|
63
|
+
name: string;
|
|
64
|
+
/** Starts listening; returns a function that stops. */
|
|
65
|
+
start: (context: TransportContext) => () => void;
|
|
66
|
+
};
|
|
67
|
+
type AgentBridgeOptions = {
|
|
68
|
+
/** Your tools, or a function returning them (read on every call). */
|
|
69
|
+
tools?: Tools | (() => Tools);
|
|
70
|
+
/** How this app shows up in `agent-bridge devices`. */
|
|
71
|
+
name?: string;
|
|
72
|
+
/** Defaults to CDP only. Add `expoTransport()` on Expo. */
|
|
73
|
+
transports?: Transport[];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export { type AgentBridgeOptions as A, type CallMessage as C, type DeviceInfo as D, type LogEntry as L, RUNTIME_MARKER as R, type Tools as T, type Transport as a, type ResultMessage as b, type ToolDefinition as c, type ToolFn as d, type ToolInfo as e };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Metro inlines NODE_ENV and drops the dead branch, so a release bundle
|
|
4
|
+
// never includes the bridge, the same way react/index.js works.
|
|
5
|
+
if (process.env.NODE_ENV === 'production') {
|
|
6
|
+
module.exports = require('../dist/noop/expo-router.cjs')
|
|
7
|
+
} else {
|
|
8
|
+
module.exports = require('../dist/adapters/expo-router.cjs')
|
|
9
|
+
}
|
package/entries/expo.cjs
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Metro inlines NODE_ENV and drops the dead branch, so a release bundle
|
|
4
|
+
// never includes the bridge, the same way react/index.js works.
|
|
5
|
+
if (process.env.NODE_ENV === 'production') {
|
|
6
|
+
module.exports = require('../dist/noop/expo.cjs')
|
|
7
|
+
} else {
|
|
8
|
+
module.exports = require('../dist/expo/index.cjs')
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Metro inlines NODE_ENV and drops the dead branch, so a release bundle
|
|
4
|
+
// never includes the bridge, the same way react/index.js works.
|
|
5
|
+
if (process.env.NODE_ENV === 'production') {
|
|
6
|
+
module.exports = require('../dist/noop/index.cjs')
|
|
7
|
+
} else {
|
|
8
|
+
module.exports = require('../dist/runtime/index.cjs')
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Metro inlines NODE_ENV and drops the dead branch, so a release bundle
|
|
4
|
+
// never includes the bridge, the same way react/index.js works.
|
|
5
|
+
if (process.env.NODE_ENV === 'production') {
|
|
6
|
+
module.exports = require('../dist/noop/network.cjs')
|
|
7
|
+
} else {
|
|
8
|
+
module.exports = require('../dist/network/index.cjs')
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Metro inlines NODE_ENV and drops the dead branch, so a release bundle
|
|
4
|
+
// never includes the bridge, the same way react/index.js works.
|
|
5
|
+
if (process.env.NODE_ENV === 'production') {
|
|
6
|
+
module.exports = require('../dist/noop/react-native-mmkv.cjs')
|
|
7
|
+
} else {
|
|
8
|
+
module.exports = require('../dist/adapters/react-native-mmkv.cjs')
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Metro inlines NODE_ENV and drops the dead branch, so a release bundle
|
|
4
|
+
// never includes the bridge, the same way react/index.js works.
|
|
5
|
+
if (process.env.NODE_ENV === 'production') {
|
|
6
|
+
module.exports = require('../dist/noop/tanstack-query.cjs')
|
|
7
|
+
} else {
|
|
8
|
+
module.exports = require('../dist/adapters/tanstack-query.cjs')
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Metro inlines NODE_ENV and drops the dead branch, so a release bundle
|
|
4
|
+
// never includes the bridge, the same way react/index.js works.
|
|
5
|
+
if (process.env.NODE_ENV === 'production') {
|
|
6
|
+
module.exports = require('../dist/noop/zustand.cjs')
|
|
7
|
+
} else {
|
|
8
|
+
module.exports = require('../dist/adapters/zustand.cjs')
|
|
9
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@avasapp/agent-bridge",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Let coding agents drive a running React Native app directly: seed state, flip flags, navigate and check the screen in about a millisecond per call, with no taps.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/avas-app/agent-bridge#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/avas-app/agent-bridge.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/avas-app/agent-bridge/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public",
|
|
17
|
+
"provenance": true
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20"
|
|
21
|
+
},
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"main": "./entries/index.cjs",
|
|
24
|
+
"types": "./dist/runtime/index.d.cts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/runtime/index.d.cts",
|
|
28
|
+
"default": "./entries/index.cjs"
|
|
29
|
+
},
|
|
30
|
+
"./expo": {
|
|
31
|
+
"types": "./dist/expo/index.d.cts",
|
|
32
|
+
"default": "./entries/expo.cjs"
|
|
33
|
+
},
|
|
34
|
+
"./tanstack-query": {
|
|
35
|
+
"types": "./dist/adapters/tanstack-query.d.cts",
|
|
36
|
+
"default": "./entries/tanstack-query.cjs"
|
|
37
|
+
},
|
|
38
|
+
"./zustand": {
|
|
39
|
+
"types": "./dist/adapters/zustand.d.cts",
|
|
40
|
+
"default": "./entries/zustand.cjs"
|
|
41
|
+
},
|
|
42
|
+
"./react-native-mmkv": {
|
|
43
|
+
"types": "./dist/adapters/react-native-mmkv.d.cts",
|
|
44
|
+
"default": "./entries/react-native-mmkv.cjs"
|
|
45
|
+
},
|
|
46
|
+
"./expo-router": {
|
|
47
|
+
"types": "./dist/adapters/expo-router.d.cts",
|
|
48
|
+
"default": "./entries/expo-router.cjs"
|
|
49
|
+
},
|
|
50
|
+
"./network": {
|
|
51
|
+
"types": "./dist/network/index.d.cts",
|
|
52
|
+
"default": "./entries/network.cjs"
|
|
53
|
+
},
|
|
54
|
+
"./client": {
|
|
55
|
+
"types": "./dist/client/index.d.ts",
|
|
56
|
+
"import": "./dist/client/index.js",
|
|
57
|
+
"require": "./dist/client/index.cjs"
|
|
58
|
+
},
|
|
59
|
+
"./package.json": "./package.json"
|
|
60
|
+
},
|
|
61
|
+
"bin": {
|
|
62
|
+
"agent-bridge": "./dist/cli.js"
|
|
63
|
+
},
|
|
64
|
+
"files": [
|
|
65
|
+
"dist",
|
|
66
|
+
"entries",
|
|
67
|
+
"skills"
|
|
68
|
+
],
|
|
69
|
+
"keywords": [
|
|
70
|
+
"react-native",
|
|
71
|
+
"expo",
|
|
72
|
+
"ai",
|
|
73
|
+
"agents",
|
|
74
|
+
"testing",
|
|
75
|
+
"e2e",
|
|
76
|
+
"devtools",
|
|
77
|
+
"cdp",
|
|
78
|
+
"tanstack-query",
|
|
79
|
+
"zustand",
|
|
80
|
+
"mmkv"
|
|
81
|
+
],
|
|
82
|
+
"scripts": {
|
|
83
|
+
"build": "rm -rf dist && tsup",
|
|
84
|
+
"test": "bun test src/",
|
|
85
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
86
|
+
"check": "bun run typecheck && bun run test && bun run build && bun run check:gates",
|
|
87
|
+
"check:gates": "node scripts/check-gates.mjs",
|
|
88
|
+
"changeset": "changeset",
|
|
89
|
+
"release": "bun run check && changeset publish",
|
|
90
|
+
"prepublishOnly": "bun run check"
|
|
91
|
+
},
|
|
92
|
+
"peerDependencies": {
|
|
93
|
+
"@tanstack/query-core": ">=5",
|
|
94
|
+
"expo": ">=52",
|
|
95
|
+
"react": ">=18",
|
|
96
|
+
"react-native": ">=0.76"
|
|
97
|
+
},
|
|
98
|
+
"peerDependenciesMeta": {
|
|
99
|
+
"@tanstack/query-core": {
|
|
100
|
+
"optional": true
|
|
101
|
+
},
|
|
102
|
+
"expo": {
|
|
103
|
+
"optional": true
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"dependencies": {
|
|
107
|
+
"ws": "^8.21.3"
|
|
108
|
+
},
|
|
109
|
+
"devDependencies": {
|
|
110
|
+
"@changesets/cli": "^3.0.3",
|
|
111
|
+
"@expo/devtools": "^57.0.1",
|
|
112
|
+
"@tanstack/query-core": "^5.90.0",
|
|
113
|
+
"@types/bun": "^1.4.2",
|
|
114
|
+
"@types/react": "^19.3.0",
|
|
115
|
+
"@types/ws": "^8.18.1",
|
|
116
|
+
"tsup": "^8.5.1",
|
|
117
|
+
"typescript": "~6.0.3",
|
|
118
|
+
"zustand": "^5.0.15"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-bridge
|
|
3
|
+
description: "Drive a running React Native or Expo dev build directly through @avasapp/agent-bridge: see what's on screen, fill in forms, press buttons, seed query data, flip feature flags, set store state, navigate, mock the network and undo it all, in milliseconds per call instead of tapping through the app. Use whenever you need the app in a particular state to test a change, or need to verify what a screen shows."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Driving an app with agent-bridge
|
|
7
|
+
|
|
8
|
+
The app mounts `useAgentBridge({ tools })` in a dev build. You call those tools
|
|
9
|
+
by name. Nothing is tapped, so there is no setup to click through.
|
|
10
|
+
|
|
11
|
+
## Start a session
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npx agent-bridge session start # add --metro host:port if Metro isn't on localhost:8081
|
|
15
|
+
npx agent-bridge tools # what this app exposes
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
A session holds one connection for all your calls. It stops itself after 15
|
|
19
|
+
minutes without calls and runs `bridge.restore` when it does.
|
|
20
|
+
|
|
21
|
+
If `tools` fails with "agent-bridge isn't running", the hook isn't mounted in
|
|
22
|
+
this build. Say so; don't fall back to tapping silently.
|
|
23
|
+
|
|
24
|
+
## See and use the screen
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npx agent-bridge call screen.snapshot # buttons, inputs, text on screen
|
|
28
|
+
npx agent-bridge call screen.press '"add-plant"' # by testID, label or text
|
|
29
|
+
npx agent-bridge call screen.fill '["plant-name", "Fern"]' # runs the input's handlers
|
|
30
|
+
npx agent-bridge call screen.waitFor '"Name is required"' # {"gone": true} waits for it to go
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`press` and `fill` return once the app has rendered the result, so the next
|
|
34
|
+
check sees it. `fill` skips the keyboard: autocorrect and native-only input
|
|
35
|
+
behaviour need one real typing step from your device tool.
|
|
36
|
+
|
|
37
|
+
## Get the app into a state
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
npx agent-bridge call query.pin '[["features"], <data>]' # stays through refetches
|
|
41
|
+
npx agent-bridge call store.call '["settings", "setTheme", "dark"]'
|
|
42
|
+
npx agent-bridge call router.navigate /inbox
|
|
43
|
+
npx agent-bridge call router.current
|
|
44
|
+
npx agent-bridge call net.mock '["/inbox", {"status": 500}]' # or {"offline": true}
|
|
45
|
+
npx agent-bridge call net.log
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Arguments are a JSON array (or a single JSON value). Read the current value
|
|
49
|
+
first (`query.get`, `store.get`) and change only what you need.
|
|
50
|
+
|
|
51
|
+
## Watch for errors
|
|
52
|
+
|
|
53
|
+
Every reply carries errors the app logged or threw since the previous reply,
|
|
54
|
+
printed as `! error during <tool>` or `! error after <tool>`. Treat them as
|
|
55
|
+
failures of the step they name. `npx agent-bridge call bridge.logs` shows the
|
|
56
|
+
last 200 errors and warnings.
|
|
57
|
+
|
|
58
|
+
## Repeat without the model
|
|
59
|
+
|
|
60
|
+
Write the steps into a flow file and run it:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
export default async ({ step }) => {
|
|
64
|
+
await step('open form', 'screen.press', 'add-plant')
|
|
65
|
+
await step('save empty', 'screen.press', 'save-plant')
|
|
66
|
+
await step('error shown', 'screen.waitFor', 'Name is required')
|
|
67
|
+
await step('undo', 'bridge.restore')
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
npx agent-bridge run flow.mjs --strict # --strict fails if the app logged an error
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Clean up
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
npx agent-bridge session stop # runs bridge.restore; --keep leaves the app as is
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`bridge.restore` unpins queries, puts back store and MMKV values and removes
|
|
82
|
+
network mocks, so the next run starts from the real state.
|
|
83
|
+
|
|
84
|
+
## Traps
|
|
85
|
+
|
|
86
|
+
- Run next to the simulator. From another machine every call pays the network.
|
|
87
|
+
- It can't see native UI (system alerts, permission prompts, native sheets) or
|
|
88
|
+
overlap. Finish a flow with one real UI check or screenshot from your device tool.
|
|
89
|
+
- With several apps on one Metro, pass `--device`.
|