@beryl-so/cli 0.29.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,18 +1,18 @@
1
- <!-- GENERATED by `npm run docs` edit the command registry (src/commands/), not this file. -->
1
+ <!-- GENERATED by `npm run docs`. Edit the command registry (src/commands/), not this file. -->
2
2
 
3
3
  # beryl CLI
4
4
 
5
5
  Beryl on the command line: create projects, watch the agent explore and author tests,
6
- trigger runs in CI, manage everything the web app can plus an MCP server (`beryl mcp`)
6
+ trigger runs in CI, manage everything the web app can, plus an MCP server (`beryl mcp`)
7
7
  that exposes every command to coding agents.
8
8
 
9
- ## Quickstart one command
9
+ ## Quickstart: one command
10
10
 
11
11
  ```bash
12
12
  npx @beryl-so/cli@latest init
13
13
  ```
14
14
 
15
- Signs you in (browser approval by default, emailed one-time code as fallback new
15
+ Signs you in (browser approval by default, emailed one-time code as fallback; new
16
16
  emails are signed up on the spot), wires the beryl + playwright MCP servers into Claude
17
17
  Code or Cursor, installs the beryl-test authoring skill, and installs Playwright for
18
18
  local runs. Safe to re-run.
@@ -27,18 +27,18 @@ npx @beryl-so/cli@latest … # or run one-off without installing
27
27
  From the monorepo (development): `cd cli && npm install && npm run build && npm link`.
28
28
 
29
29
  `beryl init` and `beryl mcp` warn (stderr, best-effort) when the running CLI is behind the
30
- latest npm release a stale MCP server silently exposes fewer tools. Set
30
+ latest npm release; a stale MCP server silently exposes fewer tools. Set
31
31
  `BERYL_NO_UPDATE_CHECK=1` to opt out.
32
32
 
33
33
  ## Authenticate
34
34
 
35
35
  ```bash
36
- beryl login # opens the browser to approve press Enter for an emailed code instead
36
+ beryl login # opens the browser to approve; press Enter for an emailed code instead
37
37
  beryl login --otp # skip the browser; emailed one-time code (signs up new emails too)
38
38
  export BERYL_API_KEY=beryl_pat_… # CI: use a token from Account → API tokens
39
39
  ```
40
40
 
41
- Config stores your token + API URL only nothing else persists. Point a command at a
41
+ Config stores your token + API URL only; nothing else persists. Point a command at a
42
42
  workspace/project with the `--workspace` / `--project` flags or the `BERYL_WORKSPACE` /
43
43
  `BERYL_PROJECT` env vars; with exactly one, the CLI auto-picks it.
44
44
  Precedence: flags > `BERYL_*` env > auto-pick-if-one.
@@ -59,18 +59,18 @@ claude mcp add beryl -- beryl mcp # or, with the CLI installe
59
59
  ```
60
60
 
61
61
  Most non-interactive commands below are exposed as an MCP tool with the same name
62
- (spaces and dashes become underscores) see the MCP tool column. `—` marks commands
62
+ (spaces and dashes become underscores); see the MCP tool column. `none` marks commands
63
63
  kept CLI-only: account/billing/team admin, feedback, and cloud exploration.
64
64
 
65
65
  ## Commands
66
66
 
67
67
  ### init
68
68
 
69
- Set up Beryl in this repo sign in and wire up your coding agent
69
+ Set up Beryl in this repo: sign in and wire up your coding agent
70
70
 
71
71
  | Command | Summary | MCP tool |
72
72
  | --- | --- | --- |
73
- | `beryl init` | Set up Beryl in this repo sign in and wire up your coding agent | |
73
+ | `beryl init` | Set up Beryl in this repo: sign in and wire up your coding agent | none |
74
74
 
75
75
  ### guide
76
76
 
@@ -86,7 +86,7 @@ Authenticate the CLI with your Beryl account
86
86
 
87
87
  | Command | Summary | MCP tool |
88
88
  | --- | --- | --- |
89
- | `beryl login` | Authenticate the CLI with your Beryl account | |
89
+ | `beryl login` | Authenticate the CLI with your Beryl account | none |
90
90
 
91
91
  ### signup
92
92
 
@@ -143,9 +143,9 @@ Manage who belongs to a workspace and their roles.
143
143
 
144
144
  | Command | Summary | MCP tool |
145
145
  | --- | --- | --- |
146
- | `beryl members list` | List workspace members | |
147
- | `beryl members set-role <user-id> <role>` | Change a member's role | |
148
- | `beryl members remove <user-id>` | Remove a member from the workspace | |
146
+ | `beryl members list` | List workspace members | none |
147
+ | `beryl members set-role <user-id> <role>` | Change a member's role | none |
148
+ | `beryl members remove <user-id>` | Remove a member from the workspace | none |
149
149
 
150
150
  ### invites
151
151
 
@@ -153,16 +153,16 @@ Send, list, and revoke workspace invitations, and accept ones sent to you.
153
153
 
154
154
  | Command | Summary | MCP tool |
155
155
  | --- | --- | --- |
156
- | `beryl invites send <email>` | Invite someone to the workspace by email | |
157
- | `beryl invites list` | List the workspace's outstanding invitations | |
158
- | `beryl invites revoke <invitation-id>` | Revoke a pending invitation | |
159
- | `beryl invites mine` | List invitations sent to you | |
160
- | `beryl invites accept <invitation>` | Accept an invitation (by id, or by the token from the invite email) | |
161
- | `beryl invites decline <invitation-id>` | Decline an invitation | |
156
+ | `beryl invites send <email>` | Invite someone to the workspace by email | none |
157
+ | `beryl invites list` | List the workspace's outstanding invitations | none |
158
+ | `beryl invites revoke <invitation-id>` | Revoke a pending invitation | none |
159
+ | `beryl invites mine` | List invitations sent to you | none |
160
+ | `beryl invites accept <invitation>` | Accept an invitation (by id, or by the token from the invite email) | none |
161
+ | `beryl invites decline <invitation-id>` | Decline an invitation | none |
162
162
 
163
163
  ### projects
164
164
 
165
- Create and manage projects a site Beryl explores, authors tests for, and runs.
165
+ Create and manage projects: a site Beryl explores, authors tests for, and runs.
166
166
 
167
167
  `beryl projects` with no subcommand runs `projects list`.
168
168
 
@@ -170,16 +170,16 @@ Create and manage projects — a site Beryl explores, authors tests for, and run
170
170
  | --- | --- | --- |
171
171
  | `beryl projects list` | List projects in the workspace | `projects_list` |
172
172
  | `beryl projects get` | Show one project, including its current exploration state | `projects_get` |
173
- | `beryl projects create [url]` | Create a project with a URL the agent starts exploring; with just --name an empty one | `projects_create` |
173
+ | `beryl projects create [url]` | Create a project. With a URL the agent starts exploring; with just --name an empty one | `projects_create` |
174
174
  | `beryl projects rename <name>` | Rename a project | `projects_rename` |
175
175
  | `beryl projects delete` | Delete a project and all its tests and runs | `projects_delete` |
176
- | `beryl projects re-explore` | Send the agent back in run/heal existing tests and discover new flows | |
176
+ | `beryl projects re-explore` | Send the agent back in to run/heal existing tests and discover new flows | none |
177
177
  | `beryl projects report` | Aggregate quality report across recent runs (pass rates, flaky tests, trend) | `projects_report` |
178
178
  | `beryl projects reusable-auth <url>` | Check whether a saved login can be reused for a URL before creating a project | `projects_reusable_auth` |
179
179
 
180
180
  ### envs
181
181
 
182
- Manage a project's environments the URLs and auth Beryl runs tests against.
182
+ Manage a project's environments: the URLs and auth Beryl runs tests against.
183
183
 
184
184
  | Command | Summary | MCP tool |
185
185
  | --- | --- | --- |
@@ -191,17 +191,18 @@ Manage a project's environments — the URLs and auth Beryl runs tests against.
191
191
 
192
192
  ### schedule
193
193
 
194
- View and set the schedule on which Beryl runs a project's tests automatically.
194
+ Manage the schedules on which Beryl runs a project's tests automatically. A project can hold many independent schedules; each has its own cadence (daily or weekly), local run time, timezone, and target groups (none = every active test).
195
195
 
196
196
  | Command | Summary | MCP tool |
197
197
  | --- | --- | --- |
198
- | `beryl schedule get` | Show the project's daily/weekly run schedule | `schedule_get` |
199
- | `beryl schedule set` | Enable scheduled runs (daily, or weekly on a given day) | `schedule_set` |
200
- | `beryl schedule disable` | Turn scheduled runs off | `schedule_disable` |
198
+ | `beryl schedule list` | List the project's schedules | `schedule_list` |
199
+ | `beryl schedule add` | Add a schedule (daily, or weekly on a given day) | `schedule_add` |
200
+ | `beryl schedule update <schedule-id>` | Change a schedule's cadence, time, timezone, or groups | `schedule_update` |
201
+ | `beryl schedule remove <schedule-id>` | Remove a schedule (its tests and groups stay) | `schedule_remove` |
201
202
 
202
203
  ### tests
203
204
 
204
- Author, inspect, version, and heal a project's tests the checks Beryl runs on each run.
205
+ Author, inspect, version, and heal a project's tests: the checks Beryl runs on each run.
205
206
 
206
207
  `beryl tests` with no subcommand runs `tests list`.
207
208
 
@@ -211,9 +212,10 @@ Author, inspect, version, and heal a project's tests — the checks Beryl runs o
211
212
  | `beryl tests list` | List the project's tests with their latest result | `tests_list` |
212
213
  | `beryl tests get <test-id>` | Show one test | `tests_get` |
213
214
  | `beryl tests plan <test-id>` | Print a test's current step plan (JSON) | `tests_plan` |
214
- | `beryl tests create` | Create a test case from a JSON action plan for tests authored locally, e.g. by your coding agent | `tests_create` |
215
+ | `beryl tests create` | Create a test case from a JSON action plan (for tests authored locally, e.g. by your coding agent) | `tests_create` |
215
216
  | `beryl tests set-plan <test-id>` | Replace a test's step plan from a JSON file (creates a new version) | `tests_set_plan` |
216
217
  | `beryl tests rename <test-id> <title>` | Rename a test | `tests_rename` |
218
+ | `beryl tests set-groups <test-id>` | Replace the groups a test belongs to | `tests_set_groups` |
217
219
  | `beryl tests quarantine <test-id> <state>` | Mute a flaky test: it keeps running, but its failures stop failing the run | `tests_quarantine` |
218
220
  | `beryl tests delete <test-id>` | Delete a test, its version history, and its results | `tests_delete` |
219
221
  | `beryl tests recompile <test-id>` | Validate + verify an edited plan against the live site before persisting | `tests_recompile` |
@@ -227,6 +229,19 @@ Author, inspect, version, and heal a project's tests — the checks Beryl runs o
227
229
  | `beryl tests script [test-id]` | Print the rendered Playwright spec for a test (or an unbanked plan file) | `tests_script` |
228
230
  | `beryl tests export <test-ids...>` | Export tests as Playwright .spec.ts files in a ZIP | `tests_export` |
229
231
 
232
+ ### groups
233
+
234
+ Manage a project's test groups: labels a test can carry any number of, used to filter, run, or schedule a slice of the suite. Groups are created only here (or in Settings → Groups); assigning a test to an unknown name is an error.
235
+
236
+ `beryl groups` with no subcommand runs `groups list`.
237
+
238
+ | Command | Summary | MCP tool |
239
+ | --- | --- | --- |
240
+ | `beryl groups list` | List the project's test groups and how many tests each holds | `groups_list` |
241
+ | `beryl groups create <name>` | Create a group | `groups_create` |
242
+ | `beryl groups rename <group> <name>` | Rename a group (tests keep their membership) | `groups_rename` |
243
+ | `beryl groups delete <group>` | Delete a group: its tests stay; a schedule targeting only this group goes with it | `groups_delete` |
244
+
230
245
  ### runs
231
246
 
232
247
  Trigger a run of a project's tests (e.g. in CI), then watch, inspect, and download results.
@@ -247,7 +262,7 @@ Trigger a run of a project's tests (e.g. in CI), then watch, inspect, and downlo
247
262
 
248
263
  ### health
249
264
 
250
- Site Health how your site reads to search engines and visitors: content, speed, mobile, links and security, graded from a real check of your live pages.
265
+ Site Health, or how your site reads to search engines and visitors: content, speed, mobile, links and security, graded from a real check of your live pages.
251
266
 
252
267
  `beryl health` with no subcommand runs `health get`.
253
268
 
@@ -258,15 +273,15 @@ Site Health — how your site reads to search engines and visitors: content, spe
258
273
 
259
274
  ### explorations
260
275
 
261
- Inspect the agent's exploration runs how it crawled a site and authored its tests.
276
+ Inspect the agent's exploration runs: how it crawled a site and authored its tests.
262
277
 
263
278
  | Command | Summary | MCP tool |
264
279
  | --- | --- | --- |
265
- | `beryl explorations list` | List the agent's exploration passes for a project | |
266
- | `beryl explorations get <exploration-id>` | Show one exploration: authored tests, abandoned flows, coverage, frontier | |
267
- | `beryl explorations steps <exploration-id>` | List every step the agent took in an exploration | |
268
- | `beryl explorations cancel <exploration-id>` | Cancel an in-flight exploration | |
269
- | `beryl explorations watch <exploration-id>` | Stream an exploration live watch the agent explore and author tests | |
280
+ | `beryl explorations list` | List the agent's exploration passes for a project | none |
281
+ | `beryl explorations get <exploration-id>` | Show one exploration: authored tests, abandoned flows, coverage, frontier | none |
282
+ | `beryl explorations steps <exploration-id>` | List every step the agent took in an exploration | none |
283
+ | `beryl explorations cancel <exploration-id>` | Cancel an in-flight exploration | none |
284
+ | `beryl explorations watch <exploration-id>` | Stream an exploration live: watch the agent explore and author tests | none |
270
285
 
271
286
  ### config
272
287
 
@@ -290,7 +305,7 @@ Manage the variables, secrets, and files the agent can use while exploring and r
290
305
 
291
306
  ### mailbox
292
307
 
293
- The project's standing email addresses where its tests receive sign-in mail.
308
+ The project's standing email addresses, where its tests receive sign-in mail.
294
309
 
295
310
  `beryl mailbox` with no subcommand runs `mailbox get`.
296
311
 
@@ -305,14 +320,14 @@ The project's standing email addresses — where its tests receive sign-in mail.
305
320
 
306
321
  ### accounts
307
322
 
308
- Durable identities on the site under test what an authenticated test signs in as.
323
+ Durable identities on the site under test: what an authenticated test signs in as.
309
324
 
310
325
  `beryl accounts` with no subcommand runs `accounts list`.
311
326
 
312
327
  | Command | Summary | MCP tool |
313
328
  | --- | --- | --- |
314
329
  | `beryl accounts list` | List the test accounts an environment's tests sign in as | `accounts_list` |
315
- | `beryl accounts create` | Add a test account the customer's own, or one Beryl signs up | `accounts_create` |
330
+ | `beryl accounts create` | Add a test account: the customer's own, or one Beryl signs up | `accounts_create` |
316
331
  | `beryl accounts provision <account-id>` | Prove a test account can get in, by replaying a plan that ends logged in | `accounts_provision` |
317
332
  | `beryl accounts set-login <account-id>` | Store the sign-in plan a run replays once, plus the probe that proves it | `accounts_set_login` |
318
333
  | `beryl accounts get-login <account-id>` | Read the stored sign-in plan, its probe, and the hash a safe write must cite | `accounts_get_login` |
@@ -326,9 +341,9 @@ View and update your personal account profile.
326
341
 
327
342
  | Command | Summary | MCP tool |
328
343
  | --- | --- | --- |
329
- | `beryl account get` | Show your account profile | |
330
- | `beryl account update` | Update your profile | |
331
- | `beryl account deletion-preview` | Preview what deleting your account would remove or leave | |
344
+ | `beryl account get` | Show your account profile | none |
345
+ | `beryl account update` | Update your profile | none |
346
+ | `beryl account deletion-preview` | Preview what deleting your account would remove or leave | none |
332
347
 
333
348
  ### feedback
334
349
 
@@ -336,7 +351,7 @@ Send product feedback to the Beryl team.
336
351
 
337
352
  | Command | Summary | MCP tool |
338
353
  | --- | --- | --- |
339
- | `beryl feedback send <message>` | Send product feedback to the Beryl team | |
354
+ | `beryl feedback send <message>` | Send product feedback to the Beryl team | none |
340
355
 
341
356
  ### billing
342
357
 
@@ -344,10 +359,10 @@ Review a workspace's plan usage, subscription, and invoices.
344
359
 
345
360
  | Command | Summary | MCP tool |
346
361
  | --- | --- | --- |
347
- | `beryl billing usage` | Show plan usage: monthly cloud run minutes | |
348
- | `beryl billing subscription` | Show the workspace's subscription | |
349
- | `beryl billing invoices` | List recent invoices | |
350
- | `beryl billing portal` | Get a Stripe billing-portal link for the workspace | |
362
+ | `beryl billing usage` | Show plan usage: monthly cloud run minutes | none |
363
+ | `beryl billing subscription` | Show the workspace's subscription | none |
364
+ | `beryl billing invoices` | List recent invoices | none |
365
+ | `beryl billing portal` | Get a Stripe billing-portal link for the workspace | none |
351
366
 
352
367
  ### version
353
368
 
@@ -359,18 +374,18 @@ Show the running CLI version, API URL, and Node version
359
374
 
360
375
  ### mcp
361
376
 
362
- Run the Beryl MCP server (stdio) every CLI command as an agent tool
377
+ Run the Beryl MCP server (stdio): every CLI command as an agent tool
363
378
 
364
379
  | Command | Summary | MCP tool |
365
380
  | --- | --- | --- |
366
- | `beryl mcp` | Run the Beryl MCP server (stdio) every CLI command as an agent tool | |
381
+ | `beryl mcp` | Run the Beryl MCP server (stdio): every CLI command as an agent tool | none |
367
382
 
368
383
  Run `beryl <command> --help` for flags and examples.
369
384
 
370
385
  ## Global flags
371
386
 
372
- - `-V, --version` Print the CLI version and exit
373
- - `--json` Print machine-readable JSON (NDJSON for streams)
374
- - `--api-url` API base URL (default https://api.beryl.so)
375
- - `--token` Personal access token (overrides config/BERYL_API_KEY)
376
- - `-h, --help` Show help
387
+ - `-V, --version`: Print the CLI version and exit
388
+ - `--json`: Print machine-readable JSON (NDJSON for streams)
389
+ - `--api-url`: API base URL (default https://api.beryl.so)
390
+ - `--token`: Personal access token (overrides config/BERYL_API_KEY)
391
+ - `-h, --help`: Show help
@@ -289,6 +289,9 @@ ${JSON.stringify(BERYL_TEST_SKILL_EXAMPLE_PLAN, null, 2)
289
289
  description. Inspect the exact spec with \`beryl tests script --file plan.json\`.
290
290
  Full ActionPlan JSON Schema:
291
291
  https://api.beryl.so/api/v1/schemas/action-plan.schema.json.
292
+ Groups are optional and never block: after the test is banked, if \`beryl groups list\`
293
+ shows any, offer them once (\`tests set-groups <id> --group <name>\`); no answer = leave
294
+ it ungrouped and move on. Never create a group yourself — that's the user's call.
292
295
 
293
296
  ### The outcome assertion is the whole game
294
297
 
@@ -37,7 +37,7 @@ export const testAccountCommands = [
37
37
  name: "accounts list",
38
38
  summary: "List the test accounts an environment's tests sign in as",
39
39
  groupDefault: true,
40
- groupSummary: "Durable identities on the site under test what an authenticated test signs in as.",
40
+ groupSummary: "Durable identities on the site under test: what an authenticated test signs in as.",
41
41
  description: "Every test account on the project, with the identity label a plan's `auth_label` " +
42
42
  "names. The row marked * is the default: what a plan gets when it sets " +
43
43
  "requires_auth without an auth_label.",
@@ -59,10 +59,10 @@ export const testAccountCommands = [
59
59
  },
60
60
  {
61
61
  name: "accounts create",
62
- summary: "Add a test account the customer's own, or one Beryl signs up",
62
+ summary: "Add a test account: the customer's own, or one Beryl signs up",
63
63
  description: "Two kinds. `--type user_provided` records a dedicated account you already have on " +
64
64
  "the site: pass --email and --password, and it is usable immediately. `--type beryl` " +
65
- "reserves one Beryl will sign up itself, addressed at the project mailbox it starts " +
65
+ "reserves one Beryl will sign up itself, addressed at the project mailbox. It starts " +
66
66
  "`pending` and becomes usable after `beryl accounts provision`. " +
67
67
  "The first account an environment gets is its default whatever you pass.",
68
68
  scope: "project",
@@ -124,11 +124,11 @@ export const testAccountCommands = [
124
124
  name: "accounts provision",
125
125
  summary: "Prove a test account can get in, by replaying a plan that ends logged in",
126
126
  description: "Runs the plan once in a real browser and, if it passes, marks the account ready. " +
127
- "The plan is an ordinary ActionPlan that ends logged in a SIGNUP when the account " +
127
+ "The plan is an ordinary ActionPlan that ends logged in: a SIGNUP when the account " +
128
128
  "does not exist yet, or a SIGN-IN when it already does (you created it by hand, or " +
129
129
  "the site already had it). Either proves the same thing, and a sign-in is what every " +
130
130
  "later test will do anyway. Type {{mailbox_address}} into the email field and " +
131
- "{{login_password}} into the password field both resolve at replay time, and an " +
131
+ "{{login_password}} into the password field. Both resolve at replay time, and an " +
132
132
  "`await_email` step reads the project mailbox, so a verification code works.",
133
133
  scope: "project",
134
134
  args: [{ name: "account-id", description: "Account id from `beryl accounts list`", required: true }],
@@ -170,7 +170,7 @@ export const testAccountCommands = [
170
170
  "field; an emailed code or magic link arrives at the account's own address, so an " +
171
171
  "`await_email` step reads it with no human involved.\n\n" +
172
172
  "--probe is the liveness check: a two-step plan (goto a gated page, then a POSITIVE " +
173
- "assertion that only holds when signed in the account menu, a 'Sign out' control). " +
173
+ "assertion that only holds when signed in: the account menu, a 'Sign out' control). " +
174
174
  "It is replayed in a fresh browser carrying only the captured session. It is " +
175
175
  "REQUIRED, and not a formality: assertions like `hidden`, `count 0`, and a URL " +
176
176
  "match on a redirect all pass against a logged-out page, so without a positive " +
@@ -97,7 +97,7 @@ export const authCommands = [
97
97
  summary: "Authenticate the CLI with your Beryl account",
98
98
  description: "Signs in via your browser (a code you confirm at beryl.so) and stores a personal " +
99
99
  "access token in the CLI config. Pass --otp (or --email) to skip the browser and " +
100
- "sign in with an emailed one-time code instead a new email gets an account " +
100
+ "sign in with an emailed one-time code instead. A new email gets an account " +
101
101
  "created automatically. Pass --token to use an existing token from Account → API " +
102
102
  "tokens. Pass --email plus --code (the 6 digits from the email, e.g. read from a " +
103
103
  "`beryl inbox`) to complete the OTP flow without a prompt. In CI, prefer the " +
@@ -108,18 +108,18 @@ export const authCommands = [
108
108
  {
109
109
  name: "otp",
110
110
  type: "boolean",
111
- description: "Skip the browser sign in with an emailed code",
111
+ description: "Skip the browser and sign in with an emailed code",
112
112
  },
113
113
  {
114
114
  name: "email",
115
115
  type: "string",
116
116
  description: "Email for the one-time code sign-in (default: your last sign-in, or your git " +
117
- "user.email offered as the prompt default)",
117
+ "user.email, offered as the prompt default)",
118
118
  },
119
119
  {
120
120
  name: "code",
121
121
  type: "string",
122
- description: "The emailed 6-digit code skips the prompt for non-interactive use " +
122
+ description: "The emailed 6-digit code. Skips the prompt for non-interactive use " +
123
123
  "(requires --email; also skips sending a fresh code, so pair it with a " +
124
124
  "code already requested via `beryl login`, `beryl signup`, or the API)",
125
125
  },
@@ -184,7 +184,7 @@ export const authCommands = [
184
184
  name: "signup",
185
185
  summary: "Register a new Beryl account (emails a 6-digit verification code)",
186
186
  description: "Creates a passwordless account for the email and sends it a 6-digit code. " +
187
- "Finish with `beryl login --email <addr> --code <the 6 digits>` that verifies " +
187
+ "Finish with `beryl login --email <addr> --code <the 6 digits>`, which verifies " +
188
188
  "the account, creates its workspace, and signs the CLI in. With an inbox from " +
189
189
  "`beryl inbox create` as the address, an agent can provision a fresh account " +
190
190
  "end-to-end with no human at a prompt.",
@@ -57,8 +57,8 @@ export const configCommands = [
57
57
  {
58
58
  name: "config vars get",
59
59
  summary: "Show one config variable",
60
- description: "Returns the variable row with its value in plaintext (variables are not secret) " +
61
- "a sensitive value lives in `config secrets`, readable only via `config secrets " +
60
+ description: "Returns the variable row with its value in plaintext (variables are not secret). " +
61
+ "A sensitive value lives in `config secrets`, readable only via `config secrets " +
62
62
  "get --reveal`.",
63
63
  scope: "project",
64
64
  args: [{ name: "key", description: "Variable key or id", required: true }],
@@ -116,15 +116,15 @@ export const configCommands = [
116
116
  {
117
117
  name: "config secrets get",
118
118
  summary: "Show one secret's metadata, or reveal its value with --reveal",
119
- description: "Returns metadata only by default; --reveal is a logged, member-gated decrypt " +
120
- "unlike `config vars get`, which returns its value in plaintext.",
119
+ description: "Returns metadata only by default; --reveal is a logged, member-gated decrypt " +
120
+ "(unlike `config vars get`, which returns its value in plaintext).",
121
121
  scope: "project",
122
122
  args: [{ name: "key", description: "Secret key or id", required: true }],
123
123
  flags: [
124
124
  {
125
125
  name: "reveal",
126
126
  type: "boolean",
127
- description: "Return the decrypted value the explicit read that lets the agent drive a " +
127
+ description: "Return the decrypted value: the explicit read that lets the agent drive a " +
128
128
  "real login while authoring. Member-gated; every reveal is logged",
129
129
  },
130
130
  {
@@ -1,11 +1,24 @@
1
1
  import { UsageError } from "../errors.js";
2
- import { arg, flagBool, flagNum, flagStr, projectPath } from "./util.js";
2
+ import { arg, findGroup, flagBool, flagNum, flagStr, projectPath } from "./util.js";
3
+ const SCHEDULE_GROUPS_FLAG = "Comma-separated group names or ids the schedule targets (the run covers the union of their active tests at fire time); omit to run every active test";
4
+ const SCHEDULE_CADENCE_FLAGS = [
5
+ {
6
+ name: "frequency",
7
+ type: "string",
8
+ enum: ["daily", "weekly"],
9
+ description: "How often (default daily)",
10
+ },
11
+ { name: "day", type: "number", description: "Weekly only: day of week, 0=Monday … 6=Sunday" },
12
+ { name: "hour", type: "number", description: "Hour of day 0-23" },
13
+ { name: "minute", type: "number", description: "Minute 0-59" },
14
+ { name: "tz", type: "string", description: "IANA timezone (e.g. America/Los_Angeles)" },
15
+ ];
3
16
  export const environmentCommands = [
4
17
  {
5
18
  name: "envs list",
6
19
  summary: "List a project's environments",
7
20
  scope: "project",
8
- groupSummary: "Manage a project's environments the URLs and auth Beryl runs tests against.",
21
+ groupSummary: "Manage a project's environments: the URLs and auth Beryl runs tests against.",
9
22
  async run(ctx, input) {
10
23
  const { workspaceId, projectId } = await ctx.requireProject(input);
11
24
  return { data: await ctx.client.get(`${projectPath(workspaceId, projectId)}/environments`) };
@@ -91,57 +104,110 @@ export const environmentCommands = [
91
104
  },
92
105
  },
93
106
  {
94
- name: "schedule get",
95
- summary: "Show the project's daily/weekly run schedule",
107
+ name: "schedule list",
108
+ summary: "List the project's schedules",
96
109
  scope: "project",
97
- groupSummary: "View and set the schedule on which Beryl runs a project's tests automatically.",
110
+ groupSummary: "Manage the schedules on which Beryl runs a project's tests automatically. A project can hold many independent schedules; each has its own cadence (daily or weekly), local run time, timezone, and target groups (none = every active test).",
98
111
  async run(ctx, input) {
99
112
  const { workspaceId, projectId } = await ctx.requireProject(input);
100
- return { data: await ctx.client.get(`${projectPath(workspaceId, projectId)}/schedule`) };
113
+ return { data: await ctx.client.get(`${projectPath(workspaceId, projectId)}/schedules`) };
101
114
  },
102
115
  },
103
116
  {
104
- name: "schedule set",
105
- summary: "Enable scheduled runs (daily, or weekly on a given day)",
117
+ name: "schedule add",
118
+ summary: "Add a schedule (daily, or weekly on a given day)",
106
119
  scope: "project",
107
120
  flags: [
108
- {
109
- name: "frequency",
110
- type: "string",
111
- enum: ["daily", "weekly"],
112
- description: "How often (default daily)",
113
- },
114
- { name: "day", type: "number", description: "Weekly only: day of week, 0=Monday … 6=Sunday" },
115
- { name: "hour", type: "number", description: "Hour of day 0-23" },
116
- { name: "minute", type: "number", description: "Minute 0-59" },
117
- { name: "tz", type: "string", description: "IANA timezone (e.g. America/Los_Angeles)" },
121
+ ...SCHEDULE_CADENCE_FLAGS,
122
+ { name: "groups", type: "string", description: SCHEDULE_GROUPS_FLAG },
123
+ ],
124
+ examples: [
125
+ "beryl schedule add --frequency daily --hour 6 --tz UTC",
126
+ 'beryl schedule add --groups "Smoke,Checkout" --frequency weekly --day 0 --hour 9 --minute 15 --tz UTC',
118
127
  ],
119
- examples: ["beryl schedule set --frequency daily --hour 6 --tz UTC"],
120
128
  async run(ctx, input) {
121
129
  const { workspaceId, projectId } = await ctx.requireProject(input);
122
130
  return {
123
- data: await ctx.client.put(`${projectPath(workspaceId, projectId)}/schedule`, {
124
- enabled: true,
131
+ data: await ctx.client.post(`${projectPath(workspaceId, projectId)}/schedules`, {
125
132
  frequency: flagStr(input, "frequency") ?? "daily",
126
133
  day_of_week: flagNum(input, "day") ?? null,
127
134
  run_hour: flagNum(input, "hour"),
128
135
  run_minute: flagNum(input, "minute"),
129
136
  timezone: flagStr(input, "tz"),
137
+ group_ids: await resolveGroupIds(ctx, input, workspaceId, projectId),
130
138
  }),
131
139
  };
132
140
  },
133
141
  },
134
142
  {
135
- name: "schedule disable",
136
- summary: "Turn scheduled runs off",
143
+ name: "schedule update",
144
+ summary: "Change a schedule's cadence, time, timezone, or groups",
137
145
  scope: "project",
146
+ args: [{ name: "schedule-id", description: "Schedule id (see schedule list)", required: true }],
147
+ flags: [
148
+ ...SCHEDULE_CADENCE_FLAGS,
149
+ { name: "groups", type: "string", description: SCHEDULE_GROUPS_FLAG },
150
+ {
151
+ name: "all-tests",
152
+ type: "boolean",
153
+ description: "Target every active test (clears the schedule's groups)",
154
+ },
155
+ ],
138
156
  async run(ctx, input) {
139
157
  const { workspaceId, projectId } = await ctx.requireProject(input);
158
+ const scheduleId = arg(input, "schedule-id");
159
+ const schedules = (await ctx.client.get(`${projectPath(workspaceId, projectId)}/schedules`));
160
+ const existing = schedules.find((sch) => sch.id === scheduleId);
161
+ if (!existing)
162
+ throw new UsageError(`No schedule '${scheduleId}' in this project.`);
163
+ if (flagBool(input, "all-tests") && flagStr(input, "groups"))
164
+ throw new UsageError("--all-tests and --groups are mutually exclusive");
165
+ const groupIds = flagBool(input, "all-tests")
166
+ ? []
167
+ : flagStr(input, "groups")
168
+ ? await resolveGroupIds(ctx, input, workspaceId, projectId)
169
+ : existing.group_ids;
140
170
  return {
141
- data: await ctx.client.put(`${projectPath(workspaceId, projectId)}/schedule`, {
142
- enabled: false,
171
+ data: await ctx.client.put(`${projectPath(workspaceId, projectId)}/schedules/${scheduleId}`, {
172
+ frequency: flagStr(input, "frequency") ?? existing.frequency,
173
+ day_of_week: flagNum(input, "day") ?? existing.day_of_week,
174
+ run_hour: flagNum(input, "hour") ?? existing.run_hour,
175
+ run_minute: flagNum(input, "minute") ?? existing.run_minute,
176
+ timezone: flagStr(input, "tz") ?? existing.timezone,
177
+ group_ids: groupIds,
143
178
  }),
144
179
  };
145
180
  },
146
181
  },
182
+ {
183
+ name: "schedule remove",
184
+ summary: "Remove a schedule (its tests and groups stay)",
185
+ scope: "project",
186
+ args: [{ name: "schedule-id", description: "Schedule id (see schedule list)", required: true }],
187
+ async run(ctx, input) {
188
+ const { workspaceId, projectId } = await ctx.requireProject(input);
189
+ await ctx.client.del(`${projectPath(workspaceId, projectId)}/schedules/${arg(input, "schedule-id")}`);
190
+ return { human: "Removed." };
191
+ },
192
+ },
147
193
  ];
194
+ async function resolveGroupIds(ctx, input, workspaceId, projectId) {
195
+ const raw = flagStr(input, "groups");
196
+ if (!raw)
197
+ return [];
198
+ const refs = raw
199
+ .split(",")
200
+ .map((r) => r.trim())
201
+ .filter(Boolean);
202
+ if (refs.length === 0)
203
+ return [];
204
+ const groups = (await ctx.client.get(`${projectPath(workspaceId, projectId)}/groups`));
205
+ return refs.map((ref) => {
206
+ const group = findGroup(groups, ref);
207
+ if (!group) {
208
+ const names = groups.map((g) => g.name).join(", ") || "none yet";
209
+ throw new UsageError(`No group '${ref}' in this project (existing: ${names}).`);
210
+ }
211
+ return group.id;
212
+ });
213
+ }
@@ -3,7 +3,7 @@ import { watchExploration } from "./watch.js";
3
3
  export const explorationCommands = [
4
4
  {
5
5
  name: "explorations list",
6
- groupSummary: "Inspect the agent's exploration runs how it crawled a site and authored its tests.",
6
+ groupSummary: "Inspect the agent's exploration runs: how it crawled a site and authored its tests.",
7
7
  summary: "List the agent's exploration passes for a project",
8
8
  scope: "project",
9
9
  async run(ctx, input) {
@@ -38,7 +38,7 @@ export const explorationCommands = [
38
38
  {
39
39
  name: "explorations cancel",
40
40
  summary: "Cancel an in-flight exploration",
41
- description: "Stops a running exploration without touching the project or its tests the " +
41
+ description: "Stops a running exploration without touching the project or its tests: the " +
42
42
  "lever for a runaway/non-convergent pass burning agent budget.",
43
43
  scope: "project",
44
44
  args: [{ name: "exploration-id", description: "Exploration id", required: true }],
@@ -51,7 +51,7 @@ export const explorationCommands = [
51
51
  },
52
52
  {
53
53
  name: "explorations watch",
54
- summary: "Stream an exploration live watch the agent explore and author tests",
54
+ summary: "Stream an exploration live: watch the agent explore and author tests",
55
55
  description: "Replays every recorded step on connect, then follows live until the exploration " +
56
56
  "completes or fails.",
57
57
  scope: "project",