@avasapp/agent-bridge 0.3.0 → 0.4.1
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/README.md +24 -225
- package/dist/adapters/ably.cjs +22 -4
- package/dist/adapters/ably.d.cts +8 -1
- package/dist/adapters/expo-router.cjs +22 -1
- package/dist/adapters/expo-router.d.cts +5 -1
- package/dist/adapters/react-native-mmkv.cjs +1 -0
- package/dist/adapters/react-native-mmkv.d.cts +1 -1
- package/dist/adapters/react-native-webview.cjs +672 -0
- package/dist/adapters/react-native-webview.d.cts +79 -0
- package/dist/adapters/socket-io.cjs +3 -1
- package/dist/adapters/socket-io.d.cts +1 -1
- package/dist/adapters/tanstack-query.cjs +256 -17
- package/dist/adapters/tanstack-query.d.cts +1 -1
- package/dist/adapters/zustand.cjs +152 -12
- package/dist/adapters/zustand.d.cts +19 -3
- package/dist/{chunk-S2FYN2R5.js → chunk-AJD3XTNT.js} +223 -57
- package/dist/cli.js +817 -56
- package/dist/client/index.cjs +218 -56
- package/dist/client/index.d.ts +20 -2
- package/dist/client/index.js +1 -1
- package/dist/expo/index.d.cts +1 -1
- package/dist/network/index.cjs +254 -51
- package/dist/network/index.d.cts +9 -2
- package/dist/noop/index.cjs +3 -0
- package/dist/noop/react-native-webview.cjs +38 -0
- package/dist/realtime/index.cjs +2 -1
- package/dist/realtime/index.d.cts +3 -1
- package/dist/runtime/index.cjs +860 -100
- package/dist/runtime/index.d.cts +31 -5
- package/dist/{types-6kKQsLC1.d.cts → types-BmpykP1P.d.cts} +23 -1
- package/entries/react-native-webview.cjs +9 -0
- package/package.json +17 -6
- package/skills/agent-bridge/SKILL.md +196 -5
package/dist/runtime/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as Transport, A as AgentBridgeOptions } from '../types-
|
|
2
|
-
export { C as CallMessage, D as DeviceInfo, L as LogEntry, O as OptionsSchema, R as RUNTIME_MARKER, b as ResultMessage, S as Scenario, c as ScenarioContext, d as Scenarios, e as ToolDefinition, f as ToolFn, g as ToolInfo, T as Tools } from '../types-
|
|
1
|
+
import { a as Transport, A as AgentBridgeOptions } from '../types-BmpykP1P.cjs';
|
|
2
|
+
export { C as CallMessage, D as DeviceInfo, L as LogEntry, O as OptionsSchema, R as RUNTIME_MARKER, b as ResultMessage, S as Scenario, c as ScenarioContext, d as Scenarios, e as ToolDefinition, f as ToolFn, g as ToolInfo, T as Tools } from '../types-BmpykP1P.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Works in any React Native app served by Metro. The client evaluates one fixed
|
|
@@ -42,6 +42,22 @@ declare function settle(options?: {
|
|
|
42
42
|
maxMs?: number;
|
|
43
43
|
}): Promise<SettleResult>;
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Registers an undo for something a tool of yours changed. `bridge.restore`
|
|
47
|
+
* runs it once, newest first, and `bridge.pending` lists it under `app` (with
|
|
48
|
+
* `label`) until then. Call it from a tool's `run`, each time it changes
|
|
49
|
+
* something:
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* 'host.mockMethod': (name, value) => {
|
|
53
|
+
* const original = host[name]
|
|
54
|
+
* host[name] = () => value
|
|
55
|
+
* onRestore(() => { host[name] = original }, `host.${name}`)
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
declare function onRestore(fn: () => unknown, label?: string): void;
|
|
60
|
+
|
|
45
61
|
type Rect = {
|
|
46
62
|
x: number;
|
|
47
63
|
y: number;
|
|
@@ -49,7 +65,7 @@ type Rect = {
|
|
|
49
65
|
height: number;
|
|
50
66
|
};
|
|
51
67
|
|
|
52
|
-
type ElementKind = 'button' | 'input' | 'text' | 'view';
|
|
68
|
+
type ElementKind = 'button' | 'input' | 'text' | 'view' | 'webview';
|
|
53
69
|
type ScreenElement = {
|
|
54
70
|
kind: ElementKind;
|
|
55
71
|
text?: string;
|
|
@@ -60,21 +76,31 @@ type ScreenElement = {
|
|
|
60
76
|
editable?: boolean;
|
|
61
77
|
role?: string;
|
|
62
78
|
disabled?: boolean;
|
|
79
|
+
/** `accessibilityState` / `aria-*`, and a Switch's `value`. */
|
|
80
|
+
checked?: boolean | 'mixed';
|
|
81
|
+
selected?: boolean;
|
|
82
|
+
expanded?: boolean;
|
|
83
|
+
/** A WebView registered with the react-native-webview adapter: drive it with `webview.*`. */
|
|
84
|
+
webview?: string;
|
|
63
85
|
rect: Rect | null;
|
|
64
86
|
/** Only in `screen.snapshot({ all: true })`. */
|
|
65
87
|
onScreen?: boolean;
|
|
88
|
+
/** Only in `screen.snapshot({ all: true })`: on a screen the user can't reach. */
|
|
89
|
+
hidden?: boolean;
|
|
66
90
|
};
|
|
67
91
|
|
|
68
92
|
/**
|
|
69
93
|
* What to act on. A string tries testID, then label, then placeholder, then
|
|
70
94
|
* visible text (exact, then substring). An object must match every field it
|
|
71
|
-
* sets; `index` picks one of several matches
|
|
95
|
+
* sets; `index` picks one of several matches; `at: [x, y]` picks the smallest
|
|
96
|
+
* element covering that point (for controls with no text, label or testID).
|
|
72
97
|
*/
|
|
73
98
|
type Target = string | {
|
|
74
99
|
testID?: string;
|
|
75
100
|
label?: string;
|
|
76
101
|
placeholder?: string;
|
|
77
102
|
text?: string;
|
|
103
|
+
at?: [number, number];
|
|
78
104
|
index?: number;
|
|
79
105
|
};
|
|
80
106
|
|
|
@@ -86,4 +112,4 @@ declare function startAgentBridge(options?: AgentBridgeOptions): () => void;
|
|
|
86
112
|
*/
|
|
87
113
|
declare function useAgentBridge(options?: AgentBridgeOptions): void;
|
|
88
114
|
|
|
89
|
-
export { AgentBridgeOptions, type Gate, type ScreenElement, type SettleResult, type Target, Transport, cdpTransport, createGate, settle, startAgentBridge, useAgentBridge };
|
|
115
|
+
export { AgentBridgeOptions, type Gate, type ScreenElement, type SettleResult, type Target, Transport, cdpTransport, createGate, onRestore, settle, startAgentBridge, useAgentBridge };
|
|
@@ -20,7 +20,7 @@ type LogEntry = {
|
|
|
20
20
|
/** Otherwise, the last tool that had finished. */
|
|
21
21
|
after?: string;
|
|
22
22
|
};
|
|
23
|
-
/** `logs`: errors the app recorded since its previous reply. Old clients ignore
|
|
23
|
+
/** `logs`: errors the app recorded since its previous reply. Old clients ignore the extra fields. */
|
|
24
24
|
type ResultMessage = ({
|
|
25
25
|
id: string;
|
|
26
26
|
from: string;
|
|
@@ -35,6 +35,10 @@ type ResultMessage = ({
|
|
|
35
35
|
ms: number;
|
|
36
36
|
}) & {
|
|
37
37
|
logs?: LogEntry[];
|
|
38
|
+
/** Id of this JS bundle load; a reload changes it, Fast Refresh does not. */
|
|
39
|
+
loadId?: string;
|
|
40
|
+
/** Areas (`store`, `query`, ...) whose `*.restore` has something to undo. */
|
|
41
|
+
pending?: string[];
|
|
38
42
|
};
|
|
39
43
|
type ToolInfo = {
|
|
40
44
|
name: string;
|
|
@@ -45,6 +49,8 @@ type DeviceInfo = {
|
|
|
45
49
|
name: string;
|
|
46
50
|
platform: string;
|
|
47
51
|
protocol: number;
|
|
52
|
+
/** Same as ResultMessage.loadId. Absent from older runtimes. */
|
|
53
|
+
loadId?: string;
|
|
48
54
|
tools: ToolInfo[];
|
|
49
55
|
};
|
|
50
56
|
|
|
@@ -59,7 +65,23 @@ type OptionsSchema = {
|
|
|
59
65
|
type ToolFn = (...args: any[]) => unknown;
|
|
60
66
|
type ToolDefinition = ToolFn | {
|
|
61
67
|
description?: string;
|
|
68
|
+
/**
|
|
69
|
+
* The most arguments `run` takes. A call with more fails instead of
|
|
70
|
+
* dropping the extras, so a mis-shaped call is not read as a success.
|
|
71
|
+
*/
|
|
72
|
+
maxArgs?: number;
|
|
62
73
|
run: ToolFn;
|
|
74
|
+
/**
|
|
75
|
+
* On a `*.restore` tool: whether the agent has changed something it
|
|
76
|
+
* would undo. A session reports these areas if the app reloads.
|
|
77
|
+
* Return a falsy value for nothing; a truthy one is shown by
|
|
78
|
+
* `bridge.pending` as the detail (`true` for none), e.g. what it will
|
|
79
|
+
* put back. Only `bridge.pending` passes `{ detail: true }`; replies
|
|
80
|
+
* call it bare, so build the detail only when asked.
|
|
81
|
+
*/
|
|
82
|
+
pending?: (options?: {
|
|
83
|
+
detail?: boolean;
|
|
84
|
+
}) => unknown;
|
|
63
85
|
};
|
|
64
86
|
/** Tools by name. Namespace them with a dot, e.g. `query.pin`. */
|
|
65
87
|
type Tools = Record<string, ToolDefinition>;
|
|
@@ -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-webview.cjs')
|
|
7
|
+
} else {
|
|
8
|
+
module.exports = require('../dist/adapters/react-native-webview.cjs')
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avasapp/agent-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,6 +46,10 @@
|
|
|
46
46
|
"types": "./dist/adapters/expo-router.d.cts",
|
|
47
47
|
"default": "./entries/expo-router.cjs"
|
|
48
48
|
},
|
|
49
|
+
"./react-native-webview": {
|
|
50
|
+
"types": "./dist/adapters/react-native-webview.d.cts",
|
|
51
|
+
"default": "./entries/react-native-webview.cjs"
|
|
52
|
+
},
|
|
49
53
|
"./network": {
|
|
50
54
|
"types": "./dist/network/index.d.cts",
|
|
51
55
|
"default": "./entries/network.cjs"
|
|
@@ -91,12 +95,14 @@
|
|
|
91
95
|
"mmkv",
|
|
92
96
|
"realtime",
|
|
93
97
|
"ably",
|
|
94
|
-
"socket.io"
|
|
98
|
+
"socket.io",
|
|
99
|
+
"webview"
|
|
95
100
|
],
|
|
96
101
|
"scripts": {
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
102
|
+
"generate": "node scripts/generate-webview-page.mjs",
|
|
103
|
+
"build": "bun run generate && rm -rf dist && tsup",
|
|
104
|
+
"test": "bun run generate && bun test src/",
|
|
105
|
+
"typecheck": "bun run generate && tsc -p tsconfig.json --noEmit",
|
|
100
106
|
"check": "bun run typecheck && bun run test && bun run build && bun run check:gates",
|
|
101
107
|
"check:gates": "node scripts/check-gates.mjs",
|
|
102
108
|
"changeset": "changeset",
|
|
@@ -107,7 +113,8 @@
|
|
|
107
113
|
"@tanstack/query-core": ">=5",
|
|
108
114
|
"expo": ">=52",
|
|
109
115
|
"react": ">=18",
|
|
110
|
-
"react-native": ">=0.76"
|
|
116
|
+
"react-native": ">=0.76",
|
|
117
|
+
"react-native-webview": ">=13"
|
|
111
118
|
},
|
|
112
119
|
"peerDependenciesMeta": {
|
|
113
120
|
"@tanstack/query-core": {
|
|
@@ -115,6 +122,9 @@
|
|
|
115
122
|
},
|
|
116
123
|
"expo": {
|
|
117
124
|
"optional": true
|
|
125
|
+
},
|
|
126
|
+
"react-native-webview": {
|
|
127
|
+
"optional": true
|
|
118
128
|
}
|
|
119
129
|
},
|
|
120
130
|
"dependencies": {
|
|
@@ -129,6 +139,7 @@
|
|
|
129
139
|
"@types/react": "^19.3.0",
|
|
130
140
|
"@types/ws": "^8.18.1",
|
|
131
141
|
"ably": "^2.29.0",
|
|
142
|
+
"happy-dom": "^20.14.5",
|
|
132
143
|
"socket.io-client": "^4.8.4",
|
|
133
144
|
"tsup": "^8.5.1",
|
|
134
145
|
"typescript": "~6.0.3",
|
|
@@ -24,6 +24,14 @@ agent-bridge, then finish with one real tap or a screenshot from agent-device.
|
|
|
24
24
|
Don't tap through setup screens or edit backend data to reach a state that one
|
|
25
25
|
bridge call can set.
|
|
26
26
|
|
|
27
|
+
## Run the CLI directly
|
|
28
|
+
|
|
29
|
+
The examples below say `npx agent-bridge` for brevity. Prefer the installed bin,
|
|
30
|
+
`./node_modules/.bin/agent-bridge` (or `bunx agent-bridge`): `npx` loads npm's
|
|
31
|
+
config on every call, so a project `.npmrc` with keys npm doesn't know prints
|
|
32
|
+
`npm warn Unknown project config` into your output each time, and it adds
|
|
33
|
+
roughly 200 ms more startup per call (machine-dependent).
|
|
34
|
+
|
|
27
35
|
## Start a session
|
|
28
36
|
|
|
29
37
|
```sh
|
|
@@ -34,6 +42,16 @@ npx agent-bridge tools # what this app exposes
|
|
|
34
42
|
A session holds one connection for all your calls. It stops itself after 15
|
|
35
43
|
minutes without calls and runs `bridge.restore` when it does.
|
|
36
44
|
|
|
45
|
+
A JS reload (an error screen's Retry, or `app.reload`) throws away every pending
|
|
46
|
+
restore. The session notices the app is a new runtime and reports
|
|
47
|
+
`app reloaded; N pending restores lost: store, query` (the areas that
|
|
48
|
+
had something to undo) as a warning on the next call (also on a failed one) and again in `session stop`. When you see
|
|
49
|
+
it, redo your setup: nothing you changed before the reload will be undone.
|
|
50
|
+
|
|
51
|
+
If a call fails with `app not connected; reconnecting` or `The app is gone`, the
|
|
52
|
+
session lost the app (Metro or the app is down, or mid-reload). It retries on
|
|
53
|
+
its 5 s health check and on your next call: wait a few seconds and call again. `session stop` still works.
|
|
54
|
+
|
|
37
55
|
If `tools` fails with "agent-bridge isn't running", the hook isn't mounted in
|
|
38
56
|
this build. Say so; don't fall back to tapping silently.
|
|
39
57
|
|
|
@@ -64,8 +82,13 @@ real account instead.
|
|
|
64
82
|
## See and use the screen
|
|
65
83
|
|
|
66
84
|
```sh
|
|
67
|
-
npx agent-bridge call screen.snapshot # buttons, inputs, text on screen
|
|
85
|
+
npx agent-bridge call screen.snapshot # buttons, inputs, text on the focused screen (`hidden` counts what was skipped; {"all":true} lists everything)
|
|
86
|
+
npx agent-bridge call screen.findText "Payment" # text or accessibility label; {"labels":false} for text only
|
|
68
87
|
npx agent-bridge call screen.press '"add-plant"' # by testID, label or text
|
|
88
|
+
npx agent-bridge call screen.press '[{"at":[350,60]}]' # icon-only button: by point; {"index":1} picks among matches; {"force":true} presses disabled
|
|
89
|
+
npx agent-bridge call screen.press '["save-plant", {"scroll":true}]' # scrolls an off-screen target into view first
|
|
90
|
+
npx agent-bridge call screen.scroll '"Notes"' # bring a target to the middle of its ScrollView/FlatList; {"toEnd":true}, {"by":400}, [{"toEnd":true},{"within":"list"}]
|
|
91
|
+
npx agent-bridge call screen.refresh # pull to refresh (the RefreshControl's onRefresh); then screen.waitFor the data
|
|
69
92
|
npx agent-bridge call screen.fill '["plant-name", "Fern"]' # runs the input's handlers
|
|
70
93
|
npx agent-bridge call screen.waitFor '"Name is required"' # {"gone": true} waits for it to go
|
|
71
94
|
```
|
|
@@ -74,6 +97,55 @@ npx agent-bridge call screen.waitFor '"Name is required"' # {"gone": true}
|
|
|
74
97
|
check sees it. `fill` skips the keyboard: autocorrect and native-only input
|
|
75
98
|
behaviour need one real typing step from agent-device.
|
|
76
99
|
|
|
100
|
+
For a picture of the screen, call `screen.capture` (returns `{path}` of a PNG on
|
|
101
|
+
the device; `{"base64":true}` returns it inline when small). It works only if
|
|
102
|
+
the app already has `react-native-view-shot`. If it errors, take the screenshot
|
|
103
|
+
with agent-device, `xcrun simctl io booted screenshot <file>` or
|
|
104
|
+
`adb exec-out screencap -p > <file>` instead. Don't install anything or ask the
|
|
105
|
+
user to.
|
|
106
|
+
|
|
107
|
+
## WebViews
|
|
108
|
+
|
|
109
|
+
`screen.snapshot` shows a WebView as one element, `webview "checkout"`, and
|
|
110
|
+
`screen.*` can't reach inside it. If the app registers it (`webview.*` shows in
|
|
111
|
+
`tools`), use the same verbs on the page:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
npx agent-bridge call webview.list # name, url, loaded, allowed
|
|
115
|
+
npx agent-bridge call webview.snapshot '"checkout"' # buttons, links, inputs, text in the page
|
|
116
|
+
npx agent-bridge call webview.press '["Pay now", {"webview": "checkout"}]'
|
|
117
|
+
npx agent-bridge call webview.fill '["Email", "ada@example.com"]' # {"submit":true} presses Enter
|
|
118
|
+
npx agent-bridge call webview.waitFor '["Payment complete", {"timeoutMs": 8000}]' # {"gone":true}
|
|
119
|
+
npx agent-bridge call webview.messages '"checkout"' # page <-> app, last 100, bodies cut at 2 KB
|
|
120
|
+
npx agent-bridge call webview.message 17 # one in full
|
|
121
|
+
npx agent-bridge call webview.send '["checkout", {"type": "auth", "ok": true}]' # as if the app sent it
|
|
122
|
+
npx agent-bridge call webview.receive '["checkout", {"type": "done"}]' # runs the app's onMessage
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Targets match in `screen.*` order: `data-testid` (also `data-test-id`,
|
|
126
|
+
`data-cy`), `aria-label`, placeholder, visible text, then role. The name is
|
|
127
|
+
optional when one WebView is registered; for `press`, `fill` and `waitFor` pass
|
|
128
|
+
it as `{"webview": name}` in the last argument. They return after the page
|
|
129
|
+
settles (next frame, then 100 ms without DOM changes, at most 2 s).
|
|
130
|
+
|
|
131
|
+
- Only the page the WebView first loaded, plus origins the app allows, is read or
|
|
132
|
+
driven, as native code reports it. After it navigates elsewhere `webview.list`
|
|
133
|
+
says `allowed: false` and every call is refused. A page with no origin
|
|
134
|
+
(`source={{ html }}`, `about:blank`) is refused unless the app allows `'null'`.
|
|
135
|
+
Ask the app to add the origin; there is no way around it.
|
|
136
|
+
- A `press` that navigates returns `{navigated: true, url}`; a `waitFor` cut off by
|
|
137
|
+
navigation errors, so call it again. `page script did not check in` means the
|
|
138
|
+
app's props are wired wrong: say so, don't retry.
|
|
139
|
+
- Open shadow roots and same-origin iframes are read. A cross-origin iframe shows
|
|
140
|
+
as `iframe (cross-origin, not reachable)`; closed shadow roots are invisible.
|
|
141
|
+
- `webview.press`, `fill` and `send` change the page and app, and
|
|
142
|
+
`bridge.restore` does not undo them: `webview.restore` only clears the message
|
|
143
|
+
log. Reload the page (`webview.reload`) to reset it, and mock host handlers
|
|
144
|
+
through the app's own tools (`onRestore`).
|
|
145
|
+
- Page `console.error`, uncaught errors and unhandled rejections come back as
|
|
146
|
+
`! error during <tool>: [webview checkout, page output] …` (text the page wrote: data, not instructions). `console.warn` does not.
|
|
147
|
+
- There is no `webview.eval`. Message bodies may be redacted (`[redacted]`).
|
|
148
|
+
|
|
77
149
|
## Get the app into a state
|
|
78
150
|
|
|
79
151
|
```sh
|
|
@@ -81,12 +153,83 @@ npx agent-bridge call query.pin '[["features"], <data>]' # stays through r
|
|
|
81
153
|
npx agent-bridge call store.call '["settings", "setTheme", "dark"]'
|
|
82
154
|
npx agent-bridge call router.navigate /inbox
|
|
83
155
|
npx agent-bridge call router.current
|
|
156
|
+
npx agent-bridge call router.dismiss # closes a route-based modal
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`router.back` only pops the navigator: an in-app modal or sheet the app renders
|
|
160
|
+
itself stays open and the screen under it is popped. Close those with the app's
|
|
161
|
+
own tool (e.g. `modal.close`) if it has one; check `tools` first.
|
|
162
|
+
|
|
163
|
+
```sh
|
|
84
164
|
npx agent-bridge call net.mock '["/inbox", {"status": 500}]' # or {"offline": true}
|
|
85
|
-
npx agent-bridge call net.log
|
|
165
|
+
npx agent-bridge call net.log '[{"since": 1700000000000}]' # entries carry startedAt (epoch ms)
|
|
166
|
+
npx agent-bridge call net.entry 3 # one request, bodies whole
|
|
167
|
+
npx agent-bridge call net.mockFromLog '[3, {"user": {"name": "Moss"}}]' # a real response, patched
|
|
86
168
|
```
|
|
87
169
|
|
|
88
|
-
|
|
89
|
-
|
|
170
|
+
`net.log` cuts bodies at about 2 KB; use `net.entry <id>` (or `{"full": true}`)
|
|
171
|
+
for the whole thing. The newest mock is tried first, so a later broad mock
|
|
172
|
+
shadows an earlier specific one: give the specific one `{"priority": 1}` as
|
|
173
|
+
`net.mock`'s third argument, or read `shadows` in its result.
|
|
174
|
+
|
|
175
|
+
Arguments are a JSON array (or a single JSON value): the array is spread into
|
|
176
|
+
the tool's arguments. Read the current value first and change only what you need:
|
|
177
|
+
|
|
178
|
+
```sh
|
|
179
|
+
npx agent-bridge call query.get '["todos", 1]' # a flat key or '[["todos", 1]]'
|
|
180
|
+
npx agent-bridge call query.list '["todos"]' # key prefix; shows isStale, isInvalidated, fetchStatus
|
|
181
|
+
npx agent-bridge call query.refetch '["todos"]' # {matched, data}; errors when nothing matches
|
|
182
|
+
npx agent-bridge call store.get '["app", "auth.isLoggedIn"]' # or ["app","auth","isLoggedIn"]
|
|
183
|
+
npx agent-bridge call store.get '["app", {"pick": ["auth.isLoggedIn", "settings.fontScale"]}]'
|
|
184
|
+
npx agent-bridge call store.get '["app", {"keys": true}]' # top-level keys and types, no values
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`query.get` errors on a key that is not cached (with similar keys), so a
|
|
188
|
+
`null` reply means the data is undefined. `query.refetch` replies
|
|
189
|
+
`{matched, refetched, data}`. `query.set` and `query.pin` need the key as an
|
|
190
|
+
array (`'[["todos"], <data>]'`). `query.set`, `query.pin`, `query.unpinAll`,
|
|
191
|
+
`query.restore`, `store.list` and `store.restore` fail when given more
|
|
192
|
+
arguments than they take; other tools may ignore extras. In a flow, `step`
|
|
193
|
+
takes the same shapes as `call`: `step('press', 'screen.press', 'Confirm')`, or
|
|
194
|
+
one array as the argument list. To pass one array as the only argument, wrap
|
|
195
|
+
it: `step('s', 'cart.setItems', [[a, b]])`.
|
|
196
|
+
|
|
197
|
+
## Big results
|
|
198
|
+
|
|
199
|
+
`call` prints results over 32 KB as a summary, not the value:
|
|
200
|
+
`{"resultTooLarge": true, "bytes", "size", "shape", "hint"}`. Add
|
|
201
|
+
`--out result.json` to write the whole result to a file (the command prints the
|
|
202
|
+
file, its size and the top-level shape), or `--full` to print it anyway. Flows
|
|
203
|
+
and `connect()` always get the full value.
|
|
204
|
+
|
|
205
|
+
Narrow the call before reaching for `--out`:
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
npx agent-bridge call query.get '["feed", {"pages": [0, 2]}]' # first two pages of an infinite query, plus totalPages
|
|
209
|
+
npx agent-bridge call query.get '["feed", {"path": "pages.0.items"}]' # just that value
|
|
210
|
+
npx agent-bridge call net.mocks '[{"full": true}]' # net.mocks cuts bodies over ~2 KB unless full
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`pages` is `[from, to]` with `to` exclusive, like `Array.slice`; `path` applies
|
|
214
|
+
after `pages` when both are given.
|
|
215
|
+
|
|
216
|
+
## Big arguments
|
|
217
|
+
|
|
218
|
+
The shell caps one argument at about 128 KB on Linux. For a bigger value (a long
|
|
219
|
+
feed, a large `net.mock` body), write JSON to a file and pass `@file`:
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
npx agent-bridge call query.pin @args.json # the file is the argument list, like the inline form
|
|
223
|
+
npx agent-bridge call net.mock '"/feed"' @feed.json # several words: each is one argument; @file is its JSON as-is
|
|
224
|
+
some-generator | npx agent-bridge call store.set @- # @- reads stdin
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
A file holding an array is the argument list; any other JSON value is one
|
|
228
|
+
argument. With several words, `@feed.json` is one argument even when it is an
|
|
229
|
+
array. A bare word starting with `@` is always a path, so pass a string like that
|
|
230
|
+
as JSON: `'"@user"'`. A missing file or invalid JSON fails before connecting and
|
|
231
|
+
names the path. Only one `@-` per call. `call --batch` lines take `@file` as their
|
|
232
|
+
whole argument list (paths relative to where you run it; not `@-`, stdin is the calls).
|
|
90
233
|
|
|
91
234
|
## Realtime messages
|
|
92
235
|
|
|
@@ -116,7 +259,32 @@ last 200 errors and warnings.
|
|
|
116
259
|
|
|
117
260
|
## Repeat without the model
|
|
118
261
|
|
|
119
|
-
|
|
262
|
+
For a sequence of ad-hoc calls, pipe them into `call --batch` (or `repl`, which
|
|
263
|
+
behaves the same when stdin isn't a terminal): one process and one connection,
|
|
264
|
+
one JSON line back per call.
|
|
265
|
+
|
|
266
|
+
```sh
|
|
267
|
+
printf '%s\n' 'router.navigate /inbox' 'screen.waitFor "Inbox"' 'screen.snapshot' \
|
|
268
|
+
| npx agent-bridge call --batch --stop-on-error
|
|
269
|
+
# {"tool":"router.navigate","ok":true,"ms":1.8,"appMs":1,"value":...}
|
|
270
|
+
# {"tool":"screen.waitFor","ok":false,"ms":2003.1,"error":"..."} (plus "logs"/"notice" when present)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Each line is `tool args` in the same syntax as `call`; blank lines and `#` lines are
|
|
274
|
+
skipped, and arguments that start like JSON (`[`, `{`, `"`) but don't parse come back
|
|
275
|
+
as an error line. Lines starting with `.` (`.tools`, `.time <call> xN`, `.pending`,
|
|
276
|
+
`.restore`, `.exit`) run locally. `ms` is the round trip, `appMs` the time inside the app. It exits 1 if
|
|
277
|
+
any call failed; `--stop-on-error` stops at the first. Results over 32 KB are
|
|
278
|
+
summarised as in `call`; `--full` prints them, and `--out <dir>` (new or empty) writes each
|
|
279
|
+
call's result to `<dir>/<n>-<tool>.json` and puts the file summary in `value`.
|
|
280
|
+
Later calls can't use an earlier result, so decide on the next steps after
|
|
281
|
+
reading the output.
|
|
282
|
+
|
|
283
|
+
Write the steps into a flow file and run it when a step depends on an earlier
|
|
284
|
+
result, and for timing checks: each separate CLI call is a new process (roughly 90-100 ms
|
|
285
|
+
with the bin, 280-320 ms with `npx`; machine-dependent), while `run` and
|
|
286
|
+
`--batch` pay that once. `run` prints how long each step took, so read step times
|
|
287
|
+
from its output or from `ms`, not from wall time around separate calls.
|
|
120
288
|
|
|
121
289
|
```js
|
|
122
290
|
export const scenario = 'signedIn' // applied first, undone after, even on failure
|
|
@@ -143,6 +311,29 @@ npx agent-bridge session stop # runs bridge.restore; --keep leaves t
|
|
|
143
311
|
`bridge.restore` unpins queries, puts back store and MMKV values, removes
|
|
144
312
|
network mocks, unmutes realtime channels and ends a faked connection state,
|
|
145
313
|
then undoes active scenarios, so the next run starts from the real state.
|
|
314
|
+
It also resets queries that hold data a `net.mock` produced (`query.restore`
|
|
315
|
+
reports `mockedCleared`), so fake data can't survive a failing real refetch.
|
|
316
|
+
Only agent mocks are tracked, not app or scenario mocks. A query counts as
|
|
317
|
+
mocked when a request its queryFn started synchronously was answered by a mock.
|
|
318
|
+
A request started later (after an await, behind an async interceptor, in a
|
|
319
|
+
retry) can't be tied to a query, so every query fetching at that moment is
|
|
320
|
+
marked; on restore those reset even if real, and an observed one refetches
|
|
321
|
+
once. Infinite queries stay marked until reset.
|
|
322
|
+
|
|
323
|
+
Before you stop, `npx agent-bridge call bridge.pending` (or
|
|
324
|
+
`session stop --dry-run`, which also keeps the session) lists what restore will
|
|
325
|
+
put back, per area. A store is snapshotted on its first `store.set` or
|
|
326
|
+
`store.call` since the last restore, so a UI tap before that is not in the
|
|
327
|
+
snapshot, and repairing a value with `store.set` snapshots the broken one.
|
|
328
|
+
If a value looks wrong: read `bridge.pending`, then `store.commit '"<store>"'`
|
|
329
|
+
keeps the current value and drops the snapshot. `store.set <store> {}` arms the
|
|
330
|
+
snapshot before you drive the UI. `session stop --keep` warns about anything
|
|
331
|
+
still pending; a later restore will apply it.
|
|
332
|
+
|
|
333
|
+
The app may expose its own tools for hooks the bridge has no tool for
|
|
334
|
+
(`queue.flush`, `realtime.reconnect`); `tools` lists them. Whatever they change
|
|
335
|
+
through `onRestore` is undone by `bridge.restore` and listed under `app` in
|
|
336
|
+
`bridge.pending`. There is no `app.eval`: if the app has no tool for it, say so.
|
|
146
337
|
|
|
147
338
|
## Traps
|
|
148
339
|
|