@verirun/cli 0.1.0 → 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/README.md +238 -30
- package/dist/cli.js +25 -16
- package/dist/gate.mjs +1 -1
- package/dist/graph.mjs +58 -0
- package/man/verirun.1 +149 -5
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# verirun
|
|
2
2
|
|
|
3
3
|
Record a browser workflow, replay it, and capture the Supabase, Vercel + Railway logs
|
|
4
|
-
your backend produced during the replay.
|
|
4
|
+
your backend produced during the replay. Plus `verirun map`: a static
|
|
5
|
+
interaction graph of your React/TypeScript app — every page, what you can click
|
|
6
|
+
on it, and which backend calls those clicks make — without running anything.
|
|
5
7
|
|
|
6
8
|
## Install
|
|
7
9
|
|
|
@@ -72,6 +74,7 @@ machine past that is blocked until you release one from
|
|
|
72
74
|
fingerprint and use a seat; release the stale one to fix it.
|
|
73
75
|
|
|
74
76
|
`verirun --help`, `verirun --version`, `verirun log` and `verirun agent` do not require an account.
|
|
77
|
+
`verirun map` does.
|
|
75
78
|
|
|
76
79
|
---
|
|
77
80
|
|
|
@@ -128,6 +131,34 @@ is saved and the command says so.
|
|
|
128
131
|
|
|
129
132
|
---
|
|
130
133
|
|
|
134
|
+
### Recording or replaying as a signed-in user
|
|
135
|
+
|
|
136
|
+
Point both commands at a Chrome profile that is already logged in:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
verirun record checkout --channel chrome --user-data-dir "$HOME/.verirun-profile"
|
|
140
|
+
verirun run checkout --channel chrome --user-data-dir "$HOME/.verirun-profile"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Create that profile once, by hand, and sign in there — identity providers block
|
|
144
|
+
sign-in in an automation-launched browser:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
google-chrome --user-data-dir="$HOME/.verirun-profile" --password-store=basic
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`--password-store=basic` (plus `--use-mock-keychain` on macOS) is not optional:
|
|
151
|
+
Playwright always launches Chrome with those flags, and cookies written under
|
|
152
|
+
your OS keyring can't be decrypted without them — the run starts logged out with
|
|
153
|
+
no error. That is also why you can't reuse your everyday Chrome profile, whose
|
|
154
|
+
cookies are keyring-encrypted; use a dedicated one. Close the profile before
|
|
155
|
+
recording or running, since Chrome locks it.
|
|
156
|
+
|
|
157
|
+
To check it took, reopen the profile on its own — if that window is signed in,
|
|
158
|
+
verirun will be too.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
131
162
|
## `verirun run <name>`
|
|
132
163
|
|
|
133
164
|
```bash
|
|
@@ -140,6 +171,7 @@ and writes `.verirun/runs/<name>-<timestamp>/logs.json`.
|
|
|
140
171
|
|
|
141
172
|
| Flag | Meaning |
|
|
142
173
|
| --- | --- |
|
|
174
|
+
| `--slow-mo <ms>` | Pause before each action. Defaults to **500 ms when `--headed`** so you can follow the run by eye, and to 0 otherwise. `--slow-mo 0` watches at full speed; a bigger number follows a fast form more easily. |
|
|
143
175
|
| `--headed` | Show the browser window instead of running headless. |
|
|
144
176
|
| `--timeout <ms>` | Per-action timeout, milliseconds. Non-negative. Defaults to Playwright's 30000. |
|
|
145
177
|
| `--channel <name>` | Replay in an installed browser: `chrome`, `msedge`, … Overrides the channel saved in the workflow. Chromium family only. |
|
|
@@ -265,28 +297,148 @@ with `railway.deploymentId` in config.) Logs are already scoped to that service
|
|
|
265
297
|
+ environment, so there's no separate project filter. Already connected →
|
|
266
298
|
one-line status.
|
|
267
299
|
|
|
268
|
-
**`verirun log vercel`** —
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
300
|
+
**`verirun log vercel`** — same shape as the other two: it opens
|
|
301
|
+
`https://vercel.com/account/settings/tokens`, you paste a token, and it picks
|
|
302
|
+
the project. If the repo has a `.vercel/project.json` (i.e. you have run
|
|
303
|
+
`vercel link`), the project and team are read from it and **you are asked
|
|
304
|
+
nothing else**; otherwise it lists your teams and projects and you choose. It
|
|
305
|
+
then resolves the newest ready production deployment and reads its runtime logs
|
|
306
|
+
to confirm the token works. No tunnel, no drain, no second terminal.
|
|
307
|
+
|
|
308
|
+
On each run it re-resolves the newest ready deployment, so a redeploy doesn't
|
|
309
|
+
break the config. Pin one with `vercel.deploymentId`, or read preview
|
|
310
|
+
deployments instead with `vercel.environment: "preview"`.
|
|
311
|
+
|
|
312
|
+
<details>
|
|
313
|
+
<summary><b>Drain mode</b> (<code>--drain</code>) — the old push-based setup</summary>
|
|
314
|
+
|
|
315
|
+
Vercel's runtime-log API is the easy path, but if it isn't available to you, the
|
|
316
|
+
original Log Drain flow is still there under `verirun log vercel --drain`: a
|
|
317
|
+
tunnel, a local listener, and a drain pointed at it (4-step checklist → tunnel →
|
|
318
|
+
`--listen` → add the drain with **Sources: Edge + Serverless, Format: JSON,
|
|
319
|
+
Endpoint: `<tunnel>/drain`** → paste the signing secret). Config keys
|
|
320
|
+
`listenerPort`, `listenerPath`, `drainSecret`, `drainGraceMs`, `project` belong
|
|
321
|
+
to this mode only. verirun uses drain mode whenever `vercel.token` is absent,
|
|
322
|
+
so the two never collide.
|
|
323
|
+
|
|
324
|
+
</details>
|
|
276
325
|
|
|
277
326
|
Non-interactive shells get the env/config values to set instead of a prompt.
|
|
278
327
|
|
|
279
328
|
| Flag | Meaning |
|
|
280
329
|
| --- | --- |
|
|
281
330
|
| `--reconnect` | Run the connect flow again and replace what was saved. Any source. |
|
|
282
|
-
| `--test` |
|
|
283
|
-
| `--
|
|
331
|
+
| `--test` | Query the last 5 minutes and print what came back, or the error. Needs a connected source. Non-zero exit if it errored. |
|
|
332
|
+
| `--drain` | **`vercel` only.** Set up the old push-based Log Drain instead of the API. |
|
|
333
|
+
| `--listen` | **`vercel` drain mode only.** Start the Log Drain listener now and keep it running until Ctrl+C, printing each delivery as `timestamp level stream message`. Binds `listenerPort` + `listenerPath` from config (defaults `4319` / `/drain`). |
|
|
284
334
|
|
|
285
335
|
An unknown `<source>` errors. A flag meant for another source is ignored.
|
|
286
336
|
Each `verirun log <source>` connect flow *writes* its own block of
|
|
287
337
|
`.verirun/config.json` (0600); the overview is read-only. A secret supplied via
|
|
288
|
-
`$VERIRUN_SUPABASE_ACCESS_TOKEN` / `$
|
|
289
|
-
`$VERIRUN_RAILWAY_TOKEN` is never written to
|
|
338
|
+
`$VERIRUN_SUPABASE_ACCESS_TOKEN` / `$VERIRUN_VERCEL_TOKEN` /
|
|
339
|
+
`$VERIRUN_VERCEL_DRAIN_SECRET` / `$VERIRUN_RAILWAY_TOKEN` is never written to
|
|
340
|
+
the file.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## `verirun map`
|
|
345
|
+
|
|
346
|
+
Reads this repository's React/TypeScript source and builds an **interaction
|
|
347
|
+
graph**: one node per page, the interactive elements on it, the pages each one
|
|
348
|
+
navigates to, and the backend calls each one makes. Nothing is executed — no
|
|
349
|
+
browser, no server, no recording.
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
verirun map # per-page summary
|
|
353
|
+
verirun map --json # the whole graph
|
|
354
|
+
verirun map --report # graph + coverage.md into .verirun/map
|
|
355
|
+
verirun map --bfs /account --depth 2 # what is reachable from one page
|
|
356
|
+
verirun map --reach # how much of each page was captured
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Routes come from a react-router `<Routes>` declaration **or** a Next.js App
|
|
360
|
+
Router `app/**/page.tsx` tree (`[id]` → `:id`, route groups dropped, `layout.tsx`
|
|
361
|
+
applied to its subtree). Components a page renders from another file are
|
|
362
|
+
followed, so a page whose real UI lives in a client component is not reported
|
|
363
|
+
empty. Import aliases are read from `tsconfig.json`; add more with
|
|
364
|
+
`--alias @/=src`.
|
|
365
|
+
|
|
366
|
+
```
|
|
367
|
+
$ verirun map
|
|
368
|
+
6 pages · 6 backend effects · 31 edges (40 files)
|
|
369
|
+
|
|
370
|
+
/ — Page, 0 element(s), 10 nav, 0 effect
|
|
371
|
+
/account — AccountPage, 5 element(s), 1 nav, 4 effect
|
|
372
|
+
/login — LoginPage, 2 element(s), 2 nav, 3 effect
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`--bfs` takes a route path (`/account`), a page id (`page:/account`) or a page
|
|
376
|
+
component name, and reports each node once at its shortest distance, with the
|
|
377
|
+
element that gets you there:
|
|
378
|
+
|
|
379
|
+
```
|
|
380
|
+
$ verirun map --bfs /account --depth 2
|
|
381
|
+
page:/account
|
|
382
|
+
└─ nav "verirun" → page:/
|
|
383
|
+
└─ effect "revoke" [if !(tokens.length === 0)] → effect:POST ?
|
|
384
|
+
└─ nav "Log in" → page:/login
|
|
385
|
+
# frontier at depth 2: page:/login, page:/signup
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Limits, honestly.** It is static analysis: a target computed at runtime shows
|
|
389
|
+
as `unresolved`, an endpoint passed through a variable as `?`, and Supabase
|
|
390
|
+
`.auth.*` / `.storage.*` calls are not yet classified as effects. `--reach`
|
|
391
|
+
tells you what fraction of each page it actually attached, so you can see when
|
|
392
|
+
it is missing something rather than guessing.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## `verirun workflow check <name>`
|
|
397
|
+
|
|
398
|
+
Validates a workflow file without running it — JSON shape, schema version, known
|
|
399
|
+
actions, a `selector` where one is needed, the required `params` per action, and
|
|
400
|
+
warnings for the things that replay tolerates but you probably didn't mean
|
|
401
|
+
(a bare `role=button` that matches five elements, an `assert*` step that replay
|
|
402
|
+
skips, a first step that isn't `navigate`, and a `navigate` *after* the first
|
|
403
|
+
step — walking the app by URL skips the UI, so a dead link or a button that
|
|
404
|
+
stopped submitting still passes).
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
verirun workflow check login
|
|
408
|
+
verirun workflow check login --json # machine-readable findings
|
|
409
|
+
verirun workflow check ./some/file.json
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Static only: it never opens a browser, so it cannot tell you whether a selector
|
|
413
|
+
matches the live page. Only `verirun run` can. Non-zero exit if anything is an
|
|
414
|
+
error.
|
|
415
|
+
|
|
416
|
+
This exists because a workflow doesn't have to come from `record`. `verirun map
|
|
417
|
+
--json` gives every element a `selector`, a `selectorConfidence`
|
|
418
|
+
(`high`/`medium`/`low`) and a suggested `action`, which is enough for you — or
|
|
419
|
+
an agent — to write `.verirun/workflows/<name>.json` directly:
|
|
420
|
+
|
|
421
|
+
```jsonc
|
|
422
|
+
{
|
|
423
|
+
"schemaVersion": 1,
|
|
424
|
+
"name": "login",
|
|
425
|
+
"createdAt": "2026-09-20T00:00:00.000Z",
|
|
426
|
+
"browser": "chromium",
|
|
427
|
+
"steps": [
|
|
428
|
+
{ "index": 0, "action": "navigate", "params": { "url": "https://app.example.com/login" }, "signals": [] },
|
|
429
|
+
{ "index": 1, "action": "fill", "selector": "role=textbox[name=\"Email\"i]", "params": { "text": "ada@example.com" }, "signals": [] },
|
|
430
|
+
{ "index": 2, "action": "click", "selector": "role=button[name=\"Log in\"i]", "params": {}, "signals": [] }
|
|
431
|
+
]
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Actions and required `params`: `navigate` (`url`, absolute), `click` /
|
|
436
|
+
`dblclick`, `fill` (`text`), `press` (`key`), `check` / `uncheck`, `select`
|
|
437
|
+
(`values`), `setInputFiles` (`files`). Everything but `navigate` needs a
|
|
438
|
+
`selector`, which is handed to `page.locator()` verbatim.
|
|
439
|
+
|
|
440
|
+
A recorded workflow is ground truth; a written one is a guess until `run`
|
|
441
|
+
confirms it. Don't overwrite a recording with a generated file — use a new name.
|
|
290
442
|
|
|
291
443
|
---
|
|
292
444
|
|
|
@@ -301,8 +453,16 @@ workflows, recent runs.
|
|
|
301
453
|
```bash
|
|
302
454
|
verirun agent > .verirun/AGENT.md # save it as context for your agent
|
|
303
455
|
verirun agent --json # machine-readable form
|
|
456
|
+
verirun agent --skill --install # .claude/skills/verirun/SKILL.md
|
|
304
457
|
```
|
|
305
458
|
|
|
459
|
+
`--skill` emits the same knowledge as a **Claude Code skill** — YAML frontmatter
|
|
460
|
+
with a `description` that tells Claude when to load it, then a shorter body
|
|
461
|
+
aimed at deciding *which* command to reach for. With `--install` it writes
|
|
462
|
+
`.claude/skills/verirun/SKILL.md` in the repo, and Claude Code loads it on its
|
|
463
|
+
own whenever a task looks like app testing. Re-run it after upgrading verirun;
|
|
464
|
+
the file is generated, not hand-maintained.
|
|
465
|
+
|
|
306
466
|
Paste the output into `CLAUDE.md`, `.cursorrules`, an `AGENTS.md`, or pipe it
|
|
307
467
|
straight into a prompt. No account needed. The agent then knows to run
|
|
308
468
|
`verirun run <name>`, read exit code + `.verirun/runs/<name>-<newest>/logs.json`,
|
|
@@ -364,11 +524,18 @@ it are sent only to the respective provider's own API.
|
|
|
364
524
|
| `supabase.projectRef` | — | Supabase project ref. Required for Supabase capture. |
|
|
365
525
|
| `supabase.accessToken` | — | Personal access token. Required for Supabase capture. |
|
|
366
526
|
| `supabase.sources` | `postgres_logs`, `edge_logs`, `auth_logs` | Log sources to query, one request each. |
|
|
367
|
-
| `vercel.
|
|
368
|
-
| `vercel.
|
|
369
|
-
| `vercel.
|
|
370
|
-
| `vercel.
|
|
371
|
-
| `vercel.
|
|
527
|
+
| `vercel.token` | — | Vercel API token (vercel.com/account/settings/tokens). Its presence is what selects API mode over drain mode. |
|
|
528
|
+
| `vercel.projectId` | — | `prj_…` id of the project to read logs from. Required in API mode. |
|
|
529
|
+
| `vercel.teamId` | — | `team_…` id, when the project belongs to a team. |
|
|
530
|
+
| `vercel.deploymentId` | — | Pin log capture to one deployment instead of the newest ready one. |
|
|
531
|
+
| `vercel.environment` | `production` | Which target to resolve the newest deployment from: `production` or `preview`. |
|
|
532
|
+
| `vercel.readMs` | `8000` | How long to read the runtime-log stream before giving up. |
|
|
533
|
+
| `vercel.tailGraceMs` | `3000` | Extra window past the run's end to keep, for lines Vercel flushes late. |
|
|
534
|
+
| `vercel.listenerPort` | `4319` | *Drain mode.* Port the local Log Drain listener binds during a run. |
|
|
535
|
+
| `vercel.listenerPath` | `/drain` | *Drain mode.* Path the listener accepts POSTs on. |
|
|
536
|
+
| `vercel.drainSecret` | — | *Drain mode.* If set, deliveries with a missing or wrong `x-vercel-signature` (HMAC-SHA1 of the body) are rejected. |
|
|
537
|
+
| `vercel.drainGraceMs` | `3000` | *Drain mode.* How long collection waits after replay for trailing drain batches. |
|
|
538
|
+
| `vercel.project` | — | *Drain mode.* Keep only deliveries whose `projectName` or `projectId` equals this (a drain carries the whole team's logs). Unset = keep all. If every delivery is filtered out, `run` warns. |
|
|
372
539
|
| `railway.token` | — | Railway API token (Account/Workspace, from railway.com/account/tokens). Required for Railway capture. |
|
|
373
540
|
| `railway.deploymentId` | — | Pin log capture to one deployment. Overrides the project/environment/service trio. |
|
|
374
541
|
| `railway.projectId` / `railway.environmentId` / `railway.serviceId` | — | Together, the target for which the newest deployment is resolved each run (so a redeploy doesn't need a config change). |
|
|
@@ -379,7 +546,8 @@ it are sent only to the respective provider's own API.
|
|
|
379
546
|
| Variable | Replaces |
|
|
380
547
|
| --- | --- |
|
|
381
548
|
| `VERIRUN_SUPABASE_ACCESS_TOKEN` | `supabase.accessToken` |
|
|
382
|
-
| `
|
|
549
|
+
| `VERIRUN_VERCEL_TOKEN` | `vercel.token` |
|
|
550
|
+
| `VERIRUN_VERCEL_DRAIN_SECRET` | `vercel.drainSecret` (drain mode) |
|
|
383
551
|
| `VERIRUN_RAILWAY_TOKEN` | `railway.token` |
|
|
384
552
|
|
|
385
553
|
---
|
|
@@ -387,8 +555,8 @@ it are sent only to the respective provider's own API.
|
|
|
387
555
|
## Log capture
|
|
388
556
|
|
|
389
557
|
A source appears only when its config block is present. Run `verirun log
|
|
390
|
-
<source>` for guided setup
|
|
391
|
-
|
|
558
|
+
<source>` for guided setup and `verirun log <source> --test` to check a
|
|
559
|
+
connected one against the last 5 minutes.
|
|
392
560
|
|
|
393
561
|
**Supabase (pull).** After replay, one query per configured source goes to the
|
|
394
562
|
Management API's `analytics/endpoints/logs.all` endpoint, scoped to the run
|
|
@@ -402,17 +570,27 @@ either `deploymentId` or `projectId` + `environmentId` + `serviceId` (the newest
|
|
|
402
570
|
deployment of that trio is resolved first). Errors are warned about; the run
|
|
403
571
|
continues. Logs are already scoped to one service + environment.
|
|
404
572
|
|
|
405
|
-
**Vercel (
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
573
|
+
**Vercel (pull).** After replay, verirun resolves the project's newest ready
|
|
574
|
+
deployment (or `vercel.deploymentId`) and reads
|
|
575
|
+
`/v1/projects/<projectId>/deployments/<deploymentId>/runtime-logs`, keeping the
|
|
576
|
+
lines whose timestamp falls in the run window plus `tailGraceMs`. Needs `token`
|
|
577
|
+
and `projectId`. The endpoint streams and stays open to tail live lines, so the
|
|
578
|
+
read stops once the stream has been quiet for a moment, or after `readMs`.
|
|
579
|
+
Errors are warned about; the run continues. Retention is Vercel's, not ours —
|
|
580
|
+
on Hobby, runtime logs are short-lived, which is fine because collection happens
|
|
581
|
+
seconds after the run.
|
|
412
582
|
|
|
413
|
-
|
|
583
|
+
<details>
|
|
584
|
+
<summary><b>Vercel drain mode</b> (only when <code>vercel.token</code> is unset)</summary>
|
|
414
585
|
|
|
415
|
-
|
|
586
|
+
During a run, verirun runs a local HTTP listener on `listenerPort` +
|
|
587
|
+
`listenerPath`; whatever the Log Drain POSTs while it is up is kept. It accepts
|
|
588
|
+
both JSON-array and NDJSON bodies, echoes the `x-vercel-verify` challenge (so
|
|
589
|
+
Vercel can confirm the endpoint), and — if `drainSecret` is set — rejects
|
|
590
|
+
unsigned or wrongly signed deliveries. After replay, collection waits
|
|
591
|
+
`drainGraceMs` for late batches, then stops the listener.
|
|
592
|
+
|
|
593
|
+
One-time setup (`verirun log vercel --drain` walks you through it):
|
|
416
594
|
|
|
417
595
|
1. Expose the listener port to the internet — `ngrok http 4319`, a Cloudflare
|
|
418
596
|
Tunnel, etc. Note the public URL.
|
|
@@ -428,3 +606,33 @@ Vercel must be able to reach your listener, so this is manual:
|
|
|
428
606
|
|
|
429
607
|
A free ngrok URL changes each session; a reserved hostname is worth it if you
|
|
430
608
|
run this often.
|
|
609
|
+
|
|
610
|
+
</details>
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## Changelog
|
|
615
|
+
|
|
616
|
+
### 0.2.0
|
|
617
|
+
|
|
618
|
+
- **`verirun map`** — static interaction graph of a React/TypeScript app:
|
|
619
|
+
every page, the interactive elements on it, where they navigate, and the
|
|
620
|
+
backend calls they make. No browser, no recording. `--json`, `--report`,
|
|
621
|
+
`--reach`, `--bfs <node> --depth N`. Routes come from react-router
|
|
622
|
+
`<Routes>` or a Next.js `app/**/page.tsx` tree.
|
|
623
|
+
- **`verirun workflow check`** — validate a workflow file without running it.
|
|
624
|
+
Together with the selectors `map` emits, a workflow can now be written by
|
|
625
|
+
hand (or by an agent) instead of recorded.
|
|
626
|
+
- **Vercel logs without a tunnel** — `verirun log vercel` is now a token and a
|
|
627
|
+
project, pulled from Vercel's runtime-log API. The old Log Drain flow
|
|
628
|
+
remains under `--drain` and is still used automatically for existing configs.
|
|
629
|
+
- **`verirun agent --skill --install`** — writes `.claude/skills/verirun/SKILL.md`
|
|
630
|
+
so Claude Code picks the tool up on its own.
|
|
631
|
+
- **Watchable replays** — `--headed` now paces actions (500ms), tunable with
|
|
632
|
+
`--slow-mo <ms>`.
|
|
633
|
+
- `verirun record`/`run` document how to use a signed-in Chrome profile.
|
|
634
|
+
|
|
635
|
+
### 0.1.0
|
|
636
|
+
|
|
637
|
+
- `record`, `run`, `log` (Supabase / Vercel drain / Railway), `agent`, and the
|
|
638
|
+
account commands `login` / `logout` / `whoami`.
|