From d8a4b0291e4789c3996c78fa95025c464960ea05 Mon Sep 17 00:00:00 2001 From: sneak Date: Wed, 13 May 2026 18:02:55 -0700 Subject: [PATCH] Rename quack to quak German for 'quack', matching the Ente (German for 'duck') naming. All references updated: package name, CLI binary, X-Client-Package header, test descriptions, temp dir prefixes, README, Makefile docker tag. --- Makefile | 2 +- README.md | 60 +++++++++++++++++----------------- package.json | 8 ++--- src/api/client.ts | 2 +- src/auth/types.ts | 2 +- src/client.ts | 2 +- src/crypto/kdf.ts | 2 +- test/api/client.test.ts | 6 ++-- test/client/usage.test.ts | 10 +++--- test/crypto/box.test.ts | 4 +-- test/crypto/encoding.test.ts | 2 +- test/crypto/init.test.ts | 2 +- test/download/download.test.ts | 4 +-- test/integration/live-login.ts | 2 +- test/model/decrypt.test.ts | 2 +- test/smoke.test.ts | 2 +- 16 files changed, 56 insertions(+), 56 deletions(-) diff --git a/Makefile b/Makefile index b7c0900..e053861 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ clean: @rm -rf dist coverage .vitest-cache *.tsbuildinfo docker: - docker build -t quack . + docker build -t quak . hooks: @printf '#!/bin/sh\nset -e\nmake lint\nmake fmt-check\n' > .git/hooks/pre-commit diff --git a/README.md b/README.md index 49cc839..efb2f08 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# quack +# quak -quack is a WTFPL-licensed TypeScript client library and CLI by +quak is a WTFPL-licensed TypeScript client library and CLI by [@sneak](https://sneak.berlin) for the [Ente](https://ente.io) end-to-end encrypted photo hosting service. It logs in, enumerates collections and files, and downloads individual images while decrypting them on the way to disk. @@ -8,29 +8,29 @@ and downloads individual images while decrypting them on the way to disk. ## Getting Started ```bash -git clone https://git.eeqj.de/sneak/quack.git -cd quack +git clone https://git.eeqj.de/sneak/quak.git +cd quak yarn install yarn build # Log in (prompts for email, password, and OTP/TOTP if required). -# Stores an encrypted session under $XDG_CONFIG_HOME/quack/. -yarn quack login +# Stores an encrypted session under $XDG_CONFIG_HOME/quak/. +yarn quak login # List the user's collections (albums). -yarn quack collections +yarn quak collections # List files in a collection. -yarn quack files --collection 12345 +yarn quak files --collection 12345 # Download and decrypt a single file to ./out/. -yarn quack get 67890 --out ./out/ +yarn quak get 67890 --out ./out/ ``` For library use: ```ts -import { Client } from "quack"; +import { Client } from "quak"; const client = await Client.fromSavedSession(); for (const c of await client.listCollections()) { @@ -49,7 +49,7 @@ Flutter app fails to sync reliably. The web app is heavy. The desktop app is the web app inside a slow Electron wrapper. The Go CLI is the closest thing to a usable tool, but it is awkward to integrate from anything that is not a shell. -quack is the first step in fixing that. This repo ships a small, correct, +quak is the first step in fixing that. This repo ships a small, correct, well-tested implementation of Ente's cryptographic protocol and its read-only API surface, plus a CLI that proves the library is enough to do real work without a UI. @@ -67,7 +67,7 @@ requires the protocol layer to be correct first. ## Development workflow -All work on quack is test-driven. No exceptions. +All work on quak is test-driven. No exceptions. 1. Every change starts on a feature branch off `main`. 2. The first commit on the branch is the test suite for what is being added or @@ -79,7 +79,7 @@ All work on quack is test-driven. No exceptions. `main` is always green. The Dockerfile runs `make check`, so a red branch cannot pass CI. 5. Tests are the canonical API documentation for this library. Every test file - is commented thoroughly enough that a reader who has never seen quack can + is commented thoroughly enough that a reader who has never seen quak can learn how to use it from the tests alone. Comments explain why a behavior matters, not just what the assertion checks. 6. Test fixtures (cryptographic vectors, recorded HTTP responses, sample files) @@ -98,13 +98,13 @@ All work on quack is test-driven. No exceptions. ## Design -quack is a TypeScript library with a thin CLI wrapper. The library does the -work; the CLI is for humans. +quak is a TypeScript library with a thin CLI wrapper. The library does the work; +the CLI is for humans. ### Layout ``` -quack/ +quak/ src/ crypto/ libsodium primitives (boxes, secretstreams, KDF, SRP) api/ HTTP client + typed endpoint wrappers @@ -114,7 +114,7 @@ quack/ client.ts high-level Client class assembled from the above index.ts public library exports bin/ - quack.ts CLI entrypoint (commander.js) + quak.ts CLI entrypoint (commander.js) test/ unit + integration tests (vitest) Makefile Dockerfile @@ -169,7 +169,7 @@ A custom API endpoint is configurable for self-hosted servers via the Required request headers on every authenticated call: - `X-Auth-Token`: the decrypted auth token from login. -- `X-Client-Package`: identifies the client. quack uses `berlin.sneak.quack`. +- `X-Client-Package`: identifies the client. quak uses `berlin.sneak.quak`. Endpoints used: @@ -187,8 +187,8 @@ Endpoints used: ### Session persistence -After login, quack writes an encrypted session blob to -`$XDG_CONFIG_HOME/quack/session.json` (default `~/.config/quack/session.json`) +After login, quak writes an encrypted session blob to +`$XDG_CONFIG_HOME/quak/session.json` (default `~/.config/quak/session.json`) containing the auth token, the user's master key, the user's secret key, and the user's email. The session file is itself encrypted with a key derived from a per-machine random value stored in the OS keychain when available, falling back @@ -197,14 +197,14 @@ secret key are never written to disk in cleartext. ### CLI surface -- `quack login`: interactive login, writes session. -- `quack logout`: deletes the session. -- `quack whoami`: prints the logged-in email. -- `quack collections`: list collections (id, name, type, file count). -- `quack files --collection `: list files in a collection (id, name, type, +- `quak login`: interactive login, writes session. +- `quak logout`: deletes the session. +- `quak whoami`: prints the logged-in email. +- `quak collections`: list collections (id, name, type, file count). +- `quak files --collection `: list files in a collection (id, name, type, creation time, size). -- `quack get --out `: download and decrypt a file. -- `quack get-thumb --out `: download and decrypt a thumbnail. +- `quak get --out `: download and decrypt a file. +- `quak get-thumb --out `: download and decrypt a thumbnail. All commands accept `--json` for machine-readable output. @@ -438,7 +438,7 @@ export interface ApiClientOptions { thumbsOrigin?: string; // default https://thumbnails.ente.io authToken?: string; fetch?: typeof fetch; // injectable for tests - userAgent?: string; // default "quack/" + userAgent?: string; // default "quak/" } export class ApiError extends Error { @@ -481,8 +481,8 @@ export interface Session { } export interface SessionStoreOptions { - path?: string; // default $XDG_CONFIG_HOME/quack/session.json - keychainService?: string; // default "berlin.sneak.quack" + path?: string; // default $XDG_CONFIG_HOME/quak/session.json + keychainService?: string; // default "berlin.sneak.quak" } export class SessionStore { diff --git a/package.json b/package.json index 21e4328..1e84485 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,19 @@ { - "name": "quack", + "name": "quak", "version": "0.0.0", "description": "TypeScript client library and CLI for the Ente end-to-end encrypted photo service", "license": "WTFPL", "author": "@sneak ", - "homepage": "https://git.eeqj.de/sneak/quack", + "homepage": "https://git.eeqj.de/sneak/quak", "repository": { "type": "git", - "url": "https://git.eeqj.de/sneak/quack.git" + "url": "https://git.eeqj.de/sneak/quak.git" }, "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", "bin": { - "quack": "./dist/bin/quack.js" + "quak": "./dist/bin/quak.js" }, "files": [ "dist/", diff --git a/src/api/client.ts b/src/api/client.ts index fe4ea52..fc5e877 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -1,7 +1,7 @@ const DEFAULT_API_ORIGIN = "https://api.ente.io"; const DEFAULT_FILES_ORIGIN = "https://files.ente.io"; const DEFAULT_THUMBS_ORIGIN = "https://thumbnails.ente.io"; -const CLIENT_PACKAGE = "berlin.sneak.quack"; +const CLIENT_PACKAGE = "berlin.sneak.quak"; export interface ApiClientOptions { apiOrigin?: string; diff --git a/src/auth/types.ts b/src/auth/types.ts index 7190c9e..e6a23f4 100644 --- a/src/auth/types.ts +++ b/src/auth/types.ts @@ -3,7 +3,7 @@ export type Base64 = string; // The blob the server returns alongside the auth token after a successful -// login. Together with the user's password, this is everything quack needs +// login. Together with the user's password, this is everything quak needs // to derive the master key, secret key, and auth token. export interface KeyAttributes { kekSalt: Base64; diff --git a/src/client.ts b/src/client.ts index 9631761..8223f87 100644 --- a/src/client.ts +++ b/src/client.ts @@ -86,7 +86,7 @@ export class Client { const code = await opts.emailOTP(); response = await submitEmailOTP(api, opts.email, code); } else if (challenge.kind === "passkey") { - throw new Error("Passkey authentication is not supported by quack"); + throw new Error("Passkey authentication is not supported by quak"); } else { throw new Error(`Unknown login challenge kind`); } diff --git a/src/crypto/kdf.ts b/src/crypto/kdf.ts index 7ac385f..69f9d84 100644 --- a/src/crypto/kdf.ts +++ b/src/crypto/kdf.ts @@ -1,7 +1,7 @@ import sodium from "libsodium-wrappers-sumo"; // Argon2id over (password, salt, opsLimit, memLimit) producing a 32-byte -// Key Encryption Key. Parameters come from the server; quack passes them +// Key Encryption Key. Parameters come from the server; quak passes them // straight through. memLimit is in bytes, opsLimit is the iteration count. export const deriveKEK = async ( password: string, diff --git a/test/api/client.test.ts b/test/api/client.test.ts index a56b236..2d1522c 100644 --- a/test/api/client.test.ts +++ b/test/api/client.test.ts @@ -1,7 +1,7 @@ /** * Tests for `ApiClient`. * - * `ApiClient` is the HTTP layer that every other module in quack calls to + * `ApiClient` is the HTTP layer that every other module in quak calls to * reach the Ente server. It handles: * * - Base URL resolution. Production uses `https://api.ente.io` for the @@ -14,7 +14,7 @@ * dedicated CDN hosts. * * - Required headers. Every request carries `X-Client-Package` - * (`berlin.sneak.quack`). Authenticated requests also carry + * (`berlin.sneak.quak`). Authenticated requests also carry * `X-Auth-Token` with the token recovered by `unwrapAuth`. * * - JSON serialization. `getJSON` and `postJSON` handle Accept / @@ -121,7 +121,7 @@ describe("ApiClient defaults", () => { await client.getJSON("/ping"); const headers = new Headers(calls[0]!.init?.headers as HeadersInit); - expect(headers.get("X-Client-Package")).toBe("berlin.sneak.quack"); + expect(headers.get("X-Client-Package")).toBe("berlin.sneak.quak"); }); }); diff --git a/test/client/usage.test.ts b/test/client/usage.test.ts index 77f5603..701bb6e 100644 --- a/test/client/usage.test.ts +++ b/test/client/usage.test.ts @@ -1,10 +1,10 @@ /** - * # Using the quack Client + * # Using the quak Client * * This test file is a tutorial. It walks through every operation the * library supports, in the order you would use them in a real program. * Each `it()` block is a self-contained example with commentary - * explaining what is happening and why. If you are reading the quack + * explaining what is happening and why. If you are reading the quak * source for the first time, start here. * * The tests run against a mock Ente server built from the same SRP and @@ -30,7 +30,7 @@ * All you need is the `Client` class: * * ```ts - * import { Client } from "quack"; + * import { Client } from "quak"; * ``` * * The Client wraps every lower-level module (crypto, auth, api, model, @@ -332,7 +332,7 @@ beforeAll(async () => { await init(); await sodium.ready; server = await buildServer(); - testDir = mkdtempSync(join(tmpdir(), "quack-usage-test-")); + testDir = mkdtempSync(join(tmpdir(), "quak-usage-test-")); }); afterAll(() => { @@ -344,7 +344,7 @@ afterAll(() => { // The tutorial // --------------------------------------------------------------------------- -describe("quack Client usage guide", () => { +describe("quak Client usage guide", () => { /** * ## 1. Logging in * diff --git a/test/crypto/box.test.ts b/test/crypto/box.test.ts index 39bfe59..4ed2523 100644 --- a/test/crypto/box.test.ts +++ b/test/crypto/box.test.ts @@ -1,7 +1,7 @@ /** * Tests for `crypto.decryptBox` and `crypto.decryptSealed`. * - * These cover the two asymmetric-and-symmetric "box" primitives quack uses + * These cover the two asymmetric-and-symmetric "box" primitives quak uses * to unwrap key material from Ente: * * - `decryptBox`: secretbox decryption. Used everywhere a small payload @@ -18,7 +18,7 @@ * * authToken = decryptSealed(encryptedToken, publicKey, secretKey) * - * Encryption is server-side; quack only ever decrypts. + * Encryption is server-side; quak only ever decrypts. */ import sodium from "libsodium-wrappers-sumo"; diff --git a/test/crypto/encoding.test.ts b/test/crypto/encoding.test.ts index 8ce4f71..85afee3 100644 --- a/test/crypto/encoding.test.ts +++ b/test/crypto/encoding.test.ts @@ -5,7 +5,7 @@ * Ente delivers most binary fields as standard base64 strings (with `+`, * `/`, and `=` padding). A few fields, notably the auth token returned by * the login flow, are URL-safe base64 (with `-` and `_` instead of `+` and - * `/`, and stripped padding). quack must accept both forms on input and + * `/`, and stripped padding). quak must accept both forms on input and * produce the right form on output. * * These tests pin the contract: diff --git a/test/crypto/init.test.ts b/test/crypto/init.test.ts index 46f85d4..6f1757f 100644 --- a/test/crypto/init.test.ts +++ b/test/crypto/init.test.ts @@ -3,7 +3,7 @@ * * libsodium ships as WebAssembly. The bindings (`libsodium-wrappers-sumo`) * load asynchronously: the runtime must `await sodium.ready` once before any - * crypto call is safe. quack hides that detail behind a single + * crypto call is safe. quak hides that detail behind a single * `init()` function. * * Every other test in `test/crypto/**` calls `init()` in `beforeAll`. New diff --git a/test/download/download.test.ts b/test/download/download.test.ts index fff12ce..1bcd625 100644 --- a/test/download/download.test.ts +++ b/test/download/download.test.ts @@ -36,7 +36,7 @@ let testDir: string; beforeAll(async () => { await init(); await sodium.ready; - testDir = mkdtempSync(join(tmpdir(), "quack-test-")); + testDir = mkdtempSync(join(tmpdir(), "quak-test-")); }); afterAll(() => { @@ -97,7 +97,7 @@ const mockFetchForBody = (body: Uint8Array) => { describe("downloadFile", () => { it("downloads, decrypts, and writes a single-chunk file", async () => { const plaintext = new TextEncoder().encode( - "Hello from quack! This is a test photo payload.", + "Hello from quak! This is a test photo payload.", ); const key = sodium.crypto_secretstream_xchacha20poly1305_keygen(); const { header, ciphertext } = encryptFileBody(plaintext, key); diff --git a/test/integration/live-login.ts b/test/integration/live-login.ts index 3096f39..2824583 100644 --- a/test/integration/live-login.ts +++ b/test/integration/live-login.ts @@ -87,7 +87,7 @@ const main = async () => { const { mkdtempSync, statSync } = await import("node:fs"); const { join } = await import("node:path"); const { tmpdir } = await import("node:os"); - const outDir = mkdtempSync(join(tmpdir(), "quack-live-test-")); + const outDir = mkdtempSync(join(tmpdir(), "quak-live-test-")); const outPath = `${outDir}/${first.metadata.title}`; console.log(`\n Downloading "${first.metadata.title}"...`); diff --git a/test/model/decrypt.test.ts b/test/model/decrypt.test.ts index 1686b0c..2ed657d 100644 --- a/test/model/decrypt.test.ts +++ b/test/model/decrypt.test.ts @@ -3,7 +3,7 @@ * * These two functions turn the raw encrypted JSON blobs the Ente server * returns into the decrypted Collection and EnteFile objects that the - * rest of quack works with. + * rest of quak works with. * * ## Collection decryption * diff --git a/test/smoke.test.ts b/test/smoke.test.ts index b94c73a..77fe023 100644 --- a/test/smoke.test.ts +++ b/test/smoke.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { VERSION } from "../src/index.js"; -describe("quack", () => { +describe("quak", () => { it("exports a version string", () => { expect(typeof VERSION).toBe("string"); expect(VERSION.length).toBeGreaterThan(0);