boox — Onyx BOOX cloud CLI (API-direct)
Send a book/doc to your BOOX device, or read its synced notes, straight
from the terminal — no browser, no BooxDrop WiFi, no send2boox email. Wraps boox.mjs
(Node built-ins only, zero npm deps).
B="${CLAUDE_PLUGIN_ROOT}/skills/aio-boox/scripts/boox.mjs"
node $B whoami # logged-in account + storage
node $B device # registered devices + online status
node $B send-book <file> # upload + push a book/doc to your device(s)
node $B repush <name-substr> # re-fire the device-wake for a file already on the cloud (no re-upload)
node $B list-push [n] # items in the device Push List
node $B remove <name-substr> # delete push-list item(s) — permanent (no recycle bin)
node $B list-books [n] # active books in the cloud Library
node $B remove-book <substr> # delete book(s); default HARD/permanent; --soft = recycle
node $B restore-book <substr> # restore book(s) from recycle bin
node $B get-notes [n] # active notes
node $B remove-note <substr> # delete note(s)/folder(s); default HARD; --soft = recycle
node $B restore-note <substr> # restore note(s) from recycle bin
send-book lands the file in BOOX device → Apps → Transfer (互传) → Push List.
Name collisions auto-dedup (foo.epub → foo(1).epub), same as the web app.
Delivery depends on the device being ONLINE at push time — not on any payload param
saveAndPush auto-resolves the target device server-side — its response echoes back the
account's mac + deviceModel (pushNum:1). So there is no missing device/mac/imAccount
parameter to add; the CLI payload is structurally complete and identical to the web app's.
The real failure mode is timing: if the device is offline/asleep when you send (the
device command's online flag is unreliable — check latestLoginTime; a stale login means it
is not actually connected), the file lands correctly in the cloud push list but the wake never
reaches the sleeping device, so it does not auto-download. It just sits in the list.
Fix — don't re-upload, just repush: once the device is on (open the Transfer app so it
logs into Onyx), run repush <name-substr> to re-fire only the saveAndPush wake for the file
already on the cloud. It pings the now-online device and the file drops in. (The server logs a
fresh push event and numbers the display name if one exists — harmless, same OSS object.)
So: device looks stale → send-book anyway → tell the user to wake the device → repush.
Deleting (read the model — book & note status are inverse)
| Surface | Active | Soft-delete (recycle) | Hard / permanent |
|---|---|---|---|
| Push list (MESSAGE) | — | (no recycle bin) | remove → SG DELETE + push/message/batchDelete |
Book (READER_LIBRARY, modeType=4) |
status=0 |
status=1 |
_deleted:true on metadata + annotations/bookmarks/collections |
| Note (NOTE_TREE tree doc) | status=1, enabled=true |
status=0, enabled=false |
_deleted:true on tree doc + page-record (commitId) docs |
remove-book/remove-notedefault to HARD (permanent). Add--softfor the recycle bin (restorable viarestore-*).- Both are dry-run by default — they print what would be deleted; add
--yesto execute. Hard delete is irreversible. remove-noteexpands a matched folder to its descendants (recursive). Hard note-delete tombstones the SG docs; the OSS handwriting resources under<uid>/<noteId>are left orphaned (harmless storage, not reclaimed).- Books in Library carry a device path (
idString=/storage/...), not an OSS object — there's nocloudFilesDELETE endpoint (/api/1/cloudFilesis GET-only); book deletion is the SGremoveMetadata/deleteMetadataAndRelatedpath. - Sync Gateway
_changeslags a few seconds behind a write. Writes (_bulk_docs) are immediate, but alist/restorerun instantly after a delete may read a stale view — wait a moment or re-run.
Auth
- JWT read from
BOOX_TOKENenv, else the file atBOOX_TOKEN_FILE(default~/.config/boox/token, honoringXDG_CONFIG_HOME). Long-lived (~6 months). It is the value oflocalStorage.tokenon push.boox.com — re-grab from a logged-in browser when it expires. - Sync Gateway session cookie is fetched on demand from
/api/1/users/syncToken(the script handles this; no separate secret). - Region default = US (
https://push.boox.com). For CN setBOOX_HOST=https://send2boox.com.
How it works (verified end-to-end against the live system)
Onyx splits state across three backends, all under push.boox.com:
| Concern | Mechanism |
|---|---|
| Account / config / push-notify | REST …/api/1/*, header Authorization: Bearer <JWT> |
| Books, notes, push-list, screensavers | Couchbase Sync Gateway at …/neocloud (one shared bucket), cookie SyncGatewaySession. Per-user/per-type docs isolated by channel: <uid>-MESSAGE (push list), <uid>-NOTE_TREE (notes). Read via _changes?filter=sync_gateway/bychannel&channels=<ch> — never _all_docs (the bucket spans all users and times out). |
| File bytes | Aliyun OSS bucket onyx-cloud-us (oss-us-west-1). Temp STS creds from /api/1/config/stss (~30 min TTL). Object key = <uid>/push/<id>.<ext>. |
send-book is dual-path (both fire, mirrors the web app — confirmed by intercepting a real push):
GET /api/1/config/stss→ STS creds → PUT file to OSS (onyx-cloud-us.oss-us-west-1.aliyuncs.com/<uid>/push/<id>.<ext>). Signed with OSS V1 (HMAC-SHA1) — implemented in pure Nodecrypto, noali-ossdep.- PUT a
digital_contentdoc into/neocloud/<guid>(channel<uid>-MESSAGE). Thecontentfield is a JSON string holdingstorage.<ext>.oss.{key,url,size}. This is what makes the file appear in the device Push List. POST /api/1/push/saveAndPush{data:{resourceKey,bucket:"onyx-cloud",resourceType,…}, cbMsg:{id,rev}}— registers it cloud-side + wakes the device.
remove reverses 2+3: DELETE /neocloud/<id>?rev=<rev> + POST /api/1/push/message/batchDelete {ids}.
Notes on scope
- get-notes reads the
NOTE_TREEchannel. Note pages are handwriting/record docs (recordType,recordFilePath); the human-named entries carrytitle. Read-only — this skill does not yet write notes back. - PushRead in the web app is an RSS-subscription feature (
webpage_contentdocs in the MESSAGE channel), not a one-shot URL push. Out of scope here. - All write operations hit a real device.
send-bookandremovemutate the cloud + device; confirm intent before bulkremove.