@vigilbase/cli 0.2.3 → 0.2.4

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.
Files changed (73) hide show
  1. package/CLOUDFLARE.md +2 -2
  2. package/COMMANDS.md +111 -44
  3. package/LOCAL.md +99 -38
  4. package/ONE.md +1 -1
  5. package/README.md +215 -67
  6. package/dist/auth/acl.d.ts +6 -0
  7. package/dist/auth/acl.js +45 -0
  8. package/dist/auth/device-auth.js +1 -0
  9. package/dist/auth/provider.js +4 -4
  10. package/dist/auth/refresh.d.ts +5 -4
  11. package/dist/auth/refresh.js +82 -41
  12. package/dist/auth/store.d.ts +25 -0
  13. package/dist/auth/store.js +91 -11
  14. package/dist/bin.js +13 -1
  15. package/dist/cli.js +205 -81
  16. package/dist/commands/account.js +11 -7
  17. package/dist/commands/auth.d.ts +6 -0
  18. package/dist/commands/auth.js +75 -24
  19. package/dist/commands/billing.js +2 -2
  20. package/dist/commands/cloudflare.d.ts +1 -1
  21. package/dist/commands/cloudflare.js +31 -17
  22. package/dist/commands/helpers.js +3 -3
  23. package/dist/commands/index.d.ts +1 -0
  24. package/dist/commands/index.js +2 -1
  25. package/dist/commands/legacy-validation.d.ts +20 -0
  26. package/dist/commands/legacy-validation.js +77 -3
  27. package/dist/commands/local.js +186 -65
  28. package/dist/commands/one.js +16 -13
  29. package/dist/commands/public-tools.js +52 -27
  30. package/dist/commands/runs.js +5 -5
  31. package/dist/commands/security.d.ts +1 -1
  32. package/dist/commands/security.js +41 -37
  33. package/dist/commands/watchdog.js +5 -5
  34. package/dist/lib/banner.d.ts +5 -11
  35. package/dist/lib/banner.js +11 -31
  36. package/dist/lib/client.js +19 -1
  37. package/dist/lib/cloudflare-client.d.ts +2 -2
  38. package/dist/lib/config.d.ts +16 -0
  39. package/dist/lib/config.js +77 -19
  40. package/dist/lib/format.d.ts +1 -1
  41. package/dist/lib/format.js +75 -24
  42. package/dist/lib/help.js +57 -6
  43. package/dist/lib/network.d.ts +12 -3
  44. package/dist/lib/network.js +73 -9
  45. package/dist/lib/one-client.js +9 -2
  46. package/dist/lib/open-url.js +2 -1
  47. package/dist/lib/platform.d.ts +2 -0
  48. package/dist/lib/platform.js +5 -0
  49. package/dist/lib/progress.d.ts +18 -3
  50. package/dist/lib/progress.js +72 -10
  51. package/dist/lib/system-trust.d.ts +2 -0
  52. package/dist/lib/system-trust.js +6 -0
  53. package/dist/lib/tui.d.ts +1 -1
  54. package/dist/lib/tui.js +5 -7
  55. package/dist/local/cloudflare-checkup/cf/child-env.js +2 -4
  56. package/dist/local/cloudflare-checkup/cf/install.d.ts +4 -2
  57. package/dist/local/cloudflare-checkup/cf/install.js +55 -24
  58. package/dist/local/cloudflare-checkup/cf/locate.d.ts +2 -0
  59. package/dist/local/cloudflare-checkup/cf/locate.js +10 -1
  60. package/dist/local/cloudflare-checkup/cf/run-cf.d.ts +18 -3
  61. package/dist/local/cloudflare-checkup/cf/run-cf.js +75 -17
  62. package/dist/local/cloudflare-checkup/contract.d.ts +16 -9
  63. package/dist/local/cloudflare-checkup/contract.js +80 -14
  64. package/dist/local/cloudflare-checkup/errors.d.ts +34 -1
  65. package/dist/local/cloudflare-checkup/errors.js +36 -5
  66. package/dist/local/cloudflare-checkup/output.d.ts +6 -0
  67. package/dist/local/cloudflare-checkup/output.js +9 -0
  68. package/dist/local/cloudflare-checkup/snapshot-file.d.ts +6 -2
  69. package/dist/local/cloudflare-checkup/snapshot-file.js +25 -11
  70. package/dist/local/cloudflare-checkup/submit-client.js +7 -1
  71. package/dist/types.d.ts +2 -1
  72. package/esm-only.cjs +3 -0
  73. package/package.json +7 -4
package/README.md CHANGED
@@ -28,14 +28,17 @@ supported on Windows yet; public tools and `cloudflare-checkup` still work there
28
28
 
29
29
  | Reference | Covers |
30
30
  | --- | --- |
31
- | [COMMANDS.md](https://app.unpkg.com/@vigilbase/cli@0.2.3/files/COMMANDS.md) | Core commands, aliases, global options, API mapping, sign-in, output |
32
- | [CLOUDFLARE.md](https://app.unpkg.com/@vigilbase/cli@0.2.3/files/CLOUDFLARE.md) | Accounts, zones, DNS, WAF, analytics, Zero Trust, Workers/Pages, audit, checkups |
33
- | [SECURITY.md](https://app.unpkg.com/@vigilbase/cli@0.2.3/files/SECURITY.md) | Incidents, logs, intelligence, analytics, assets, connections, policies, notifications, reports |
34
- | [ONE.md](https://app.unpkg.com/@vigilbase/cli@0.2.3/files/ONE.md) | Customer records, billing, reports, investigations, organizations, monitoring, support |
35
- | [LOCAL.md](https://app.unpkg.com/@vigilbase/cli@0.2.3/files/LOCAL.md) | `cloudflare-checkup`: verifies your work email, collects your Cloudflare configuration on your machine with Cloudflare's `cf` and uploads it after you confirm |
31
+ | [COMMANDS.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/COMMANDS.md) | Core commands, aliases, global options, API mapping, sign-in, output |
32
+ | [CLOUDFLARE.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/CLOUDFLARE.md) | Accounts, zones, DNS, WAF, analytics, Zero Trust, Workers/Pages, audit, checkups |
33
+ | [SECURITY.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/SECURITY.md) | Incidents, logs, intelligence, analytics, assets, connections, policies, notifications, reports |
34
+ | [ONE.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/ONE.md) | Customer records, billing, reports, investigations, organizations, monitoring, support |
35
+ | [LOCAL.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/LOCAL.md) | `cloudflare-checkup`: verifies your work email, collects your Cloudflare configuration on your machine with Cloudflare's `cf` and uploads it after you confirm |
36
36
 
37
37
  These files ship with the package. Built-in help: `vigilbase <command> --help`
38
- or `vigilbase help <command>`; `vigilbase help <tool>` shows how to run a tool.
38
+ or `vigilbase help <command>`; `vigilbase help <command> <subcommand>` (for
39
+ example `vigilbase help cf zones`) shows the same help as
40
+ `vigilbase cf zones --help`, and `vigilbase help <tool>` shows how to run a
41
+ tool. The library is ESM-only: use `import()`, not `require()`.
39
42
 
40
43
  ## Examples
41
44
 
@@ -143,7 +146,7 @@ read-only Cloudflare API token instead. When you are done:
143
146
  npx @vigilbase/cli cloudflare-checkup logout # removes the read-only cf login and the cf it installed
144
147
  ```
145
148
 
146
- See [LOCAL.md](https://app.unpkg.com/@vigilbase/cli@0.2.3/files/LOCAL.md) for
149
+ See [LOCAL.md](https://app.unpkg.com/@vigilbase/cli@0.2.4/files/LOCAL.md) for
147
150
  every step, what is uploaded and the error codes.
148
151
 
149
152
  ## Public tools and sign-in
@@ -152,51 +155,106 @@ every step, what is uploaded and the error codes.
152
155
  `run security-headers|email-security` call the public API anonymously and
153
156
  never read your saved sign-in, so an expired, broken or unreachable sign-in
154
157
  cannot make them fail. `--public` and `--via public` force the public API for
155
- any tool.
158
+ the public tools (`security-headers`, `email-security`).
156
159
  - **One-only scans need a sign-in.** `website-security`, `react2shell`,
157
160
  `fortibleed-check` and any run with `--via one` go through Vigilbase One.
158
161
  Without a usable sign-in they stop with
159
- ``This tool needs a Vigilbase One sign-in: run `vigilbase login`.``
162
+ ``This command needs a Vigilbase One sign-in: run `vigilbase login`.``
163
+ (code `NOT_SIGNED_IN`), as every One command does. With `--public` or
164
+ `--via public` a One-only tool stops before any request:
165
+ ``website-security runs only through Vigilbase One: drop --public and run `vigilbase login`.``
166
+ `run` with a tool name it doesn't know fails locally with
167
+ ``Unknown tool "<name>". Run `vigilbase list` for the allowlist.``
168
+ (code `INVALID_ARGUMENT`).
169
+ - **Scan quota.** Public scans share an hourly quota of 20 scans per client IP
170
+ address and 20 per target (1,000 an hour overall). When it is used up,
171
+ vigilbase.com answers HTTP 429 (`rate_limit_exceeded`) and the CLI exits 2
172
+ with the delay until the quota resets (up to an hour; `retryAfterSec` in
173
+ JSON). The CLI never retries on its own.
160
174
  - **`list`** reads your saved sign-in from disk only (no refresh, no network,
161
175
  no waiting for another command) to show which tools you can run. If it can't
162
176
  use it, it lists the tools as signed out and prints one line on stderr:
163
177
  ``Couldn't use your saved sign-in (<reason>); running anonymously. Run `vigilbase login` to fix.``
164
178
  When the reason is an unsafe config path or credentials file, the line ends
165
179
  with its fix instead (see [Directory checks](#directory-checks)).
166
- - **`doctor`** reports a sign-in problem as its own failed `signIn` check.
180
+ - **`doctor`** reports a sign-in problem as its own failed `signIn` check,
181
+ including a sign-in saved for another One origin, Identity issuer or client
182
+ ID: ``Saved for a different One origin, Identity issuer or client ID. Pass the matching --one-url, --auth-url and --client-id, or run `vigilbase login`.``
167
183
 
168
184
  ## Automation and output
169
185
 
170
186
  Every command supports `-j` / `--json`. JSON goes to stdout; progress and
171
- browser instructions go to stderr. Progress animations are off for JSON, CI and
172
- output that isn't a terminal. List commands never fetch every page on their
173
- own: keep the JSON pagination fields to fetch the next page. Text output shows
174
- complete identifiers. Human-readable output removes terminal control sequences
175
- and bidirectional overrides from everything it prints, including names, tool
176
- results, skills and errors. JSON keeps successful response data, with control
177
- characters escaped.
178
-
179
- `vigilbase --version` prints the banner in a terminal and only the version
180
- number when stdout is a pipe or a file, so `v=$(vigilbase --version)` works in
181
- scripts. With `--json` it prints `{"name":"@vigilbase/cli","version":"…"}`.
187
+ browser instructions go to stderr. Errors are JSON on stdout whenever `--json`
188
+ or `-j` is given, also inside a cluster of short flags (`-hj`) and as
189
+ `--json=…`; a `--json` after `--` is an argument, not the flag. Commands are
190
+ matched in any case (`vigilbase LIST` is `vigilbase list`).
191
+
192
+ Progress animations are off for `--json`, for output that isn't a terminal, in
193
+ CI (`CI` set to anything but `0` or `false`, in any case), with `TERM=dumb` and
194
+ with `VIGILBASE_CLI_NO_PROGRESS=1`. `NO_COLOR` doesn't turn them off, because
195
+ the spinner has no colour. The spinner fits the terminal width, and Ctrl-C
196
+ during a scan clears its line and exits 130. The same CI rule decides the
197
+ checkup's terminal UI and its colours and opening a browser (login, billing),
198
+ so `CI=yes` neither animates nor opens a browser.
199
+
200
+ List commands never fetch every page on their own: keep the JSON pagination
201
+ fields to fetch the next page. Text output shows complete identifiers: the
202
+ first column of `runs list` works with `runs get`, and `watchdog list` starts
203
+ with the target ID that `watchdog scans --target-id` takes.
204
+
205
+ Human-readable output removes terminal control sequences, bidirectional
206
+ overrides and invisible characters (zero-width characters, word joiners, the
207
+ byte order mark, U+2028/U+2029 line separators and Unicode tag characters) from
208
+ everything it prints, including names, tool results, skills and errors. Server
209
+ text in scan results never starts a line of its own: every line of a message is
210
+ indented. JSON keeps successful response data, with the same characters escaped
211
+ as `\uXXXX`, so values stay lossless.
212
+
213
+ `vigilbase --version` and `vigilbase help` print the banner only in a terminal.
214
+ When stdout is a pipe or a file, `--version` prints only the version number and
215
+ help starts at `Usage:`, so `v=$(vigilbase --version)` works in scripts. With
216
+ `--json`, `--version` prints `{"name":"@vigilbase/cli","version":"…"}`. The
217
+ banner is plain text.
182
218
 
183
219
  | Exit code | Meaning |
184
220
  | --- | --- |
185
- | `0` | Success (including a signed-out `whoami`) |
186
- | `1` | Validation, API, network or sign-in error, or an unsuccessful scan result |
221
+ | `0` | Success (including a signed-out `whoami`, and a graded scan whatever its grade) |
222
+ | `1` | Validation, API, network or sign-in error, or a scan that failed or couldn't be assessed |
187
223
  | `2` | Rate limited (`HTTP 429`) |
224
+ | `130`, `143`, `129` | `cloudflare-checkup` cancelled by Ctrl-C (SIGINT), SIGTERM or SIGHUP (128 + the signal number) |
188
225
 
189
- Errors keep a safe status, error code and retry delay. When a public scan
190
- fails and vigilbase.com explains why, the CLI shows that reason:
191
- `Error: <message> (<code>)` in text, and `error` plus `code` in JSON. The
192
- reason is shown only when vigilbase.com sends it as a known error code, and it
193
- is cleaned to one line of at most 300 characters. Anything else, and every
226
+ A graded scan exits 0 even with an F. Its first line starts with `✓` only when
227
+ the result's `status` is `secure` and the grade isn't F; otherwise it starts
228
+ with `•`. Scripts should gate on `.grade` or `.result.status` in the JSON.
229
+
230
+ Errors keep a safe status, error code and retry delay. These codes come from
231
+ the CLI itself:
232
+
233
+ | Code | Meaning |
234
+ | --- | --- |
235
+ | `NOT_SIGNED_IN` | A command that needs Vigilbase One has no usable sign-in |
236
+ | `SIGN_IN_ENDED` | Vigilbase Identity ended the saved sign-in, and `identity-credentials.json` was removed |
237
+ | `SIGN_IN_UNSUPPORTED` | Signing in isn't supported on Windows yet |
238
+ | `UNKNOWN_OPTION` | An option the CLI doesn't know, with `Did you mean --x?` when one is close |
239
+ | `INVALID_ARGUMENT` | A missing or unexpected option value (`--timeout needs a value.`, `--json does not take a value.`), an invalid value, an unknown subcommand or a token that isn't valid |
240
+ | `UNEXPECTED_REDIRECT` | An origin answered with a redirect, which the CLI never follows |
241
+ | `BLOCKED_PORT` | Node's fetch refuses the port (see [Troubleshooting](#troubleshooting)) |
242
+ | `TIMEOUT` | A request took longer than `--timeout` |
243
+
244
+ Network failures otherwise carry Node's code (for example `ENOTFOUND` or
245
+ `ECONNREFUSED`), and `cloudflare-checkup` has codes of its own (LOCAL.md).
246
+
247
+ When a public scan fails and vigilbase.com explains why, the CLI shows that
248
+ reason: `Error: <message> (<code>)` in text, and `error` plus `code` in JSON.
249
+ The reason is shown only when vigilbase.com sends it as a known error code, and
250
+ it is cleaned to one line of at most 300 characters. Anything else, and every
194
251
  Vigilbase One error, prints a message the CLI wrote (for example
195
252
  `You don't have access to this.` or `Request failed (HTTP <status>).`), never
196
- the server's text. The exception is a One scan the scanner couldn't assess
197
- (for example a bot verification challenge): the CLI prints the scanner's
198
- reason, cleaned the same way. Hints (`Hint: …`, `hint` in JSON) are always written by the
199
- CLI; COMMANDS.md lists the One errors that get one.
253
+ the server's text. The exception is a scan the scanner couldn't assess (for
254
+ example a bot verification challenge), on the public API or through One: the
255
+ CLI prints `✗ <tool> · <target> · n/a` and the scanner's reason, cleaned the
256
+ same way (code `scan_unassessed`). Hints (`Hint: …`, `hint` in JSON) are always
257
+ written by the CLI; COMMANDS.md lists the One errors that get one.
200
258
 
201
259
  Commands use named One APIs and One's own authorization. They never take
202
260
  Cloudflare or other provider secrets, delegated identity claims or an arbitrary
@@ -229,12 +287,26 @@ command arguments can show up in process listings and shell history; neither
229
287
  combines with `--token-stdin`. Login checks the token with One before saving
230
288
  it.
231
289
 
290
+ A token must be one value of printable ASCII (no spaces or control
291
+ characters), at most 16 KiB, whether it comes from `--token-stdin`, `--token`
292
+ or `VIGILBASE_ONE_TOKEN`. Otherwise login stops before any request with
293
+ `The token must be a single value of printable ASCII characters.`,
294
+ `The token exceeds 16 KiB.` or `The token must not be empty.` (code
295
+ `INVALID_ARGUMENT`); an empty `VIGILBASE_ONE_TOKEN` counts as unset. If an Identity
296
+ sign-in saved with other `--auth-url`, `--one-url` or `--client-id` values
297
+ exists, a token login or a legacy device login stops before it sends the token
298
+ or asks for a device code:
299
+ `A saved Identity sign-in belongs to <issuer> for <One origin> (client ID <id>). Pass the matching --auth-url, --one-url and --client-id, or log out with them first.`
300
+
232
301
  ### Where credentials are stored
233
302
 
234
303
  Credentials live in the config directory: `VIGILBASE_CONFIG_DIR` if set,
235
304
  otherwise `$XDG_CONFIG_HOME/vigilbase`, otherwise `~/.config/vigilbase`.
236
305
  `VIGILBASE_CONFIG_DIR` must be an absolute path; a relative `XDG_CONFIG_HOME`
237
- is ignored.
306
+ is ignored. A set but empty `VIGILBASE_CONFIG_DIR` doesn't fall back: when the
307
+ credentials are needed it is an error
308
+ (`VIGILBASE_CONFIG_DIR is empty. Set it to an absolute path or unset it.`). If
309
+ `HOME` is relative and nothing else is set, the error names `HOME`.
238
310
 
239
311
  | File | Holds |
240
312
  | --- | --- |
@@ -289,6 +361,11 @@ that passes the [directory checks](#directory-checks) but can't be parsed.
289
361
  warning, without being revoked; an unsafe file stops logout and stays in
290
362
  place.
291
363
 
364
+ With nothing saved, logout creates no directories. `logout --json` prints
365
+ `{success, cleared, warnings}`: `warnings` lists the warnings text mode prints
366
+ on stderr (an unparseable file removed without revoking, a failed legacy
367
+ sign-out) and is `[]` when there are none.
368
+
292
369
  Saved Identity credentials belong to one issuer, client and One. If you signed
293
370
  in with `--auth-url`, `--one-url` or `--client-id`, log out with the same
294
371
  options; otherwise logout stops, before revoking anything, with
@@ -298,35 +375,60 @@ options; otherwise logout stops, before revoking anything, with
298
375
 
299
376
  Identity access tokens last five minutes. The CLI refreshes within 60 seconds
300
377
  of expiry, or once after a 401. The lock coordinates CLI processes, and each
301
- holder rereads the file before rotating the refresh token. If a refresh may
302
- have been used but its answer was lost, the CLI marks it and asks you to run
303
- `vigilbase login` again rather than retrying it. A command waits up to 15
304
- seconds (or `--timeout`, if shorter) for the lock, then stops with
305
- `Credentials are locked by another command. …`. `login` and `logout` may
306
- remove a lock left by a process that has exited, but never one held by a
307
- running process. If a damaged lock can't be recovered, stop every `vigilbase`
308
- process, delete `credentials.json.lock` and log in again. Never reuse a refresh
309
- token by hand.
378
+ holder rereads the file before rotating the refresh token.
379
+
380
+ - A command waits up to 15 seconds (or `--timeout`, if shorter) for a lock
381
+ held by a running command, then stops with
382
+ ``Credentials are locked by another vigilbase command (process <pid>). If none is running, delete <path> and run `vigilbase login`.``
383
+ - If the command holding the lock has exited (an interrupted refresh, for
384
+ example after Ctrl-C), other commands stop at once with
385
+ ``A previous vigilbase command was interrupted while it held <path>. Run `vigilbase login` to recover.``
386
+ `login` and `logout` replace such a lock, but never one held by a running
387
+ command.
388
+ - An empty or unreadable lock stops with
389
+ ``<path> is damaged. Stop every vigilbase command, delete it, and run `vigilbase login`.``
390
+ `login` and `logout` replace one older than 30 seconds.
391
+ - If a refresh request gets no answer (a timeout or a dropped connection),
392
+ Identity may have rotated the token already, so the first error says so:
393
+ ``The Identity refresh may not have completed (…). Run `vigilbase login` again; its token cannot be retried safely.``
394
+ A definite refusal (a 4xx other than `invalid_grant`) leaves the refresh
395
+ retryable; a 5xx counts as uncertain.
396
+ - An `invalid_grant` answer (for example after a browser sign-out ended the
397
+ session) removes `identity-credentials.json`. The command then fails with
398
+ ``Your saved sign-in was ended by Vigilbase Identity and has been removed. Run `vigilbase login` again.``
399
+ (code `SIGN_IN_ENDED`), while `whoami` reports you as signed out (exit 0).
400
+
401
+ Never reuse a refresh token by hand.
310
402
 
311
403
  ### Directory checks
312
404
 
313
- Commands that read or write credentials (`login`, `logout`, `whoami`, every One
314
- command, `--via one` and the One-only scans) check the whole config path first:
405
+ Commands that read or write credentials (`login`, `logout`, every One command,
406
+ `--via one` and the One-only scans, and `whoami` when credentials may be there)
407
+ check the whole config path first:
315
408
 
316
409
  - no component of the path may be a symbolic link;
317
410
  - every parent directory must be owned by you or root, and not writable by its
318
411
  group or by others unless it has the sticky bit;
319
412
  - the config directory must be owned by you with mode `0700`;
320
413
  - credential files must be regular files owned by you, with one hard link and
321
- mode `0600`.
414
+ mode `0600`;
415
+ - on macOS, the config directory and credential files must have no access
416
+ control list (the CLI removes inherited entries from what it creates), and no
417
+ parent may have an ACL entry that lets another user add, remove or replace
418
+ its entries.
322
419
 
323
420
  Each error names the path that failed and how to fix it, for example:
324
421
 
325
422
  ```
326
423
  Error: /tmp is a symbolic link. Set VIGILBASE_CONFIG_DIR to a private directory without symbolic links in its path.
327
424
  Error: /Users/jane/.config/vigilbase must have mode 0700. Run chmod 700 /Users/jane/.config/vigilbase, or set VIGILBASE_CONFIG_DIR to another directory.
425
+ Error: /Users/jane/.config/vigilbase has an access control list. Run chmod -N /Users/jane/.config/vigilbase, or set VIGILBASE_CONFIG_DIR to another directory.
426
+ Error: /Users/jane/Shared is writable by other users through its access control list. Set VIGILBASE_CONFIG_DIR to a private directory without symbolic links in its path.
328
427
  ```
329
428
 
429
+ Paths with spaces, quotes or other special characters are shell-quoted in the
430
+ suggested commands.
431
+
330
432
  On macOS `/tmp` and `/var` are symbolic links, and dotfiles managers often make
331
433
  `~/.config` one. Fix the mode with the `chmod` the error names, or point
332
434
  `VIGILBASE_CONFIG_DIR` at a private directory of its own, for example:
@@ -341,11 +443,12 @@ files hold your refresh token.
341
443
 
342
444
  Public commands don't need these checks: public scans, `describe`, `skill` and
343
445
  `badge` never open the config directory, and work even when
344
- `VIGILBASE_CONFIG_DIR` isn't absolute. `list` and `doctor` never create or open
345
- anything in a directory that fails them, and only treat it as a problem when a
346
- credentials file may be there: `list` then shows you as signed out with one
347
- warning, and `doctor` reports it in its `signIn` check. With no saved sign-in,
348
- both carry on as usual.
446
+ `VIGILBASE_CONFIG_DIR` isn't absolute. `list`, `doctor` and `whoami` never
447
+ create or open anything in a directory that fails them, and only treat it as a
448
+ problem when a credentials file may be there: `list` then shows you as signed
449
+ out with one warning, `doctor` reports it in its `signIn` check, and `whoami`
450
+ fails with the error. With no saved sign-in, `list` and `doctor` carry on as
451
+ usual and `whoami` reports you as signed out (exit 0).
349
452
 
350
453
  ### Windows
351
454
 
@@ -367,21 +470,32 @@ work and ignore any saved credentials.
367
470
  | `--client-id <id>` | `VIGILBASE_CLIENT_ID` | `vigilbase-cli` |
368
471
  | `--timeout <ms>` | — | `60000` per request |
369
472
 
370
- Origins are HTTP(S) URLs without credentials, paths, queries or fragments. One
371
- needs HTTPS; HTTP is allowed only for loopback development (`localhost`,
372
- `127.0.0.0/8` or `[::1]`). This applies to device sign-in, tokens and saved
373
- sessions. `VIGILBASE_API_URL` and `VIGILBASE_PUBLIC_URL` are older names for
374
- the public origin, used after `VIGILBASE_BASE_URL` when no flag is given.
375
- `VIGILBASE_CLI_NO_OPEN=1` stops the CLI from opening a browser, and
376
- `VIGILBASE_CLI_NO_BANNER=1` hides the banner.
473
+ Origins are HTTP(S) URLs without credentials, paths, queries or fragments, and
474
+ need HTTPS; HTTP is allowed only for loopback development (`localhost`,
475
+ `127.0.0.0/8` or `[::1]`). This applies to `--base-url`, `--one-url` and
476
+ `--auth-url`. An empty flag value (`--base-url ""`) is an error; an empty
477
+ environment variable counts as unset. `VIGILBASE_API_URL` and
478
+ `VIGILBASE_PUBLIC_URL` are older names for the public origin, used after
479
+ `VIGILBASE_BASE_URL` when no flag is given; errors about the public origin name
480
+ the variable that held the bad value.
481
+
482
+ Commands check only the origins they use. Public scans, `list`, `describe`,
483
+ `skill` and `badge` ignore `--one-url`, `--auth-url` and their variables, and
484
+ One-only commands such as `whoami` ignore the public origin. `doctor` checks
485
+ both.
486
+
487
+ | Environment variable | Effect |
488
+ | --- | --- |
489
+ | `VIGILBASE_CLI_NO_OPEN=1` | Never open a browser |
490
+ | `VIGILBASE_CLI_NO_BANNER=1` | Hide the banner |
491
+ | `VIGILBASE_CLI_NO_PROGRESS=1` | Turn off progress animations |
377
492
 
378
493
  **Identity issuer.** The CLI trusts one Identity issuer: `--auth-url` /
379
494
  `VIGILBASE_AUTH_URL` when set, otherwise `https://auth.vigilbase.com`. The
380
495
  issuer One names must be exactly that origin, or sign-in stops before any
381
- request to it. `--auth-url` accepts `https://auth.vigilbase.com`,
382
- `https://identity-staging.vigilbase.com`, or a loopback origin when `--one-url`
383
- is loopback too; staging is trusted only when you pass it. Saved Identity
384
- credentials from another issuer count as signed out.
496
+ request to it. `--auth-url` accepts `https://auth.vigilbase.com`, or a loopback
497
+ origin (`localhost`, `127.0.0.0/8` or `[::1]`) when `--one-url` is loopback
498
+ too. Saved Identity credentials from another issuer count as signed out.
385
499
 
386
500
  Billing portal URLs must use HTTPS on `billing.stripe.com`. Skill downloads are
387
501
  limited to HTTPS `vigilbase.com/agent-skills/<name>.md` (or
@@ -404,9 +518,25 @@ three checks on their own and exits 1 if any fails:
404
518
  - `signIn`: whether your saved sign-in works, including a refresh if one is
405
519
  due.
406
520
 
407
- DNS, TLS, connection and timeout failures name the endpoint and the cause.
408
- Use `--base-url`, `--one-url`, `--auth-url` and `--timeout` to check other
409
- environments.
521
+ DNS, TLS, connection and timeout failures name the endpoint and the cause. A
522
+ connection failure ends with the hint ``Run `vigilbase doctor` for connectivity checks.``,
523
+ which `doctor` itself doesn't print. Doctor's `--json` top level includes the
524
+ first failing check's `code`. Use `--base-url`, `--one-url`, `--auth-url` and
525
+ `--timeout` to check other environments.
526
+
527
+ **Redirects and blocked ports.** The CLI never follows redirects. An origin
528
+ that redirects fails with code `UNEXPECTED_REDIRECT`, naming the origin it
529
+ redirects to and the option to set (for example
530
+ `… redirected (HTTP 301) to https://www.example.com. Set --base-url to that origin.`).
531
+ Node's fetch refuses some ports (the Fetch standard's blocked ports, such as
532
+ 6000 and 10080); they fail with code `BLOCKED_PORT`. Use another port.
533
+
534
+ **Proxies.** The CLI uses Node's built-in fetch, which ignores `HTTP_PROXY` and
535
+ `HTTPS_PROXY` unless `NODE_USE_ENV_PROXY=1` is set (Node 22.21+ or 24.5+; Node
536
+ prints an experimental warning). Skill documents (`skill`, `describe`) are
537
+ fetched directly, with DNS pinned to a checked public address, and never go
538
+ through a proxy: on a network that allows only proxied connections, `skill`
539
+ fails and `describe` shows no docs.
410
540
 
411
541
  **`Unknown command "cloudflare-checkup"`.** An older global install (0.1.x)
412
542
  takes priority over a bare `npx @vigilbase/cli`. Update it with
@@ -419,7 +549,11 @@ pnpm 11 and later skip releases published in the last 24 hours by default
419
549
  (Yarn's `npmMinimalAgeGate`, pnpm's `minimumReleaseAge`). For a day after a
420
550
  release, `yarn dlx @vigilbase/cli` and `pnpm dlx @vigilbase/cli` run an
421
551
  older release, and Yarn refuses the new version by number
422
- (`All versions satisfying "…" are quarantined`). Use `npx @vigilbase/cli`, or
552
+ (`All versions satisfying "…" are quarantined`). pnpm 11 runs a pinned
553
+ version (`pnpm dlx @vigilbase/cli@<version>`). pnpm 10.16 and later apply
554
+ `minimumReleaseAge` only when you set it, and then refuse a pinned version
555
+ released within that time with `ERR_PNPM_NO_MATURE_MATCHING_VERSION`. Use
556
+ `npx @vigilbase/cli`, add `--config.minimum-release-age=0` to `pnpm dlx`, or
423
557
  wait a day.
424
558
 
425
559
  **`Device sign-in isn't available at <origin> right now.`** Login got a 404
@@ -432,6 +566,14 @@ in.
432
566
 
433
567
  **`A previous refresh has an uncertain result.`** Run `vigilbase login` again.
434
568
 
569
+ **`A previous vigilbase command was interrupted while it held …` or
570
+ `… credentials.json.lock is damaged.`** Run `vigilbase login`, or delete the
571
+ file as the message says (see
572
+ [Refresh and the credentials lock](#refresh-and-the-credentials-lock)).
573
+
574
+ **`Your saved sign-in was ended by Vigilbase Identity and has been removed.`**
575
+ You signed out in the browser, or the session ended. Run `vigilbase login`.
576
+
435
577
  **Certificates.** On Node 22.19+ and 24.5+ the CLI adds your operating
436
578
  system's trusted certificates, keeping Node's bundled roots and
437
579
  `NODE_EXTRA_CA_CERTS`. This covers private CAs trusted in the macOS Keychain or
@@ -445,8 +587,14 @@ NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--use-system-ca" vigilbase login
445
587
  ```
446
588
 
447
589
  For a private CA outside the system store, set
448
- `NODE_EXTRA_CA_CERTS=/path/to/trusted-ca.pem` before starting the CLI.
449
- Certificate checks always stay on.
590
+ `NODE_EXTRA_CA_CERTS=/path/to/trusted-ca.pem` before starting the CLI. The
591
+ advice to use Node 22.19+ or 24.5+ appears in TLS errors only when the running
592
+ Node couldn't add the operating system's certificates.
593
+
594
+ The CLI never turns certificate checks off. `NODE_TLS_REJECT_UNAUTHORIZED=0`
595
+ does, for every request of the process, including those that carry your One
596
+ sign-in; Node then prints a warning. It is not supported: use
597
+ `NODE_EXTRA_CA_CERTS` instead.
450
598
 
451
599
  **Timeouts.** Use `--timeout` for slower queries; some security analytics APIs
452
600
  take longer than the default.
@@ -0,0 +1,6 @@
1
+ /** Remove every entry from a path the CLI just created, so it doesn't keep one inherited from its parent. */
2
+ export declare function stripAcl(path: string): Promise<void>;
3
+ /** The ACL entries of each path, in order, like ` 0: group:everyone allow read`. Empty off darwin. */
4
+ export declare function aclEntries(paths: string[]): Promise<string[][]>;
5
+ /** Whether an entry lets someone other than the current user change the directory's entries. */
6
+ export declare function letsOthersChangeEntries(entry: string): boolean;
@@ -0,0 +1,45 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { userInfo } from 'node:os';
3
+ import { promisify } from 'node:util';
4
+ import { platform } from '../lib/platform.js';
5
+ /**
6
+ * macOS access control lists. An allow entry grants access whatever the mode bits say, and Node's stat can't see
7
+ * entries, so on darwin the credentials paths are checked with /bin/ls and cleaned with /bin/chmod. Other platforms
8
+ * have nothing to do: Linux's chmod 0600 masks a default POSIX ACL.
9
+ */
10
+ const run = promisify(execFile);
11
+ /**
12
+ * Directory rights that let another user add, remove or replace an entry, remove the directory, or take over its ACL
13
+ * or ownership (and then grant themselves the rest). The boundaries keep writeattr and writeextattr from matching.
14
+ */
15
+ const CHANGES_ENTRIES = /\b(?:add_file|add_subdirectory|delete_child|delete|write|append|writesecurity|chown)\b/;
16
+ /** Remove every entry from a path the CLI just created, so it doesn't keep one inherited from its parent. */
17
+ export async function stripAcl(path) {
18
+ if (platform() !== 'darwin')
19
+ return;
20
+ await run('/bin/chmod', ['-N', path]);
21
+ }
22
+ /** The ACL entries of each path, in order, like ` 0: group:everyone allow read`. Empty off darwin. */
23
+ export async function aclEntries(paths) {
24
+ if (platform() !== 'darwin' || !paths.length)
25
+ return paths.map(() => []);
26
+ // -q prints a newline in a name as ?, -f keeps the operands' order.
27
+ const { stdout } = await run('/bin/ls', ['-ledqf', '--', ...paths]);
28
+ const entries = [];
29
+ for (const line of stdout.split('\n')) {
30
+ if (/^\s*\d+: /.test(line))
31
+ entries.at(-1)?.push(line.trim());
32
+ else if (line)
33
+ entries.push([]);
34
+ }
35
+ if (entries.length !== paths.length)
36
+ throw new Error('Unexpected /bin/ls output.');
37
+ return entries;
38
+ }
39
+ /** Whether an entry lets someone other than the current user change the directory's entries. */
40
+ export function letsOthersChangeEntries(entry) {
41
+ const match = /^\d+: (.+?) (?:inherited )?(allow|deny) (\S+)/.exec(entry);
42
+ if (!match || match[2] !== 'allow' || !CHANGES_ENTRIES.test(match[3]))
43
+ return false;
44
+ return match[1] !== `user:${userInfo().username}`;
45
+ }
@@ -34,6 +34,7 @@ async function postJson(config, path, body) {
34
34
  const url = provider ? (path === '/device/code' ? provider.deviceEndpoint : provider.tokenEndpoint) : `${authBase(config)}${path}`;
35
35
  const form = provider && path === '/device/token';
36
36
  const { response, text } = await requestText(config, url, {
37
+ option: provider ? '--auth-url' : '--one-url',
37
38
  method: 'POST',
38
39
  headers: {
39
40
  Accept: 'application/json',
@@ -1,6 +1,6 @@
1
1
  import { DeviceAuthError } from './device-auth.js';
2
2
  import { z } from 'zod';
3
- import { DEFAULT_IDENTITY_ISSUER, parseOrigin, STAGING_IDENTITY_ISSUER, trustedIdentityIssuer, validateOneOrigin } from '../lib/config.js';
3
+ import { DEFAULT_IDENTITY_ISSUER, parseOrigin, trustedIdentityIssuer, validateOneOrigin } from '../lib/config.js';
4
4
  import { requestText, parseJsonResponse } from '../lib/network.js';
5
5
  /** An issuer (discovered from One, or saved with credentials) is trusted only when its origin is exactly trustedIdentityIssuer(config). */
6
6
  export function validateIdentityIssuer(raw, config) {
@@ -17,9 +17,9 @@ export function validateIdentityIssuer(raw, config) {
17
17
  return origin;
18
18
  throw new Error(config.identityBaseUrl
19
19
  ? `Untrusted Identity issuer ${origin}: --auth-url / VIGILBASE_AUTH_URL is ${trusted}. Set it to the issuer for this One, or unset it to use ${DEFAULT_IDENTITY_ISSUER}.`
20
- : `Untrusted Identity issuer ${origin}: only ${DEFAULT_IDENTITY_ISSUER} is trusted by default. For staging, pass --auth-url ${STAGING_IDENTITY_ISSUER} or set VIGILBASE_AUTH_URL.`);
20
+ : `Untrusted Identity issuer ${origin}: only ${DEFAULT_IDENTITY_ISSUER} is trusted by default. To use another issuer, pass --auth-url <issuer> or set VIGILBASE_AUTH_URL.`);
21
21
  }
22
- async function json(config, url) { const { response, text } = await requestText(config, url, { headers: { Accept: 'application/json', 'User-Agent': config.userAgent } }); return { response, body: parseJsonResponse(response, text) }; }
22
+ async function json(config, url) { const { response, text } = await requestText(config, url, { option: '--auth-url', headers: { Accept: 'application/json', 'User-Agent': config.userAgent } }); return { response, body: parseJsonResponse(response, text) }; }
23
23
  export async function identityProvider(config, issuer) {
24
24
  issuer = validateIdentityIssuer(issuer, config);
25
25
  const { response, body } = await json(config, `${issuer}/.well-known/openid-configuration`);
@@ -42,7 +42,7 @@ export async function identityProvider(config, issuer) {
42
42
  */
43
43
  export async function discoverAuthProvider(config) {
44
44
  const one = validateOneOrigin(config.oneBaseUrl);
45
- const { response, text } = await requestText(config, `${one}/.well-known/oauth-protected-resource`, { headers: { Accept: 'application/json', 'User-Agent': config.userAgent }, redirect: 'manual' });
45
+ const { response, text } = await requestText(config, `${one}/.well-known/oauth-protected-resource`, { option: '--one-url', headers: { Accept: 'application/json', 'User-Agent': config.userAgent }, redirect: 'manual' });
46
46
  if (response.type === 'opaqueredirect' || response.status === 0 || (response.status >= 300 && response.status < 400))
47
47
  return undefined;
48
48
  if (response.status === 404)
@@ -3,14 +3,15 @@ import type { OneCredentials } from './types.js';
3
3
  export declare function boundIdentityCredentials(config: CliConfig, one: OneCredentials): boolean;
4
4
  /** Library callers may omit or mistype timeoutMs; NaN would make the wait unbounded. */
5
5
  export declare function credentialsLockWaitMs(timeoutMs: unknown): number;
6
- /** Another command holds credentials.json.lock. */
6
+ /** credentials.json.lock is held: by a running command, by one that was interrupted, or it is damaged. */
7
7
  export declare class CredentialsLockedError extends Error {
8
- constructor();
8
+ constructor(message: string);
9
9
  }
10
10
  /**
11
11
  * credentials.json.lock guards both credentials files; CLI 0.2.0 takes the same lock.
12
- * Never steal a stale lock: a crashed refresh may have consumed the rotating token.
13
- * With `wait` false, a held lock fails at once instead of after the lock wait.
12
+ * Only login and logout (`replaceDeadLock`) replace a lock whose owner has exited, or an old damaged one: a crashed
13
+ * refresh may have consumed the rotating token, and both start over without it. Other commands fail at once on a
14
+ * dead owner. With `wait` false, a held lock fails at once instead of after the lock wait.
14
15
  */
15
16
  export declare function withCredentialsLock<T>(config: Pick<CliConfig, 'configDir'> & Partial<Pick<CliConfig, 'timeoutMs'>>, callback: () => Promise<T>, replaceDeadLock?: boolean, wait?: boolean): Promise<T>;
16
17
  export declare function managedAccessToken(config: CliConfig, failedToken?: string): Promise<string | null>;