@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 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`** — Vercel only *pushes* runtime logs (no pull API), so
269
- the listener needs a public tunnel and a Log Drain pointed at it, which the CLI
270
- can't create. It prints a 4-step checklist (tunnel → `--listen` → add the drain
271
- with **Sources: Edge + Serverless, Format: JSON, Endpoint: `<tunnel>/drain`** →
272
- copy the signing secret), opens the Log Drains page, then prompts for the secret
273
- (refusing to save silently without one) and an optional **project** name/ID to
274
- scope deliveries. Already connected → one-line status. Change the listener port
275
- via `vercel.listenerPort` in config, not a prompt.
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` | **`supabase` / `railway`.** Query the last 5 minutes and print `<source> <row count>` or the error. Needs a connected source. Non-zero exit if it errored. |
283
- | `--listen` | **`vercel` only.** Start the Log Drain listener now and keep it running until Ctrl+C, printing each delivery as `timestamp level stream message`. Use it for the dashboard's endpoint-verification step and for live tailing. Binds `listenerPort` + `listenerPath` from config (defaults `4319` / `/drain`). |
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` / `$VERIRUN_VERCEL_DRAIN_SECRET` /
289
- `$VERIRUN_RAILWAY_TOKEN` is never written to the file.
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.listenerPort` | `4319` | Port the local Log Drain listener binds during a run. |
368
- | `vercel.listenerPath` | `/drain` | Path the listener accepts POSTs on. |
369
- | `vercel.drainSecret` | — | If set, deliveries with a missing or wrong `x-vercel-signature` (HMAC-SHA1 of the body) are rejected. |
370
- | `vercel.drainGraceMs` | `3000` | How long collection waits after replay for trailing drain batches. |
371
- | `vercel.project` | — | 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. |
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
- | `VERIRUN_VERCEL_DRAIN_SECRET` | `vercel.drainSecret` |
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, `verirun log supabase --test` to check the Supabase
391
- token, and `verirun log vercel --listen` to bring the listener up on its own.
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 (push).** Vercel has no pull API for runtime logs. During a run,
406
- verirun runs a local HTTP listener on `listenerPort` + `listenerPath`; whatever
407
- the Log Drain POSTs while it is up is kept. It accepts both JSON-array and
408
- NDJSON bodies, echoes the `x-vercel-verify` challenge (so Vercel can confirm the
409
- endpoint), and — if `drainSecret` is set — rejects unsigned or wrongly signed
410
- deliveries. After replay, collection waits `drainGraceMs` for late batches, then
411
- stops the listener.
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
- ### One-time Vercel Log Drain setup
583
+ <details>
584
+ <summary><b>Vercel drain mode</b> (only when <code>vercel.token</code> is unset)</summary>
414
585
 
415
- Vercel must be able to reach your listener, so this is manual:
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`.