flow-walker-cli 0.2.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/AGENTS.md +299 -0
- package/CLAUDE.md +81 -0
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/package.json +21 -0
- package/src/agent-bridge.ts +189 -0
- package/src/capture.ts +102 -0
- package/src/cli.ts +352 -0
- package/src/command-schema.ts +178 -0
- package/src/errors.ts +63 -0
- package/src/fingerprint.ts +82 -0
- package/src/flow-parser.ts +222 -0
- package/src/graph.ts +73 -0
- package/src/push.ts +170 -0
- package/src/reporter.ts +211 -0
- package/src/run-schema.ts +71 -0
- package/src/runner.ts +391 -0
- package/src/safety.ts +74 -0
- package/src/types.ts +82 -0
- package/src/validate.ts +115 -0
- package/src/walker.ts +656 -0
- package/src/yaml-writer.ts +194 -0
- package/tests/capture.test.ts +75 -0
- package/tests/command-schema.test.ts +133 -0
- package/tests/errors.test.ts +93 -0
- package/tests/fingerprint.test.ts +85 -0
- package/tests/flow-parser.test.ts +264 -0
- package/tests/graph.test.ts +111 -0
- package/tests/reporter.test.ts +188 -0
- package/tests/run-schema.test.ts +138 -0
- package/tests/runner.test.ts +150 -0
- package/tests/safety.test.ts +115 -0
- package/tests/validate.test.ts +193 -0
- package/tests/yaml-writer.test.ts +146 -0
- package/tsconfig.json +15 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
# flow-walker — Agent Workflow Guide
|
|
2
|
+
|
|
3
|
+
## What is flow-walker
|
|
4
|
+
|
|
5
|
+
flow-walker is the **flow layer** — it discovers, executes, and reports on app flows.
|
|
6
|
+
It uses [agent-flutter](https://github.com/beastoin/agent-flutter) and [agent-swift](https://github.com/beastoin/agent-swift) as **transport layers** that control specific platforms.
|
|
7
|
+
|
|
8
|
+
**Six commands:**
|
|
9
|
+
- `walk` — BFS-explore the app, discover screens, generate YAML flows
|
|
10
|
+
- `run` — Execute a YAML flow, produce run.json + video + screenshots
|
|
11
|
+
- `report` — Generate self-contained HTML report from run results
|
|
12
|
+
- `push` — Upload report to hosted service, return shareable URL
|
|
13
|
+
- `get` — Fetch run data from hosted service by run ID
|
|
14
|
+
- `schema` — Machine-readable command introspection (agent discovery)
|
|
15
|
+
|
|
16
|
+
## Agent-first workflow
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 1. Discover available commands
|
|
20
|
+
flow-walker schema # → { version, commands: [...] }
|
|
21
|
+
flow-walker schema run # → args, flags with types, exit codes
|
|
22
|
+
|
|
23
|
+
# 2. Dry-run to verify flow resolves
|
|
24
|
+
flow-walker run flow.yaml --dry-run # → per-step resolved/unresolved + reasons
|
|
25
|
+
|
|
26
|
+
# 3. Execute
|
|
27
|
+
flow-walker run flow.yaml --json # → run.json with unique run ID
|
|
28
|
+
|
|
29
|
+
# 4. Report
|
|
30
|
+
flow-walker report ./run-output/<run-id>/
|
|
31
|
+
|
|
32
|
+
# 5. Share (hosted)
|
|
33
|
+
flow-walker push ./run-output/<run-id>/ --json # → { id, url, htmlUrl, expiresAt }
|
|
34
|
+
|
|
35
|
+
# 6. Retrieve run data later
|
|
36
|
+
flow-walker get 25h7afGwBK --json # → run.json content
|
|
37
|
+
|
|
38
|
+
# Version check
|
|
39
|
+
flow-walker --version # → flow-walker 0.1.0
|
|
40
|
+
flow-walker --version --json # → {"version":"0.1.0"}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Prerequisites
|
|
44
|
+
|
|
45
|
+
1. **agent-flutter** installed and in PATH (`npm install -g agent-flutter-cli`)
|
|
46
|
+
2. Flutter app running with Marionette initialized
|
|
47
|
+
3. ADB connected (Android) or Simulator running (iOS)
|
|
48
|
+
4. Run `agent-flutter doctor` to verify setup
|
|
49
|
+
|
|
50
|
+
## Run IDs
|
|
51
|
+
|
|
52
|
+
Every `flow-walker run` generates a unique **10-char base64url ID** (e.g. `25h7afGwBK`).
|
|
53
|
+
|
|
54
|
+
- Output goes to `<output-dir>/<run-id>/` — multiple runs never overwrite
|
|
55
|
+
- `run.json` includes `"id": "25h7afGwBK"` as top-level field
|
|
56
|
+
- Agents can correlate runs by ID across logs, reports, and API calls
|
|
57
|
+
- Composite key `{flow}/{id}` (e.g. `tab-navigation/25h7afGwBK`) for human reference
|
|
58
|
+
|
|
59
|
+
## Canonical workflows
|
|
60
|
+
|
|
61
|
+
### Auto-explore an app
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
agent-flutter connect ws://127.0.0.1:38047/abc=/ws
|
|
65
|
+
flow-walker walk --skip-connect --max-depth 3 --output-dir ./flows/
|
|
66
|
+
# Output: YAML flows + _nav-graph.json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Execute a flow
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
flow-walker run flows/tab-navigation.yaml --output-dir ./results/
|
|
73
|
+
# => Run ID: 25h7afGwBK
|
|
74
|
+
# => Output: ./results/25h7afGwBK/run.json, recording.mp4, step-*.png, device.log
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Generate report
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
flow-walker report ./results/25h7afGwBK/
|
|
81
|
+
# Output: report.html (self-contained, can be shared)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Run a flow suite
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
for flow in flows/*.yaml; do
|
|
88
|
+
flow-walker run "$flow" --output-dir ./results/ --json
|
|
89
|
+
done
|
|
90
|
+
# Each run gets its own subdirectory by run ID
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Output shapes
|
|
94
|
+
|
|
95
|
+
### run.json
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"id": "25h7afGwBK",
|
|
100
|
+
"flow": "tab-navigation",
|
|
101
|
+
"device": "Pixel_7a",
|
|
102
|
+
"startedAt": "2026-03-12T10:00:00Z",
|
|
103
|
+
"duration": 14200,
|
|
104
|
+
"result": "pass",
|
|
105
|
+
"steps": [
|
|
106
|
+
{
|
|
107
|
+
"index": 0,
|
|
108
|
+
"name": "Verify home tab",
|
|
109
|
+
"action": "assert",
|
|
110
|
+
"status": "pass",
|
|
111
|
+
"timestamp": 0,
|
|
112
|
+
"duration": 2300,
|
|
113
|
+
"elementCount": 22,
|
|
114
|
+
"screenshot": "step-1-tab-home.png",
|
|
115
|
+
"assertion": {
|
|
116
|
+
"interactive_count": { "min": 20, "actual": 22 },
|
|
117
|
+
"bottom_nav_tabs": { "min": 4, "actual": 4 }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"video": "recording.mp4",
|
|
122
|
+
"log": "device.log"
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### _nav-graph.json (from walk)
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"nodes": [
|
|
131
|
+
{ "id": "abc123", "name": "home-screen", "elementCount": 24, "visits": 3 }
|
|
132
|
+
],
|
|
133
|
+
"edges": [
|
|
134
|
+
{ "source": "abc123", "target": "def456", "element": { "ref": "@e3", "type": "button", "text": "Settings" } }
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Schema envelope (from schema)
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"version": "0.1.0",
|
|
144
|
+
"commands": [
|
|
145
|
+
{
|
|
146
|
+
"name": "run",
|
|
147
|
+
"description": "Execute a YAML flow...",
|
|
148
|
+
"args": [{ "name": "flow", "required": true, "type": "path", "description": "..." }],
|
|
149
|
+
"flags": [{ "name": "--json", "type": "boolean", "description": "..." }],
|
|
150
|
+
"exitCodes": { "0": "all steps pass", "1": "one or more steps fail", "2": "error" },
|
|
151
|
+
"examples": ["flow-walker run flows/tab-navigation.yaml"]
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### push result (from push --json)
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"id": "25h7afGwBK",
|
|
162
|
+
"url": "https://flow-walker.beastoin.workers.dev/runs/25h7afGwBK",
|
|
163
|
+
"htmlUrl": "https://flow-walker.beastoin.workers.dev/runs/25h7afGwBK.html",
|
|
164
|
+
"expiresAt": "2026-04-11T13:22:12.070Z"
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Command outputShape (from schema)
|
|
169
|
+
|
|
170
|
+
Commands that produce structured output declare their fields via `outputShape`:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"name": "run",
|
|
175
|
+
"outputShape": [
|
|
176
|
+
{ "name": "id", "type": "string", "description": "Unique 10-char run ID" },
|
|
177
|
+
{ "name": "flow", "type": "string", "description": "Flow name" },
|
|
178
|
+
{ "name": "result", "type": "pass|fail", "description": "Overall result" },
|
|
179
|
+
{ "name": "duration", "type": "number", "description": "Total milliseconds" },
|
|
180
|
+
{ "name": "steps", "type": "StepResult[]", "description": "Per-step results" }
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Commands with `outputShape`: `run`, `push`, `get`. Use `flow-walker schema <cmd>` to inspect.
|
|
186
|
+
|
|
187
|
+
### Agent-readable run data
|
|
188
|
+
|
|
189
|
+
After push, structured run data is available. URLs are agent-first — JSON by default:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# JSON (default) — for agents
|
|
193
|
+
curl https://flow-walker.beastoin.workers.dev/runs/25h7afGwBK
|
|
194
|
+
curl https://flow-walker.beastoin.workers.dev/runs/25h7afGwBK.json
|
|
195
|
+
|
|
196
|
+
# HTML — for humans
|
|
197
|
+
open https://flow-walker.beastoin.workers.dev/runs/25h7afGwBK.html
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Returns run.json structure (without local file paths like video/screenshot filenames).
|
|
201
|
+
|
|
202
|
+
CLI equivalent:
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
flow-walker get 25h7afGwBK # pretty-printed JSON
|
|
206
|
+
flow-walker get 25h7afGwBK --json # compact JSON (pipe-friendly)
|
|
207
|
+
flow-walker get 25h7afGwBK | jq '.steps[] | select(.status=="fail")'
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Structured error (on failure)
|
|
211
|
+
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"error": {
|
|
215
|
+
"code": "INVALID_INPUT",
|
|
216
|
+
"message": "Path contains traversal sequences",
|
|
217
|
+
"hint": "Remove .. from path",
|
|
218
|
+
"diagnosticId": "a1b2c3d4"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## YAML flow format
|
|
224
|
+
|
|
225
|
+
```yaml
|
|
226
|
+
name: flow-name
|
|
227
|
+
description: What this flow tests
|
|
228
|
+
app: Omi # optional: app name
|
|
229
|
+
app_url: https://omi.me # optional: app URL
|
|
230
|
+
covers:
|
|
231
|
+
- app/lib/pages/home.dart
|
|
232
|
+
prerequisites:
|
|
233
|
+
- auth_ready
|
|
234
|
+
setup: normal
|
|
235
|
+
|
|
236
|
+
steps:
|
|
237
|
+
- name: Step description
|
|
238
|
+
press: { type: button, position: rightmost }
|
|
239
|
+
assert:
|
|
240
|
+
interactive_count: { min: 20 }
|
|
241
|
+
has_type: { type: switch, min: 2 }
|
|
242
|
+
screenshot: label
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Press targets
|
|
246
|
+
|
|
247
|
+
| Target | Syntax |
|
|
248
|
+
|--------|--------|
|
|
249
|
+
| By ref | `{ ref: "@e3" }` |
|
|
250
|
+
| By type | `{ type: button }` |
|
|
251
|
+
| By position | `{ type: button, position: rightmost }` |
|
|
252
|
+
| By nav tab | `{ bottom_nav_tab: 0 }` |
|
|
253
|
+
|
|
254
|
+
### Assertions
|
|
255
|
+
|
|
256
|
+
| Assertion | Syntax |
|
|
257
|
+
|-----------|--------|
|
|
258
|
+
| Element count | `interactive_count: { min: 20 }` |
|
|
259
|
+
| Nav tabs | `bottom_nav_tabs: { min: 4 }` |
|
|
260
|
+
| Element type | `has_type: { type: switch, min: 2 }` |
|
|
261
|
+
| Text visible | `text_visible: ["Featured", "Home"]` |
|
|
262
|
+
| Text absent | `text_not_visible: ["Error", "Sign In"]` |
|
|
263
|
+
|
|
264
|
+
Text assertions use Android UIAutomator (via `agent-flutter text`) to check visible text from the accessibility layer. This captures text that Marionette snapshots miss (labels, content descriptions, system UI).
|
|
265
|
+
|
|
266
|
+
## Exit codes
|
|
267
|
+
|
|
268
|
+
| Code | Meaning |
|
|
269
|
+
|------|---------|
|
|
270
|
+
| `0` | Success |
|
|
271
|
+
| `1` | Flow has failing steps |
|
|
272
|
+
| `2` | Error (invalid args, file not found, device error) |
|
|
273
|
+
|
|
274
|
+
## Error codes
|
|
275
|
+
|
|
276
|
+
Every error returns `{"error": {"code": "...", "message": "...", "hint": "...", "diagnosticId": "..."}}`.
|
|
277
|
+
|
|
278
|
+
| Code | Meaning | Common cause |
|
|
279
|
+
|------|---------|-------------|
|
|
280
|
+
| `INVALID_ARGS` | Bad CLI arguments | Missing required arg, unknown subcommand |
|
|
281
|
+
| `INVALID_INPUT` | Input fails validation | Path traversal, control chars, bad URI format |
|
|
282
|
+
| `FILE_NOT_FOUND` | Required file missing | No flow YAML, no run.json, remote run not found |
|
|
283
|
+
| `FLOW_PARSE_ERROR` | Invalid YAML flow | Malformed YAML, missing name/steps |
|
|
284
|
+
| `COMMAND_FAILED` | External command error | agent-flutter failure, network error, upload failure |
|
|
285
|
+
|
|
286
|
+
## Environment variables
|
|
287
|
+
|
|
288
|
+
Precedence: CLI flag > env var > default.
|
|
289
|
+
|
|
290
|
+
| Variable | Purpose | Default |
|
|
291
|
+
|----------|---------|---------|
|
|
292
|
+
| `FLOW_WALKER_OUTPUT_DIR` | Default output directory | `./run-output/` |
|
|
293
|
+
| `FLOW_WALKER_AGENT_PATH` | Path to agent-flutter binary | `agent-flutter` |
|
|
294
|
+
| `FLOW_WALKER_DRY_RUN` | Enable dry-run mode | `0` |
|
|
295
|
+
| `FLOW_WALKER_JSON` | Force JSON output | auto (TTY detection) |
|
|
296
|
+
| `FLOW_WALKER_API_URL` | Hosted service URL for push/get | `https://flow-walker.beastoin.workers.dev` |
|
|
297
|
+
| `AGENT_FLUTTER_DEVICE` | ADB device ID | auto-detect |
|
|
298
|
+
|
|
299
|
+
JSON output precedence: `--no-json` > `--json` > `FLOW_WALKER_JSON=1` > TTY auto-detect (non-TTY defaults to JSON).
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# CLAUDE.md — Working on flow-walker
|
|
2
|
+
|
|
3
|
+
## Publish rule
|
|
4
|
+
|
|
5
|
+
This repo is the **publish target** only. Source of truth is [`beastoin/autoloop`](https://github.com/beastoin/autoloop).
|
|
6
|
+
All code changes must go through autoloop's phase-gated build loop first, then get copied here for npm publish.
|
|
7
|
+
Do not edit this repo directly — add a new phase program in autoloop instead.
|
|
8
|
+
|
|
9
|
+
## Project overview
|
|
10
|
+
|
|
11
|
+
`flow-walker` is a Node.js CLI that auto-explores Flutter apps, executes YAML test flows, and generates HTML reports.
|
|
12
|
+
It builds on [agent-flutter](https://github.com/beastoin/agent-flutter) for all device interaction.
|
|
13
|
+
|
|
14
|
+
**Six commands:**
|
|
15
|
+
- `walk` — BFS-explore the app, discover screens, generate YAML flows
|
|
16
|
+
- `run` — Execute a YAML flow, produce run.json + video + screenshots
|
|
17
|
+
- `report` — Generate self-contained HTML report from run results
|
|
18
|
+
- `push` — Upload report to hosted service, return shareable URL
|
|
19
|
+
- `get` — Fetch run data from hosted service by run ID
|
|
20
|
+
- `schema` — Machine-readable command introspection
|
|
21
|
+
|
|
22
|
+
**Design principles:**
|
|
23
|
+
1. **agent-flutter as transport** — never touches VM Service or ADB directly
|
|
24
|
+
2. **Fingerprint by structure** — screen identity uses element types/counts, not text
|
|
25
|
+
3. **Safety first** — blocklist prevents pressing destructive elements
|
|
26
|
+
4. **Self-contained output** — HTML reports embed everything as base64
|
|
27
|
+
5. **YAML as contract** — flows are portable, readable, version-controllable
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
- `src/cli.ts` — entry point, arg parsing, subcommand routing
|
|
32
|
+
- `src/walker.ts` — BFS exploration algorithm
|
|
33
|
+
- `src/fingerprint.ts` — screen identity hashing
|
|
34
|
+
- `src/graph.ts` — navigation graph data structure
|
|
35
|
+
- `src/safety.ts` — blocklist evaluation
|
|
36
|
+
- `src/yaml-writer.ts` — YAML flow generation
|
|
37
|
+
- `src/agent-bridge.ts` — thin wrapper around agent-flutter CLI
|
|
38
|
+
- `src/flow-parser.ts` — YAML → Flow object parsing
|
|
39
|
+
- `src/runner.ts` — flow step execution engine
|
|
40
|
+
- `src/reporter.ts` — HTML report generation
|
|
41
|
+
- `src/capture.ts` — video, screenshot, logcat helpers
|
|
42
|
+
- `src/run-schema.ts` — RunResult type + validation + run ID generation
|
|
43
|
+
- `src/types.ts` — shared type definitions
|
|
44
|
+
- `src/errors.ts` — structured error handling (FlowWalkerError)
|
|
45
|
+
- `src/validate.ts` — input validation (paths, URIs, control chars)
|
|
46
|
+
- `src/command-schema.ts` — command schema for agent discovery
|
|
47
|
+
- `src/push.ts` — report upload to hosted service
|
|
48
|
+
|
|
49
|
+
## Build and test
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install
|
|
53
|
+
npm test # all tests
|
|
54
|
+
npx tsc --noEmit # typecheck
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Code conventions
|
|
58
|
+
|
|
59
|
+
- TypeScript ESM modules (`.ts` imports with explicit extension)
|
|
60
|
+
- Node built-ins only; no external runtime dependencies
|
|
61
|
+
- Node.js ≥ 22 with `--experimental-strip-types`
|
|
62
|
+
- Exit codes: 0 = success, 1 = flow failure, 2 = error
|
|
63
|
+
|
|
64
|
+
## Phase history
|
|
65
|
+
|
|
66
|
+
| Phase | Focus | Eval |
|
|
67
|
+
|-------|-------|------|
|
|
68
|
+
| 1 | walk: BFS explorer, fingerprinting, safety, YAML generation | eval.sh (19 gates) |
|
|
69
|
+
| 2 | run + report: flow executor, video/screenshots, HTML viewer | eval2.sh (25 gates) |
|
|
70
|
+
| 3 | Agent-grade: structured errors, schema, input hardening, run IDs | eval3.sh (41 gates) |
|
|
71
|
+
| 4 | Hosted reports: push command, Cloudflare Worker + R2 | eval4.sh (17 gates) |
|
|
72
|
+
| 5 | Landing page: live metrics, stats tracking | eval5.sh (14 gates) |
|
|
73
|
+
| 6 | Agent-friendly run data + app metadata | eval6.sh (13 gates) |
|
|
74
|
+
|
|
75
|
+
## What not to do
|
|
76
|
+
|
|
77
|
+
- Do not edit this repo directly — use autoloop
|
|
78
|
+
- Do not add external runtime dependencies
|
|
79
|
+
- Do not change exit code semantics
|
|
80
|
+
- Do not bypass blocklist safety checks
|
|
81
|
+
- Do not access VM Service or ADB directly (use agent-flutter)
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 beastoin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# flow-walker
|
|
2
|
+
|
|
3
|
+
Auto-discover app flows, execute YAML test flows, generate HTML reports.
|
|
4
|
+
|
|
5
|
+
flow-walker is the **flow layer** — it defines, discovers, executes, and reports on flows. It uses [agent-flutter](https://github.com/beastoin/agent-flutter) and [agent-swift](https://github.com/beastoin/agent-swift) as **transport layers** that control specific platforms.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
flow-walker (flows: walk, run, report, push, get, schema)
|
|
9
|
+
|
|
|
10
|
+
agent-flutter (Flutter apps on Android/iOS)
|
|
11
|
+
agent-swift (native macOS/iOS apps)
|
|
12
|
+
|
|
|
13
|
+
devices
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g flow-walker-cli
|
|
20
|
+
|
|
21
|
+
# Explore app automatically
|
|
22
|
+
flow-walker walk --app-uri ws://127.0.0.1:38047/abc=/ws
|
|
23
|
+
|
|
24
|
+
# Execute a specific flow
|
|
25
|
+
flow-walker run flows/tab-navigation.yaml
|
|
26
|
+
|
|
27
|
+
# Generate HTML report
|
|
28
|
+
flow-walker report ./run-output/<run-id>/
|
|
29
|
+
|
|
30
|
+
# Share report (hosted)
|
|
31
|
+
flow-walker push ./run-output/<run-id>/
|
|
32
|
+
|
|
33
|
+
# Retrieve run data
|
|
34
|
+
flow-walker get 25h7afGwBK
|
|
35
|
+
|
|
36
|
+
# Discover commands (agent-first)
|
|
37
|
+
flow-walker schema
|
|
38
|
+
flow-walker schema run
|
|
39
|
+
|
|
40
|
+
# Version
|
|
41
|
+
flow-walker --version
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Prerequisites
|
|
45
|
+
|
|
46
|
+
- Node.js >= 22
|
|
47
|
+
- [agent-flutter](https://github.com/beastoin/agent-flutter) installed and in PATH
|
|
48
|
+
- Flutter app running with Marionette initialized
|
|
49
|
+
- ADB connected (Android) or Simulator running (iOS)
|
|
50
|
+
|
|
51
|
+
## Commands
|
|
52
|
+
|
|
53
|
+
### `walk` — Auto-explore
|
|
54
|
+
|
|
55
|
+
Discovers screens by pressing every interactive element, building a navigation graph, and generating YAML flow files.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
flow-walker walk --app-uri ws://... --max-depth 3 --output-dir ./flows/
|
|
59
|
+
flow-walker walk --skip-connect --json # NDJSON: one event per line
|
|
60
|
+
flow-walker walk --dry-run # plan without pressing
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `run` — Execute flow
|
|
64
|
+
|
|
65
|
+
Runs a YAML flow step-by-step. Each run gets a **unique ID** (10-char base64url like `P-tnB_sgKA`). Output goes to `<output-dir>/<run-id>/` so multiple runs never overwrite each other.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
flow-walker run flows/tab-navigation.yaml
|
|
69
|
+
# => Run ID: 25h7afGwBK
|
|
70
|
+
# => Output: ./run-output/25h7afGwBK/
|
|
71
|
+
|
|
72
|
+
flow-walker run flows/login.yaml --json # machine-readable
|
|
73
|
+
flow-walker run flows/settings.yaml --dry-run # parse + resolve without executing
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Output per run:
|
|
77
|
+
- `run.json` — structured results with run ID, per-step status, timing, assertions
|
|
78
|
+
- `recording.mp4` — screen recording with step timestamps
|
|
79
|
+
- `step-N-*.png` — per-step screenshots
|
|
80
|
+
- `device.log` — filtered device logs
|
|
81
|
+
|
|
82
|
+
### `report` — Generate HTML viewer
|
|
83
|
+
|
|
84
|
+
Self-contained HTML with embedded video, screenshots, and clickable step timeline.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
flow-walker report ./run-output/25h7afGwBK/
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### `push` — Share report
|
|
91
|
+
|
|
92
|
+
Uploads report.html to the hosted service and returns a shareable URL. No auth, no config.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
flow-walker push ./run-output/25h7afGwBK/
|
|
96
|
+
# => URL: https://flow-walker.beastoin.workers.dev/runs/25h7afGwBK
|
|
97
|
+
|
|
98
|
+
flow-walker push ./run-output/25h7afGwBK/ --json
|
|
99
|
+
# => {"id":"25h7afGwBK","url":"https://...","htmlUrl":"https://....html","expiresAt":"2026-04-11T..."}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Reports are stored for 30 days. Re-pushing the same run is idempotent — returns the same URL with updated expiry. Use `FLOW_WALKER_API_URL` env var to point at a custom server.
|
|
103
|
+
|
|
104
|
+
Push also uploads `run.json` (stripped of local file paths). URL scheme is agent-first:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Agent: JSON by default
|
|
108
|
+
curl https://flow-walker.beastoin.workers.dev/runs/25h7afGwBK
|
|
109
|
+
curl https://flow-walker.beastoin.workers.dev/runs/25h7afGwBK.json
|
|
110
|
+
|
|
111
|
+
# Human: HTML report
|
|
112
|
+
open https://flow-walker.beastoin.workers.dev/runs/25h7afGwBK.html
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `get` — Retrieve run data
|
|
116
|
+
|
|
117
|
+
Fetches structured run data from the hosted service by run ID. Returns JSON (the same run.json uploaded during push, minus local file paths).
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
flow-walker get 25h7afGwBK # pretty-printed
|
|
121
|
+
flow-walker get 25h7afGwBK --json # compact (pipe-friendly)
|
|
122
|
+
flow-walker get 25h7afGwBK | jq '.steps[] | select(.status=="fail")'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `schema` — Agent discovery
|
|
126
|
+
|
|
127
|
+
Machine-readable command introspection. Returns versioned JSON with args, flags (with types), exit codes, and examples.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
flow-walker schema # all commands with version envelope
|
|
131
|
+
flow-walker schema run # single command detail
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Agents can discover capabilities programmatically — no --help parsing needed.
|
|
135
|
+
|
|
136
|
+
## YAML flow format
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
name: tab-navigation
|
|
140
|
+
description: Bottom nav bar detection, switch between 4 tabs
|
|
141
|
+
app: Omi # optional: app name (shown in reports)
|
|
142
|
+
app_url: https://omi.me # optional: app URL (linked in reports)
|
|
143
|
+
covers:
|
|
144
|
+
- app/lib/pages/home/page.dart
|
|
145
|
+
prerequisites:
|
|
146
|
+
- auth_ready
|
|
147
|
+
setup: normal
|
|
148
|
+
|
|
149
|
+
steps:
|
|
150
|
+
- name: Verify home tab and nav bar
|
|
151
|
+
assert:
|
|
152
|
+
interactive_count: { min: 20 }
|
|
153
|
+
bottom_nav_tabs: { min: 4 }
|
|
154
|
+
screenshot: tab-home
|
|
155
|
+
|
|
156
|
+
- name: Switch to tab 2
|
|
157
|
+
press: { bottom_nav_tab: 1 }
|
|
158
|
+
screenshot: tab-2
|
|
159
|
+
|
|
160
|
+
- name: Scroll in current tab
|
|
161
|
+
scroll: down
|
|
162
|
+
|
|
163
|
+
- name: Verify developer settings has switches
|
|
164
|
+
assert:
|
|
165
|
+
has_type: { type: switch, min: 2 }
|
|
166
|
+
|
|
167
|
+
- name: Verify visible text on screen
|
|
168
|
+
assert:
|
|
169
|
+
text_visible: ["Featured", "Home"]
|
|
170
|
+
text_not_visible: ["Error", "Sign In"]
|
|
171
|
+
|
|
172
|
+
- name: Return to home tab
|
|
173
|
+
press: { bottom_nav_tab: 0 }
|
|
174
|
+
screenshot: final
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Step actions
|
|
178
|
+
|
|
179
|
+
| Action | Syntax | Description |
|
|
180
|
+
|--------|--------|-------------|
|
|
181
|
+
| `press` | `{ type: button, position: rightmost }` | Press by type, position, ref, or bottom_nav_tab |
|
|
182
|
+
| `scroll` | `down` / `up` / `left` / `right` | Scroll the screen |
|
|
183
|
+
| `fill` | `{ type: textfield, value: "text" }` | Enter text into a field |
|
|
184
|
+
| `back` | `true` | Navigate back |
|
|
185
|
+
| `assert` | `{ interactive_count: { min: N } }` | Assert element counts, nav tabs, element types |
|
|
186
|
+
| `screenshot` | `name` | Capture screenshot with label |
|
|
187
|
+
|
|
188
|
+
### Assertions
|
|
189
|
+
|
|
190
|
+
| Assertion | Syntax | Description |
|
|
191
|
+
|-----------|--------|-------------|
|
|
192
|
+
| `interactive_count` | `{ min: 20 }` | Min total interactive elements on screen |
|
|
193
|
+
| `bottom_nav_tabs` | `{ min: 4 }` | Min bottom navigation tabs |
|
|
194
|
+
| `has_type` | `{ type: switch, min: 2 }` | Min elements of a specific type |
|
|
195
|
+
| `text_visible` | `["Featured", "Home"]` | Text must be visible on screen (via UIAutomator) |
|
|
196
|
+
| `text_not_visible` | `["Error", "Sign In"]` | Text must NOT be visible on screen |
|
|
197
|
+
|
|
198
|
+
## Agent-friendly design
|
|
199
|
+
|
|
200
|
+
Built following [Poehnelt's CLI-for-agents principles](https://justin.poehnelt.com/posts/rewrite-your-cli-for-ai-agents/):
|
|
201
|
+
|
|
202
|
+
- **Schema introspection** — `flow-walker schema` returns versioned JSON with typed args/flags
|
|
203
|
+
- **Structured errors** — every error returns `{code, message, hint, diagnosticId}` in JSON
|
|
204
|
+
- **Input hardening** — path traversal, control chars, URI format all validated
|
|
205
|
+
- **TTY-aware JSON** — `--no-json` > `--json` > `FLOW_WALKER_JSON=1` > TTY auto-detect
|
|
206
|
+
- **Dry-run** — `--dry-run` parses and resolves targets without executing (includes resolve reasons)
|
|
207
|
+
- **NDJSON streaming** — walk emits `walk:start`, `screen`, `edge`, `skip` events as one JSON per line
|
|
208
|
+
- **Unique run IDs** — 10-char base64url per run, filesystem-safe, URL-safe
|
|
209
|
+
- **Hosted sharing** — `flow-walker push` uploads report and returns a URL, no auth needed
|
|
210
|
+
- **Agent-first URLs** — `/runs/:id` defaults to JSON; `.html` suffix for humans
|
|
211
|
+
- **App metadata** — optional `app` + `app_url` in YAML flows, shown in reports and landing page
|
|
212
|
+
- **Environment variables** — `FLOW_WALKER_OUTPUT_DIR`, `FLOW_WALKER_AGENT_PATH`, `FLOW_WALKER_DRY_RUN`, `FLOW_WALKER_JSON`, `FLOW_WALKER_API_URL`
|
|
213
|
+
- **Exit codes** — 0 = success, 1 = flow failure, 2 = error
|
|
214
|
+
|
|
215
|
+
## Architecture
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
flow-walker CLI (flow layer)
|
|
219
|
+
| shells out to
|
|
220
|
+
agent-flutter CLI / agent-swift CLI (transport layer)
|
|
221
|
+
| connects via
|
|
222
|
+
VM Service + Marionette / XCTest (platform-specific)
|
|
223
|
+
| controls
|
|
224
|
+
App on device/emulator/desktop
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Design principles:**
|
|
228
|
+
1. **Pluggable transport** — same YAML flows, different backends
|
|
229
|
+
2. **Fingerprint by structure** — screen identity uses element types/counts, not text
|
|
230
|
+
3. **Safety first** — blocklist prevents pressing destructive elements
|
|
231
|
+
4. **Self-contained output** — HTML reports embed everything as base64
|
|
232
|
+
5. **YAML as contract** — flows are portable, readable, version-controllable
|
|
233
|
+
|
|
234
|
+
## Phase history
|
|
235
|
+
|
|
236
|
+
| Phase | Focus | Status |
|
|
237
|
+
|-------|-------|--------|
|
|
238
|
+
| 1 | walk: BFS explorer, fingerprinting, safety, YAML generation | Complete |
|
|
239
|
+
| 2 | run + report: flow executor, video/screenshots, HTML viewer | Complete |
|
|
240
|
+
| 3 | Agent-grade: structured errors, schema, input hardening, run IDs | Complete |
|
|
241
|
+
| 4 | Hosted reports: push command, Cloudflare Worker + R2 | Complete |
|
|
242
|
+
| 5 | Landing page: live metrics, stats tracking | Complete |
|
|
243
|
+
| 6 | Agent-friendly run data + app metadata | Complete |
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flow-walker-cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Automatic app flow extraction via agent-flutter",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"flow-walker": "./src/cli.ts"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
|
11
|
+
"build": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=22"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.7.0",
|
|
19
|
+
"@types/node": "^22.0.0"
|
|
20
|
+
}
|
|
21
|
+
}
|