@verirun/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +24 -0
- package/README.md +430 -0
- package/dist/cli.js +30 -0
- package/dist/gate.mjs +1 -0
- package/man/verirun.1 +360 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Verirun — Proprietary Software License
|
|
2
|
+
Copyright (c) 2026 Verirun. All Rights Reserved.
|
|
3
|
+
|
|
4
|
+
This software is proprietary and confidential. It is licensed, not sold.
|
|
5
|
+
|
|
6
|
+
You may not copy, modify, adapt, translate, distribute, sublicense, sell,
|
|
7
|
+
rent, lease, publish, or create derivative works of this software or any
|
|
8
|
+
part of it, and you may not reverse engineer, decompile, or disassemble it,
|
|
9
|
+
except to the extent that this restriction is expressly prohibited by
|
|
10
|
+
applicable law.
|
|
11
|
+
|
|
12
|
+
Use of this software requires a valid Verirun license key and a current,
|
|
13
|
+
active paid subscription. Your right to use the software ends when that
|
|
14
|
+
subscription lapses, is cancelled, or is terminated.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY,
|
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF,
|
|
21
|
+
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For licensing enquiries: support@get-verirun.duely.in
|
package/README.md
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# verirun
|
|
2
|
+
|
|
3
|
+
Record a browser workflow, replay it, and capture the Supabase, Vercel + Railway logs
|
|
4
|
+
your backend produced during the replay.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install -g @verirun/cli
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
That installs the CLI and, in a postinstall step, downloads the Chromium build
|
|
13
|
+
Playwright drives. `verirun` is then on your PATH. To also record in Firefox or
|
|
14
|
+
WebKit: `npx playwright install firefox webkit`. To skip the browser download
|
|
15
|
+
(you already have Playwright browsers, or you are in CI): set
|
|
16
|
+
`PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` before installing.
|
|
17
|
+
|
|
18
|
+
verirun operates on **the directory you run it from**: it reads and writes
|
|
19
|
+
`.verirun/` at the root of that repo (the nearest ancestor with a `.git` or, if
|
|
20
|
+
none, `package.json`; otherwise the current directory). `cd` into the project
|
|
21
|
+
you want to test before recording or running.
|
|
22
|
+
|
|
23
|
+
### Help
|
|
24
|
+
|
|
25
|
+
`verirun --help` and `verirun <command> --help` work everywhere. A `verirun(1)`
|
|
26
|
+
man page ships with the package: a global `npm install -g @verirun/cli` puts it on
|
|
27
|
+
your `MANPATH` (`man verirun`) on macOS, Linux, and WSL / Git Bash. Native
|
|
28
|
+
Windows (cmd, PowerShell) has no `man` — use `--help`, or `man -l` the file
|
|
29
|
+
under a POSIX shell. From a `npm link` checkout: `man -l man/verirun.1`.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Account
|
|
34
|
+
|
|
35
|
+
`record` and `run` need a connected Verirun account. First run on a machine:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
verirun login
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This prints a short code and a link. Open the link, sign in (a 7 day free trial
|
|
42
|
+
starts automatically if you are new), and enter the code. The CLI then holds a
|
|
43
|
+
device token for that machine.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
verirun whoami # account, plan, subscription status
|
|
47
|
+
verirun logout # disconnect this machine
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| | |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `~/.verirun/config.json` | Holds the device token. Per user, not per project. |
|
|
53
|
+
| `~/.verirun/state.json` | The last **server-signed lease** (Ed25519) plus a local tamper check. Editing it, or a lease not signed by Verirun's key, is ignored and forces a fresh online check. |
|
|
54
|
+
| `$VERIRUN_LICENSE_KEY` | A raw key for CI, used instead of `verirun login`. |
|
|
55
|
+
| `$VERIRUN_LICENSE_SERVER` | Point the CLI at a different server. |
|
|
56
|
+
|
|
57
|
+
**Revalidation.** Each successful check hands back a short-lived lease signed by
|
|
58
|
+
the license server; the CLI verifies it against a public key built into the
|
|
59
|
+
binary, so a fake or patched server cannot mint one. A lease is trusted for ~3
|
|
60
|
+
days (by its own signed timestamp), so most runs need no network. After that the
|
|
61
|
+
CLI re-checks. An explicit "expired", "canceled", "revoked" or "seat limit"
|
|
62
|
+
answer stops `record`/`run` immediately. A network failure never counts as
|
|
63
|
+
invalid: the CLI keeps working off the last lease, silently for 15 days, then
|
|
64
|
+
with a "connect to the internet" notice every run, and stops at 30 days offline
|
|
65
|
+
until it can revalidate. The server can also refuse very old CLI versions.
|
|
66
|
+
|
|
67
|
+
**Hardware binding.** Each check sends a fingerprint of this machine (a hash of
|
|
68
|
+
the physical MAC, hostname, platform and OS user; the raw values never leave the
|
|
69
|
+
machine). Your plan includes a set number of machine seats; connecting a new
|
|
70
|
+
machine past that is blocked until you release one from
|
|
71
|
+
<https://get-verirun.duely.in/account>. Swapping a dock or toggling a VPN can shift the
|
|
72
|
+
fingerprint and use a seat; release the stale one to fix it.
|
|
73
|
+
|
|
74
|
+
`verirun --help`, `verirun --version`, `verirun log` and `verirun agent` do not require an account.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## `verirun record <name>`
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
verirun record checkout --url https://app.example.com
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Opens a headed browser. Perform the workflow by hand — click, type, navigate.
|
|
85
|
+
**Close the browser to finish**; the step sequence is saved to
|
|
86
|
+
`.verirun/workflows/<name>.json`.
|
|
87
|
+
|
|
88
|
+
| Flag | Meaning |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| `-u, --url <url>` | URL to open at. Without it the browser starts blank and you navigate yourself. |
|
|
91
|
+
| `-b, --browser <name>` | `chromium` (default), `firefox`, or `webkit`. |
|
|
92
|
+
| `--channel <name>` | Record in an installed browser instead of bundled Chromium: `chrome`, `msedge`, `chrome-beta`, `msedge-dev`, … Saved into the workflow file so `run` reuses it. |
|
|
93
|
+
| `--default-browser` | Record in your OS default browser. Chrome / Edge → that install (`--channel`); Firefox → Playwright's Firefox; Safari, Brave, Arc, Vivaldi, Opera → nearest engine (bundled Chromium or WebKit) with a warning. Falls back to bundled Chromium if detection fails. Overrides `-b` / `--channel`. The resolved browser/channel is saved to the workflow, so `run` reuses it. |
|
|
94
|
+
| `--user-data-dir <dir>` | Record in a persistent profile directory, so the session is already logged in and stays logged in for later runs. The real browser using that profile must be closed first. |
|
|
95
|
+
| `--test-id-attribute <attr>` | Attribute preferred for test-id selectors. Defaults to `data-testid`; also settable as `testIdAttribute` in config. |
|
|
96
|
+
| `-f, --force` | Overwrite an existing workflow of the same name. Without it, recording onto an existing name errors. |
|
|
97
|
+
|
|
98
|
+
**Name rules.** `<name>` becomes a filename: letters, digits, dot, dash,
|
|
99
|
+
underscore; must start with a letter or digit; 64 chars max. It is not
|
|
100
|
+
sanitised — an invalid name errors rather than being rewritten.
|
|
101
|
+
|
|
102
|
+
**Selectors.** Recording goes through Playwright's codegen engine, so each step
|
|
103
|
+
gets its stable selector — `getByTestId`, then `getByRole` with an accessible
|
|
104
|
+
name, falling back to CSS only when nothing better exists. A step stores three
|
|
105
|
+
views of its target:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"index": 3,
|
|
110
|
+
"action": "fill",
|
|
111
|
+
"params": { "text": "founder@example.com" },
|
|
112
|
+
"selector": "internal:role=textbox[name=\"Email\"i]",
|
|
113
|
+
"locator": { "kind": "role", "body": "textbox", "options": { "name": "Email" } },
|
|
114
|
+
"description": "getByRole('textbox', { name: 'Email' })"
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`selector` is the raw string replay feeds to `page.locator()`, `locator` is its
|
|
119
|
+
structured form, `description` is for you to read. Replay uses `selector`, so
|
|
120
|
+
what you recorded is what runs.
|
|
121
|
+
|
|
122
|
+
**Blur quirk.** A text field is only committed when it loses focus. If you type
|
|
123
|
+
into a field and close the browser without clicking or tabbing away, that last
|
|
124
|
+
value is not recorded. Tab out before closing.
|
|
125
|
+
|
|
126
|
+
**Nothing recorded.** If you close the browser without doing anything, nothing
|
|
127
|
+
is saved and the command says so.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## `verirun run <name>`
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
verirun run checkout
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Replays the recorded steps — headless, in the same browser kind the workflow was
|
|
138
|
+
recorded in — then captures logs for the wall-clock window the replay occupied
|
|
139
|
+
and writes `.verirun/runs/<name>-<timestamp>/logs.json`.
|
|
140
|
+
|
|
141
|
+
| Flag | Meaning |
|
|
142
|
+
| --- | --- |
|
|
143
|
+
| `--headed` | Show the browser window instead of running headless. |
|
|
144
|
+
| `--timeout <ms>` | Per-action timeout, milliseconds. Non-negative. Defaults to Playwright's 30000. |
|
|
145
|
+
| `--channel <name>` | Replay in an installed browser: `chrome`, `msedge`, … Overrides the channel saved in the workflow. Chromium family only. |
|
|
146
|
+
| `--user-data-dir <dir>` | Replay in a persistent profile directory, so it runs already logged in. The profile must not be open in another browser. |
|
|
147
|
+
| `--cdp <url>` | Attach to a browser already running with remote debugging and drive it. See below. |
|
|
148
|
+
| `--user <id>` | Keep only log entries whose message or raw payload contains this string, case-insensitive — e.g. a user's email. Overrides `config.userFilter`. Recorded in `logs.json` as `"filter": { "user": … }`; `entries` and the per-source counts are post-filter. If everything is filtered out, `run` warns. |
|
|
149
|
+
|
|
150
|
+
**Where the user filter runs.** For **Supabase** it goes into the log query's
|
|
151
|
+
`WHERE` (matched against the rendered message and the JSON of `metadata`), so
|
|
152
|
+
only that user's rows leave the project — this matters on a busy database. Plain
|
|
153
|
+
identifier needles (emails, UUIDs, usernames) qualify; a needle with quotes,
|
|
154
|
+
spaces or other symbols isn't spliced into SQL and is filtered client-side
|
|
155
|
+
instead (a dim note says so), as is a source whose backend rejects the `WHERE`.
|
|
156
|
+
For **Railway** it's passed as a quoted phrase to the GraphQL log `filter`, same
|
|
157
|
+
idea. For **Vercel** there is no server-side option — a Log Drain just pushes,
|
|
158
|
+
with no content predicate — so it's always filtered client-side after delivery.
|
|
159
|
+
Volume there is bounded by the ~seconds-long run window, not your user count; if
|
|
160
|
+
it's still too much, point the drain at one project or a staging environment, or
|
|
161
|
+
set a sampling rate on the drain in Vercel. Every source is also run through the
|
|
162
|
+
client-side match as the canonical pass.
|
|
163
|
+
|
|
164
|
+
Target selection is first match of: `--cdp`, then `--user-data-dir`, then
|
|
165
|
+
`--channel` (or the workflow's saved channel), then a fresh Playwright browser
|
|
166
|
+
of the workflow's kind.
|
|
167
|
+
|
|
168
|
+
### Running against a browser you already have open
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
google-chrome --remote-debugging-port=9222 # keep this window open
|
|
172
|
+
verirun run checkout --cdp http://localhost:9222
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`run` attaches over CDP, reuses the existing context (cookies, auth, open tab),
|
|
176
|
+
replays, and on finish **disconnects without closing your browser**. Only
|
|
177
|
+
Chromium-family browsers expose CDP. If the workflow's first step is a
|
|
178
|
+
`navigate` it drives the current tab there; otherwise it acts on whatever tab is
|
|
179
|
+
frontmost.
|
|
180
|
+
|
|
181
|
+
### Replay behaviour
|
|
182
|
+
|
|
183
|
+
- Steps replay off the raw `selector`, so the recorded target is reproduced
|
|
184
|
+
exactly.
|
|
185
|
+
- Supported actions: `navigate`, `click`, `dblclick`, `fill`, `press` (modifier
|
|
186
|
+
bitmask + key are recombined), `check`, `uncheck`, `select`, `setInputFiles`,
|
|
187
|
+
`openPage`, `closePage`.
|
|
188
|
+
- Assertions added in codegen (`assert*`) are skipped with a warning — they do
|
|
189
|
+
not change which actions run.
|
|
190
|
+
- An action the replayer does not recognise **stops the run**.
|
|
191
|
+
- A step that fails does **not** abort log capture: the run is marked `failed`,
|
|
192
|
+
whatever logs already landed are still written, and the exit code is non-zero.
|
|
193
|
+
- Popups and newly opened tabs become the active target as they open. A workflow
|
|
194
|
+
that drives several tabs at once is not supported yet.
|
|
195
|
+
|
|
196
|
+
### Sequencing
|
|
197
|
+
|
|
198
|
+
Start every configured log source → replay → collect from each source for the
|
|
199
|
+
`[start, end]` window → dispose every source. Dispose always runs, including
|
|
200
|
+
after a replay failure or a source that failed to start. If one source fails to
|
|
201
|
+
start, the run aborts before replay; if one fails during collection, it is
|
|
202
|
+
warned about and contributes no entries.
|
|
203
|
+
|
|
204
|
+
### `logs.json`
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"schemaVersion": 1,
|
|
209
|
+
"workflow": "checkout",
|
|
210
|
+
"status": "ok",
|
|
211
|
+
"window": { "startedAt": "2026-08-31T13:01:50.998Z", "endedAt": "2026-08-31T13:01:51.499Z" },
|
|
212
|
+
"steps": { "total": 12, "replayed": 12 },
|
|
213
|
+
"filter": { "user": "ada@example.com" },
|
|
214
|
+
"sources": [{ "id": "vercel", "name": "Vercel", "entries": 8 }],
|
|
215
|
+
"entries": [
|
|
216
|
+
{
|
|
217
|
+
"source": "vercel",
|
|
218
|
+
"stream": "lambda",
|
|
219
|
+
"timestamp": "2026-08-31T13:01:50.000Z",
|
|
220
|
+
"level": "info",
|
|
221
|
+
"message": "POST /api/checkout 200",
|
|
222
|
+
"raw": {}
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- `status` is `ok` or `failed`. On `failed`, `steps` also carries `failedAt`
|
|
229
|
+
(the step index) and the file carries a top-level `error` string.
|
|
230
|
+
- `filter` is present only when `--user` / `config.userFilter` was in effect;
|
|
231
|
+
`entries` and every `sources[].entries` count are then post-filter.
|
|
232
|
+
- `entries` merges every source, sorted by `timestamp`. `stream` is the
|
|
233
|
+
sub-stream within a source (`lambda`, `edge`, `postgres_logs`, …). `raw` is
|
|
234
|
+
the provider's untouched payload (for Supabase that now includes the row's
|
|
235
|
+
`metadata`, so a `--user` filter can match a nested email field).
|
|
236
|
+
- With no log sources configured, `run` still replays and warns; `entries` is
|
|
237
|
+
empty.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## `verirun log [source]`
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
verirun log # overview: which sources are configured
|
|
245
|
+
verirun log supabase # connect Supabase logs (interactive)
|
|
246
|
+
verirun log vercel # connect Vercel logs (interactive)
|
|
247
|
+
verirun log railway # connect Railway logs (interactive)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
All three are connect flows, not pages of instructions.
|
|
251
|
+
|
|
252
|
+
**`verirun log supabase`** — if nothing is configured it opens
|
|
253
|
+
`https://supabase.com/dashboard/account/tokens`, asks you to paste the token you
|
|
254
|
+
create there, works out the project ref (from the `supabase` CLI if it's
|
|
255
|
+
installed, otherwise one prompt), writes both to `.verirun/config.json`, and
|
|
256
|
+
runs a test query to confirm it works. Already connected → one-line status.
|
|
257
|
+
|
|
258
|
+
**`verirun log railway`** — Railway has no HTTP drain, but its GraphQL API
|
|
259
|
+
serves point-in-time deployment logs, so this works like Supabase. Two things:
|
|
260
|
+
(1) create a token on the page it opens (`https://railway.com/account/tokens`);
|
|
261
|
+
(2) paste the **URL of the service** you want logs from — verirun pulls the
|
|
262
|
+
project / service / environment IDs out of it. The newest deployment of that
|
|
263
|
+
service is resolved on every run, so it survives redeploys. (Pin a specific one
|
|
264
|
+
with `railway.deploymentId` in config.) Logs are already scoped to that service
|
|
265
|
+
+ environment, so there's no separate project filter. Already connected →
|
|
266
|
+
one-line status.
|
|
267
|
+
|
|
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.
|
|
276
|
+
|
|
277
|
+
Non-interactive shells get the env/config values to set instead of a prompt.
|
|
278
|
+
|
|
279
|
+
| Flag | Meaning |
|
|
280
|
+
| --- | --- |
|
|
281
|
+
| `--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`). |
|
|
284
|
+
|
|
285
|
+
An unknown `<source>` errors. A flag meant for another source is ignored.
|
|
286
|
+
Each `verirun log <source>` connect flow *writes* its own block of
|
|
287
|
+
`.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.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## `verirun agent`
|
|
294
|
+
|
|
295
|
+
Prints a single self-contained reference for an AI coding agent: every command
|
|
296
|
+
and flag (read from the CLI itself, so it can't drift), the `.verirun/` layout,
|
|
297
|
+
the `logs.json` schema, the recommended record-then-run loop, and a snapshot of
|
|
298
|
+
**this** checkout — resolved repo root, configured log sources, recorded
|
|
299
|
+
workflows, recent runs.
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
verirun agent > .verirun/AGENT.md # save it as context for your agent
|
|
303
|
+
verirun agent --json # machine-readable form
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Paste the output into `CLAUDE.md`, `.cursorrules`, an `AGENTS.md`, or pipe it
|
|
307
|
+
straight into a prompt. No account needed. The agent then knows to run
|
|
308
|
+
`verirun run <name>`, read exit code + `.verirun/runs/<name>-<newest>/logs.json`,
|
|
309
|
+
and act on `status` / `error` / `entries` without you explaining the tool.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## `.verirun/` layout
|
|
314
|
+
|
|
315
|
+
Everything lives at the repo root:
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
.verirun/
|
|
319
|
+
config.json # secrets and endpoints
|
|
320
|
+
workflows/<name>.json # recorded step sequence
|
|
321
|
+
runs/<name>-<timestamp>/logs.json # captured logs, one directory per run
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`record` appends `.verirun/` to the repo's `.gitignore` the first time it runs.
|
|
325
|
+
If the repo has no `.gitignore`, it warns instead of creating one — run logs
|
|
326
|
+
contain request bodies, tokens and user data, and recorded steps contain
|
|
327
|
+
whatever you typed, including passwords.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Config — `.verirun/config.json`
|
|
332
|
+
|
|
333
|
+
Read once at startup. Optional; include only the blocks you use. Credentials in
|
|
334
|
+
it are sent only to the respective provider's own API.
|
|
335
|
+
|
|
336
|
+
```json
|
|
337
|
+
{
|
|
338
|
+
"testIdAttribute": "data-testid",
|
|
339
|
+
"userFilter": "ada@example.com",
|
|
340
|
+
"supabase": {
|
|
341
|
+
"projectRef": "abcdefghijklmnopqrst",
|
|
342
|
+
"accessToken": "sbp_...",
|
|
343
|
+
"sources": ["postgres_logs", "edge_logs", "auth_logs"]
|
|
344
|
+
},
|
|
345
|
+
"vercel": {
|
|
346
|
+
"listenerPort": 4319,
|
|
347
|
+
"listenerPath": "/drain",
|
|
348
|
+
"drainSecret": "whsec_...",
|
|
349
|
+
"drainGraceMs": 3000,
|
|
350
|
+
"project": "my-app"
|
|
351
|
+
},
|
|
352
|
+
"railway": {
|
|
353
|
+
"token": "...",
|
|
354
|
+
"projectId": "...",
|
|
355
|
+
"environmentId": "...",
|
|
356
|
+
"serviceId": "..."
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
| Key | Default | Meaning |
|
|
362
|
+
| --- | --- | --- |
|
|
363
|
+
| `testIdAttribute` | `data-testid` | Attribute for test-id selectors. `--test-id-attribute` overrides. |
|
|
364
|
+
| `supabase.projectRef` | — | Supabase project ref. Required for Supabase capture. |
|
|
365
|
+
| `supabase.accessToken` | — | Personal access token. Required for Supabase capture. |
|
|
366
|
+
| `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. |
|
|
372
|
+
| `railway.token` | — | Railway API token (Account/Workspace, from railway.com/account/tokens). Required for Railway capture. |
|
|
373
|
+
| `railway.deploymentId` | — | Pin log capture to one deployment. Overrides the project/environment/service trio. |
|
|
374
|
+
| `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). |
|
|
375
|
+
| `userFilter` | — | Keep only log entries mentioning this string, case-insensitive — e.g. a user's email. Pushed server-side into the Supabase query `WHERE` and the Railway log `filter` when it's a plain identifier; client-side for Vercel. `verirun run --user <value>` overrides it per run. |
|
|
376
|
+
|
|
377
|
+
### Environment variables (override the file)
|
|
378
|
+
|
|
379
|
+
| Variable | Replaces |
|
|
380
|
+
| --- | --- |
|
|
381
|
+
| `VERIRUN_SUPABASE_ACCESS_TOKEN` | `supabase.accessToken` |
|
|
382
|
+
| `VERIRUN_VERCEL_DRAIN_SECRET` | `vercel.drainSecret` |
|
|
383
|
+
| `VERIRUN_RAILWAY_TOKEN` | `railway.token` |
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Log capture
|
|
388
|
+
|
|
389
|
+
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.
|
|
392
|
+
|
|
393
|
+
**Supabase (pull).** After replay, one query per configured source goes to the
|
|
394
|
+
Management API's `analytics/endpoints/logs.all` endpoint, scoped to the run
|
|
395
|
+
window. Needs `projectRef` and an access token — missing either aborts the run
|
|
396
|
+
before replay. A source that returns an error is warned about and skipped;
|
|
397
|
+
others still run. Timestamps are normalised to ISO 8601.
|
|
398
|
+
|
|
399
|
+
**Railway (pull).** After replay, one `deploymentLogs` GraphQL query to
|
|
400
|
+
`backboard.railway.com/graphql/v2`, scoped to the run window. Needs `token` and
|
|
401
|
+
either `deploymentId` or `projectId` + `environmentId` + `serviceId` (the newest
|
|
402
|
+
deployment of that trio is resolved first). Errors are warned about; the run
|
|
403
|
+
continues. Logs are already scoped to one service + environment.
|
|
404
|
+
|
|
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.
|
|
412
|
+
|
|
413
|
+
### One-time Vercel Log Drain setup
|
|
414
|
+
|
|
415
|
+
Vercel must be able to reach your listener, so this is manual:
|
|
416
|
+
|
|
417
|
+
1. Expose the listener port to the internet — `ngrok http 4319`, a Cloudflare
|
|
418
|
+
Tunnel, etc. Note the public URL.
|
|
419
|
+
2. Vercel dashboard → your team's **Settings → Log Drains**.
|
|
420
|
+
3. New drain: source **Runtime Logs**, delivery format **JSON**, endpoint the
|
|
421
|
+
public URL plus your `listenerPath` (e.g.
|
|
422
|
+
`https://your-tunnel.ngrok.app/drain`). Vercel verifies the endpoint on
|
|
423
|
+
creation, so the listener must be reachable then — run `verirun log vercel
|
|
424
|
+
--listen` in another terminal during setup.
|
|
425
|
+
4. Copy the signing secret into `VERIRUN_VERCEL_DRAIN_SECRET` or
|
|
426
|
+
`vercel.drainSecret`.
|
|
427
|
+
5. Put `listenerPort` and `listenerPath` in `.verirun/config.json`.
|
|
428
|
+
|
|
429
|
+
A free ngrok URL changes each session; a reserved hostname is worth it if you
|
|
430
|
+
run this often.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{createRequire as __cr}from'node:module';const require=__cr(import.meta.url);
|
|
3
|
+
import{createPublicKey as Nt,verify as Tt}from"node:crypto";import{readFileSync as Pt}from"node:fs";import Ze from"node:path";import{fileURLToPath as _t}from"node:url";import{Command as Dt}from"commander";import Q from"node:os";import{setTimeout as de}from"node:timers/promises";import{spawn as or}from"node:child_process";function x(e){let r=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",o=process.platform==="win32"?["/c","start","",e]:[e];try{let n=or(r,o,{stdio:"ignore",detached:!0});n.on("error",()=>{}),n.unref()}catch{}}var ir=!!process.stdout.isTTY&&!process.env.NO_COLOR,L=(e,r)=>ir?`\x1B[${e}m${r}\x1B[0m`:r,t={info:e=>console.log(e),step:e=>console.log(`${L("90","\xB7")} ${e}`),success:e=>console.log(`${L("32","\u2713")} ${e}`),warn:e=>console.warn(`${L("33","!")} ${e}`),error:e=>console.error(`${L("31","\u2717")} ${e}`),dim:e=>L("90",e),bold:e=>L("1",e)};var k="0.1.0";var sr=1440*60*1e3;function X(){return import("./gate.mjs")}async function le(e){let{getMachineId:r,saveGlobalConfig:o,clearLicenseState:n,loadGlobalConfig:i,resolveServer:s,persistLease:a}=await X(),c=await i(),d=e.server?e.server.replace(/\/+$/,""):s(c.licenseServer);if(c.token&&!e.force){t.info("This machine is already connected. Run `verirun whoami`, or"),t.info("`verirun login --force` to connect it to a different account.");return}let u=await r(),p=await fetch(`${d}/api/cli/link`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({fingerprint:u,hostname:Q.hostname(),os:`${Q.platform()} ${Q.release()}`,cliVersion:k})}).catch(()=>{});if(!p||!p.ok)throw new Error(`Could not start login against ${d}. Check your connection.`);let f=await p.json(),m=`${f.verifyUrl}?code=${encodeURIComponent(f.userCode)}`;t.info(""),t.info("To connect this machine to your Verirun account:"),t.info(""),t.info(` 1. Open ${t.bold(f.verifyUrl)}`),t.info(` 2. Enter ${t.bold(f.userCode)}`),t.info(""),t.info(t.dim(`(trying to open ${m} for you)`)),x(m),t.info(""),t.step("Waiting for approval in the browser..."),e.server&&await o({licenseServer:d});let h=Date.now()+f.expiresIn*1e3,R=Math.max(2,f.interval)*1e3;for(;Date.now()<h;){await de(R);let g=await fetch(`${d}/api/cli/link/poll`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({deviceCode:f.deviceCode,fingerprint:u})}).catch(()=>{});if(!g)continue;if(g.status===429){await de(R);continue}let l=await g.json().catch(()=>({status:"server_error"}));if(l.status==="ok"&&l.token){await o({token:l.token,licenseKey:void 0}),await n();let z={kind:"token",value:l.token,server:d},N=l.license?.lease;if(N){await a(z,N);let y=l.license,I=y?.planName??y?.plan??"trial",A=typeof y?.seats=="number"&&y.seats>1?`, ${y.seats} machine seats`:"";t.success(`Connected. Plan: ${I} (${y?.status??"ok"})${A}.`)}else l.license&&l.license.valid===!1?(t.success("Connected."),l.license.reason==="trial_device_limit"?t.warn("This machine has already been used for the free trial on two other accounts, so record and run are blocked. Email support to request another trial, or subscribe from your account."):t.warn(`The account is connected but not active (${l.license.reason??l.license.status??"unknown"}). Check your subscription at your account page.`)):t.success("Connected.");return}if(l.status!=="pending"){if(l.status==="expired")throw new Error("The code expired. Run `verirun login` again.");if(l.status==="denied")throw new Error("The request was denied in the browser.");if(l.status==="no_license")throw new Error("That account has no license. Contact support.");if(l.status==="fingerprint_mismatch")throw new Error("This machine does not match the one that started the login.")}}throw new Error("Timed out waiting for approval. Run `verirun login` again.")}async function ue(){let{loadGlobalConfig:e,resolveServer:r,saveGlobalConfig:o,clearLicenseState:n}=await X(),i=await e(),s=r(i.licenseServer);i.token&&await fetch(`${s}/api/cli/logout`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:i.token})}).catch(()=>{}),await o({token:void 0,licenseKey:void 0}),await n(),t.success("Disconnected this machine. Run `verirun login` to reconnect.")}async function fe(e){let{resolveCredential:r,checkWithServer:o,persistLease:n,readStoredLease:i}=await X(),s=await r();if(t.info(""),!s){t.info(t.bold("Verirun: not connected")),t.info(""),t.info(`Run ${t.bold("verirun login")} to connect this machine.`),t.info("");return}let a=e.server?e.server.replace(/\/+$/,""):s.server,c={...s,server:a};t.info(t.bold("Verirun")),t.info(` credential ${s.kind==="token"?"device token":"license key"}`+(process.env.VERIRUN_LICENSE_KEY?t.dim(" (from $VERIRUN_LICENSE_KEY)"):"")),t.info(` server ${a}`);let d=await o(c);if(d.reachable&&d.lease){await n(c,d.lease);let f=(await i(c))?.payload;if(t.info(` status ${t.bold(f?.status??"valid")}`),f?.planName||f?.plan){let m=f.planName&&f.plan&&f.planName!==f.plan?t.dim(` (${f.plan})`):"";t.info(` plan ${f.planName??f.plan}${m}`)}typeof f?.seats=="number"&&t.info(` seats ${f.seats} machine${f.seats===1?"":"s"}`),f?.exp&&t.info(` lease until ${new Date(f.exp*1e3).toISOString()}`),t.info(""),t.info(t.dim("Checked just now, lease signature verified.")),t.info("");return}if(d.reachable&&d.result&&!d.result.valid){t.info(` status ${d.result.status??"invalid"} (${d.result.reason??"\u2014"})`),t.info(""),t.info("record and run will not proceed."),t.info("");return}t.warn(" the license server is not reachable right now.");let u=await i(c);if(u){let p=Math.floor((Date.now()-u.payload.iat*1e3)/sr);t.info(` lease valid, issued ${p} day${p===1?"":"s"} ago`),t.info(t.dim(" Nags after 15 days offline, stops after 30."))}else t.info(t.dim(" No valid lease stored. Connect to the internet and run this again."));t.info("")}import{existsSync as ve}from"node:fs";import{readdir as be}from"node:fs/promises";import{existsSync as pe}from"node:fs";import{chmod as ar,mkdir as cr,readFile as me,writeFile as dr}from"node:fs/promises";import lr from"node:path";var ge="data-testid";async function E(e){let r={};if(pe(e)){let s=await me(e,"utf8");try{r=JSON.parse(s)}catch(a){throw new Error(`Config file is not valid JSON: ${e}`,{cause:a})}}let o=process.env.VERIRUN_SUPABASE_ACCESS_TOKEN;o&&(r.supabase={...r.supabase,accessToken:o});let n=process.env.VERIRUN_RAILWAY_TOKEN;n&&(r.railway={...r.railway,token:n});let i=process.env.VERIRUN_VERCEL_DRAIN_SECRET;return i&&(r.vercel={...r.vercel,drainSecret:i}),r}async function O(e,r){let o={};if(pe(e))try{o=JSON.parse(await me(e,"utf8"))}catch(i){throw new Error(`Config file is not valid JSON: ${e}`,{cause:i})}let n={...o,...r,supabase:r.supabase?{...o.supabase,...r.supabase}:o.supabase,vercel:r.vercel?{...o.vercel,...r.vercel}:o.vercel,railway:r.railway?{...o.railway,...r.railway}:o.railway};await cr(lr.dirname(e),{recursive:!0,mode:448}),await dr(e,`${JSON.stringify(n,null,2)}
|
|
4
|
+
`,{mode:384}),await ar(e,384).catch(()=>{})}import{existsSync as we}from"node:fs";import v from"node:path";var U=".verirun";function ur(e=process.cwd()){let r=v.resolve(e),o;for(;;){if(we(v.join(r,".git")))return r;!o&&we(v.join(r,"package.json"))&&(o=r);let n=v.dirname(r);if(n===r)break;r=n}return o??v.resolve(e)}function b(e){let r=ur(e),o=v.join(r,U);return{repoRoot:r,dataDir:o,workflowsDir:v.join(o,"workflows"),runsDir:v.join(o,"runs"),configPath:v.join(o,"config.json")}}function V(e,r){return v.join(e.workflowsDir,`${r}.json`)}function he(e=new Date){return e.toISOString().replace(/[:.]/g,"-")}function ye(e,r,o){return v.join(e.runsDir,`${r}-${o}`)}var Z=1;function fr(e){return e.commands.filter(r=>r.name()!=="help").map(r=>{let o=r.registeredArguments.map(n=>n.required?`<${n.name()}>`:`[${n.name()}]`).join(" ");return{name:r.name(),usage:`verirun ${r.name()}${o?` ${o}`:""}${r.options.length?" [options]":""}`,description:r.description(),options:r.options.map(n=>({flags:n.flags,description:n.description}))}})}async function pr(e){try{return(await be(e)).filter(r=>r.endsWith(".json")).map(r=>r.slice(0,-5)).sort()}catch{return[]}}async function mr(e,r){try{return(await be(e,{withFileTypes:!0})).filter(n=>n.isDirectory()).map(n=>n.name).sort().slice(-r).reverse()}catch{return[]}}async function gr(){let e=b(),r={};try{r=await E(e.configPath)}catch{}let o=["supabase","vercel","railway"].filter(n=>r[n]);return{repoRoot:e.repoRoot,dataDir:e.dataDir,dataDirExists:ve(e.dataDir),configPath:e.configPath,configExists:ve(e.configPath),logSources:[...o],userFilter:typeof r.userFilter=="string"?r.userFilter:null,workflows:await pr(e.workflowsDir),recentRuns:await mr(e.runsDir,5)}}var Se="verirun records a browser workflow once, replays those exact steps headless on demand, and captures the backend logs produced during the replay. Deterministic: the same recording gives the same run and the same answer.",Re=["A human records the workflow once: `verirun record <name> --url <app-url>` (needs a display).","After a code change, run it: `verirun run <name>`. Add `--user <email>` to keep only that user's log lines.","Exit code 0 = every step replayed. Exit code 1 = a step failed, or a licensing / setup error.","Read `.verirun/runs/<name>-<newest>/logs.json`: check `status`; on `failed` read `error` and `steps.failedAt`; then scan `entries` for the backend's side \u2014 a 200 that hid an exception, a write that never landed, a job that threw after the screen said done.","`verirun run` writes logs.json even when replay fails, so partial logs survive.","Do not edit files under `.verirun/workflows/` by hand unless you understand the selector format; re-record instead."];function wr(e,r){let o=[],n=(i="")=>{o.push(i)};n("# verirun \u2014 agent reference"),n(),n(Se),n(),n(`Version ${k}. Full docs: https://get-verirun.duely.in and the bundled README.md / \`man verirun\`.`),n(),n("## This checkout"),n(),n(`- repo root: ${e.repoRoot}`),n(`- data dir: ${e.dataDir} ${e.dataDirExists?"(exists)":"(not created yet)"}`),n(`- config: ${e.configPath} ${e.configExists?"(present)":"(absent)"}`),n(`- log sources: ${e.logSources.length?e.logSources.join(", "):"none configured \u2014 `verirun run` still replays, just with no backend logs"}`),n(`- user filter: ${e.userFilter??"none"}`),n(`- workflows: ${e.workflows.length?e.workflows.join(", "):"none recorded yet"}`),n(`- recent runs: ${e.recentRuns.length?e.recentRuns.join(", "):"none yet"}`),n(),n("Account: `record` and `run` need a connected account (`verirun login`, or `$VERIRUN_LICENSE_KEY` in CI). `verirun whoami` shows status. `--help`, `--version`, `verirun log`, and `verirun agent` do not need one."),n(),n("## Commands");for(let i of r){n(),n(`### ${i.usage}`),i.description&&n(i.description);for(let s of i.options)n(`- \`${s.flags}\` \u2014 ${s.description}`)}return n(),n("## `.verirun/` layout (at the repo root shown above)"),n(),n("```"),n(".verirun/"),n(" config.json log sources, userFilter, testIdAttribute"),n(" workflows/<name>.json one recorded workflow each"),n(" runs/<name>-<timestamp>/logs.json captured logs, one directory per run"),n("```"),n(),n("Per-user, not per-project: `~/.verirun/config.json` (device token), `~/.verirun/state.json` (signed license lease)."),n(),n(`## logs.json (schemaVersion ${Z})`),n(),n("```jsonc"),n("{"),n(` "schemaVersion": ${Z},`),n(' "workflow": "<name>",'),n(' "status": "ok" | "failed", // "failed" => process exit code 1'),n(' "window": { "startedAt": "<ISO>", "endedAt": "<ISO>" },'),n(' "steps": { "total": N, "replayed": N, "failedAt"?: N },'),n(' "error"?: "<message>", // present only when status is "failed"'),n(' "filter"?: { "user": "<needle>" }, // present when --user / config.userFilter narrowed entries'),n(' "sources": [ { "id": "supabase"|"vercel"|"railway", "name": "...", "entries": N } ],'),n(' "entries": ['),n(" {"),n(' "source": "supabase"|"vercel"|"railway",'),n(' "stream": "<sub-stream, e.g. edge_logs / lambda>",'),n(' "timestamp": "<ISO>",'),n(' "level": "<info|warn|error|...>",'),n(' "message": "<rendered log line>",'),n(' "raw": { /* provider payload, untouched */ }'),n(" }"),n(" ]"),n("}"),n("```"),n(),n("`entries` is every source merged and sorted by `timestamp`. When a `--user` filter is active, `entries` and every `sources[].entries` count are post-filter."),n(),n("## Recommended agent loop"),n(),Re.forEach((i,s)=>n(`${s+1}. ${i}`)),n(),n("Save this into your agent context: `verirun agent > .verirun/AGENT.md`, or paste it into `CLAUDE.md` / `.cursorrules` / an AGENTS.md. `verirun agent --json` emits the same information as JSON."),n(),o.join(`
|
|
5
|
+
`)}async function Ce(e,r){let o=await gr(),n=fr(e);if(r.json){process.stdout.write(`${JSON.stringify({about:Se,version:k,docs:"https://get-verirun.duely.in",checkout:o,commands:n,layout:{config:"<repo>/.verirun/config.json",workflows:"<repo>/.verirun/workflows/<name>.json",runs:"<repo>/.verirun/runs/<name>-<timestamp>/logs.json",userConfig:"~/.verirun/config.json",userState:"~/.verirun/state.json"},logsJsonSchemaVersion:Z,agentLoop:Re},null,2)}
|
|
6
|
+
`);return}process.stdout.write(`${wr(o,n)}
|
|
7
|
+
`)}import{spawnSync as xr}from"node:child_process";import{createInterface as Lr}from"node:readline/promises";var hr="https://backboard.railway.com/graphql/v2",re="https://railway.com/account/tokens";async function $e(e,r,o){let n;try{n=await fetch(hr,{method:"POST",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify({query:r,variables:o})})}catch(s){return{error:s instanceof Error?s.message:String(s)}}let i=await n.json().catch(()=>{});return n.ok?i?.errors?.length?{error:i.errors.map(s=>s.message).filter(Boolean).join("; ")||"GraphQL error"}:i?.data?{data:i.data}:{error:"Railway returned no data."}:{error:i?.errors?.[0]?.message??`HTTP ${n.status}`}}async function B(e){if(e.deploymentId)return{deploymentId:e.deploymentId};if(!e.token||!e.projectId||!e.environmentId||!e.serviceId)return{error:"set railway.deploymentId, or railway.projectId + railway.environmentId + railway.serviceId"};let o=await $e(e.token,`
|
|
8
|
+
query($input: JSON!) {
|
|
9
|
+
deployments(input: $input, first: 1) {
|
|
10
|
+
edges { node { id status createdAt } }
|
|
11
|
+
}
|
|
12
|
+
}`,{input:{projectId:e.projectId,environmentId:e.environmentId,serviceId:e.serviceId}});if("error"in o)return o;let n=o.data.deployments.edges[0]?.node;return n?{deploymentId:n.id}:{error:"no deployments found for that project / environment / service"}}async function te(e,r,o,n={}){let i=n.userNeedle?.trim(),s=i?`"${i.replace(/"/g,"")}"`:void 0,c=await $e(e,`
|
|
13
|
+
query($id: String!, $start: DateTime, $end: DateTime, $filter: String) {
|
|
14
|
+
deploymentLogs(deploymentId: $id, startDate: $start, endDate: $end, filter: $filter, limit: 1000) {
|
|
15
|
+
timestamp message severity tags attributes
|
|
16
|
+
}
|
|
17
|
+
}`,{id:r,start:o.startedAt,end:o.endedAt,filter:s});return"error"in c?c:{entries:Array.isArray(c.data.deploymentLogs)?c.data.deploymentLogs:[]}}function yr(e){let r=typeof e.severity=="string"?e.severity:void 0;return{source:"railway",stream:r?r.toLowerCase():"deploy",timestamp:typeof e.timestamp=="string"?e.timestamp:void 0,level:r,message:typeof e.message=="string"?e.message:void 0,raw:e}}var ee=class{id="railway";name="Railway";cfg;constructor(r){this.cfg=r}async start(){if(!this.cfg.token)throw new Error("Railway log capture needs an API token. Set VERIRUN_RAILWAY_TOKEN or railway.token in .verirun/config.json.");if(!this.cfg.deploymentId&&!(this.cfg.projectId&&this.cfg.environmentId&&this.cfg.serviceId))throw new Error("Railway log capture needs railway.deploymentId, or railway.projectId + railway.environmentId + railway.serviceId, in .verirun/config.json.")}async collect(r,o){let n=await B(this.cfg);if("error"in n)return t.warn(`Railway: ${n.error}`),[];let i=await te(this.cfg.token,n.deploymentId,o,{userNeedle:r.userFilter});if("error"in i)return t.warn(`Railway: ${i.error}`),[];let s=i.entries.map(yr);return s.sort((a,c)=>(a.timestamp??"").localeCompare(c.timestamp??"")),s}async dispose(){}};function Ee(e){return e.railway?new ee(e.railway):void 0}var vr="https://api.supabase.com",oe=["postgres_logs","edge_logs","auth_logs"],ne=class{id="supabase";name="Supabase";projectRef;accessToken;sources;constructor(r){this.projectRef=r.projectRef,this.accessToken=r.accessToken,this.sources=r.sources&&r.sources.length>0?r.sources:oe}async start(){if(!this.projectRef)throw new Error("Supabase log capture needs supabase.projectRef in .verirun/config.json.");if(!this.accessToken)throw new Error("Supabase log capture needs an access token. Set VERIRUN_SUPABASE_ACCESS_TOKEN or supabase.accessToken in .verirun/config.json.")}async collect(r,o){let n=r.userFilter,i=[],s=!1;for(let a of this.sources){let c=await W(this.projectRef,this.accessToken,a,o,{userNeedle:n});if("error"in c){t.warn(`Supabase ${a}: ${c.error}`);continue}n&&!c.serverFiltered&&(s=!0);for(let d of c.rows)i.push(Sr(a,d))}return n&&s&&t.info(t.dim("Supabase: user filter applied client-side for a source (more rows pulled).")),i.sort((a,c)=>(a.timestamp??"").localeCompare(c.timestamp??"")),i}async dispose(){}},br=/^[A-Za-z0-9._@%+-]{1,256}$/;async function ke(e,r,o,n){let i=new URL(`/v1/projects/${e}/analytics/endpoints/logs.all`,vr);i.searchParams.set("sql",o),i.searchParams.set("iso_timestamp_start",n.startedAt),i.searchParams.set("iso_timestamp_end",n.endedAt);let s;try{s=await fetch(i,{headers:{Authorization:`Bearer ${r}`,Accept:"application/json"}})}catch(c){return{error:c instanceof Error?c.message:String(c)}}let a=await s.json().catch(()=>{});if(!s.ok){let c=(typeof a?.error=="string"?a.error:a?.error?.message)??a?.message??`HTTP ${s.status}`;return{error:`${c}${c.startsWith("HTTP")?"":` (HTTP ${s.status})`}`}}return{rows:Array.isArray(a?.result)?a.result:[]}}async function W(e,r,o,n,i={}){let s=i.userNeedle?.trim().toLowerCase(),a=s&&br.test(s)?s:void 0,c=p=>{let f=p&&a?` where strpos(lower(event_message), '${a}') > 0 or strpos(lower(to_json_string(metadata)), '${a}') > 0`:"";return`select id, timestamp, event_message, metadata from ${o}${f} order by timestamp asc limit 1000`},d=await ke(e,r,c(!0),n);if(!("error"in d))return{rows:d.rows,serverFiltered:!!a};if(!a)return d;let u=await ke(e,r,c(!1),n);return"error"in u?u:{rows:u.rows,serverFiltered:!1}}function Sr(e,r){return{source:"supabase",stream:e,timestamp:Rr(r.timestamp),message:typeof r.event_message=="string"?r.event_message:void 0,raw:r}}function Rr(e){if(typeof e=="string")return e;if(typeof e=="number"&&Number.isFinite(e)){let r=e>1e14?e/1e3:e;return new Date(r).toISOString()}}function Ie(e){return e.supabase?new ne(e.supabase):void 0}import{createHmac as Cr,timingSafeEqual as $r}from"node:crypto";import{createServer as Er}from"node:http";import{setTimeout as kr}from"node:timers/promises";var F=4319,M="/drain",ie=3e3,T=class{id="vercel";name="Vercel";port;path;secret;graceMs;project;onEntry;server;received=[];droppedByProject=0;constructor(r,o={}){this.port=r.listenerPort??F,this.path=r.listenerPath??M,this.secret=r.drainSecret,this.graceMs=r.drainGraceMs??ie,this.project=r.project?.trim()||void 0,this.onEntry=o.onEntry}projectMatches(r){return this.project?r.projectId===this.project||r.projectName===this.project:!0}async start(){let r=Er((o,n)=>this.handle(o,n));this.server=r,await new Promise((o,n)=>{let i=s=>{n(s.code==="EADDRINUSE"?new Error(`Vercel log listener: port ${this.port} is already in use.`):s)};r.once("error",i),r.listen(this.port,()=>{r.off("error",i),o()})}),t.step(`Vercel log listener on http://localhost:${this.port}${this.path}`)}async collect(){return this.graceMs>0&&await kr(this.graceMs),this.received.length===0&&this.droppedByProject>0&&t.warn(`Vercel: ${this.droppedByProject} log line(s) arrived but none matched project "${this.project}". Check the name/ID in .verirun/config.json (vercel.project).`),[...this.received]}async dispose(){let r=this.server;r&&(this.server=void 0,await new Promise(o=>r.close(()=>o())))}handle(r,o){let n=r.headers["x-vercel-verify"];if(typeof n=="string"&&o.setHeader("x-vercel-verify",n),r.method!=="POST"||!this.matchesPath(r.url)){o.statusCode=r.method==="POST"?404:200,o.end();return}let i=[];r.on("data",s=>i.push(s)),r.on("error",()=>{o.statusCode=400,o.end()}),r.on("end",()=>{let s=Buffer.concat(i);if(!this.verifySignature(r,s)){t.warn("Vercel log drain: rejected a delivery with a bad signature."),o.statusCode=401,o.end();return}try{for(let a of Ir(s.toString("utf8"))){if(!this.projectMatches(a)){this.droppedByProject+=1;continue}let c=Ar(a);this.received.push(c),this.onEntry?.(c)}}catch{t.warn("Vercel log drain: could not parse a delivery body.")}o.statusCode=200,o.end()})}matchesPath(r){return r?(r.split("?")[0]??"")===this.path:!1}verifySignature(r,o){if(!this.secret)return!0;let n=r.headers["x-vercel-signature"];if(typeof n!="string")return!1;let i=Cr("sha1",this.secret).update(o).digest("hex"),s=Buffer.from(i),a=Buffer.from(n);return s.length===a.length&&$r(s,a)}};function Ir(e){let r=e.trim();if(!r)return[];if(r.startsWith("[")){let o=JSON.parse(r);return Array.isArray(o)?o:[]}return r.split(`
|
|
18
|
+
`).map(o=>o.trim()).filter(Boolean).map(o=>JSON.parse(o))}function Ar(e){return{source:"vercel",stream:typeof e.source=="string"?e.source:void 0,timestamp:typeof e.timestamp=="number"?new Date(e.timestamp).toISOString():void 0,level:typeof e.level=="string"?e.level:void 0,message:typeof e.message=="string"?e.message:void 0,raw:e}}function Ae(e){return e.vercel?new T(e.vercel):void 0}var xe=["vercel","supabase","railway"];async function Le(e,r){if(e!==void 0&&!xe.includes(e))throw new Error(`Unknown log source "${e}". Use one of: ${xe.join(", ")}.`);let o=b(),n=await E(o.configPath);if(e===void 0){jr(n);return}if(e==="vercel"){await Tr(n,r);return}if(e==="railway"){await Wr(n,r);return}await Ur(n,r)}function jr(e){let r=e.vercel,o=e.supabase,n=e.railway;t.info(""),t.info(t.bold("Log sources")),t.info(""),t.info(` ${"vercel".padEnd(10)} ${r?`configured listener :${r.listenerPort??F}${r.listenerPath??M}`:t.dim("not configured")}`),t.info(` ${"supabase".padEnd(10)} ${o?`configured project ${o.projectRef??t.dim("(no projectRef)")}`:t.dim("not configured")}`),t.info(` ${"railway".padEnd(10)} ${n?`configured ${n.deploymentId?`deployment ${n.deploymentId}`:`service ${n.serviceId??t.dim("(none)")}`}`:t.dim("not configured")}`),t.info(""),t.info(`Run ${t.bold("verirun log vercel")}, ${t.bold("verirun log supabase")} or ${t.bold("verirun log railway")} to connect one.`)}var Nr="https://vercel.com/d?to=%2F%5Bteam%5D%2F~%2Fsettings%2Flog-drains&title=Log%20Drains";async function Tr(e,r){let{configPath:o}=b(),n=e.vercel??{},i=n.listenerPort??F,s=n.listenerPath??M,a=n.drainGraceMs??ie;if(r.listen){await Pr({...n,listenerPort:i,listenerPath:s});return}let c=process.env.VERIRUN_VERCEL_DRAIN_SECRET;if(!!e.vercel&&!r.reconnect){t.info(""),t.info(t.bold("Vercel logs \u2014 connected")),t.info(` endpoint http://localhost:${i}${s}`),t.info(` drain secret ${c?"from $VERIRUN_VERCEL_DRAIN_SECRET":n.drainSecret?"from config.json":t.dim("none \u2014 deliveries are not verified")}`),t.info(` grace ${a}ms`),t.info(` project ${n.project??t.dim("all projects on the drain")}`),t.info(""),t.info(t.dim("--reconnect to change it \xB7 --listen to run the verification listener"));return}if(t.info(""),t.info(t.bold("Connect Vercel logs \u2014 do these in order:")),t.info(""),t.info(" 1. New terminal. Start a tunnel to port "+i+" and copy the https URL it prints:"),t.info(` ${t.bold(`ngrok http ${i}`)}`),t.info(t.dim(` (or: cloudflared tunnel --url http://localhost:${i})`)),t.info(""),t.info(" 2. Another new terminal. Start the listener and leave it running:"),t.info(` ${t.bold("verirun log vercel --listen")}`),t.info(""),t.info(" 3. Vercel page opening now (your team \u2192 Settings \u2192 Log Drains) \u2192 Add a drain:"),t.info(" Sources ......... check "+t.bold("Edge")+" and "+t.bold("Serverless")+", uncheck the rest"),t.info(" Delivery format . "+t.bold("JSON")),t.info(` Endpoint ........ ${t.bold(`<https URL from step 1>${s}`)}`),t.info(" then Save."),t.info(""),t.info(" 4. Vercel shows a Signing Secret. Copy it, paste it below."),!process.stdin.isTTY){t.info(""),t.info(t.dim(" Then: VERIRUN_VERCEL_DRAIN_SECRET=<secret> (or vercel.drainSecret in .verirun/config.json)"));return}x(Nr),t.info("");let u=await S(" Signing secret: ");if(!u){t.warn(" No secret. Deliveries would NOT be verified \u2014 anyone who finds the tunnel URL"),t.warn(" could feed fake logs into your runs.");let m=(await S(" Save without a secret anyway? [y/N]: ")).toLowerCase();if(m!=="y"&&m!=="yes"){t.info(" Nothing saved. Re-run `verirun log vercel` once you have the secret.");return}u=""}let p=await S(" Only one project's logs? Paste its name or prj_\u2026 ID, or press Enter for all: "),f={};u&&(f.drainSecret=u),p&&(f.project=p),await O(o,{vercel:f}),t.info(""),t.info(` Saved to ${t.dim(o)}`),t.success(" Done. `verirun run` starts its own listener \u2014 you only need --listen for tailing.")}async function Pr(e){let r=new T(e,{onEntry:o=>{let n=o.timestamp??new Date().toISOString(),i=(o.level??"log").padEnd(5),s=o.stream?`${o.stream} `:"";t.info(`${t.dim(n)} ${i} ${t.dim(s)}${o.message??""}`)}});await r.start(),e.project&&t.info(t.dim(`Filtering to project "${e.project}".`)),t.info(t.dim("Waiting for deliveries. Press Ctrl+C to stop.")),t.info(""),await new Promise(o=>{let n=()=>{process.off("SIGINT",n),process.off("SIGTERM",n),t.info(""),r.dispose().then(o)};process.on("SIGINT",n),process.on("SIGTERM",n)}),t.step("Listener stopped.")}var se="https://supabase.com/dashboard/account/tokens";async function S(e){if(!process.stdin.isTTY)return"";let r=Lr({input:process.stdin,output:process.stdout});try{return(await r.question(e)).trim()}finally{r.close()}}function _r(){let e=xr("supabase",["projects","list","--output","json"],{encoding:"utf8"});if(!(e.error||e.status!==0))try{let o=JSON.parse(e.stdout).map(n=>({ref:String(n.id??n.ref??n.reference_id??""),name:String(n.name??"")})).filter(n=>n.ref!=="");return o.length>0?o:void 0}catch{return}}async function Dr(e){if(e)return e;let r=_r();if(r&&r.length===1){let n=r[0];return t.info(` project ${n.name} (${n.ref}) ${t.dim("\u2014 from the supabase CLI")}`),n.ref}if(r&&r.length>1){t.info(" Which project?"),r.forEach((i,s)=>t.info(` ${s+1}. ${i.name} (${i.ref})`));let n=Number.parseInt(await S(" Number: "),10)-1;if(r[n])return r[n].ref;t.warn(" Not one of the choices.")}return await S(" Project ref (the <ref> in dashboard/project/<ref>): ")||void 0}async function Or(e,r,o){if(!e.projectRef)throw new Error("--test needs a connected project. Run `verirun log supabase` first.");if(!o)throw new Error("--test needs an access token. Run `verirun log supabase`, or set $VERIRUN_SUPABASE_ACCESS_TOKEN.");let n=new Date,i={startedAt:new Date(n.getTime()-5*6e4).toISOString(),endedAt:n.toISOString()};t.info(""),t.info(`Querying ${r.length} source${r.length===1?"":"s"} for the last 5 minutes\u2026`),t.info("");let s=!1;for(let a of r){let c=await W(e.projectRef,o,a,i);"error"in c?(s=!0,t.error(`${a.padEnd(16)} ${c.error}`)):t.success(`${a.padEnd(16)} ${c.rows.length} row${c.rows.length===1?"":"s"}`)}s&&(process.exitCode=1)}async function Ur(e,r){let{configPath:o}=b(),n=e.supabase??{},i=n.sources&&n.sources.length>0?n.sources:oe,s=process.env.VERIRUN_SUPABASE_ACCESS_TOKEN,a=s??n.accessToken;if(r.test){await Or(n,i,a);return}if(n.projectRef&&a&&!r.reconnect){t.info(""),t.info(t.bold("Supabase logs \u2014 connected")),t.info(` project ${n.projectRef}`),t.info(` token ${s?"from $VERIRUN_SUPABASE_ACCESS_TOKEN":"from config.json"}`),t.info(` sources ${i.join(", ")}`),t.info(""),t.info(t.dim("--reconnect to replace the token \xB7 --test to query the last 5 minutes"));return}if(t.info(""),t.info(t.bold("Connect Supabase logs")),!a||r.reconnect){if(!process.stdin.isTTY){t.info(""),t.info("Not an interactive shell. Set these and re-run:"),t.info(` export VERIRUN_SUPABASE_ACCESS_TOKEN=sbp_... ${t.dim(`(create at ${se})`)}`),t.info(` ${t.dim("and supabase.projectRef in .verirun/config.json")}`);return}t.info(` Opening ${t.bold(se)}`),t.info(' Click "Generate new token", copy it, come back here.'),x(se),t.info("");let m=await S(" Paste the token: ");if(!m){t.warn(" Nothing pasted. No changes made.");return}m.startsWith("sbp_")||t.warn(" That does not look like a Supabase token (expected sbp_\u2026). Saving it anyway."),a=m}let c=await Dr(r.reconnect?void 0:n.projectRef);if(!c){t.warn(" No project ref given. No changes made.");return}let d={projectRef:c};s||(d.accessToken=a),await O(o,{supabase:d}),t.info(""),t.info(` Saved to ${t.dim(o)}`),t.info(" Checking it works\u2026");let u=new Date,p={startedAt:new Date(u.getTime()-5*6e4).toISOString(),endedAt:u.toISOString()},f=await W(c,a,i[0],p);if("error"in f){t.error(` ${i[0]} query failed: ${f.error}`),t.info(t.dim(" Token or project ref looks wrong. Re-run `verirun log supabase --reconnect`.")),process.exitCode=1;return}t.success(` Connected. \`verirun run\` now includes Supabase logs (${i.join(", ")}).`)}function je(e){return!!(e.deploymentId||e.projectId&&e.environmentId&&e.serviceId)}function Vr(e){try{let r=new URL(e.trim());if(!/(^|\.)railway\.(com|app)$/.test(r.hostname))return;let o=r.pathname.match(/\/project\/([^/]+)\/service\/([^/?#]+)/),n=r.searchParams.get("environmentId");return!o||!o[1]||!o[2]||!n?void 0:{projectId:o[1],environmentId:n,serviceId:o[2]}}catch{return}}async function Br(e,r){if(!r)throw new Error("--test needs a token. Run `verirun log railway`, or set $VERIRUN_RAILWAY_TOKEN.");if(!je(e))throw new Error("--test needs a connected deployment. Run `verirun log railway` first.");let o=await B({...e,token:r});if("error"in o)throw new Error(`Railway: ${o.error}`);let n=new Date,i={startedAt:new Date(n.getTime()-5*6e4).toISOString(),endedAt:n.toISOString()};t.info(""),t.info(`Querying deployment ${o.deploymentId} for the last 5 minutes\u2026`);let s=await te(r,o.deploymentId,i);if("error"in s){t.error(`railway ${s.error}`),process.exitCode=1;return}t.success(`railway ${s.entries.length} row${s.entries.length===1?"":"s"}`)}async function Wr(e,r){let{configPath:o}=b(),n=e.railway??{},i=process.env.VERIRUN_RAILWAY_TOKEN,s=i??n.token;if(r.test){await Br(n,s);return}if(s&&je(n)&&!r.reconnect){t.info(""),t.info(t.bold("Railway logs \u2014 connected")),t.info(` token ${i?"from $VERIRUN_RAILWAY_TOKEN":"from config.json"}`),n.deploymentId?t.info(` deployment ${n.deploymentId} ${t.dim("(pinned)")}`):(t.info(` project ${n.projectId}`),t.info(` environment ${n.environmentId}`),t.info(` service ${n.serviceId} ${t.dim("(latest deployment resolved per run)")}`)),t.info(""),t.info(t.dim("--reconnect to change it \xB7 --test to query the last 5 minutes"));return}if(t.info(""),t.info(t.bold("Connect Railway logs \u2014 2 things:")),t.info(""),t.info(` 1. On the page opening now (${re}):`),t.info(' "Create Token" \u2192 name it \u2192 pick your team \u2192 Create \u2192 copy the value.'),t.info(" 2. In Railway, open the service you want logs from. Copy its URL from the"),t.info(" address bar \u2014 looks like:"),t.info(" https://railway.com/project/PROJECT/service/SERVICE?environmentId=ENV"),!process.stdin.isTTY){t.info(""),t.info(t.dim("Then: VERIRUN_RAILWAY_TOKEN=<token>, and railway.projectId / environmentId / serviceId (from that URL) in .verirun/config.json."));return}if(!s||r.reconnect){x(re),t.info("");let u=await S(" Railway token: ");if(!u){t.warn(" Nothing pasted. No changes made.");return}s=u}let a={};i||(a.token=s);let c=Vr(await S(" Service URL (paste the whole thing): "));if(c)a.projectId=c.projectId,a.environmentId=c.environmentId,a.serviceId=c.serviceId;else{t.warn(" Couldn't read that URL. Enter the three IDs from it by hand:");let u=await S(" Project ID: "),p=await S(" Environment ID: "),f=await S(" Service ID: ");if(!u||!p||!f){t.warn(" Need all three. No changes made.");return}a.projectId=u,a.environmentId=p,a.serviceId=f}await O(o,{railway:a}),t.info(""),t.info(` Saved to ${t.dim(o)}`),t.info(" Checking it works\u2026");let d=await B({...n,...a,token:s});if("error"in d){t.error(` ${d.error}`),t.info(t.dim(" Re-run `verirun log railway --reconnect` with the right token / IDs.")),process.exitCode=1;return}t.success(` Connected. Deployment ${d.deploymentId}. \`verirun run\` now includes Railway logs.`)}import{existsSync as gt}from"node:fs";import{mkdir as wt}from"node:fs/promises";import Be from"node:path";import{spawnSync as Fr}from"node:child_process";var Mr={browser:"chromium",channel:"chrome",label:"Chrome",approximated:!1},Gr={browser:"chromium",channel:"msedge",label:"Edge",approximated:!1},Hr={browser:"firefox",label:"Firefox",approximated:!1},Jr={browser:"chromium",label:"Chromium",approximated:!1};function ae(e,r){let o=e.toLowerCase(),n=i=>r?{...i,label:r}:i;if(o.includes("firefox")||o.includes("mozilla"))return n(Hr);if(o.includes("msedge")||o.includes("microsoft-edge")||o.includes("edgemac")||o.includes("microsoftedge"))return n(Gr);if(o.includes("google-chrome")||o.includes("com.google.chrome")||o==="chromehtml"||o.includes("chrome"))return n(Mr);if(o.includes("chromium"))return n(Jr);if(o.includes("safari"))return{browser:"webkit",label:"Safari",approximated:!0};if(o.includes("brave"))return{browser:"chromium",label:"Brave",approximated:!0};if(o.includes("thebrowser")||o.includes("arc"))return{browser:"chromium",label:"Arc",approximated:!0};if(o.includes("vivaldi"))return{browser:"chromium",label:"Vivaldi",approximated:!0};if(o.includes("opera")||o.includes("opr"))return{browser:"chromium",label:"Opera",approximated:!0}}function G(e,r){let o=Fr(e,r,{encoding:"utf8",timeout:4e3});if(!(o.error||o.status!==0))return(o.stdout||"").trim()||void 0}function Kr(){let e=G("xdg-settings",["get","default-web-browser"])??G("xdg-mime",["query","default","x-scheme-handler/https"]);return e?ae(e):void 0}function qr(){let e=G("plutil",["-convert","json","-o","-",`${process.env.HOME}/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist`]);if(e)try{let o=(JSON.parse(e).LSHandlers??[]).find(i=>i.LSHandlerURLScheme==="https"),n=typeof o?.LSHandlerRoleAll=="string"?o.LSHandlerRoleAll:void 0;return n?ae(n):void 0}catch{return}}function Yr(){let r=G("reg",["query","HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice","/v","ProgId"])?.match(/ProgId\s+REG_SZ\s+(\S+)/i)?.[1];return r?ae(r):void 0}function Ne(){try{if(process.platform==="darwin")return qr();if(process.platform==="linux")return Kr();if(process.platform==="win32")return Yr()}catch{}}import{spawn as zr}from"node:child_process";import{mkdtemp as Qr,readFile as Xr,rm as Zr}from"node:fs/promises";import{createRequire as et}from"node:module";import rt from"node:os";import H from"node:path";var tt=new Set(["name","selector","locator","signals","pageGuid","ariaSnapshot"]);function Te(e){let r={};for(let[o,n]of Object.entries(e))!tt.has(o)&&n!==void 0&&(r[o]=n);return r}function nt(){let e=et(import.meta.url),r=e.resolve("playwright/package.json"),o=e("playwright/package.json"),n=typeof o.bin=="string"?o.bin:o.bin?.playwright;if(!n)throw new Error("Could not locate the Playwright CLI entrypoint in playwright/package.json.");return H.join(H.dirname(r),n)}async function Pe(e){let r=nt(),o=await Qr(H.join(rt.tmpdir(),"verirun-record-")),n=H.join(o,"recording.jsonl"),i=[r,"codegen","--target=jsonl",`--output=${n}`];e.browser&&i.push(`--browser=${e.browser}`),e.channel&&i.push(`--channel=${e.channel}`),e.userDataDir&&i.push(`--user-data-dir=${e.userDataDir}`),e.testIdAttribute&&i.push(`--test-id-attribute=${e.testIdAttribute}`),e.url&&i.push(e.url);try{let s=await new Promise((c,d)=>{let u=zr(process.execPath,i,{stdio:["ignore","inherit","inherit"]});u.on("error",d),u.on("close",p=>c(p))}),a=await Xr(n,"utf8").catch(()=>"");return{...ot(a),exitCode:s}}finally{await Zr(o,{recursive:!0,force:!0})}}function ot(e){let r=e.split(`
|
|
19
|
+
`).filter(i=>i.trim().length>0),o={},n=[];for(let[i,s]of r.entries()){let a;try{a=JSON.parse(s)}catch{continue}if(i===0&&a&&typeof a=="object"&&!("name"in a)){o=a;continue}a&&typeof a=="object"&&"name"in a&&n.push(a)}return{header:o,actions:n}}import{existsSync as it}from"node:fs";import{appendFile as st,readFile as at}from"node:fs/promises";import ct from"node:path";function dt(e){return e.split(`
|
|
20
|
+
`).map(r=>r.trim()).filter(r=>r&&!r.startsWith("#")).some(r=>r.replace(/^\//,"").replace(/\/$/,"")===U)}async function _e(e){let r=ct.join(e,".gitignore");if(!it(r))return"no-gitignore";let o=await at(r,"utf8");if(dt(o))return"already-present";let n=o.length===0||o.endsWith(`
|
|
21
|
+
`)?"":`
|
|
22
|
+
`;return await st(r,`${n}
|
|
23
|
+
# verirun: recorded workflows and captured run logs (may contain secrets)
|
|
24
|
+
${U}/
|
|
25
|
+
`,"utf8"),"added"}function j(e){return`'${e.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/\n/g,"\\n")}'`}function P(e){if(e&&typeof e=="object"&&"source"in e){let r=e;return`/${r.source??""}/${r.flags??""}`}return j(String(e))}function lt(e){let r=e.options??{},o=e.body;switch(e.kind){case"role":{let n=[];r.name!==void 0&&n.push(`name: ${P(r.name)}`),r.exact===!0&&n.push("exact: true");for(let i of r.attrs??[])n.push(`${i.name}: ${JSON.stringify(i.value)}`);return n.length?`getByRole(${j(o)}, { ${n.join(", ")} })`:`getByRole(${j(o)})`}case"test-id":return`getByTestId(${j(o)})`;case"text":case"label":case"placeholder":case"alt":case"title":{let n={text:"getByText",label:"getByLabel",placeholder:"getByPlaceholder",alt:"getByAltText",title:"getByTitle"}[e.kind];return r.exact===!0?`${n}(${P(o)}, { exact: true })`:`${n}(${P(o)})`}case"nth":return`nth(${o})`;case"first":return"first()";case"last":return"last()";case"visible":return`filter({ visible: ${o==="true"} })`;case"frame":return"contentFrame()";case"frame-locator":return`frameLocator(${j(o)})`;case"has-text":return`filter({ hasText: ${P(o)} })`;case"has-not-text":return`filter({ hasNotText: ${P(o)} })`;default:return`locator(${j(o)})`}}function De(e){if(!e)return;let r=[],o=e;for(;o;)r.push(lt(o)),o=o.next;return r.join(".")}var ut=/^[A-Za-z0-9][A-Za-z0-9._-]*$/;function J(e){if(!e)throw new Error("Workflow name is required.");if(e.length>64)throw new Error("Workflow name must be 64 characters or fewer.");if(!ut.test(e))throw new Error(`Invalid workflow name "${e}". Use letters, digits, dot, dash or underscore, starting with a letter or digit.`);if(e==="."||e==="..")throw new Error(`Invalid workflow name "${e}".`)}import{mkdir as ft,readFile as pt,writeFile as mt}from"node:fs/promises";import Oe from"node:path";var K=1;async function Ue(e,r){await ft(Oe.dirname(e),{recursive:!0}),await mt(e,`${JSON.stringify(r,null,2)}
|
|
26
|
+
`,"utf8")}async function Ve(e){let r=await pt(e,"utf8"),o;try{o=JSON.parse(r)}catch(i){throw new Error(`Workflow file is not valid JSON: ${e}`,{cause:i})}let n=o;if(!n||typeof n!="object"||!Array.isArray(n.steps))throw new Error(`Workflow file is malformed (no steps array): ${e}`);if(n.schemaVersion!==K)throw new Error(`Workflow "${n.name??Oe.basename(e)}" uses schema version ${n.schemaVersion}, but this build expects ${K}. Re-record it.`);return n}function ht(e){return!(e.name==="openPage"&&e.url==="about:blank")}function yt(e){let r=e.length;for(;r>0&&e[r-1]?.name==="closePage";)r-=1;return e.slice(0,r)}function vt(e,r){let o={index:r,action:e.name,params:Te(e),signals:Array.isArray(e.signals)?e.signals:[]};e.selector&&(o.selector=e.selector),e.locator&&(o.locator=e.locator);let n=De(e.locator);return n&&(o.description=n),e.pageGuid&&(o.pageGuid=e.pageGuid),o}function bt(e){let r=e.description??e.selector??"";switch(e.action){case"navigate":return`navigate ${String(e.params.url??"")}`;case"fill":return`fill ${r} ${t.dim(`= ${JSON.stringify(e.params.text??"")}`)}`;case"press":return`press ${r} ${t.dim(String(e.params.key??""))}`;default:return`${e.action.padEnd(9)} ${r}`.trimEnd()}}async function We(e,r){let{assertLicensed:o}=await import("./gate.mjs"),n=await o();n.note&&t.warn(n.note),J(e);let i=b(),s=await E(i.configPath),a=V(i,e);if(gt(a)&&!r.force)throw new Error(`Workflow "${e}" already exists at ${Be.relative(i.repoRoot,a)}. Re-run with --force to overwrite it.`);await wt(i.workflowsDir,{recursive:!0});let c=await _e(i.repoRoot);c==="added"?t.step("Added .verirun/ to .gitignore"):c==="no-gitignore"&&t.warn("No .gitignore at the repo root, so .verirun/ is not ignored. Recorded values and run logs may contain secrets.");let d=r.testIdAttribute??s.testIdAttribute??ge,u=r.browser,p=r.channel;if(r.defaultBrowser){(r.browser||r.channel)&&t.warn("--default-browser overrides -b / --channel.");let l=Ne();l?(u=l.browser,p=l.channel,l.approximated?t.warn(`Your default browser is ${l.label}, which Playwright can't drive directly \u2014 recording in ${p??u}.`):t.info(t.dim(`Recording in your default browser: ${l.label}`))):(t.warn("Couldn't detect your default browser; recording in bundled Chromium."),u="chromium",p=void 0)}t.info(""),t.info(`Recording ${t.bold(e)}. A browser window will open.`),t.info(t.dim("Perform the workflow, then close the browser to save.")),t.info("");let f=await Pe({url:r.url,browser:u,channel:p,userDataDir:r.userDataDir,testIdAttribute:d}),m=yt(f.actions.filter(ht));if(m.length===0){if(f.exitCode!==0)throw new Error(`Playwright codegen exited with code ${f.exitCode} and recorded nothing. If this persists, the installed Playwright may no longer support the \`jsonl\` codegen target that recording depends on.`);t.warn("No actions were recorded, so nothing was saved.");return}let h=m.map(vt),R=h.find(l=>l.action==="navigate"),g={schemaVersion:K,name:e,createdAt:new Date().toISOString(),startUrl:r.url??R?.params.url,browser:f.header.browserName??u??"chromium",channel:p,testIdAttribute:d,steps:h};await Ue(a,g),t.info("");for(let l of h)t.info(` ${t.dim(String(l.index).padStart(2))} ${bt(l)}`);t.info(""),t.success(`Saved ${h.length} step${h.length===1?"":"s"} to ${Be.relative(i.repoRoot,a)}`),h.some(l=>l.action==="fill"&&l.params.text)&&t.warn("Recorded steps include text you typed. Check the workflow file before sharing it.")}import{existsSync as It}from"node:fs";import{mkdir as At,writeFile as xt}from"node:fs/promises";import ze from"node:path";import{chromium as _,firefox as St,selectors as Rt,webkit as Ct}from"playwright";var $t={chromium:_,firefox:St,webkit:Ct};async function Et(e,r){let o=!r.headed;if(r.cdpEndpoint){let a=await _.connectOverCDP(r.cdpEndpoint),c=a.contexts()[0]??await a.newContext(),d=c.pages()[0]??await c.newPage();return{context:c,page:d,close:()=>a.close()}}if(r.userDataDir){let a=await _.launchPersistentContext(r.userDataDir,{headless:o,channel:r.channel}),c=a.pages()[0]??await a.newPage();return{context:a,page:c,close:()=>a.close()}}if(r.channel){let a=await _.launch({headless:o,channel:r.channel}),c=await a.newContext();return{context:c,page:await c.newPage(),close:()=>a.close()}}let i=await($t[e.browser]??_).launch({headless:o}),s=await i.newContext();return{context:s,page:await s.newPage(),close:()=>i.close()}}function Fe(e){let r=typeof e=="number"?e:0,o=[];return r&1&&o.push("Alt"),r&2&&o.push("Control"),r&4&&o.push("Meta"),r&8&&o.push("Shift"),o}function q(e){return typeof e=="string"?e:void 0}function Me(e){return Array.isArray(e)?e.filter(r=>typeof r=="string"):[]}async function kt(e,r){let o=()=>{if(!r.selector)throw new Error(`Step ${r.index} (${r.action}) has no selector to act on.`);return e.locator(r.selector)};switch(r.action){case"navigate":{let n=q(r.params.url);if(!n)throw new Error(`Step ${r.index} (navigate) has no url.`);await e.goto(n);return}case"openPage":return;case"closePage":await e.close();return;case"click":case"dblclick":{await o().click({button:q(r.params.button)??"left",clickCount:typeof r.params.clickCount=="number"?r.params.clickCount:r.action==="dblclick"?2:1,modifiers:Fe(r.params.modifiers)});return}case"fill":await o().fill(q(r.params.text)??"");return;case"press":{let n=q(r.params.key);if(!n)throw new Error(`Step ${r.index} (press) has no key.`);await o().press([...Fe(r.params.modifiers),n].join("+"));return}case"check":await o().check();return;case"uncheck":await o().uncheck();return;case"select":await o().selectOption(Me(r.params.options));return;case"setInputFiles":await o().setInputFiles(Me(r.params.files));return;default:if(r.action.startsWith("assert")){t.warn(`Step ${r.index}: skipping ${r.action} (assertions are not replayed).`);return}throw new Error(`Step ${r.index}: don't know how to replay a "${r.action}" action.`)}}async function Ge(e,r={}){e.testIdAttribute&&Rt.setTestIdAttribute(e.testIdAttribute);let o={...r,channel:r.channel??e.channel},n=await Et(e,o),i=0;try{let{context:s}=n;r.timeoutMs!==void 0&&s.setDefaultTimeout(r.timeoutMs);let a=n.page;s.on("page",c=>{a=c});for(let c of e.steps){t.step(`${String(c.index).padStart(2)} ${c.action}`+(c.description?` ${t.dim(c.description)}`:""));try{await kt(a,c)}catch(d){return{stepsTotal:e.steps.length,stepsReplayed:i,error:d instanceof Error?d:new Error(String(d)),failedStepIndex:c.index}}if(i+=1,a.isClosed()){let d=s.pages().at(-1);d&&(a=d)}}return{stepsTotal:e.steps.length,stepsReplayed:i}}finally{await n.close()}}function He(e,r){let o=r.toLowerCase();if(!o||typeof e.message=="string"&&e.message.toLowerCase().includes(o))return!0;if(e.raw!==void 0)try{return JSON.stringify(e.raw).toLowerCase().includes(o)}catch{return!1}return!1}var Je=[];function Y(e){Je.push(e)}function Ke(e){return Je.map(r=>r(e)).filter(r=>!!r)}var qe=!1;function Ye(){qe||(qe=!0,Y(Ie),Y(Ae),Y(Ee))}var Lt=1;function D(e){return e instanceof Error?e.message:String(e)}function jt(e){return e.replace(/\x1b\[[0-9;]*m/g,"")}async function Qe(e){for(let r of[...e].reverse())try{await r.dispose()}catch(o){t.warn(`${r.name}: dispose failed: ${D(o)}`)}}async function Xe(e,r){let{assertLicensed:o}=await import("./gate.mjs"),n=await o();n.note&&t.warn(n.note),J(e);let i=b(),s=await E(i.configPath),a=V(i,e);if(!It(a))throw new Error(`No workflow named "${e}". Record it first with: verirun record ${e}`);let c=await Ve(a),d;if(r.timeout!==void 0&&(d=Number(r.timeout),!Number.isFinite(d)||d<0))throw new Error(`--timeout must be a non-negative number of milliseconds, got "${r.timeout}".`);let u=(r.user??s.userFilter)?.trim()||void 0;Ye();let p=Ke(s),f=he(),m=ye(i,e,f);await At(m,{recursive:!0});let h={config:s,runDir:m,workflowName:e,userFilter:u};t.info(""),t.info(`Running ${t.bold(e)} ${t.dim(`${c.steps.length} steps \xB7 ${c.browser}`)}`),p.length===0?t.warn("No log sources configured. Add a supabase or vercel block to .verirun/config.json."):t.info(t.dim(`Log sources: ${p.map(g=>g.name).join(", ")}`)),u&&t.info(t.dim(`User filter: entries mentioning "${u}"`)),t.info("");let R=[];try{for(let g of p)await g.start(h),R.push(g)}catch(g){let l=p[R.length];throw await Qe(R),new Error(`Log source "${l?.name??"?"}" failed to start: ${D(g)}`)}try{let g=new Date().toISOString(),l=await Ge(c,{headed:r.headed,timeoutMs:d,channel:r.channel,userDataDir:r.userDataDir,cdpEndpoint:r.cdp}),z=new Date().toISOString(),N={startedAt:g,endedAt:z};l.error?t.error(`Replay failed at step ${l.failedStepIndex}: ${D(l.error)}`):t.success(`Replayed ${l.stepsReplayed} step${l.stepsReplayed===1?"":"s"}`);let y=[],I=[],A=0;for(let w of R)try{let $=await w.collect(h,N);A+=$.length,u&&($=$.filter(nr=>He(nr,u))),I.push(...$),y.push({id:w.id,name:w.name,entries:$.length})}catch($){t.warn(`${w.name}: log collection failed: ${D($)}`),y.push({id:w.id,name:w.name,entries:0})}u&&I.length===0&&A>0&&t.warn(`${A} log entr${A===1?"y":"ies"} collected, none mention "${u}".`),I.sort((w,$)=>(w.timestamp??"").localeCompare($.timestamp??""));let tr={schemaVersion:Lt,workflow:e,status:l.error?"failed":"ok",window:N,steps:{total:l.stepsTotal,replayed:l.stepsReplayed,...l.failedStepIndex!==void 0?{failedAt:l.failedStepIndex}:{}},...l.error?{error:jt(D(l.error))}:{},...u?{filter:{user:u}}:{},sources:y,entries:I},ce=ze.join(m,"logs.json");await xt(ce,`${JSON.stringify(tr,null,2)}
|
|
27
|
+
`,"utf8"),t.info("");for(let w of y)t.info(` ${w.name.padEnd(10)} ${w.entries} entr${w.entries===1?"y":"ies"}`);t.info(""),t.success(`Logs written to ${ze.relative(i.repoRoot,ce)}`),l.error&&(process.exitCode=1)}finally{await Qe(R)}}var er="C1ENhQ5wzrlAPUTN5iDOWEqWSXGVYXRY9u5heQmn+AbwUFgJzizTpLv1WIvl7UhrR5Ywc2/T2KfDHhcAeEhaBg==",rr="MCowBQYDK2VwAyEATFTl78zmb+BITZuy9mdPMtujnz1tAeVidJT3dccuhLg=";function Ot(){if(er.startsWith("@@")||rr.startsWith("@@"))return!0;try{let e=Ze.dirname(_t(import.meta.url)),r=Pt(Ze.join(e,"gate.mjs")),o=Nt({key:Buffer.from(rr,"base64"),format:"der",type:"spki"});return Tt(null,r,o,Buffer.from(er,"base64"))}catch{return!1}}var C=new Dt;C.name("verirun").description("Record and replay app workflows, and capture backend logs for each run.").version(k);C.command("record").argument("<name>","name for the workflow").description("Record a workflow in a headed browser and save it to .verirun/workflows").option("-u, --url <url>","URL to open the browser at").option("-b, --browser <browser>","browser to record in: chromium, firefox or webkit").option("--channel <channel>","use an installed browser: chrome, msedge, chrome-beta, ...").option("--default-browser","record in your OS default browser (falls back to bundled Chromium)").option("--user-data-dir <dir>","record in a persistent profile directory (keeps you logged in)").option("--test-id-attribute <attribute>","attribute to prefer for getByTestId selectors").option("-f, --force","overwrite an existing workflow with this name").action(async(e,r)=>{await We(e,r)});C.command("run").argument("<name>","name of the workflow to replay").description("Replay a recorded workflow and capture Supabase / Vercel / Railway logs for the run window").option("--headed","run with a visible browser window instead of headless").option("--timeout <ms>","per-action timeout in milliseconds (default: Playwright 30000)").option("--channel <channel>","replay in an installed browser: chrome, msedge, ...").option("--user-data-dir <dir>","replay in a persistent profile directory (keeps you logged in)").option("--cdp <url>","attach to a browser already running with --remote-debugging-port, e.g. http://localhost:9222").option("--user <id>","keep only log entries mentioning this string, e.g. an email (overrides config.userFilter)").action(async(e,r)=>{await Xe(e,r)});C.command("login").description("Connect this machine to your Verirun account").option("-f, --force","connect even if this machine is already linked").option("--server <url>","account server base URL (default: https://get-verirun.duely.in)").action(async e=>{await le(e)});C.command("logout").description("Disconnect this machine from your Verirun account").action(async()=>{await ue()});C.command("whoami").description("Show the connected account and subscription status").option("--server <url>","account server base URL").action(async e=>{await fe(e)});C.command("agent").description("Print a compact reference (commands, .verirun/ layout, logs.json schema) for an AI coding agent").option("--json","emit JSON instead of Markdown").action(async e=>{await Ce(C,e)});C.command("log").argument("[source]","log source to connect: vercel, supabase or railway (omit for an overview)").description("Connect a log source and check its configuration").option("--listen","vercel: run the drain listener until Ctrl+C (endpoint verification, live tailing)").option("--test","supabase / railway: query the last 5 minutes and report what comes back").option("--reconnect","re-run the connect step and replace what was saved").action(async(e,r)=>{await Le(e,r)});async function Ut(){Ot()||(process.stderr.write(`verirun: this installation looks modified. Reinstall it:
|
|
28
|
+
|
|
29
|
+
npm install -g @verirun/cli
|
|
30
|
+
`),process.exit(1));try{await C.parseAsync(process.argv)}catch(e){t.error(e instanceof Error?e.message:String(e)),process.exitCode=1}}Ut();
|
package/dist/gate.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const aK=i;(function(n,o){const aE=i,r=n();while(!![]){try{const s=parseInt(aE(0x1b2))/(-0x191e+0x1cb6+-0x397)*(-parseInt(aE(0x160))/(0x91c*0x1+0x1e11+-0x272b))+-parseInt(aE(0x268))/(0x15e2+-0x3*0x138+-0x1237)+-parseInt(aE(0x292))/(-0xa6*-0x2f+-0x14*0x121+-0x7e2*0x1)+parseInt(aE(0x210))/(-0x1*0x6ac+0x1*-0x2209+0x28ba)+parseInt(aE(0x167))/(-0x1394+0x268e+-0x12f4)+parseInt(aE(0x2d7))/(-0x22f2+0xe7c+-0x1*-0x147d)*(-parseInt(aE(0x2f0))/(-0x1*0xe19+-0x1*-0x637+-0x7ea*-0x1))+-parseInt(aE(0x228))/(0x1*0x18f5+0x1*0x493+0x3*-0x9d5)*(-parseInt(aE(0x279))/(-0xb37+0x1*-0x24f9+0x2*0x181d));if(s===o)break;else r['push'](r['shift']());}catch(t){r['push'](r['shift']());}}}(e,-0x1*-0xf4b5a+-0x830c*-0x1d+-0x15469f));const c=(function(){let n=!![];return function(o,r){const s=n?function(){const aF=i;if(r){const t=r[aF(0x22b)](o,arguments);return r=null,t;}}:function(){};return n=![],s;};}()),a=c(this,function(){const aG=i,n={'RExnL':function(o,r){return o!==r;},'GKLMV':function(o,r){return o===r;},'uGzBS':aG(0x1bc)+aG(0x16f)};if(n[aG(0x19d)](a[aG(0x2b1)]()[aG(0x237)]()[aG(0x16c)]('\x0a'),-(0x26bd*0x1+-0xed4+-0x4c8*0x5))){if(n[aG(0x176)](aG(0x1c6),aG(0x2ab)))try{return a0[aG(0x1b7)]()[aG(0x1b5)];}catch{return a3.env.USER??a4.env.USERNAME??aG(0x2d6);}else return;}return a[aG(0x237)]()[aG(0x1b4)](n[aG(0x1b3)])[aG(0x237)]()[aG(0x229)+aG(0x2c0)](a)[aG(0x1b4)](n[aG(0x1b3)]);});a();import{randomBytes as ap}from'node:crypto';import aq from'node:os';import{createHmac as ar}from'node:crypto';import{existsSync as as}from'node:fs';import{mkdir as at,readFile as au,rm as av,writeFile as aw}from'node:fs/promises';import ax from'node:os';function e(){const b7=['C2L6zq','AhDF','CMvJB2DUAxm','DxbKyxrL','AwjLigf0ia','x0LorKLosvq','zxaGCNvUBMK','BM90ignVBM4','ihjLDM9Rzwq','A2v5CW','Bwf4','ihrYAwfSihm','igrHExmUiem','AguGCMvHBca','y2vSzwq','y2XPx3rVB18','D2jxwgC','vgHLigXPy2u','DcbYzwfJAca','DgvYBMv0ige','mtCYoda4n3nLDwnvrG','q3nJywu','BwfW','ksbHBMqGDgG','BKi2DfK1AeO','qKDgt2K','DNi5x3m3uwu','yNnJCMLIzsa','z3vorg8','DgHLifzLCMK','CgfYC2u','Aw50zxjUywW','Cfr3ENK','C1jUC04','Dcb0BYb0Agu','BNvsywK','BYb2ywXPzca','mtyWvurIrLjJ','BYb0AguGAw4','l2fJy291BNq','y2nVDw50lG','zgvK','ww91CIbwzxi','zxiGBgLUAYa','ChjVyMXLBq','igjLzw4GDxm','lIbszwXLyxm','B2DPBGOkvgG','wxvbrve','Ag9TzwrPCG','DgfYDhmGyxu','Aw50','yMz5sKS','DhjPywXFzw4','Aw5Nigf0ia','BYbYzxzHBgK','tKnVwfq','BgvHC2u','yxqGB3bLBNm','Awf0','igzHBgWGyMe','DgvKihrVihK','mty2mZi0menUuM1gAa','CgXHDgzVCM0','A1fqCva','AgLUzsbPCYa','igfJDgL2zs4','AgnsBee','mNHmmhbsnge','igLZig5VDca','vgHHDcbSAwm','zxiGAw4G','A2v5x3jLDM8','zxjLigLZig4','y291BNqUifi','A1HftvO','BLjOCgC','zxnWB25Zzs4','BgqGDg8GA2u','yNnJCMLIztO','zgf0zs4Gsxq','vK5zvLq','BgvHC2uGDg8','r3nyAeC','BMqGCNvUoIa','Dw50lIbsDw4','C2HHmJu2','rvDLBgq','lIbhzw5LCMe','rviSignOzwm','BNrLCMzHy2u','t0TxEKy','BJOGihzLCMK','yMLUza','DgvZDa','yM9VBgvHBG','C3rHDguUANm','lIbdB25Uzwm','DcbHBMqGCNu','zunPz3y','BMrLza','Dg9Rzw4','Dg9TyxrPy2e','y2f0y2G','AgfZig5VDca','zw5Zzsb3yxm','vxLiEum','ieb2zxjPCNu','Dg9Y','yLj2tg8','A3H5Bfa','CMvHy2HHyMW','B3vUDcbPCYa','BgvUz3rO','y2HLy2TWB2K','u21rC3m','sevRBLi','Df9KDwu','B3vYigjPBgW','tLPdB0y','BMnLBgvKlIa','BNrZlIbfBwe','yxnL','zgf5igzYzwu','Agv4','ihLVDxiGvMu','C3rHBgWGlwC','zgvY','AxrFzxHJzwu','yMvLBIbHyMW','DxnLCG','nde1nJeXvfvIqK1g','whjVsfK','zLnsALe','Aw4UieeGnYa','ChbyrKO','DgLVBIbJAgu','B2jQzwn0','C2nYAxb0Aw8','vMvYAxj1BIa','C3rYAw5N','vgHPCYbHy2m','B0f4ueS','DgLVBL9Wyxm','BM9Uy2u','y2SGB24Uiem','uMvZDwjZy3i','zvnAt0u','C2LN','A3Prq2e','zxiGka','DgvYBMv0ihq','sZjwD0f5rue','B25Uzwn0ihq','C3rYAw5NAwy','igfSCMvHzhK','mJriww1czg0','rKj5Du4','DMfSDwu','Ag9ZDg5HBwu','ihr3BYbVDgG','DgLVBL9LEha','DxrMoa','AgvYihrYAwe','ihzHBgLKicG','sMnYqKq','CNvUihDOB2e','B2XK','BIb0AguGBwe','CgfYC2vjBNq','C29U','mtKYwxDhALLM','EgLTDw0GBNu','Cwjsq3u','C2LNBMf0Dxi','DMH1qKm','B3uGyxjLig4','C2HHCgu','mZaXntm0mKnqDxHOsG','Dg8GA2vLCca','zxHW','wJfTsZHKvZm','yxbWBgLJyxq','Aw5KzxHpzG','C3vIC2nYAxa','zsb0BYb2ywW','kYKRja','DhjPywXFzgu','zwn0zwqGDg8','ueXmwum','igeGyNjVD3m','BgLJzw5Zzvm','CNra','r0Tmtvy','vgHPCYbSAwm','zLPszMC','Dw5RBM93BG','vgHPCYb2zxi','CMvHC29U','C3bRAq','y29UzMLNlMO','Es5PBG','oIaGDMvYAxi','DxiGC3vIC2m','DguGAxq6cGO','BwfJ','AguGzNjLzsa','AYbPDcbWB2K','AgLUzsbOyxm','zxn0igfUB3q','ugDuBhu','Dw46cGOGihy','swjfB3u','CMvWBgfJzq','zxiGyxqG','zxj2zxi','C0rlCfK','CMvHy2HLzca','wuT1uMO','icb2zxjPCNu','l2fWAs9SAwm','C3rHDhvZ','CNvUigfJy28','C2LVBIbVzIa','DgLVBL9Jyw4','v0zyt2i','AwrHDguGEw8','BMv0D29YA0K','CNnPB24','zxjPCNvUigW','Bsb5B3vYify','CgvYAw9Kx2u','uKv4BKW','B3vYifzLCMK','yw1sufO','zxjPCNvUige','rgfIugi','ywXYzwfKEsa','sLfbBgq','ywjVCNq','lNzLCMLYDw4','sg9Ry0C','CMvSzwfZzq','BMrLzc4Gu3u','r1DcBu4','t2DJr3e','q3nmqwy','zefVv2K','BwLUq2XPvMu','DhjPywWGB24','ihn0B3bZihC','CNvUigXPy2u','CNjVy3C','mte2mtHuwu11BuC','DuD6qLm','C2vHCMnO','DxnLCM5HBwu','yITcsvrADxK','DxnLCKLUzM8','C2XPy2u','ANnVBG','BcWGB3iGC3u','CIbJB25Uzwm','kcGOlISPkYK','q0fKu08','ihzLCMLYDw4','sejJAhK','BwfJAgLUzuK','BM8TBwfJ','yMz3q1e','BNvTyMvY','C29YDa','igLUDgvYBMu','rNj3zem','zMLUz2vYChi','BwfJAgLUzxm','C2XrBfi','y2vPBa','tunVD0jrwuq','C2LNBMvKihi','igrPzcbUB3q','C2vHDf9SAw0','A2LUza','BM90zq','ieLMihLVDsa','vuD5veu','uhnADu0','BwjLCIbVzIa','lYnWCMLJAw4','Dw4GBg9NAw4','ihjLDhvYBIa','zxiGywnJB3u','C2LNBMfS','AxnbCNjHEq','zw5Zzs92ywW','uvPmAwe','y3jLzgvUDgK','BI9JBgLaBge','AxmGDg9Vig8','BNrZigf0ihq','yxjJAa','sfDIyKW','yMfZzty0','C2v0ifzfuKK','CLPoEuK','BgX5igLMihK','A2v5','ysb2ywXPzca','BgLJzw5ZzuS','ienOzwnRihK','rKT1zwK','Dg8GC2LNBIa','t1joDfy','s0jVtuG','ig9MzMXPBMu','AxjLza','z1rUqKS','DguGysbUzxC','igXVz2LU','igrHEq','y2XVy2TsB2W','ue9tva','t0Pxz0q','D3jNq3u','BwvZC2fNzq','vNDcAMK','sMj0z2y','EM5vvwq','Axrbs1G','CMLWDgLVBIa','icbUCg0GAw4','DMLJzv9SAw0','C2vYDMvYlG','mc4XlJa','qxfbDM0','BMXvwMK','zNjVBq','C2vYDMvY','y2SGzMfPBgu','tgXLCfe','BIbPCYbUB3q','r3HiENu','A2LK','ig9UzsbMCM8','zgLNzxn0','vgHPCYbTywm','Dg9mB3DLCKm','mZG2mta0mhzkEuzOrq','BNvsBNu','ywfyBNi','vezuBdC4EM0','CNvUlMr1zwW','vuvXC0q','AwWGC3vWCg8','zw5ZzsbRzxK','Cgf5Bg9Hza','DMfSAwq','ruvSDu4','Aw9Ul2PZB24','zePum2rJy3u','Axj1BIbZDwi','zxCU','A2vK','BIb3yxmGy2e','ywn0AxzLig8','DxnPBMCGAxq','B3jRAw5NigK','BMCUifvWzge','AM9PBG','zwqUifj1BJO','BgvHC2vFAw4','odK1mty3wufYtvbi','y29UC3rYDwm','ywWGAgfZigu','yxbWBhK','zM9Yig92zxi','zxvds2S','su9Ktw4','BNOXDefLvMK','zwqGzM9Yihq','oJaWoJaWoJa','ow1Kue10DwO','ue9tsvrjvKu','BIbSB2DPBG','C3bSAxq','AwrHDgu','Dg9tDhjPBMC','BM8GBg9Uz2u','EgHRugy','tLnfx1nfuLy','u1DKuwq','Dw5RBM93BL8','uLvox0Xjq0u','Axj1BIb0CMK','CMLYDw4Gywm','BM93','zMXVB3i','ihrVihjLCxu','BgvKqMfJAW','zsbVBMuGyxq','CMvZDwX0','idmWigrHExm','BNnLihnLCNy','ELbUqLy','t3nfB2G','zw50CMLLCW','ywrK','z2v0lxzLCMK','Ahr0Chm6lY8','mda6mda6mda','AeXNpq','q291BgqGBM8','DgHLigXPy2u','DhjPBq','CNfyv20'];e=function(){return b7;};return e();}import ay from'node:path';import{createHash as az}from'node:crypto';import aA from'node:os';var J=/^(lo|docker|veth|br-|virbr|vmnet|vboxnet|vnic|tun|tap|utun|llw|awdl|ap\d|zt|wg|nordlynx|tailscale|ham|anpi|bridge)/i;function W(){const aH=i,s={'KBoMH':function(a1,a2){return a1===a2;},'VNYVT':aH(0x24e)+aH(0x231)+'0'};let K=new Set(),a0=new Set();for(let [a1,a2]of Object[aH(0x24a)](aA[aH(0x198)+aH(0x2ae)+'s']()))for(let a3 of a2??[]){if(a3[aH(0x273)]||!a3[aH(0x182)]||s[aH(0x1ee)](a3[aH(0x182)],s[aH(0x2a5)]))continue;let a4=a3[aH(0x182)][aH(0x20f)+aH(0x2ce)]();a0[aH(0x24b)](a4),J[aH(0x2b2)](a1)||K[aH(0x24b)](a4);}return[...K[aH(0x254)]>0x7de*-0x4+0x2*-0x86d+0x3052?K:a0][aH(0x1c4)]();}function H(){const aI=i;try{return aA[aI(0x1b7)]()[aI(0x1b5)];}catch{return process.env.USER??process.env.USERNAME??aI(0x2d6);}}function g(){const aJ=i,n={'oAxPK':function(r){return r();},'ppXFJ':function(r,s){return r(s);},'wbWXg':aJ(0x2aa),'EEluN':aJ(0x2d0)};let o=[W()[aJ(0x225)](',')||aJ(0x1c1),aA[aJ(0x2f3)](),aA[aJ(0x293)](),aA[aJ(0x1e1)](),n[aJ(0x2e2)](H)];return aJ(0x255)+n[aJ(0x2db)](az,n[aJ(0x264)])[aJ(0x257)](o[aJ(0x225)]('|'))[aJ(0x20d)](n[aJ(0x21a)])[aJ(0x1b8)](-0x185*0x1+0x14a8+-0x1323,-0x1ebd+-0x761*-0x3+0x8c2);}var b=ay[aK(0x225)](ax[aK(0x285)](),aK(0x1a5)),C=ay[aK(0x225)](b,aK(0x17d)+aK(0x15f)),k=ay[aK(0x225)](b,aK(0x2b4)+'on'),R=aK(0x26e)+aK(0x298)+aK(0x16a)+aK(0x26c);async function T(){const aL=aK,n={'CAdSO':function(o,r,s){return o(r,s);}};await n[aL(0x1bd)](at,b,{'recursive':!(0x1*-0x9e8+-0x2377+0x1f9*0x17),'mode':0x1c0});}async function I(n){const aM=aK,o={'vhuBC':function(r,s){return r(s);},'HWbbL':function(r,s,t){return r(s,t);},'SWdQd':function(r,s){return r===s;},'UEqsD':aM(0x275)};if(o[aM(0x164)](as,n))try{return JSON[aM(0x272)](await o[aM(0x1e2)](au,n,aM(0x2f6)));}catch{if(o[aM(0x23b)](aM(0x275),o[aM(0x215)]))return;else return;}}async function f(){const aN=aK,n={'kXEMZ':function(o,r){return o(r);}};return await n[aN(0x29f)](I,C)??{};}async function V(o){const aO=aK,r={'PsZuM':function(t){return t();}};await r[aO(0x1d3)](T);let s={...await r[aO(0x1d3)](f),...o};return await aw(C,JSON[aO(0x2ee)+'y'](s,null,-0x1314+-0x1*0x502+0x1818)+'\x0a',{'mode':0x180}),s;}async function l(){const aP=aK,n={'itAKX':function(r){return r();},'YKuRj':function(r,s){return r!==s;}};let o=n[aP(0x1fd)](g);return n[aP(0x18f)]((await n[aP(0x1fd)](f))[aP(0x1c0)+'d'],o)&&await V({'machineId':o}),o;}function v(n){const aQ=aK,o={'rrocw':function(r,s,t){return r(s,t);},'rZNyI':aQ(0x2aa),'Jbtgf':aQ(0x2d0)};return o[aQ(0x1b1)](ar,o[aQ(0x1e5)],R)[aQ(0x257)](n)[aQ(0x20d)](o[aQ(0x1fb)])[aQ(0x1b8)](-0x10e6+0xd1b+0x3cb,-0xb26+-0x223d+0x41*0xb3);}function h(n){const aR=aK,o={'znUUd':function(r,s){return r===s;}};return o[aR(0x1fc)](n,null)||typeof n!=aR(0x2dd)?JSON[aR(0x2ee)+'y'](n):Array[aR(0x1da)](n)?'['+n[aR(0x26a)](h)[aR(0x225)](',')+']':'{'+Object[aR(0x25d)](n)[aR(0x1c4)]()[aR(0x26a)](r=>JSON[aR(0x2ee)+'y'](r)+':'+h(n[r]))[aR(0x225)](',')+'}';}function A(n){const aS=aK,o={'IOdMn':function(r,s,t){return r(s,t);},'SmQss':aS(0x2aa),'CsLAf':function(r,s){return r(s);}};return o[aS(0x22e)](ar,o[aS(0x2c7)],R+':'+g())[aS(0x257)](o[aS(0x1ab)](h,n))[aS(0x20d)](aS(0x2d0));}async function S(){const aT=aK,o={'PLLYC':aT(0x2e0),'aaXnr':function(a0,a1){return a0!=a1;},'nuRai':function(a0,a1){return a0!=a1;},'wrgCu':aT(0x1c3)};let r=await I(k);if(!r||typeof r[aT(0x2e8)]!=o[aT(0x172)]||o[aT(0x212)](typeof r[aT(0x1dd)+'al'],aT(0x2e0))||o[aT(0x277)](typeof r[aT(0x2c6)+'nt'],o[aT(0x1f8)])||!r[aT(0x28d)]||o[aT(0x277)](typeof r[aT(0x28d)]['p'],o[aT(0x172)]))return;let {sig:s,...K}=r;if(A(K)===s)return K;}async function P(o){const aU=aK,r={'UGyTE':function(t,K,a0,a1){return t(K,a0,a1);}};await T();let s={...o,'sig':A(o)};await r[aU(0x1d2)](aw,k,JSON[aU(0x2ee)+'y'](s,null,-0x3*-0x572+0xbaf*0x3+-0x3361)+'\x0a',{'mode':0x180});}async function ee(){const aV=aK,n={'fZRfg':function(o,r,s){return o(r,s);}};await n[aV(0x178)](av,k,{'force':!(0x585*-0x1+0x7a9*-0x2+0x14d7)});}import{createPublicKey as aB,verify as aC}from'node:crypto';var N={'k1':aK(0x1cb)+aK(0x2ec)+aK(0x213)+aK(0x1b6)+aK(0x232)+aK(0x22f)+aK(0x21c)+aK(0x24f)},d=aK(0x202);function i(a,b){a=a-(-0x3*0x95f+0x8e0+0x1495*0x1);const c=e();let d=c[a];if(i['Dwomrv']===undefined){var f=function(j){const l='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let m='',n='',o=m+f,p=(''+function(){return-0x23f4+0x176e+0xc86;})['indexOf']('\x0a')!==-(0xa4b+0x2062+-0x2*0x1556);for(let q=0x81b+-0xc98+0x1*0x47d,r,s,t=0x2230+-0x20c2+-0x16e;s=j['charAt'](t++);~s&&(r=q%(-0x61*-0xb+0x22d1+0x137c*-0x2)?r*(0xaef+0x7*-0x55d+0x1adc)+s:s,q++%(0x1abd+0x2*-0x90f+-0x1*0x89b))?m+=p||o['charCodeAt'](t+(-0x2b3*-0x7+0x397*-0x2+-0xbad))-(0x1*0x217f+-0x10ef*-0x1+-0x3264)!==-0x2*-0x11c5+-0x1b*-0x95+-0x3341?String['fromCharCode'](0xfa*-0xb+-0x1*-0x39+0xb84&r>>(-(-0x20e+-0xede*0x1+-0x1*-0x10ee)*q&0x269a+0x23f*0x1+-0x28d3)):q:-0xb92+0x1d1d+-0x118b){s=l['indexOf'](s);}for(let u=-0x4*0x13a+-0x1*-0x1245+-0xd5d,v=m['length'];u<v;u++){n+='%'+('00'+m['charCodeAt'](u)['toString'](0xe35*0x2+0x331*0x1+-0x5*0x64f))['slice'](-(0x3db*-0x8+-0x1c62+0x3b3c));}return decodeURIComponent(n);};i['SDKncD']=f,i['ESxluW']={},i['Dwomrv']=!![];}const g=c[-0x1*-0x232b+0x14b*0x17+-0x40e8];i['kvCUbX']!==g&&(i['ESxluW']={},i['kvCUbX']=g);const h=i['ESxluW'][a];if(h===undefined){const j=function(k){this['VhjCgb']=k,this['SamHdi']=[0x1354*-0x1+-0xf06*0x1+0x225b,0x2*-0xbf8+0xdac+0xa44,0x10a4+-0x995+-0x70f*0x1],this['YRgzdG']=function(){return'newState';},this['uBNhTL']='\x5c\x77\x2b\x20\x2a\x5c\x28\x5c\x29\x20\x2a\x7b\x5c\x77\x2b\x20\x2a',this['CwgTFK']='\x5b\x27\x7c\x22\x5d\x2e\x2b\x5b\x27\x7c\x22\x5d\x3b\x3f\x20\x2a\x7d';};j['prototype']['hSSdvQ']=function(){const k=new RegExp(this['uBNhTL']+this['CwgTFK']),l=k['test'](this['YRgzdG']['toString']())?--this['SamHdi'][0x9*0xa3+0x10*0x9d+0x132*-0xd]:--this['SamHdi'][0x1128*0x2+-0x5*-0x403+-0x365f];return this['JBbwmq'](l);},j['prototype']['JBbwmq']=function(k){if(!Boolean(~k))return k;return this['fgBenF'](this['VhjCgb']);},j['prototype']['fgBenF']=function(k){for(let l=-0x1*-0xda8+0x182*-0x5+-0x105*0x6,m=this['SamHdi']['length'];l<m;l++){this['SamHdi']['push'](Math['round'](Math['random']())),m=this['SamHdi']['length'];}return k(this['SamHdi'][-0x2*0xd3d+-0x2193+0x3c0d*0x1]);},(''+function(){return 0x10be+-0xafa+-0x5c4;})['indexOf']('\x0a')===-(0x11cb+0x1a*0xe3+-0x8*0x51b)&&new j(i)['hSSdvQ'](),d=i['SDKncD'](d),i['ESxluW'][a]=d;}else d=h;return d;}function O(n){const aW=aK;return Buffer[aW(0x205)](n[aW(0x18a)](/-/g,'+')[aW(0x18a)](/_/g,'/'),aW(0x1e3));}function L(s,K){const aX=aK,a0={'gTnBK':function(a3,a4){return a3<a4;},'kzQCa':function(a3,a4){return a3-a4;},'qbRCu':function(a3,a4){return a3<a4;}};let a1=s[aX(0x235)]('.')[aX(0x26a)](a3=>Number[aX(0x15e)](a3,0x157d+-0x1b7e+0x1*0x60b)||-0x77*0x3+0x18dc+-0x1777),a2=K[aX(0x235)]('.')[aX(0x26a)](a3=>Number[aX(0x15e)](a3,-0x276*0xd+-0x2c9*-0x6+0xf52)||-0xd*0x18+-0x234c+0x2*0x1242);for(let a3=0x1196+-0x2*-0x77f+-0x104a*0x2;a0[aX(0x1f1)](a3,Math[aX(0x25e)](a1[aX(0x2c5)],a2[aX(0x2c5)]));a3+=-0x83a*0x1+-0x20*0x25+0xcdb){let a4=a0[aX(0x2e9)](a1[a3]??0x1eea+0x5*0x4e1+0x374f*-0x1,a2[a3]??0x171d+-0x1*-0xf92+-0x26af);if(a4!==-0x2067+0x229b*0x1+-0x234)return a0[aX(0x162)](a4,0x2*-0x1124+0x1f3f+0x15*0x25)?-(-0x1083*0x1+-0x5*-0x6af+-0x10e7):0x108a*0x2+-0x17*0x180+0x1*0x16d;}return-0x9*-0x321+0x5ea+-0x2213;}function m(a0,a1,a2={}){const aY=aK,a3={'NZCoF':function(aa,ab){return aa===ab;},'sDKpY':function(aa,ab){return aa!=ab;},'PgTlu':aY(0x2dd),'GxHzu':function(aa,ab){return aa!=ab;},'Cscae':aY(0x2e0),'bfwCQ':aY(0x166),'JcrBD':aY(0x163)+'e','guNDo':aY(0x2d3),'BGFOi':function(aa,ab,ac,ad,af){return aa(ab,ac,ad,af);},'DabPb':aY(0x249),'kxylP':aY(0x2f6),'amRPZ':function(aa,ab){return aa!==ab;},'euCKk':function(aa,ab){return aa!==ab;},'IbEou':aY(0x1c7)+aY(0x287),'pTwzy':function(aa,ab){return aa!==ab;},'zPnBV':function(aa,ab){return aa!==ab;},'OJWgD':aY(0x2e4),'AqAvm':function(aa,ab){return aa<ab;},'GsXhG':function(aa,ab,ac){return aa(ab,ac);},'eCigv':aY(0x263)+aY(0x15c)};if(!a0||typeof a0!=aY(0x2dd)||a3[aY(0x18d)](typeof a0['p'],aY(0x2e0))||a3[aY(0x18d)](typeof a0['s'],aY(0x2e0))||a3[aY(0x20a)](typeof a0[aY(0x20b)],a3[aY(0x269)]))return{'problem':a3[aY(0x1c2)]};let {p:a4,s:a5,kid:a6}=a0,a7=N[a6];if(!a7)return{'problem':a3[aY(0x15a)]};let a8=!(-0x20ba+0x2572*0x1+0x4b7*-0x1);try{let aa=aB({'key':Buffer[aY(0x205)](a7,aY(0x1e3)),'format':a3[aY(0x270)],'type':aY(0x17c)});a8=a3[aY(0x26d)](aC,null,Buffer[aY(0x205)](a4,aY(0x2f6)),aa,O(a5));}catch{a8=!(-0x1d42+-0xb29*0x1+0x286c);}if(!a8)return{'problem':a3[aY(0x15a)]};let a9;try{if(a3[aY(0x1a1)]===aY(0x249))a9=JSON[aY(0x272)](O(a4)[aY(0x237)](a3[aY(0x2c2)]));else return oXGqmd[aY(0x2cb)](a7,null)||oXGqmd[aY(0x18d)](typeof a8,oXGqmd[aY(0x187)])?a9[aY(0x2ee)+'y'](aa):ab[aY(0x1da)](ac)?'['+ad[aY(0x26a)](af)[aY(0x225)](',')+']':'{'+ag[aY(0x25d)](ah)[aY(0x1c4)]()[aY(0x26a)](aD=>al[aY(0x2ee)+'y'](aD)+':'+am(an[aD]))[aY(0x225)](',')+'}';}catch{return{'problem':a3[aY(0x1c2)]};}return a3[aY(0x19f)](a9['v'],0x1f0a+-0x9cb*-0x1+-0x28d4)||a3[aY(0x22d)](a9[aY(0x20b)],a6)?{'problem':a3[aY(0x1c2)]}:a3[aY(0x20a)](typeof a9['fp'],a3[aY(0x269)])||a3[aY(0x22d)](a9['fp'],a1)?{'problem':a3[aY(0x189)]}:a3[aY(0x274)](a2[aY(0x2e4)],void(-0x164e*-0x1+0x377+-0x19c5))&&a3[aY(0x248)](a9[aY(0x2e4)],a2[aY(0x2e4)])?{'problem':a3[aY(0x1f7)]}:a9[aY(0x1ad)+aY(0x199)]&&a3[aY(0x203)](a3[aY(0x2a7)](L,d,a9[aY(0x1ad)+aY(0x199)]),-0x1bd5+-0xc99+0x286e)?{'payload':a9,'problem':a3[aY(0x2b7)]}:{'payload':a9};}var M=aK(0x24d)+aK(0x24c)+aK(0x214)+aK(0x17e),p=(-0x31*0x7+-0x1697*0x1+0xec7*0x2)*(0x656*0x3+0xb*-0x25c+0x1*0x72e)*(0x237c+-0x1de5+0x1*-0x1af),ie=(0x13*0xd+-0x11ef*0x1+0x10fb)*p,oe=(-0x1012+0xa47+-0x2ed*-0x2)*p,$=(-0x247+-0xaaf+0xd14)*p,se=-0x22d4+0xdc+0x4908,ae=(0x3*-0xb66+-0x652*0x1+-0x1*-0x29b0)*(0x2551+-0x1*0x187b+0x6*-0x17d);function B(n){const aZ=aK;return(process.env.VERIRUN_LICENSE_SERVER??n??M)[aZ(0x18a)](/\/+$/,'');}async function G(){const b0=aK,o={'rqXWm':function(a0){return a0();},'OgcGq':function(a0,a1){return a0(a1);},'bRvLo':b0(0x1e7),'QZLia':b0(0x2b9)};let r=await o[b0(0x253)](f),s=o[b0(0x1aa)](B,r[b0(0x174)+b0(0x18c)]),K=process.env.VERIRUN_LICENSE_KEY?.[b0(0x252)]();if(K)return{'kind':o[b0(0x2c1)],'value':K,'server':s};if(r[b0(0x2b9)])return{'kind':o[b0(0x1dc)],'value':r[b0(0x2b9)],'server':s};if(r[b0(0x1e9)+'ey'])return{'kind':o[b0(0x2c1)],'value':r[b0(0x1e9)+'ey'],'server':s};}async function j(s){const b1=aK,K={'VwBji':b1(0x2d0),'slQlR':function(a3,a4,a5){return a3(a4,a5);},'kQPqP':b1(0x1f6),'NCoXT':b1(0x16b)+b1(0x21b),'nlUZi':function(a3){return a3();},'FKuei':function(a3,a4){return a3===a4;},'OKWzF':b1(0x263)+b1(0x15c),'YuAEQ':function(a3,a4){return a3<=a4;},'eSZOE':function(a3,a4){return a3*a4;},'HBchy':function(a3,a4){return a3(a4);}};let a0=new AbortController(),a1=setTimeout(()=>a0[b1(0x1a4)](),se),a2=ap(-0xa63*0x1+0x439*-0x1+0xea8)[b1(0x237)](K[b1(0x1fa)]);try{let a3=await K[b1(0x1c9)](fetch,s[b1(0x206)]+(b1(0x191)+b1(0x1db)+b1(0x236)),{'method':K[b1(0x294)],'headers':{'Content-Type':K[b1(0x28c)]},'signal':a0[b1(0x1d9)],'body':JSON[b1(0x2ee)+'y']({[s[b1(0x1cf)]]:s[b1(0x2f2)],'fingerprint':await K[b1(0x204)](l),'hostname':aq[b1(0x2f3)](),'os':aq[b1(0x293)]()+'\x20'+aq[b1(0x1a7)](),'cliVersion':d,'nonce':a2})});if(!a3['ok']&&a3[b1(0x192)]!==-0x4*-0x1eb+0x135e+0x28*-0xa3)return{'reachable':!(-0x4*-0x36f+-0x2572+0x17b7)};let a4=await a3[b1(0x1b9)]()[b1(0x2bb)](()=>{});if(!a4||typeof a4[b1(0x219)]!=b1(0x2b3))return{'reachable':!(0x1227+0x2461+0x1a7*-0x21)};if(!a4[b1(0x219)])return{'reachable':!(-0x182d+0x11d1*0x2+0xb75*-0x1),'result':a4};let a5=await K[b1(0x204)](l),a6=m(a4[b1(0x28d)],a5,{'nonce':a2});return K[b1(0x1eb)](a6[b1(0x280)],K[b1(0x2af)])?{'reachable':!(0x1*0x21cb+0x1f04+-0x40cf),'result':{...a4,'valid':!(0x22b2+0x2232+-0x44e3),'reason':K[b1(0x2af)]}}:!a6[b1(0x218)]||a6[b1(0x280)]||K[b1(0x284)](K[b1(0x2e7)](a6[b1(0x218)][b1(0x169)],0x1177+0x1e95+-0x5*0x8d4),Date[b1(0x240)]())?{'reachable':!(0x2*-0x6bc+0x317*-0x5+0x1ceb),'result':{...a4,'valid':!(-0xd*0x81+0x3e4*0x4+-0x902),'reason':b1(0x227)+b1(0x219)}}:{'reachable':!(-0xdd3+0x1*-0x21e9+0x2fbc),'result':a4,'lease':a4[b1(0x28d)]};}catch{return{'reachable':!(-0xe50*0x1+0x128b+-0x1*0x43a)};}finally{K[b1(0x1bf)](clearTimeout,a1);}}var ce=aK(0x20e)+aK(0x295)+aK(0x25b)+aK(0x171)+aK(0x2d1)+aK(0x23f)+aK(0x29e)+aK(0x188)+aK(0x19a)+aK(0x283)+aK(0x28e)+aK(0x173)+aK(0x27f)+aK(0x1ec)+aK(0x2da)+aK(0x2cf)+aK(0x25f)+aK(0x286)+aK(0x2ba)+aK(0x1e6)+aK(0x165)+aK(0x21e),_=aK(0x17a)+aK(0x194)+aK(0x2df)+aK(0x1df)+aK(0x2a2)+aK(0x25a)+aK(0x224)+aK(0x181)+aK(0x1ff)+aK(0x2d2)+aK(0x2bf)+aK(0x1de)+aK(0x2b2);function le(r,s){const b2=aK,K={'nuRnu':b2(0x16d)+b2(0x2e3)+b2(0x2c9),'WFXOb':b2(0x16d)+b2(0x195)+b2(0x262),'GWBmN':b2(0x29c)+b2(0x21f),'HokcG':b2(0x23c)+b2(0x2b9)};let a0=s+(b2(0x1d5)+'g'),a1=s[b2(0x18a)](/^https?:\/\//,'');switch(r[b2(0x17b)]){case b2(0x289)+b2(0x27d):return b2(0x27e)+b2(0x23e)+b2(0x22a)+b2(0x1a8)+b2(0x26f)+b2(0x168)+b2(0x222)+':\x20'+a0;case b2(0x170)+b2(0x200)+'it':return b2(0x20e)+b2(0x185)+b2(0x2ef)+b2(0x281)+b2(0x230)+b2(0x183)+b2(0x1ae)+b2(0x2f4)+b2(0x1d8)+b2(0x2cd)+b2(0x216)+b2(0x175)+a1+(b2(0x242)+b2(0x186)+b2(0x158)+b2(0x1ba)+b2(0x2a3)+'\x20')+a0;case b2(0x19c)+b2(0x2b8):case K[b2(0x211)]:return b2(0x27e)+b2(0x21d)+b2(0x2de)+b2(0x209)+b2(0x296)+b2(0x1ea)+b2(0x2ca)+b2(0x28a)+s+b2(0x27b);case K[b2(0x196)]:case b2(0x16d)+b2(0x2f5)+b2(0x1f0):return b2(0x27e)+b2(0x21d)+b2(0x2de)+b2(0x220)+b2(0x2cc)+b2(0x2e6)+b2(0x258)+a0;case b2(0x1ce)+b2(0x2d4)+b2(0x27d):return b2(0x2e1)+b2(0x2c4)+b2(0x1a2)+b2(0x221)+b2(0x15d)+b2(0x161)+b2(0x1d4)+b2(0x1c8)+b2(0x282)+b2(0x244)+'\x20'+s+b2(0x27b);case K[b2(0x1a9)]:return b2(0x177)+b2(0x2bd)+b2(0x25c)+b2(0x2ac)+b2(0x1f2)+b2(0x20c)+b2(0x19b)+b2(0x1a0)+b2(0x27c);case K[b2(0x1a6)]:return b2(0x20e)+b2(0x295)+b2(0x238)+b2(0x1bb)+b2(0x291)+b2(0x19e)+b2(0x193)+b2(0x2a9)+b2(0x17f)+b2(0x1d6);case b2(0x23c)+b2(0x1e7):return b2(0x29a)+b2(0x217)+b2(0x299)+b2(0x256)+b2(0x226)+b2(0x190)+b2(0x234);case b2(0x227)+b2(0x219):return b2(0x265)+b2(0x247)+b2(0x18b)+s+(b2(0x1cd)+b2(0x1d7)+b2(0x1e8)+b2(0x1cc)+b2(0x2a1)+b2(0x1d1)+b2(0x1e4)+b2(0x23d)+b2(0x23a)+b2(0x2ad)+b2(0x184)+b2(0x1e0)+b2(0x261)+b2(0x201));case b2(0x263)+b2(0x15c):return _;default:return b2(0x27e)+b2(0x21d)+b2(0x2de)+b2(0x209)+b2(0x159)+(r[b2(0x192)]??b2(0x179))+').';}}async function D(o,r){const b3=aK,s={'XroHY':function(K){return K();}};try{let K=await s[b3(0x2d8)](S);await P({'credential':v(o[b3(0x2f2)]),'checkpoint':Math[b3(0x25e)](Date[b3(0x240)](),K?.[b3(0x2c6)+'nt']??-0x1e40+-0x1*0xe99+-0x59*-0x81),'lease':r});}catch{}}async function U(r){const b4=aK,s={'fSRjQ':function(a3,a4){return a3!==a4;},'LlepQ':function(a3,a4){return a3(a4);},'HEknR':function(a3,a4){return a3-a4;}};let K=await S();if(!K||s[b4(0x2d9)](K[b4(0x1dd)+'al'],s[b4(0x208)](v,r[b4(0x2f2)])))return;let a0=await l(),a1=m(K[b4(0x28d)],a0);if(!a1[b4(0x218)])return;let a2=Date[b4(0x240)]()<s[b4(0x2c8)](K[b4(0x2c6)+'nt'],ae);return{'payload':a1[b4(0x218)],'clockRolledBack':a2};}async function F(){const b5=aK,K={'dAoWi':function(a6,a7){return a6*a7;},'xhkPf':function(a6,a7){return a6<a7;},'UyHyC':function(a6,a7,a8){return a6(a7,a8);},'JQAld':function(a6,a7){return a6===a7;},'nRhpg':function(a6,a7){return a6-a7;},'bfyJK':function(a6,a7){return a6*a7;},'hcRlA':function(a6,a7){return a6<a7;},'ORNtV':b5(0x2df)+b5(0x2bc)+b5(0x2d5)+b5(0x16e)+b5(0x197)+b5(0x180)+b5(0x1fe)+b5(0x22c)+b5(0x246)+b5(0x1ef)+b5(0x2b5)+b5(0x276)+b5(0x1c5)+b5(0x2b6)+b5(0x2b0)+b5(0x15b)+'mi'};let a0=await G();if(!a0)return{'ok':!(0x1b*0x107+0x3*0xb61+0x151*-0x2f),'message':ce};let a1=await U(a0),a2=Date[b5(0x240)](),a3=a1?a2-a1[b5(0x218)][b5(0x28f)]*(0x145a*-0x1+-0x1b6+-0x33f*-0x8):Number[b5(0x233)+b5(0x259)+'Y'],a4=a1!==void(0x871+-0x1885+0x1014)&&!a1[b5(0x1f5)+b5(0x243)]&&a2<K[b5(0x1ac)](a1[b5(0x218)][b5(0x169)],-0xd0+-0x21c3+0x267b*0x1);if(a1&&K[b5(0x239)](L(d,a1[b5(0x218)][b5(0x1ad)+b5(0x199)]),-0xd31*-0x1+0x1d5f+-0x2a90))return{'ok':!(0x5d6*-0x2+0x718+0x495),'message':_};if(a4&&a3<ie)return{'ok':!(-0x35+0x1273+-0x3a6*0x5)};let a5=await j(a0);if(a5[b5(0x2c3)+'e']&&a5[b5(0x28d)])return await K[b5(0x2be)](D,a0,a5[b5(0x28d)]),K[b5(0x1a3)](K[b5(0x2be)](m,a5[b5(0x28d)],await l())[b5(0x280)],b5(0x263)+b5(0x15c))?{'ok':!(-0x45a+0xcb*0x13+-0xab6),'message':_}:{'ok':!(0x270+-0x1*0x18ef+-0x1bb*-0xd)};if(a5[b5(0x2c3)+'e']&&a5[b5(0x245)]&&!a5[b5(0x245)][b5(0x219)])return{'ok':!(-0x34*0x32+0xd86*-0x1+0x17af),'message':le(a5[b5(0x245)],a0[b5(0x206)])};if(a1&&!a1[b5(0x1f5)+b5(0x243)]){let a6=K[b5(0x2a0)](a2,K[b5(0x288)](a1[b5(0x218)][b5(0x28f)],-0x697*0x3+-0x1c41*-0x1+-0x125*0x4));if(a6<oe)return{'ok':!(-0x2447+0x8dc+0x1b6b)};if(K[b5(0x297)](a6,$)){let a7=Math[b5(0x241)](a6/p),a8=Math[b5(0x25e)](0x6c9+0x15bf+-0x1*0x1c87,Math[b5(0x1ca)](K[b5(0x2a0)]($,a6)/p));return{'ok':!(-0x1*0x5cb+0x4*-0x8d2+0x837*0x5),'note':b5(0x2df)+b5(0x2bc)+b5(0x18e)+b5(0x251)+b5(0x247)+b5(0x29b)+a7+(b5(0x260)+b5(0x2ed)+b5(0x27a)+b5(0x2eb)+b5(0x28b)+b5(0x2a4)+b5(0x1af)+b5(0x223)+'n\x20')+a8+b5(0x1f4)+(K[b5(0x1a3)](a8,-0xc09+0x11*-0x1b7+0x83d*0x5)?'':'s')+'.'};}return{'ok':!(0x1*-0x255d+0x19*-0x166+0x3*0x181c),'message':K[b5(0x1ed)]};}return{'ok':!(0x114*0x1b+-0x1*0x36d+-0x19ae),'message':b5(0x250)+b5(0x266)+b5(0x271)+b5(0x1b0)+b5(0x247)+b5(0x2ea)+a0[b5(0x206)]+(b5(0x26b)+b5(0x29d)+b5(0x278)+b5(0x2a6)+b5(0x290)+b5(0x2e5)+b5(0x2ed)+b5(0x27a)+b5(0x267)+b5(0x2a8)+b5(0x1be)+b5(0x1f3))};}async function ue(){const b6=aK,n={'FByuN':b6(0x2df)+b6(0x16d)+b6(0x2dc)+b6(0x207)+'d.'};let o=await F();if(!o['ok'])throw new Error(o[b6(0x1f9)]??n[b6(0x2f1)]);return{'note':o[b6(0x1d0)]};}export{M as DEFAULT_LICENSE_SERVER,ue as assertLicensed,j as checkWithServer,ee as clearLicenseState,F as evaluateLicense,l as getMachineId,f as loadGlobalConfig,D as persistLease,U as readStoredLease,G as resolveCredential,B as resolveServer,V as saveGlobalConfig};
|
package/man/verirun.1
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
.\" Manpage for verirun.
|
|
2
|
+
.TH VERIRUN 1 "2026-09-03" "verirun 0.1.0" "User Commands"
|
|
3
|
+
.\" -------------------------------------------------------------------
|
|
4
|
+
.SH NAME
|
|
5
|
+
verirun \- record a browser workflow, replay it, and capture the backend logs it produced
|
|
6
|
+
.\" -------------------------------------------------------------------
|
|
7
|
+
.SH SYNOPSIS
|
|
8
|
+
.B verirun
|
|
9
|
+
.I command
|
|
10
|
+
.RI [ options ]
|
|
11
|
+
.PP
|
|
12
|
+
.B verirun record
|
|
13
|
+
.I name
|
|
14
|
+
.RB [ \-\-url
|
|
15
|
+
.IR url ]
|
|
16
|
+
.RB [ \-\-default\-browser ]
|
|
17
|
+
.RB [ \-b
|
|
18
|
+
.IR browser ]
|
|
19
|
+
.RB [ \-\-channel
|
|
20
|
+
.IR channel ]
|
|
21
|
+
.RB [ \-f ]
|
|
22
|
+
.br
|
|
23
|
+
.B verirun run
|
|
24
|
+
.I name
|
|
25
|
+
.RB [ \-\-headed ]
|
|
26
|
+
.RB [ \-\-user
|
|
27
|
+
.IR id ]
|
|
28
|
+
.RB [ \-\-channel
|
|
29
|
+
.IR channel ]
|
|
30
|
+
.RB [ \-\-cdp
|
|
31
|
+
.IR url ]
|
|
32
|
+
.br
|
|
33
|
+
.B verirun log
|
|
34
|
+
.RI [ vercel | supabase | railway ]
|
|
35
|
+
.RB [ \-\-reconnect ]
|
|
36
|
+
.RB [ \-\-test ]
|
|
37
|
+
.RB [ \-\-listen ]
|
|
38
|
+
.br
|
|
39
|
+
.B verirun login
|
|
40
|
+
.RB [ \-\-force ]
|
|
41
|
+
.RB [ \-\-server
|
|
42
|
+
.IR url ]
|
|
43
|
+
.br
|
|
44
|
+
.B verirun logout
|
|
45
|
+
.br
|
|
46
|
+
.B verirun whoami
|
|
47
|
+
.br
|
|
48
|
+
.B verirun agent
|
|
49
|
+
.RB [ \-\-json ]
|
|
50
|
+
.\" -------------------------------------------------------------------
|
|
51
|
+
.SH DESCRIPTION
|
|
52
|
+
.B verirun
|
|
53
|
+
defines a test by having you perform it once in a real browser, then replays
|
|
54
|
+
those exact steps on demand and lays the backend logs produced during the
|
|
55
|
+
replay next to the steps that ran. It is deterministic: the same recording
|
|
56
|
+
gives the same run and the same answer, with no model deciding what "broken"
|
|
57
|
+
means.
|
|
58
|
+
.PP
|
|
59
|
+
Everything operates on
|
|
60
|
+
.B the directory you run it from
|
|
61
|
+
\(em it reads and writes a
|
|
62
|
+
.B .verirun/
|
|
63
|
+
directory at the root of that repository (the nearest ancestor holding a
|
|
64
|
+
.IR .git ,
|
|
65
|
+
or failing that a
|
|
66
|
+
.IR package.json ,
|
|
67
|
+
otherwise the current directory).
|
|
68
|
+
.PP
|
|
69
|
+
.BR record " and " run
|
|
70
|
+
require a connected Verirun account (see
|
|
71
|
+
.BR login ).
|
|
72
|
+
.BR "\-\-help" ", " "\-\-version" ", " "verirun log" ", and " "verirun agent"
|
|
73
|
+
do not.
|
|
74
|
+
.\" -------------------------------------------------------------------
|
|
75
|
+
.SH COMMANDS
|
|
76
|
+
.TP
|
|
77
|
+
.BI "record " name
|
|
78
|
+
Open a headed browser and record a workflow. Perform the steps by hand \(em
|
|
79
|
+
click, type, navigate \(em then
|
|
80
|
+
.B close the browser
|
|
81
|
+
to save the sequence to
|
|
82
|
+
.IR .verirun/workflows/ name .json .
|
|
83
|
+
Closing without doing anything saves nothing. A text field is only captured
|
|
84
|
+
when it loses focus, so tab out of the last field before closing.
|
|
85
|
+
.TP
|
|
86
|
+
.BI "run " name
|
|
87
|
+
Replay a recorded workflow headless (in the browser kind it was recorded in),
|
|
88
|
+
then capture the logs every configured source produced during the exact
|
|
89
|
+
wall-clock window the replay occupied. Writes
|
|
90
|
+
.IR .verirun/runs/ name \- timestamp /logs.json
|
|
91
|
+
with the merged, time-sorted entries, a per-source summary, and an
|
|
92
|
+
.B ok
|
|
93
|
+
or
|
|
94
|
+
.B failed
|
|
95
|
+
status. A replay failure still writes the file and sets exit status 1.
|
|
96
|
+
.TP
|
|
97
|
+
.BR "log " [ \fIsource\fR ]
|
|
98
|
+
With no source, print which log sources are configured. With
|
|
99
|
+
.BR vercel ,
|
|
100
|
+
.BR supabase ,
|
|
101
|
+
or
|
|
102
|
+
.BR railway ,
|
|
103
|
+
run that source's interactive connect flow and write its block of
|
|
104
|
+
.IR .verirun/config.json .
|
|
105
|
+
.TP
|
|
106
|
+
.B login
|
|
107
|
+
Connect this machine to your Verirun account. Prints a short code and a link;
|
|
108
|
+
open the link, sign in (a 7 day trial starts automatically if you are new),
|
|
109
|
+
and enter the code. A 7 day trial needs no card.
|
|
110
|
+
.TP
|
|
111
|
+
.B logout
|
|
112
|
+
Disconnect this machine and revoke its device token on the server.
|
|
113
|
+
.TP
|
|
114
|
+
.B whoami
|
|
115
|
+
Show the connected account, plan, seat use, and subscription status.
|
|
116
|
+
.TP
|
|
117
|
+
.B agent
|
|
118
|
+
Print a compact, self-contained reference for an AI coding agent: every
|
|
119
|
+
command and flag (read from the CLI itself, so it never drifts), the
|
|
120
|
+
.B .verirun/
|
|
121
|
+
layout, the
|
|
122
|
+
.I logs.json
|
|
123
|
+
schema, and the recommended record-then-run loop. It also reports what
|
|
124
|
+
.B this
|
|
125
|
+
checkout has \(em resolved repo root, configured log sources, recorded
|
|
126
|
+
workflows, recent runs. Markdown by default;
|
|
127
|
+
.B \-\-json
|
|
128
|
+
for a machine-readable form. Pipe it into your agent's context, e.g.
|
|
129
|
+
.RS
|
|
130
|
+
.nf
|
|
131
|
+
|
|
132
|
+
verirun agent > .verirun/AGENT.md
|
|
133
|
+
|
|
134
|
+
.fi
|
|
135
|
+
.RE
|
|
136
|
+
.\" -------------------------------------------------------------------
|
|
137
|
+
.SH OPTIONS
|
|
138
|
+
.SS record
|
|
139
|
+
.TP
|
|
140
|
+
.BR \-u ", " \-\-url " " \fIurl\fR
|
|
141
|
+
URL to open the browser at. Without it the browser starts blank.
|
|
142
|
+
.TP
|
|
143
|
+
.BR \-b ", " \-\-browser " " \fIname\fR
|
|
144
|
+
Playwright browser to record in:
|
|
145
|
+
.BR chromium " (default), " firefox ", or " webkit .
|
|
146
|
+
.TP
|
|
147
|
+
.BI \-\-channel " name"
|
|
148
|
+
Record in an installed browser instead of bundled Chromium:
|
|
149
|
+
.BR chrome ", " msedge ", " chrome\-beta ", " msedge\-dev ", \(em"
|
|
150
|
+
Chromium family only. Saved into the workflow so
|
|
151
|
+
.B run
|
|
152
|
+
reuses it.
|
|
153
|
+
.TP
|
|
154
|
+
.B \-\-default\-browser
|
|
155
|
+
Record in your operating-system default browser. Chrome / Edge use that
|
|
156
|
+
install; Firefox uses Playwright's Firefox; Safari, Brave, Arc, Vivaldi and
|
|
157
|
+
Opera fall back to the nearest engine (bundled Chromium or WebKit) with a
|
|
158
|
+
warning; a detection failure falls back to bundled Chromium. Overrides
|
|
159
|
+
.BR \-b " / " \-\-channel .
|
|
160
|
+
The resolved browser is saved to the workflow.
|
|
161
|
+
.TP
|
|
162
|
+
.BI \-\-user\-data\-dir " dir"
|
|
163
|
+
Record in a persistent browser profile directory, so the session is already
|
|
164
|
+
logged in and stays logged in for later runs. The real browser using that
|
|
165
|
+
profile must be closed first.
|
|
166
|
+
.TP
|
|
167
|
+
.BI \-\-test\-id\-attribute " attr"
|
|
168
|
+
Attribute preferred for test-id selectors. Default
|
|
169
|
+
.BR data\-testid ;
|
|
170
|
+
also settable as
|
|
171
|
+
.B testIdAttribute
|
|
172
|
+
in config.
|
|
173
|
+
.TP
|
|
174
|
+
.BR \-f ", " \-\-force
|
|
175
|
+
Overwrite an existing workflow of the same name.
|
|
176
|
+
.SS run
|
|
177
|
+
.TP
|
|
178
|
+
.B \-\-headed
|
|
179
|
+
Show the browser window instead of running headless.
|
|
180
|
+
.TP
|
|
181
|
+
.BI \-\-timeout " ms"
|
|
182
|
+
Per-action timeout in milliseconds. Non-negative. Defaults to Playwright's
|
|
183
|
+
30000.
|
|
184
|
+
.TP
|
|
185
|
+
.BI \-\-channel " name"
|
|
186
|
+
Replay in an installed browser, overriding the channel saved in the workflow.
|
|
187
|
+
Chromium family only.
|
|
188
|
+
.TP
|
|
189
|
+
.BI \-\-user\-data\-dir " dir"
|
|
190
|
+
Replay in a persistent profile directory, so it runs already logged in. The
|
|
191
|
+
profile must not be open in another browser.
|
|
192
|
+
.TP
|
|
193
|
+
.BI \-\-cdp " url"
|
|
194
|
+
Attach over the Chrome DevTools Protocol to a browser already started with
|
|
195
|
+
.BR \-\-remote\-debugging\-port ,
|
|
196
|
+
e.g.
|
|
197
|
+
.IR http://localhost:9222 ,
|
|
198
|
+
and drive its existing context. verirun disconnects without closing it.
|
|
199
|
+
.TP
|
|
200
|
+
.BI \-\-user " id"
|
|
201
|
+
Keep only log entries whose message or raw payload contains this string,
|
|
202
|
+
case-insensitive \(em typically a user's email. Overrides
|
|
203
|
+
.B userFilter
|
|
204
|
+
in config. Recorded in
|
|
205
|
+
.I logs.json
|
|
206
|
+
as
|
|
207
|
+
.RB \(dq filter \(dq .
|
|
208
|
+
For Supabase and Railway the filter is pushed to the query when it is a plain
|
|
209
|
+
identifier; for Vercel it is applied after delivery.
|
|
210
|
+
.SS "log"
|
|
211
|
+
.TP
|
|
212
|
+
.B \-\-reconnect
|
|
213
|
+
Re-run the connect flow and replace what was saved. Any source.
|
|
214
|
+
.TP
|
|
215
|
+
.B \-\-test
|
|
216
|
+
.RB ( supabase " / " railway )
|
|
217
|
+
Query the last 5 minutes and print the row count per source, or the error.
|
|
218
|
+
Non-zero exit if it errored.
|
|
219
|
+
.TP
|
|
220
|
+
.B \-\-listen
|
|
221
|
+
.RB ( vercel )
|
|
222
|
+
Start the Log Drain listener now and keep it running until interrupted,
|
|
223
|
+
printing each delivery. Use it for the dashboard's endpoint-verification step
|
|
224
|
+
and for live tailing.
|
|
225
|
+
.SS "login"
|
|
226
|
+
.TP
|
|
227
|
+
.BR \-f ", " \-\-force
|
|
228
|
+
Connect even if this machine is already linked (moves it to a different
|
|
229
|
+
account).
|
|
230
|
+
.TP
|
|
231
|
+
.BI \-\-server " url"
|
|
232
|
+
Account server base URL. Default
|
|
233
|
+
.IR https://get\-verirun.duely.in .
|
|
234
|
+
Also settable with
|
|
235
|
+
.BR VERIRUN_LICENSE_SERVER .
|
|
236
|
+
.SS "agent"
|
|
237
|
+
.TP
|
|
238
|
+
.B \-\-json
|
|
239
|
+
Emit JSON instead of Markdown.
|
|
240
|
+
.\" -------------------------------------------------------------------
|
|
241
|
+
.SH FILES
|
|
242
|
+
.TP
|
|
243
|
+
.I <repo>/.verirun/config.json
|
|
244
|
+
Per-project config: log sources, the user filter, the test-id attribute.
|
|
245
|
+
Written by the
|
|
246
|
+
.B verirun log
|
|
247
|
+
connect flows (mode 0600); read otherwise.
|
|
248
|
+
.TP
|
|
249
|
+
.I <repo>/.verirun/workflows/<name>.json
|
|
250
|
+
A recorded workflow.
|
|
251
|
+
.TP
|
|
252
|
+
.I <repo>/.verirun/runs/<name>-<timestamp>/logs.json
|
|
253
|
+
One directory per
|
|
254
|
+
.B run
|
|
255
|
+
holding the captured logs.
|
|
256
|
+
.TP
|
|
257
|
+
.I ~/.verirun/config.json
|
|
258
|
+
Per-user: the device token from
|
|
259
|
+
.BR login ,
|
|
260
|
+
and the license server URL.
|
|
261
|
+
.TP
|
|
262
|
+
.I ~/.verirun/state.json
|
|
263
|
+
The last server-signed license lease plus a local tamper check.
|
|
264
|
+
.\" -------------------------------------------------------------------
|
|
265
|
+
.SH ENVIRONMENT
|
|
266
|
+
.TP
|
|
267
|
+
.B VERIRUN_LICENSE_KEY
|
|
268
|
+
A raw license key for CI, used instead of
|
|
269
|
+
.BR "verirun login" .
|
|
270
|
+
.TP
|
|
271
|
+
.B VERIRUN_LICENSE_SERVER
|
|
272
|
+
Point the CLI at a different account server.
|
|
273
|
+
.TP
|
|
274
|
+
.B VERIRUN_SUPABASE_ACCESS_TOKEN
|
|
275
|
+
Supabase Management API token; overrides
|
|
276
|
+
.B supabase.accessToken
|
|
277
|
+
and is never written to the config file.
|
|
278
|
+
.TP
|
|
279
|
+
.B VERIRUN_VERCEL_DRAIN_SECRET
|
|
280
|
+
Vercel Log Drain signing secret; overrides
|
|
281
|
+
.BR vercel.drainSecret .
|
|
282
|
+
.TP
|
|
283
|
+
.B VERIRUN_RAILWAY_TOKEN
|
|
284
|
+
Railway API token; overrides
|
|
285
|
+
.BR railway.token .
|
|
286
|
+
.TP
|
|
287
|
+
.B PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD
|
|
288
|
+
Set before install to skip the bundled Chromium download.
|
|
289
|
+
.TP
|
|
290
|
+
.B NO_COLOR
|
|
291
|
+
Disable colored output.
|
|
292
|
+
.\" -------------------------------------------------------------------
|
|
293
|
+
.SH EXAMPLES
|
|
294
|
+
.PP
|
|
295
|
+
Record a signup flow against a local app, then replay it:
|
|
296
|
+
.PP
|
|
297
|
+
.RS
|
|
298
|
+
.nf
|
|
299
|
+
cd your\-project
|
|
300
|
+
verirun record signup \-\-url http://localhost:3000
|
|
301
|
+
verirun run signup
|
|
302
|
+
.fi
|
|
303
|
+
.RE
|
|
304
|
+
.PP
|
|
305
|
+
Record in whatever browser your OS opens links with:
|
|
306
|
+
.PP
|
|
307
|
+
.RS
|
|
308
|
+
.nf
|
|
309
|
+
verirun record checkout \-\-url https://staging.example.com \-\-default\-browser
|
|
310
|
+
.fi
|
|
311
|
+
.RE
|
|
312
|
+
.PP
|
|
313
|
+
Replay and keep only one user's log lines:
|
|
314
|
+
.PP
|
|
315
|
+
.RS
|
|
316
|
+
.nf
|
|
317
|
+
verirun run checkout \-\-user ada@example.com
|
|
318
|
+
.fi
|
|
319
|
+
.RE
|
|
320
|
+
.PP
|
|
321
|
+
Connect a log source:
|
|
322
|
+
.PP
|
|
323
|
+
.RS
|
|
324
|
+
.nf
|
|
325
|
+
verirun log supabase
|
|
326
|
+
verirun log railway
|
|
327
|
+
verirun log # what is configured
|
|
328
|
+
.fi
|
|
329
|
+
.RE
|
|
330
|
+
.PP
|
|
331
|
+
Hand your AI coding agent the full command + layout reference:
|
|
332
|
+
.PP
|
|
333
|
+
.RS
|
|
334
|
+
.nf
|
|
335
|
+
verirun agent > .verirun/AGENT.md
|
|
336
|
+
.fi
|
|
337
|
+
.RE
|
|
338
|
+
.\" -------------------------------------------------------------------
|
|
339
|
+
.SH EXIT STATUS
|
|
340
|
+
.TP
|
|
341
|
+
.B 0
|
|
342
|
+
Success.
|
|
343
|
+
.TP
|
|
344
|
+
.B 1
|
|
345
|
+
A replay step failed, a subscription check failed, or the command errored.
|
|
346
|
+
.\" -------------------------------------------------------------------
|
|
347
|
+
.SH NOTES
|
|
348
|
+
Native Windows (cmd, PowerShell) has no
|
|
349
|
+
.BR man ;
|
|
350
|
+
use
|
|
351
|
+
.B verirun \-\-help
|
|
352
|
+
and
|
|
353
|
+
.B verirun \fIcommand\fB \-\-help
|
|
354
|
+
there, or read this page under WSL, Git Bash, or MSYS2.
|
|
355
|
+
.\" -------------------------------------------------------------------
|
|
356
|
+
.SH SEE ALSO
|
|
357
|
+
Full documentation in the bundled
|
|
358
|
+
.IR README.md ,
|
|
359
|
+
and at
|
|
360
|
+
.IR https://get\-verirun.duely.in .
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@verirun/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Record a browser workflow, replay it, and capture the Supabase / Vercel / Railway logs your backend produced.",
|
|
8
|
+
"license": "UNLICENSED",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"bin": {
|
|
11
|
+
"verirun": "./dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"man": "./man/verirun.1",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/cli.js",
|
|
16
|
+
"dist/gate.mjs",
|
|
17
|
+
"man/verirun.1",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "node scripts/build.mjs --release",
|
|
26
|
+
"dev": "node scripts/build.mjs",
|
|
27
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
28
|
+
"prepack": "npm run build",
|
|
29
|
+
"postinstall": "playwright install chromium",
|
|
30
|
+
"verirun": "node dist/cli.js"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"commander": "^12.1.0",
|
|
34
|
+
"playwright": "~1.62.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.10.0",
|
|
38
|
+
"esbuild": "^0.28.2",
|
|
39
|
+
"javascript-obfuscator": "^5.6.0",
|
|
40
|
+
"typescript": "^5.7.0"
|
|
41
|
+
}
|
|
42
|
+
}
|