# Update a Lunelet shelf

Use this after the reader asks to change their books. Read the project's LUNELET.md first to identify its real source and publishing workflow. Do not create a second shelf when one already exists. Respect project instructions and authorisation; do not deploy, delete books or change reading sources without the user's request covering that action.

Distribution mirror: https://lunelet.pages.dev/ serves the same guides, component, manifest and tool at the same paths if lunelet.com is unavailable. Do not change the reader's own feed/API to the mirror.

## What to establish

1. Find the actual feed: a local JSON file, a user-owned write API, or a read-only Goodreads feed. Do not guess a target from the demonstration.
2. Read the latest data and identify books by stable ID. Ask if the request could match several titles or editions. Do not guess authors or fabricate dates/notes/progress.
3. Change only what was requested. “Finish & next” must retain all other current books and the whole existing history. Completing a book is not deleting it.
4. Validate, publish if file-based, and read the public feed back before claiming success. A network failure after an API write is ambiguous: read back first, never blindly repeat the write.

## File-based shelves: the usual setup

No write key or account is needed. The agent uses its existing authorised access to the user's website project.

Download https://lunelet.com/v1/lunelet.mjs to a non-public tools directory if it is not already present. Inspect it and verify its SHA-256 using https://lunelet.com/manifest.json before execution. Node 22+; no packages. This tool is optional; direct JSON edits following the same rules are supported.

Read the shelf and get the real IDs and revision:

```sh
node tools/lunelet.mjs list --file public/reading.json
```

The following commands are templates. Replace IDs, books, paths, dates and revision values; do not execute placeholders.

Start another current book (started date optional; unknown remains null):

```sh
node tools/lunelet.mjs start --file public/reading.json \
  --title "THE READER'S NEXT BOOK" --author "VERIFIED AUTHOR"
```

Finish one and start another in one local write:

```sh
node tools/lunelet.mjs next --file public/reading.json \
  --id CURRENT_BOOK_ID --date YYYY-MM-DD \
  --title "THE READER'S NEXT BOOK" --author "VERIFIED AUTHOR" \
  --if-revision CURRENT_REVISION
```

Finish without replacing:

```sh
node tools/lunelet.mjs finish --file public/reading.json \
  --id CURRENT_BOOK_ID --date YYYY-MM-DD
```

Update public progress or a short thought:

```sh
node tools/lunelet.mjs update --file public/reading.json \
  --id BOOK_ID --progress 45 --note "A thought the reader asked to share."
```

Use --progress none to hide progress, --note "" to clear a note. The update command also accepts --title, --author, --cover, --url, --work-id and --started (YYYY-MM-DD or unknown). A finished book with no recorded finish date can be kept in a static feed; ask for its real date before using a write operation that requires that date. Never invent one to satisfy validation.

Resume a finished book:

```sh
node tools/lunelet.mjs resume --file public/reading.json --id BOOK_ID
```

Append --dry-run to a write command for a preview without saving. --if-revision must use the real revision just read, never a hard-coded example. The tool validates the full feed, takes a local exclusive lock, checks for intervening changes, writes through a temporary file and replaces the target only after successful validation. It never silently overwrites an existing file during init. A lock conflict is a reason to wait/read again, not delete someone else's lock. If a crashed tool leaves a lock, confirm no writer is active before removing that exact lock with the owner's approval.

The tool increments revision and uses the actual edit timestamp. It does not invent a start date when one was not supplied. Finish/next require --date to avoid an accidental timezone/date assumption. Ask for the finish date if unknown; if the user explicitly says “today”, resolve that in the user's timezone.

Finally:

```sh
node tools/lunelet.mjs validate --file public/reading.json
```

Publish through the existing website workflow recorded in LUNELET.md. Read the public feed and confirm the changed titles, statuses, dates and revision. Purge or revalidate reading.json if the host caches it. The widget checks every 30 seconds while visible and when returning to the tab; caching can add delay. The tool outputs published:false for a local save. Do not tell the reader it is live until publication and verification succeed.

For manual JSON edits, follow the same ID/preservation/revision/timestamp rules, keep unknown optional dates null, validate against https://lunelet.com/schema.json, and do not run simultaneous edits to the same feed. Read all existing records before changing one; preserve fields you do not understand rather than dropping them.

## Hosted API

Only use an HTTPS API belonging to the reader and identified in their project instructions. A public GET URL by itself grants no editing authority. The contract at https://lunelet.com/openapi.json is an implementation reference, not an automatically provisioned hosted account. There is no writable /api/reading endpoint on lunelet.com. Never send another reader's changes to lilygeidelberg.com/api/reading.

Obtain the owner's write key through their authorised secret store. Set LUNELET_WRITE_KEY in the agent process environment. Do not paste the secret into chat, a prompt, a command argument, URL, public code, JSON, logs or git. The public embed never receives a write key.

```sh
node tools/lunelet.mjs list \
  --api https://your-site.example/api/reading

node tools/lunelet.mjs next \
  --api https://your-site.example/api/reading \
  --id CURRENT_BOOK_ID --date YYYY-MM-DD \
  --title "THE READER'S NEXT BOOK" --author "VERIFIED AUTHOR" \
  --if-revision CURRENT_REVISION
```

The tool does a public GET without credentials, validates the feed, and sends the smallest action via PATCH with Authorization: Bearer and If-Match equal to the quoted current JSON revision. It refuses non-HTTPS URLs, embedded credentials, query parameters, fragments and redirects, so a redirect cannot forward the key to a different host. An unexpected response or revision is not called a successful save.

HTTP 412 means a concurrent change: read again and reconcile the request, do not retry a stale overwrite. HTTP 409 can mean Goodreads is connected or an incompatible book state. Do not disconnect Goodreads to force a write without asking. On a timeout or ambiguous error, GET the current feed and determine whether the change already happened before resubmitting.

If making requests without the tool:

1. GET the real owner endpoint and read its JSON revision.
2. PATCH with Content-Type: application/json, Authorization: Bearer from secure storage, and If-Match: "ACTUAL_REVISION".
3. For finish & next, use {"action":"start","finishId":"CURRENT_BOOK_ID","date":"YYYY-MM-DD","book":{"title":"VERIFIED TITLE","author":"VERIFIED AUTHOR","startedAt":null}}.
4. Other actions: finish, add-finished, resume, update. See the contract. Never replace the entire shelf to implement a single-book change.
5. Confirm the response and public readback. Preserve unrelated books and never log credentials.

## Goodreads source

Goodreads is one-way and read-only here. Book updates happen on Goodreads; do not edit its generated JSON or claim the Lunelet tool writes to Goodreads. Use only a public profile, with no password, private RSS key or cookie. Its cache can delay changes. Moving from Goodreads to a manually maintained shelf is a separate user-approved source change, not an automatic workaround.
