docs: tokens in a file, no keyring (U0e)

Deploy recipe loses its D-Bus dance, install.sh its macOS skip, README and
site their macOS caveat; packaging, architecture and testing records follow;
U0e As built filled in (D-63).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HS8WZziDnyxunC23MUxrLp
This commit is contained in:
swaitsandClaude Fable 5.1 committed 2026-10-29 13:31:42 -06:00
1 parent d703b68911
commit d8816f9ee4
9 files changed
+72 -53

No files matched your search

+4 -9
View File
@@ -67,11 +67,9 @@ Sheet is frozen as of 2026-10-11. The remaining work, its order and its
hand-off are `plan/served.md` section 8.0; the destination is one hand-off are `plan/served.md` section 8.0; the destination is one
installed app for Mac, Windows and Linux (P11), then the hosted service installed app for Mac, Windows and Linux (P11), then the hosted service
the plan describes and does not build. Today the agent path runs on the plan describes and does not build. Today the agent path runs on
Linux: tokens live in the OS keyring (the Secret Service on Linux, the Linux and macOS: tokens live in a 0600 file under the private data root
login Keychain on macOS, the Credential Manager on Windows; the last two (D-63); the rest of the host is Unix-only, so Windows is WSL2 only until
are written and compiled by the CI matrix, P11a step 0, and not yet its sockets and data root are ported.
proven on a machine of ours); the rest of the host is Unix-only, so
Windows is WSL2 only until its sockets and data root are ported.
See [`plan/STATUS.md`](plan/STATUS.md) for where things stand and See [`plan/STATUS.md`](plan/STATUS.md) for where things stand and
[`plan/roadmap.md`](plan/roadmap.md) for where it's going. [`plan/roadmap.md`](plan/roadmap.md) for where it's going.
@@ -90,10 +88,7 @@ them; nothing needs sudo):
curl -fsSL https://wanderbooks.ai/install.sh | sh -s -- acme curl -fsSL https://wanderbooks.ai/install.sh | sh -s -- acme
``` ```
(Linux today. On macOS the same line installs and sets up, and the (Linux and macOS. On Windows run it inside WSL2.)
viewer runs; connecting an agent uses the login Keychain, new with
P11a step 0 and proven only by the CI matrix so far. On Windows run it
inside WSL2.)
Already installed, three commands: `wanderbooks setup`, `wanderbooks books Already installed, three commands: `wanderbooks setup`, `wanderbooks books
create acme`, `wanderbooks connect claude --book acme` (or `connect codex`). create acme`, `wanderbooks connect claude --book acme` (or `connect codex`).
+8 -23
View File
@@ -123,27 +123,12 @@ sudo -u wanderbooks -H /usr/local/bin/wanderbooks token print \
--root /srv/wanderbooks --book acme --name laptop --root /srv/wanderbooks --book acme --name laptop
``` ```
`token create` keeps the text in the server account's OS keyring and `token create` keeps the text in a file in the server account's private
`token print` shows it once; copy it to that client and nowhere else. data root (mode 0600, readable by that account alone) and `token print`
Revoke it with `token revoke --book acme --name laptop` when a client is shows it once; copy it to that client and nowhere else. It works on a
lost. headless machine as it is. The server checks the token's SHA-256 from the
books. Revoke it with `token revoke --book acme --name laptop` when a
A headless machine has no Secret Service by default, and `token create` client is lost.
refuses to mint a token it cannot keep. Run the two commands inside a
session that has one, for example:
```sh
sudo -u wanderbooks -H dbus-run-session -- sh -c '
gnome-keyring-daemon --unlock --components=secrets >/dev/null
/usr/local/bin/wanderbooks token create --root /srv/wanderbooks --book acme --name laptop
/usr/local/bin/wanderbooks token print --root /srv/wanderbooks --book acme --name laptop
'
```
The keyring lives only for that session, which is enough: the text is
copied to the laptop, and the server checks the token's SHA-256 from the
books, not from the keyring. (Making a token without keeping it anywhere
is not a command; if your machine has a persistent Secret Service, use it.)
## 6. TLS and the door ## 6. TLS and the door
@@ -201,8 +186,8 @@ export WANDERBOOKS_ACME_TOKEN='<the token from step 5>'
``` ```
The plan's local rule is that no client configuration holds a token (the The plan's local rule is that no client configuration holds a token (the
bridge reads the keyring). For a remote machine there is no command yet bridge reads its token file). For a remote machine there is no command yet
that keeps a remote token in the client's keyring, so this recipe puts it that keeps a remote token in the client's own token file, so this recipe puts it
in Claude's configuration (mode 0600) or in Codex's environment. That in Claude's configuration (mode 0600) or in Codex's environment. That
gap is recorded in `plan/served.md` 8.2 P9a's As-built block. gap is recorded in `plan/served.md` 8.2 P9a's As-built block.
+1 -1
View File
@@ -26,7 +26,7 @@ Type=simple
User=wanderbooks User=wanderbooks
Group=wanderbooks Group=wanderbooks
DynamicUser=no DynamicUser=no
# The account's own home (the keyring, when one runs; the recorded root). # The account's own home (the recorded root).
StateDirectory=wanderbooks StateDirectory=wanderbooks
Environment=HOME=/var/lib/wanderbooks Environment=HOME=/var/lib/wanderbooks
WorkingDirectory=/ WorkingDirectory=/
+1 -1
View File
@@ -36,7 +36,7 @@ the specification in `bookspec/` (which never depends on the engine).
| `ledger-import` | formats: QuickBooks CSV in; IIF, XLSX, Typst PDFs (statements, the invoice, labels) out | `ledger-core`, `ledger-reader` | | `ledger-import` | formats: QuickBooks CSV in; IIF, XLSX, Typst PDFs (statements, the invoice, labels) out | `ledger-core`, `ledger-reader` |
| `ledger-package` | the shared read-only view over `Store` or `Snapshot` (`PackageBooks`): the CPA package, the 1099 worksheet, the invoice document, Needs attention | `ledger-core`, `ledger-store`, `ledger-config`, `ledger-import`, `ledger-vault` | | `ledger-package` | the shared read-only view over `Store` or `Snapshot` (`PackageBooks`): the CPA package, the 1099 worksheet, the invoice document, Needs attention | `ledger-core`, `ledger-store`, `ledger-config`, `ledger-import`, `ledger-vault` |
| `ledger-mcp` | the books server: the MCP tools (122), resources, the handbook, scopes, the door and the access log, backups' scheduling | all of the above | | `ledger-mcp` | the books server: the MCP tools (122), resources, the handbook, scopes, the door and the access log, backups' scheduling | all of the above |
| `ledger-host` | the host and its leases, the agent bridge (`wanderbooks mcp`), tokens in the OS keyring (Secret Service, macOS Keychain, Windows Credential Manager), the owner's commands, uploads and downloads | `ledger-store`, `ledger-mcp`, `ledger-config` | | `ledger-host` | the host and its leases, the agent bridge (`wanderbooks mcp`), tokens in 0600 files under the data root (D-63), the owner's commands, uploads and downloads | `ledger-store`, `ledger-mcp`, `ledger-config` |
| `ledger-gui` | the viewer: read-only, live, egui; the Books menu hands work to the server | `ledger-core`, `ledger-store`, `ledger-config`, `ledger-import`, `ledger-package`, `ledger-vault`, `ledger-host` | | `ledger-gui` | the viewer: read-only, live, egui; the Books menu hands work to the server | `ledger-core`, `ledger-store`, `ledger-config`, `ledger-import`, `ledger-package`, `ledger-vault`, `ledger-host` |
| `ledger-cli` | `wanderbooks`, the one executable: every mode (`serve`, `server`, `host`, `mcp`, `gui`, `setup`, `books`, `token`, `connect`, `doctor`, …) and the over-the-wire test suites | everything | | `ledger-cli` | `wanderbooks`, the one executable: every mode (`serve`, `server`, `host`, `mcp`, `gui`, `setup`, `books`, `token`, `connect`, `doctor`, …) and the over-the-wire test suites | everything |
| `bookspec/checker`, `bookspec/runner` | the example schema and parser, the rule catalogue, the `Engine` trait; the per-rule runner | nothing of ours (the adapter in `ledger-core`'s tests depends on them) | | `bookspec/checker`, `bookspec/runner` | the example schema and parser, the rule catalogue, the `Engine` trait; the per-rule runner | nothing of ours (the adapter in `ledger-core`'s tests depends on them) |
+4 -5
View File
@@ -21,11 +21,10 @@ table is filled by the person who runs it.
## What step 0 found about the platforms (read before the macOS and Windows rows) ## What step 0 found about the platforms (read before the macOS and Windows rows)
The agent path was Linux-only because of the keyring. Step 0 adds the The agent path was Linux-only because of the keyring; D-63 removed the
macOS login Keychain and the Windows Credential Manager behind keyring on every OS (tokens are 0600 files under the data root, unit U0e),
`ledger-host/src/tokens.rs::Keeper`; CI (`portable` job) is the evidence. so nothing about tokens is OS-specific now. The Windows host is a port,
Beyond the keyring, the Windows host is a port, not a `cfg` gap. Today not a `cfg` gap. Today Unix-only, found by grep (2026-10-29):
Unix-only, found by grep (2026-10-29):
- `ledger-host`: `bridge`, `host`, `start`, `opener` and others are - `ledger-host`: `bridge`, `host`, `start`, `opener` and others are
`#[cfg(unix)]` modules; `host.rs`/`start.rs` use `setsid`, `umask`, `#[cfg(unix)]` modules; `host.rs`/`start.rs` use `setsid`, `umask`,
+49 -4
View File
@@ -494,7 +494,9 @@ recorded once with refusals, a damaged token log refusing every agent),
`open_while_held.rs` (the bridge picking up a token made mid-session), `open_while_held.rs` (the bridge picking up a token made mid-session),
`setup.rs` (the passphrase prompts, at a real terminal). `setup.rs` (the passphrase prompts, at a real terminal).
**One keyring item per token (2026-10-22).** On the live books a **One keyring item per token (2026-10-22; superseded by U0e, D-63: there
is no keyring, so no second item and no locked or unreachable one, and
the tests named below were replaced, see "U0e" at the end).** On the live books a
reconnected bridge got no token from a keyring that couldn't be read, reconnected bridge got no token from a keyring that couldn't be read,
was told to "make a token", and after `token create --replace` ran on a was told to "make a token", and after `token create --replace` ran on a
token the keyring no longer gave while `download` read a revoked one. token the keyring no longer gave while `download` read a revoked one.
@@ -3086,14 +3088,14 @@ loopback door (`--http`) for Caddy or a tunnel.
the repository's `p2-served` fixture into a desktop root at this version, the repository's `p2-served` fixture into a desktop root at this version,
copies the book as the transport (D-31), adopts it into copies the book as the transport (D-31), adopts it into
`/srv/wanderbooks` behind `--isolation enforced --http`, mints a token `/srv/wanderbooks` behind `--isolation enforced --http`, mints a token
with the `file_keyring` test knob (a runner has no Secret Service), and with `token create` as on a real server (the knob that once kept it in a
runs the test as the runner. `deploy/README.md` is the same recipe by file is gone, U0e: D-63), and runs the test as the runner. `deploy/README.md` is the same recipe by
hand; the job does not install the systemd unit (it drives `serve` with hand; the job does not install the systemd unit (it drives `serve` with
the same flags), so the unit's lines are checked by the test's file read the same flags), so the unit's lines are checked by the test's file read
and by review, not executed as a unit. and by review, not executed as a unit.
- **Not covered:** the recipe walked on a clean VM by someone other than - **Not covered:** the recipe walked on a clean VM by someone other than
its author (Done when 2), a real TLS tunnel (Tailscale Funnel or Caddy) its author (Done when 2), a real TLS tunnel (Tailscale Funnel or Caddy)
and a real Secret Service on a headless server. Those are the owner's and a headless server of the owner's own. Those are the owner's
and the EM's checks; the As-built block says so. and the EM's checks; the As-built block says so.
- **Docs:** `docs/architecture.md` "Self-host", `docs/mcp-interface.md` - **Docs:** `docs/architecture.md` "Self-host", `docs/mcp-interface.md`
(`books.start`'s two `isolation` values), `docs/parity.md` (no semantic (`books.start`'s two `isolation` values), `docs/parity.md` (no semantic
@@ -3533,6 +3535,11 @@ arms, the 8 KiB cut, the 64 KiB hand-over threshold and `page - 1`.
## P11a step 0: keyring stores for macOS and Windows (2026-10-29) ## P11a step 0: keyring stores for macOS and Windows (2026-10-29)
*Superseded the same day by D-63 (unit U0e, below): the `file_keyring`
knob, the `Items` trait, the OS stores, `os_keyring_keeps_and_forgets`
and the `portable` job's step that ran it are gone; the tests named here
no longer exist.*
No new test body: the keeper's laws are tested once, over the `Items` No new test body: the keeper's laws are tested once, over the `Items`
trait, through the `file_keyring` knob (`the_test_keyring_keeps_and_forgets`, trait, through the `file_keyring` knob (`the_test_keyring_keeps_and_forgets`,
`two_items_for_one_token_are_said_never_one_of_them_given`, `two_items_for_one_token_are_said_never_one_of_them_given`,
@@ -3549,3 +3556,41 @@ Secret Service run needs a desktop): the CI run is the evidence.
**For P12 (mutation targets, none run, D-33):** `tokens.rs`'s `os` modules **For P12 (mutation targets, none run, D-33):** `tokens.rs`'s `os` modules
are not reachable by mutation testing (they need the real OS store); the are not reachable by mutation testing (they need the real OS store); the
`Items` logic they share is, through the file store. `Items` logic they share is, through the file store.
## U0e: tokens in a file, the OS keyring removed (2026-10-29, D-63)
An agent's token is a 0600 file under the root's `run/tokens/`, on every
OS, written atomically (a temporary file made 0600 before any byte is
written, synced, then renamed over the old one). Tests, all in
`crates/ledger-host/src/tokens.rs` unless noted:
`an_account_names_the_root_by_hash_and_the_book_and_token` (kept);
`the_token_file_keeps_and_forgets` (was `the_test_keyring_keeps_and_forgets`,
no knob); `a_token_file_is_owner_only` (new: mode 0600, folder 0700, a
second create for the same name replaces the first, one file and no
temporary left); `an_unreadable_token_file_is_an_error_never_no_token`
(new: a folder in the file's place; replaces
`a_locked_or_unreachable_keyring_is_an_error_never_no_token`). Deleted
because the case cannot exist with one file per token:
`two_items_for_one_token_are_said_never_one_of_them_given`,
`a_token_kept_is_read_back_or_refused` (the read-back is still done by
`commands.rs::make_token`, and the CLI test below covers a token the file
cannot keep) and the ignored `os_keyring_keeps_and_forgets`.
`bridge.rs`: `a_token_file_that_cant_be_read_is_said_never_make_a_token`
and `each_new_session_reads_the_token_file_again` (renamed from the
keyring versions; the unreadable case is a folder in the file's place).
`crates/ledger-cli/tests/open_while_held.rs`:
`token_check_says_where_the_token_file_stands_and_an_unreadable_one_is_said`
(was `token_check_one_item_per_token_and_a_keyring_that_cant_be_read`; the
second-item parts are gone, the file's mode is asserted) and
`connect_makes_anew_a_token_whose_file_is_gone_and_says_when_an_agent_refuses`
(renamed). `knobs.rs`: `file_keyring` is now refused like any unknown knob.
`two-user.yml` mints its tokens with no knob (provable only on GitHub).
**For P12 (mutation targets, none run, D-33):** `tokens.rs`'s `Keeper::set`
(the temporary file's mode and the rename: a mutant that writes the final
file directly or with a wider mode must die on `a_token_file_is_owner_only`),
`Keeper::get`'s `NotFound` arm (a mutant that turns any error into "no
token" must die on `an_unreadable_token_file_is_an_error_never_no_token`),
`Keeper::delete`'s `NotFound` arm, and `Account::key`'s three parts
(`an_account_names_the_root_by_hash_and_the_book_and_token`);
`bridge.rs::token_file_refusal`'s `holds_token` guard.
+3 -8
View File
@@ -3,11 +3,8 @@
# #
# curl -fsSL https://wanderbooks.ai/install.sh | sh -s -- acme # curl -fsSL https://wanderbooks.ai/install.sh | sh -s -- acme
# #
# Linux, and macOS with one gap: agents' tokens live in the OS keyring and # Linux and macOS. On Windows, run it inside WSL2 (Ubuntu): the viewer
# only the Linux store exists until the macOS app (plan P11a), so on macOS # opens as a normal window on Windows 11.
# this installs, sets up and starts the books but cannot connect an agent
# yet (it says so instead of failing). On Windows, run it inside WSL2
# (Ubuntu): the viewer opens as a normal window on Windows 11.
# #
# Installs Rust if needed, then the one `wanderbooks` program (into # Installs Rust if needed, then the one `wanderbooks` program (into
# ~/.cargo/bin), then makes the books' private folder (`wanderbooks setup`). # ~/.cargo/bin), then makes the books' private folder (`wanderbooks setup`).
@@ -79,9 +76,7 @@ if [ $# -gt 0 ]; then
if ! "$wb" books list | cut -d' ' -f1 | grep -qx "$1"; then if ! "$wb" books list | cut -d' ' -f1 | grep -qx "$1"; then
"$wb" books create "$1" "$wb" books create "$1"
fi fi
if [ "$os" = macos ]; then if command -v claude >/dev/null 2>&1; then
say "Done. Your books \"$1\" exist and 'wanderbooks gui $1' shows them. Connecting Claude Code waits for the macOS keyring store (the app, plan P11a); until then agents connect from Linux."
elif command -v claude >/dev/null 2>&1; then
"$wb" connect claude --book "$1" "$wb" connect claude --book "$1"
say "Done. Start Claude Code and say hello: your books \"$1\" are ready." say "Done. Start Claude Code and say hello: your books \"$1\" are ready."
else else
+1 -1
View File
@@ -1316,7 +1316,7 @@ Each unit follows the 8.0.4 template. "A-n" is an owner answer numbered in 8.3.1
5. **Files:** owns `crates/ledger-host/src/tokens.rs`; touches `knobs.rs`, `commands.rs`, `bridge.rs`, `crates/ledger-host/Cargo.toml`, `Cargo.lock`, the two CLI tests, `.github/workflows/two-user.yml`, `deploy/README.md`, `install.sh`, `README.md`, `site/index.html`, `docs/packaging.md`, `docs/testing.md`, `docs/architecture.md`. 5. **Files:** owns `crates/ledger-host/src/tokens.rs`; touches `knobs.rs`, `commands.rs`, `bridge.rs`, `crates/ledger-host/Cargo.toml`, `Cargo.lock`, the two CLI tests, `.github/workflows/two-user.yml`, `deploy/README.md`, `install.sh`, `README.md`, `site/index.html`, `docs/packaging.md`, `docs/testing.md`, `docs/architecture.md`.
6. **Tests and proofs:** the existing keeper tests in `tokens.rs` run against the file store (they already do, through the knob: they lose the knob); `a_token_file_is_owner_only` (new, `tokens.rs`: mode 0600 on Unix, created atomically, a second `create` for the same name replaces the first); the bridge and `open_while_held` tests unchanged in what they assert. No kernel, Kani or Lean change. 6. **Tests and proofs:** the existing keeper tests in `tokens.rs` run against the file store (they already do, through the knob: they lose the knob); `a_token_file_is_owner_only` (new, `tokens.rs`: mode 0600 on Unix, created atomically, a second `create` for the same name replaces the first); the bridge and `open_while_held` tests unchanged in what they assert. No kernel, Kani or Lean change.
7. **Done when:** (1) `grep -ri "keyring\|keychain\|secret service\|credential manager" crates/ deploy/ install.sh README.md site/ .github/` finds only the history notes this unit leaves (one sentence in `tokens.rs`'s module doc saying what was removed and why, D-63); (2) `cargo tree -p ledger-host -e normal | grep -i keyring` prints nothing; (3) the named tests pass; (4) `two-user.yml` runs with no knob (the EM sees it green on GitHub); (5) `cargo build --workspace --all-targets` green. 7. **Done when:** (1) `grep -ri "keyring\|keychain\|secret service\|credential manager" crates/ deploy/ install.sh README.md site/ .github/` finds only the history notes this unit leaves (one sentence in `tokens.rs`'s module doc saying what was removed and why, D-63); (2) `cargo tree -p ledger-host -e normal | grep -i keyring` prints nothing; (3) the named tests pass; (4) `two-user.yml` runs with no knob (the EM sees it green on GitHub); (5) `cargo build --workspace --all-targets` green.
8. **As built:** none yet. 8. **As built:** (2026-10-29, branch `U0e`, EM-approved Option A after a stop) Build steps 1-3 done, with these departures. (1) The unit said the keeper tests and the bridge and `open_while_held` tests were "unchanged in what they assert"; they were not, because several asserted keyring-only states (a second item in another collection, a locked or unreachable keyring), which one file per token cannot have. `tokens.rs` is now direct file get/set/delete: the `Keeper` trait, `Items`, `get_one`/`set_one`/`delete_all`, `KeyringError::Duplicates`, `TEST_LOCKED`/`TEST_UNREACHABLE`/`TEST_DEFAULT` and the `login` collection are gone; `TokenFileError` is one message. The file is `run/tokens/<sha256 of the account key>` (the knob's `run/test-keyring/default/` was renamed), the folder 0700, the file made 0600 as a temporary beside it, synced and renamed. (2) Tests: `an_unreadable_token_file_is_an_error_never_no_token` (a folder in the file's place) replaces the locked/unreachable test; `a_token_file_is_owner_only` is new; `two_items_for_one_token_are_said_never_one_of_them_given` and `a_token_kept_is_read_back_or_refused` are deleted (case cannot exist; `make_token` still reads back); `os_keyring_keeps_and_forgets` is deleted; bridge tests renamed to `a_token_file_that_cant_be_read_is_said_never_make_a_token` and `each_new_session_reads_the_token_file_again`; in `open_while_held.rs`, `token_check_one_item_per_token_and_a_keyring_that_cant_be_read` became `token_check_says_where_the_token_file_stands_and_an_unreadable_one_is_said` (second-item and unreachable-marker parts deleted, 0600 mode asserted, unreadable = a folder in the file's place) and `connect_makes_anew_a_token_the_keyring_lost_and_says_when_an_agent_refuses` became `connect_makes_anew_a_token_whose_file_is_gone_and_says_when_an_agent_refuses`. (3) User-facing strings say "the token file in the data root" (no path), in `commands.rs`, `bridge.rs`, `ledger-gui/src/owner.rs`, `ledger-mcp/src/local.rs`, `admin.rs`. (4) Also changed beyond the unit's list: `.github/workflows/check.yml` (the `os_keyring` step of the portable job deleted), `deploy/wanderbooks-server.service` (a comment). (5) `start.rs` and the `DBUS_SESSION_BUS_ADDRESS` scrub and its tests stay: they keep a bridge session from inheriting a D-Bus session for any reason, not only the keyring. (6) Existing Linux books whose tokens were in the Secret Service lose them (nothing migrates): `wanderbooks connect` or `token create --replace` makes them anew. (7) `two-user.yml` has no knob; only GitHub can prove it. Checks: fmt, clippy (`ledger-host`, `ledger-cli`), `cargo test -p ledger-host`, `open_while_held`, `format_upgrade`, workspace build.
#### P4b S4: `documents_v2` #### P4b S4: `documents_v2`
+1 -1
View File
@@ -292,7 +292,7 @@ footer { padding: 72px 0 48px; color: var(--faint); font-size: 14px; display: fl
<p class="sub">The one line at the top does everything: installs Rust if you don't have it, builds wanderbooks, makes a private folder for your books, starts them, and connects Claude Code. Nothing needs administrator rights, and nothing keeps running in the background once you're done.</p> <p class="sub">The one line at the top does everything: installs Rust if you don't have it, builds wanderbooks, makes a private folder for your books, starts them, and connects Claude Code. Nothing needs administrator rights, and nothing keeps running in the background once you're done.</p>
<div class="grid"> <div class="grid">
<div class="card"><h3>Linux</h3><p>Needs a C compiler and git (<code class="inline">build-essential</code> on Debian and Ubuntu). The installer tells you if anything's missing.</p></div> <div class="card"><h3>Linux</h3><p>Needs a C compiler and git (<code class="inline">build-essential</code> on Debian and Ubuntu). The installer tells you if anything's missing.</p></div>
<div class="card"><h3>macOS</h3><p>Needs Apple's command line tools: <code class="inline">xcode-select --install</code>. The install, your books and the viewer work today; connecting Claude Code or Codex waits for the macOS keyring store, which comes with the app (soon).</p></div> <div class="card"><h3>macOS</h3><p>Needs Apple's command line tools: <code class="inline">xcode-select --install</code>. The install, your books, the viewer and connecting Claude Code or Codex work today.</p></div>
<div class="card"><h3>Windows</h3><p>Run it inside WSL2 (<code class="inline">wsl --install</code>, then Ubuntu). On Windows 11 the viewer opens as a normal window. Native Windows is on the way.</p></div> <div class="card"><h3>Windows</h3><p>Run it inside WSL2 (<code class="inline">wsl --install</code>, then Ubuntu). On Windows 11 the viewer opens as a normal window. Native Windows is on the way.</p></div>
</div> </div>
<p class="note" style="margin-top:24px">Already installed? <code class="inline">wanderbooks books create another</code>, then <code class="inline">wanderbooks connect claude --book another</code>. <code class="inline">wanderbooks gui</code> opens the viewer; its Books menu backs up, restores, starts new books and closes them.</p> <p class="note" style="margin-top:24px">Already installed? <code class="inline">wanderbooks books create another</code>, then <code class="inline">wanderbooks connect claude --book another</code>. <code class="inline">wanderbooks gui</code> opens the viewer; its Books menu backs up, restores, starts new books and closes them.</p>