1 Commits
Author SHA1 Message Date
sneak 55742ded11 README.md rewritten sentence by sentence (closes #219)
check / check (push) Canceled after 0s
The old text served as the list of what a reader needs covered. The new
text leads with what quak is, what it is for and how to use it. Every
command, option, exit code and file layout follows in reference sections,
then the library reference, then design, entrypoints and development.
Every statement was checked against the code on next; the places where
the old text disagreed with it are listed in the pull request. No sentence
runs past about 30 words, every table is now a list, and no issue links or
project history remain in text meant for users. Two code comments that
named README sections now name the new ones.

Model: fable-5-1
2026-10-26 13:27:32 +00:00
+45 -40
View File
@@ -314,8 +314,8 @@ refused, because both files would land on it.
#### `quak get-thumb <fileID> [--out <path>]` #### `quak get-thumb <fileID> [--out <path>]`
Downloads and decrypts one file's thumbnail, the small JPEG Ente shows in a Downloads and decrypts one file's thumbnail, the small JPEG Ente shows in a
grid. It works as `get` does. Without `--out`, the name is `thumb_` followed by grid. It writes one file, also for a live photo. Without `--out`, the name is
the title. `thumb_` followed by the title. In all else it works as `get` does.
#### `quak backup <dir> [--json] [--verify]` #### `quak backup <dir> [--json] [--verify]`
@@ -362,7 +362,8 @@ Writes the metadata quak keeps about the account, its collections and its files
as JSON files under `<dir>`. The layout is described under "The metadata dump as JSON files under `<dir>`. The layout is described under "The metadata dump
directory". Photo content is not written. With `--exif`, or its alias `--all`, directory". Photo content is not written. With `--exif`, or its alias `--all`,
the command also fetches each file's original and records the image's own the command also fetches each file's original and records the image's own
metadata from it (see "The metadata dump directory"). metadata from it (see "The metadata dump directory"). Each original it fetches
stays in the cache.
Ente's apps compute machine-learning data for each photo, such as the faces in Ente's apps compute machine-learning data for each photo, such as the faces in
it (see "The JSON beside each original"). The command fetches it in requests of it (see "The JSON beside each original"). The command fetches it in requests of
@@ -377,9 +378,9 @@ word, and does not change the exit status.
#### `quak helper list-missing-thumbnails [--json]` #### `quak helper list-missing-thumbnails [--json]`
Finds the files whose thumbnail is missing on the server. It downloads every Finds the files whose thumbnail is missing on the server. It downloads the
thumbnail in the account, one collection at a time, newest collection first, so thumbnail of every file you own, one collection at a time, newest updated first,
on a large account it takes a while. A thumbnail counts as missing when the so on a large account it takes a while. A thumbnail counts as missing when the
server answers with an empty body or with HTTP 404. Any other failure is logged server answers with an empty body or with HTTP 404. Any other failure is logged
as `[<collection>] Could not check <title>: <reason> (not reported as missing)`, as `[<collection>] Could not check <title>: <reason> (not reported as missing)`,
and the file is left out of the list. The command exits 0 even when some files and the file is left out of the list. The command exits 0 even when some files
@@ -477,16 +478,16 @@ before any request is sent. The command logs `Invalid file ID: <arg>` or
### What quak prints ### What quak prints
stdout holds only a command's output: the listing, the JSON, or nothing. The stdout holds a command's output, such as the listing or the JSON, and the
prompts of `login` go to stdout too. Every other line goes to stderr as a log prompts of `login`. Every other line goes to stderr as a log line at one of six
line at one of six levels. From the quietest, the levels are fatal, error, levels. From the quietest, the levels are fatal, error, warning, info, debug and
warning, info, debug and trace. A run prints the lines at its own level and at trace. A run prints the lines at its own level and at every quieter one. The
every quieter one. The level is info unless `-v`, `-vv` or `-q` says otherwise. level is info unless `-v`, `-vv` or `-q` says otherwise.
The one exception is a command line quak cannot parse, such as a missing Two things on stderr are not log lines. A command line quak cannot parse, such
argument, an unknown option or `-q` with `-v`. quak refuses it with one as a missing argument, an unknown option or `-q` with `-v`, is refused with an
`error: …` line that is not a log line, and exits 1. `-q` does not hide that `error: …` message, and quak exits 1. `-q` does not hide the message. `quak`
line. `quak` with no command prints the usage on stderr and exits 1. with no command prints the usage on stderr and exits 1.
`yarn quak` prints yarn's own lines on stdout as well. To pipe a command's `yarn quak` prints yarn's own lines on stdout as well. To pipe a command's
output, run `yarn --silent quak files --json` or output, run `yarn --silent quak files --json` or
@@ -783,10 +784,11 @@ goes away.
Each run removes every symlink in a collection's directory that points into the Each run removes every symlink in a collection's directory that points into the
date folders and is not one of its current links, yours included. A file or date folders and is not one of its current links, yours included. A file or
symlink of yours at the name of one of its links is replaced by the link. A symlink of yours at the name of one of its links is replaced by the link. A
directory at that name stays, and the file goes into `failures.json`. Anything directory of yours at that name stays, and the photo whose link it blocks goes
else you put under `collections/` stays. A run removes the directory and JSON of into `failures.json`. Anything else you put under `collections/` stays. A run
a collection that was deleted or renamed, once the directory holds nothing else. removes the directory and JSON of a collection that was deleted or renamed, once
One that still holds something of yours stays, with its JSON. the directory holds nothing else. One that still holds something of yours stays,
with its JSON.
A collection whose directory or JSON cannot be written does not stop the run. A collection whose directory or JSON cannot be written does not stop the run.
For example, a file you put there may already have the directory's name. The run For example, a file you put there may already have the directory's name. The run
@@ -831,7 +833,7 @@ Each entry holds:
- `fileID` and `title`; - `fileID` and `title`;
- `classification`: the kind of failure; - `classification`: the kind of failure;
- `attempts`: how many runs have tried the file; - `attempts`: how many runs in a row have failed the file;
- `lastTriedAt`: when it was last tried, as a Unix time in milliseconds; - `lastTriedAt`: when it was last tried, as a Unix time in milliseconds;
- `error`: the error, as quak printed it. - `error`: the error, as quak printed it.
@@ -1183,11 +1185,11 @@ progress of that one file.
keyed by tag name, each as exifreader decodes it with its `id`, `value`, keyed by tag name, each as exifreader decodes it with its `id`, `value`,
`description` and `computed` value. For example, `Make` is `description` and `computed` value. For example, `Make` is
`{ id: 271, value: ["Example"], description: "Example", computed: "Example" }`. `{ id: 271, value: ["Example"], description: "Example", computed: "Example" }`.
A tag exifreader has no name for is keyed `undefined-<tag number>`. The tags A tag that exifreader has no name for is keyed `undefined-<tag number>`. The
of the thumbnail embedded in the file are under `Thumbnail`, so they cannot tags of the thumbnail embedded in the file are under `Thumbnail`, so they
hide the main image's tags of the same name. The thumbnail image itself is cannot hide the main image's tags of the same name. The thumbnail image itself
left out. Any file exifreader cannot read gives `{}`, and a video gives `{}` is left out. Any file exifreader cannot read gives `{}`, and a video gives
without being downloaded. `{}` without being downloaded.
- `await photo.make(opts?)` returns one common EXIF field, picked from the tags - `await photo.make(opts?)` returns one common EXIF field, picked from the tags
`exif()` returns and typed as in `PhotoExif`, or `undefined` when the file `exif()` returns and typed as in `PhotoExif`, or `undefined` when the file
lacks it. These methods do the same for their fields: lacks it. These methods do the same for their fields:
@@ -1307,8 +1309,9 @@ Only the requests named above go through the pools. A refresh,
with with
`new RequestPools({ metadataConcurrency, contentConcurrency, thumbnailConcurrency })` `new RequestPools({ metadataConcurrency, contentConcurrency, thumbnailConcurrency })`
and pass it as `pools`. Each pool serves on-demand work ahead of background and pass it as `pools`. Each pool serves on-demand work ahead of background
work. A task submitted under a key that is still pending is not run a second work. Two tasks given to `BoundedPool.run()` with the same `key` run once while
time. An idle pool never lends its slots to a busy one. the first is pending, and both get its result. An idle pool never lends its
slots to a busy one.
`lib.backup()` works on as many originals at once as the content pool allows, 5 `lib.backup()` works on as many originals at once as the content pool allows, 5
by default. `quak backup` uses the default. The limit covers everything it does by default. `quak backup` uses the default. The limit covers everything it does
@@ -1402,13 +1405,13 @@ that rule although they look like connect-time failures. On Linux they are also
delivered on an established socket, after the request may have been written. delivered on an established socket, after the request may have been written.
They stay retryable for requests that change nothing. Bun's `ConnectionRefused` They stay retryable for requests that change nothing. Bun's `ConnectionRefused`
and `FailedToOpenSocket` are treated the same way: a request that changes and `FailedToOpenSocket` are treated the same way: a request that changes
nothing is retried on them, and a `POST` or `PUT` is not replayed on them. When nothing is retried on them, and a `POST` or `PUT` is not replayed on them. Bun
a connection bun kept open from an earlier request closes before any answer, bun reuses a connection it kept open from an earlier request. When that connection
sends the request again on a new connection. If that one fails, bun gives one of closes before any answer, bun sends the request again on a new one. If that one
these codes although the server may already have the request. The upload of a fails, bun gives one of these codes although the server may already have the
thumbnail to its presigned URL is exempt from the rule. It stores one whole request. The upload of a thumbnail to its presigned URL is exempt from the rule.
object at one key in one request, so replaying it has no partial state to It stores one whole object at one key in one request, so replaying it has no
damage. partial state to damage.
A download is retried as a whole: the request, the reading of the body and the A download is retried as a whole: the request, the reading of the body and the
decryption. A socket reset after the response headers have arrived surfaces in decryption. A socket reset after the response headers have arrived surfaces in
@@ -1446,11 +1449,11 @@ working client from that snapshot without logging in again. It checks every
field and each key's length first, and throws an error naming the bad field. field and each key's length first, and throws an error naming the bad field.
`Client.fromJSON` works only once libsodium, the cryptography library quak uses, `Client.fromJSON` works only once libsodium, the cryptography library quak uses,
is loaded. Only `Client.login` loads it. A program that restores a session in a is loaded. `Client.login` loads it, and `Client.fromJSON` does not. A program
new process first runs `await sodium.ready`, with `sodium` imported from that restores a session in a new process first runs `await sodium.ready`, with
`libsodium-wrappers-sumo`, which quak depends on. The exported `beginLogin`, `sodium` imported from `libsodium-wrappers-sumo`, which quak depends on. The
`unwrapAuth`, `decryptCollection`, `decryptFile`, `downloadFile` and exported `beginLogin`, `unwrapAuth`, `decryptCollection`, `decryptFile`,
`downloadThumbnail` need the same. `downloadFile` and `downloadThumbnail` need the same.
`client.logout()` clears the token and zeroes the key buffers in place. After `client.logout()` clears the token and zeroes the key buffers in place. After
it, every other method on that client throws. It does not contact the server, so it, every other method on that client throws. It does not contact the server, so
@@ -1907,7 +1910,9 @@ These apply to people and to coding agents alike.
- Not pinned: - Not pinned:
- bun, which `make build-bin` takes from `nix-shell -p bun`; - bun, which `make build-bin` takes from `nix-shell -p bun`;
- `make` and `git`, which `script/bootstrap` installs from the system - `make` and `git`, which `script/bootstrap` installs from the system
package manager when they are missing, as every `Dockerfile` stage does. package manager when they are missing, as every `Dockerfile` stage does;
- `bash` and `curl`, which `script/bootstrap` installs the same way when it
has to install Node.
- Follow the workflow above: branches off `next`, tests first and committed - Follow the workflow above: branches off `next`, tests first and committed
failing, and `next` merged into `main` by sneak alone. failing, and `next` merged into `main` by sneak alone.
- Before every commit, `make lint` and `make fmt-check` must pass. `make lint` - Before every commit, `make lint` and `make fmt-check` must pass. `make lint`