@wafflebase/cli
v0.6.6
Published
CLI for Wafflebase spreadsheet API
Readme
@wafflebase/cli
wafflebase — terminal access to the Wafflebase REST API for data
pipelines, scripting, CSV/JSON import/export, document management, and
word-processor (docs content / export / import) operations.
Install
# Local install (recommended for scripts)
npm install -D @wafflebase/cli
npx wafflebase --help
# Global install (recommended for interactive use)
npm install -g @wafflebase/cli
wafflebase --helpAuth
Two paths, in priority order:
- API key (recommended for scripts) — pass
--api-key/WAFFLEBASE_API_KEY, or setapi-key:in~/.wafflebase/config.yaml. - OAuth session —
wafflebase loginopens a browser, completes GitHub OAuth, and writes a JWT session to~/.wafflebase/session.json. The login callback is bound to a per-attempt nonce, which the backend echoes back as the callback'sstate, so a server that predates that echo never completes one; against such a server (a self-hosted backend that has not upgraded yet) passwafflebase login --allow-unbound-callbackto accept itsstate-less callback anyway. It warns on stderr, and a mismatchedstatestays refused under the flag.
Always pair API keys with a workspace ID:
export WAFFLEBASE_API_KEY=wfb_…
export WAFFLEBASE_WORKSPACE=ws-…Image fetching
docs export / slides export fetch the images a document references. An
image src is content someone else may have written, so the fetcher speaks
only http/https/data and refuses a non-public address (loopback,
private, 169.254.169.254, CGNAT, multicast/reserved, and the IPv6
spellings of all of those) unless it is the configured --server's own host
and port. The hostname is resolved before it is fetched and judged per
address, so 169.254.169.254.nip.io and friends get no further than the
literal would, and the request is then pinned to the addresses that check
approved — DNS cannot answer differently between the check and the
connection. Every redirect hop is gated the same way, up to five.
An image that is refused or unreachable is reported on stderr and skipped,
rather than failing the whole export: one src you cannot fix must not cost
you the export you asked for. The rest of the document still exports, minus
those images.
Command Tree (v0.3.7)
Plural namespaces are canonical; singular forms are accepted as aliases for back-compat with earlier scripts.
wafflebase
├── login / logout / status Browser OAuth + session
├── ctx list / switch <name|id> Workspace context
│
├── docs (aliases: doc, document, documents)
│ ├── list [--type doc|sheet]
│ ├── create <title> [--type doc|sheet]
│ ├── get <doc-id>
│ ├── rename <doc-id> <title>
│ ├── delete <doc-id>
│ ├── content <doc-id> --format json|md|text + --pages
│ ├── export <doc-id> <file> --format pdf|docx + --pages (PDF)
│ └── import <file> --replace <id> --yes for in-place
│
├── sheets (aliases: sheet, spreadsheet, spreadsheets)
│ ├── tabs (alias: tab)
│ │ ├── list <doc-id>
│ │ ├── create <doc-id> [name] --type sheet
│ │ └── rename <doc-id> <tab-id> <name>
│ ├── cells (alias: cell)
│ │ ├── get <doc-id> [<range>]
│ │ ├── set <doc-id> <ref> <value> --formula
│ │ ├── batch <doc-id> --data <json> | stdin
│ │ └── delete <doc-id> <ref>
│ ├── import <doc-id> <file> CSV/JSON
│ └── export <doc-id> <file> CSV/JSON
│
├── api-keys (alias: api-key)
│ ├── create <name>
│ ├── list
│ └── revoke <key-id>
│
└── schema [<command>] Discover parameters/safetyGlobal flags: --server, --api-key, --workspace, --profile,
--format json|table|csv|yaml (default json), --quiet (suppresses
progress notices only — the result body and the JSON error envelope are
always emitted), --verbose,
--dry-run. The --format flag also doubles as the per-content shape
on docs content (json|md|text) and the export type override on
docs export (pdf|docx).
Examples
# Documents
wafflebase docs list
wafflebase docs create "Q1 Notes" --type doc
# Spreadsheets
wafflebase sheets tabs create abc-123 "History"
wafflebase sheets tabs rename abc-123 tab-1 "Summary"
wafflebase sheets cells get abc-123 A1:D100
echo '{"A1":"Name","B1":"Score"}' | wafflebase sheets cells batch abc-123
wafflebase sheets export abc-123 out.csv
# Word-processor docs
wafflebase docs content abc-123 --format md
wafflebase docs export abc-123 out.pdf --pages 1-3
wafflebase docs import draft.docx --title "Final Draft"
wafflebase docs import revision.docx --replace abc-123 --yes
# Schema introspection (singular aliases resolve too)
wafflebase schema docs.content
wafflebase schema cell.get # → sheets.cells.getOutput Conventions
- Text results (json/md/text): stdout by default;
--out <file>to redirect;-writes to stdout explicitly.--forceis required to overwrite an existing--outtarget. - Binary results (pdf/docx): positional
<file>;-writes to stdout.--forceis required to overwrite an existing target. - Errors: a single JSON line on stderr —
{"error":{"code":"…","message":"…","command":"docs.content"}}.commandis the dotted command name (the same stringschemaindexes on), so a caller running several commands can tell which one failed. Typed errors (e.g.,INVALID_DOCX,TYPE_MISMATCH,CONFIRMATION_REQ) carry a command-specificcodeagents can branch on; argument-parsing failures (missing argument, unknown option, unknown command) reportUSAGE. A failed request whose body the backend did not send in that shape (an Express/Nest{message, error, statusCode}404/500, an HTML proxy page) reports"HTTP_ERROR"— or"AUTH_ERROR"/"SERVER_ERROR"when the status says so — with"HTTP <status>"plus the upstream's own wording when it had any, as"HTTP 404: Document has no file". Every command reports the same code for that condition, so the branch does not depend on which subcommand ran. Local failures (bad input, a filesystem error) still report"ERROR". - Forwarded backend errors are bounded: when the backend did send
the envelope, its own
codeis what you get — that is the value to branch on, and it is never rewritten. The surrounding text is capped, because it is upstream-controlled content going straight into an agent's stderr:codeis truncated at 80 characters,messageat 500 (with a trailing…), amessagethat is an HTML document is replaced by"HTTP <status>", and any extra field the backend attached (a request id) is dropped once the whole body exceeds 4,000 bytes, leaving{code, message}. Treatmessageas a display string, not a parseable payload.commandis the one field never forwarded: attribution is the CLI's statement about which command it ran, so a server cannot relabel which call failed. - Exit codes:
0success,1user error (bad input, 404, type mismatch),2system error — an unreachable server (NETWORK_ERROR), rejected credentials (AUTH_ERROR, HTTP 401/403), or a server fault (SERVER_ERROR, HTTP 5xx). A 2xx the CLI cannot use — a create that returned no id, a download that returned no bytes — is a server fault too. The class is decided where the failure is raised, so--quietreports it too. - Proxies: image downloads during
docs export/slides exporthonorhttp_proxy/https_proxy/all_proxyandno_proxy(either letter case).
Skills (for AI agents)
Skill files live in skills/ and ship with the package — namespace
prefixed (docs-…, sheets-…, recipe-…). Agents load them, read
the YAML frontmatter for safety + tool list, and wafflebase schema
<command> for parameter shapes. See skills/SKILL.md
for the index.
Design
Full design in /docs/design/rest-api.md
and /docs/design/cli.md (the
docs-side content / export / import pipeline).
