-v raises the CLI's log level per repeat; -q prints only fatal errors (closes #212)
check / check (push) Waiting to run

Every line quak prints on stderr now goes through one log, in src/cli-log.ts, at one of six levels. The default, info, prints what quak printed before. -v adds each retry and the start and end of each refresh and ML data fetch; -vv adds each request with its result. -q prints only fatal lines, and is refused with -v; both are program options. Every non-zero exit logs one fatal line, so a run that exits 1 because files failed says how many and where to look. Every line reporting a failure is at error. --json changes stdout only, so backup and --json runs log their progress on stderr. withRetry and ApiClient report retries and requests through new callbacks.

Model: opus-5-5
This commit was merged in pull request #216.
This commit is contained in:
2026-10-18 21:46:59 +02:00
parent 92e0c6c791
commit 68d22b9927
21 changed files with 1099 additions and 188 deletions
+79 -18
View File
@@ -137,12 +137,12 @@ matters.
### Exit codes
| Code | Meaning |
| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0` | The backup is complete: every original is at its save path, and no file failed. |
| `1` | The run finished with files in `failures.json` or albums it could not decrypt or write (see "Backup layout"), or it stopped on another error, printed as one line `quak: <message>`. The next run tries again. |
| `2` | Another backup of the same directory is running. This run sent no request and changed nothing. |
| `3` | There is no usable session: none is saved, the saved one is corrupt, or the server no longer accepts it. Run `quak login` as the user the cron job runs as. |
| Code | Meaning |
| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0` | The backup is complete: every original is at its save path, and no file failed. |
| `1` | The run finished with files in `failures.json` or albums it could not decrypt or write (see "Backup layout"), and its last line says how many, or it stopped on another error, printed as one line `quak: <message>`. The next run tries again. |
| `2` | Another backup of the same directory is running. This run sent no request and changed nothing. |
| `3` | There is no usable session: none is saved, the saved one is corrupt, or the server no longer accepts it. Run `quak login` as the user the cron job runs as. |
`<message>` is the error's message, followed by the message of each error that
caused it. Under Node, as `yarn quak` runs quak, `fetch` reports every failure
@@ -378,6 +378,8 @@ quak/
thumbnails.ts detect + regenerate missing thumbnails
client.ts high-level Client class assembled from the above
cli-commands.ts the CLI's commands as functions returning exit codes
cli-log.ts the CLI's log level, and the one function the
commands write stderr lines through
cli-output.ts how the CLI prints a file's title and time
cli-read.ts fresh reads for the CLI's read commands
cli-run.ts run a command, print its error, exit with its code
@@ -456,6 +458,12 @@ A custom API endpoint is configurable for self-hosted servers via the
constructor option `apiOrigin`. When set, file downloads route through
`<apiOrigin>/files/download/<id>` instead of the dedicated CDN host.
The constructor option `onRequest` is called once for each request sent, a
retried request once for each attempt, with its method, its URL and the status
of the answer or the error the request failed with. The URL of a presigned
upload is given without its query, which holds the upload's signature. The CLI
logs each request with `-vv` (see "CLI surface").
Required request headers on every authenticated call:
- `X-Auth-Token`: the decrypted auth token from login.
@@ -532,7 +540,9 @@ each wait is drawn at random below its ceiling. Every other command uses the
defaults. A library user gets the same budget by passing
`UNATTENDED_RETRY_OPTIONS` as `ApiClientOptions.retry`. `sleep` and `random` are
injectable through the same option, which is how the test suite exercises the
whole policy without waiting.
whole policy without waiting. `onRetry`, given there too, is called before each
wait with the error the attempt failed with, the attempt's number and the wait;
the CLI logs it with `-v` (see "CLI surface").
Two deadlines, renewed for each attempt:
@@ -646,6 +656,7 @@ thumbnails), for the user to delete if they want it gone.
```
quak [--cache-dir <path>] <command> global: local metadata/content cache location
quak [-v | -vv | -q] <command> global: log more on stderr, or only fatal errors
quak login interactive or QUAK_EMAIL/QUAK_PASSWORD
quak whoami print logged-in account as JSON
quak logout end the session, delete it
@@ -669,16 +680,66 @@ request, and neither fetches any collection's files nor changes the cache. The
others wait for a refresh. With an empty cache, as on a first run, that refresh
fetches the files of every collection, one collection after another, and stores
nothing until it has them all, which on a large account takes a while. Meanwhile
the command prints a line on stderr for each collection whose files it has
fetched, such as `Fetched the files of 3 of 120 collections`; with `--json`,
`files` and the two helpers print no such line. stdout holds only the command's
output. If that round-trip fails, the command prints the error on one line and
exits 1, or 3 when the server no longer accepts the saved session (see "Session
handling"). An album or file quak cannot decrypt does not fail it; the command
answers from what did decrypt (see "Default reads vs. fresh reads").
the command logs a line on stderr for each collection whose files it has
fetched, such as `Fetched the files of 3 of 120 collections`, with `--json` too;
`backup` does the same while the refresh it starts with runs. stdout holds only
the command's output. If that round-trip fails, the command prints the error on
one line and exits 1, or 3 when the server no longer accepts the saved session
(see "Session handling"). An album or file quak cannot decrypt does not fail it;
the command answers from what did decrypt (see "Default reads vs. fresh reads").
`--cache-dir` overrides where the cache lives; without it each account gets its
own directory under the per-user cache path.
`-v` (`--verbose`) and `-q` (`--quiet`) set how much quak logs on stderr. Like
`--cache-dir`, they go before or after any command's name. Every line quak
prints on stderr is a log line at one of six levels, from the quietest: fatal,
error, warning, info, debug and trace. A run prints the lines at its own level
and at every quieter one. stdout holds only the command's output, at every
level.
- Every command that exits non-zero logs a fatal line saying why, such as
`quak: <message>`, `Not logged in`, an invalid ID or a collection that does
not exist. A command that exits 1 because files or albums failed logs it last,
saying how many failed and where to look: `backup` points to `failures.json`
and its `FAILED` lines, `backup-metadata` to the `mlDataError` in the JSON of
each such file, and `helper fix-missing-thumbnails` to its `Failed files:`
list, or to its JSON with `--json`.
- An error line reports something that failed: the `FAILED` lines that `backup`
and `helper fix-missing-thumbnails` log as they work,
`ML data of file <id> could not be read` and
`ML data request for <n> file(s) failed` from `backup-metadata`,
`Could not check <title>` from both thumbnail helpers, and the `Failed files:`
list at the end of `backup` and of `helper fix-missing-thumbnails`.
- A warning reports something that did not fail but needs your attention: after
`logout`, that the cache directory still holds decrypted data.
- Every other line quak logs by default is info.
With `--json`, a command's result on stdout takes the place of its summary on
stderr: for `backup`, the lines from `--- Backup complete ---` to the end of its
`Failed files:` list; for `helper list-missing-thumbnails`,
`No missing thumbnails found.` or `<n> file(s) with missing thumbnails:`; and
for `helper fix-missing-thumbnails`, the lines from `--- Done ---` to the end of
its `Failed files:` list. Every other line is logged as it is without `--json`.
- With neither option the level is info: the lines quak prints as it works, such
as a refresh's progress, the files a backup fetches, its summary and the files
that failed.
- `-v` raises the level to debug, which adds each retry of a request, with why
the attempt failed and how long quak waits before the next, and the start and
end of each refresh and each ML data fetch, with the error of one that failed.
- `-vv` raises it to trace, which adds each request quak sends: its method, its
URL and the status of the answer, or the error the request failed with. The
URL of an upload of a thumbnail is logged without its query, which holds the
upload's signature. More repeats stay at trace, and `--verbose --verbose` is
`-vv`.
- `-q` lowers it to fatal: a run that exits 0 prints nothing on stderr, and one
that exits non-zero prints only its fatal lines. The prompts of `login` are
not log lines, and `-q` leaves them as they are.
`-q` with `-v` is refused with one line,
`error: option '-q, --quiet' cannot be used with option '-v, --verbose'`, and
exit status 1.
`files <collectionID>` lists the files in that collection. `files` with no
argument lists every file in the account, collection by collection, each file
once: a file in several collections is listed with the first of them, which is
@@ -731,7 +792,7 @@ fails, the error is logged, each of its files is written with the reason in an
`mlDataError` field instead of `mlData`, and the dump goes on. A file whose ML
data does not decrypt, gunzip or parse is logged and written the same way, with
why in `mlDataError`. The exit code is non-zero if any file was written with
`mlDataError`.
`mlDataError`, and the last line then says how many were.
`helper fix-missing-thumbnails` regenerates thumbnails for JPEG images only,
because the bundled decoder (`jpeg-js`) decodes only JPEG. A non-JPEG image
@@ -828,8 +889,8 @@ reads vs. fresh reads") goes into `failures.json` with an error that starts
`could not be decrypted:` and gives the reason. If a copy of it in another album
decrypted, it does not: its original is stored from that copy, and the album
where it does not decrypt neither links nor lists it. An album the library could
not decrypt has no name quak can read and no files it can list. A run without
`--json` prints one line for it,
not decrypt has no name quak can read and no files it can list. `quak backup`
prints one line for it on stderr,
`FAILED album <id>: could not be decrypted: <reason>`, and `--json` and the
`BackupResult` list its ID in `undecryptableAlbums`. Such a file in
`failures.json`, or such an album, keeps the exit code at 1, run after run,
@@ -883,7 +944,7 @@ appears or goes away.
A collection whose directory or JSON cannot be written, such as one whose
directory name is taken by a file you put in `collections/`, does not stop the
run. A run without `--json` prints `FAILED album <name>: <reason>` for it,
run. `quak backup` prints `FAILED album <name>: <reason>` for it on stderr,
`--json` and the `BackupResult` list its ID in `failedAlbums`, and the exit code
is 1. The other collections and `failures.json` are still written.