@staywire/cli
v0.1.0
Published
sw — command-line client for the Staywire booking API: bookings, rooms, availability blocks, guests, day sheet, revenue stats and property config.
Downloads
21
Maintainers
Readme
sw — the Staywire CLI
A complete terminal client for the Staywire v1 API: bookings, rooms, blocks, guests, the day sheet, revenue stats, property config, API keys, webhooks, logs and the WRP parity registers.
It exists so that operators and agents drive Staywire through one audited,
self-documenting surface instead of hand-rolled curl invocations — which is
how Authorization headers end up in shell history and how a PATCH lands on
the wrong booking.
Install
From npm (recommended)
npm install -g @staywire/cli
sw --helpThat puts sw on your $PATH. The package has zero runtime dependencies —
it is a single compiled bundle against Node's built-ins and global fetch — so
the install is instant and pulls nothing transitive. Requires Node >= 22.
Per-project instead of global:
npm install --save-dev @staywire/cli
npx sw bookings listOr run it without installing at all:
npx -y @staywire/cli bookings listFrom this repo (contributors)
pnpm --filter @staywire/cli sw bookings list # tsx, straight from sourceTo get a sw binary on $PATH from a working copy:
pnpm --filter @staywire/cli build
cd apps/cli && npm link⚠️ A linked sw points at dist/, which is gitignored. A fresh clone has no
dist/ — build before linking. And the linked binary does not pick up source
edits until you rebuild, so use pnpm --filter @staywire/cli sw <args> while
developing or you will be running yesterday's CLI.
On SBTM01 the link lives under the nvm v24 prefix
(~/.nvm/versions/node/v24.14.1/bin/sw), which is on the default PATH.
Quick start
sw bookings list
sw day-sheet
sw stats revenue --granularity week
sw help bookingsConfiguration
| What | Flag | Env | Fallback |
|---|---|---|---|
| API key | --key <key> | STAYWIRE_API_KEY | macOS keychain -a sbtm01 -s sblabs-v2-STAYWIRE_MIRROR_API_KEY |
| API base | --base <url> | STAYWIRE_API_URL | https://staywire-api.vercel.app |
The keychain fallback is the SB Labs host convention. It is best-effort and silent when unavailable, so the CLI behaves identically on a machine that only exports the env var. To seed it:
security add-generic-password -a sbtm01 -s sblabs-v2-STAYWIRE_MIRROR_API_KEY -w '<key>'Safety model
This CLI points at production guest data by default, and it is routinely driven by agents. Three rules follow from that:
- Reads run freely.
list,get,show,revenue,paymentsneed nothing. - Writes refuse without
--yes. Any command that mutates exits3with a message unless you pass--yes. There is deliberately no interactive prompt fallback — a prompt that a non-TTY caller auto-answers is worse than no prompt at all. --dry-runpreviews any write. It prints the exact method, URL and body that would be sent, then exits0without touching the API.
# refuses — exit 3
sw bookings patch <id> --check-out 2026-08-29
# shows the request, sends nothing — exit 0
sw bookings patch <id> --check-out 2026-08-29 --dry-run
# actually writes
sw bookings patch <id> --check-out 2026-08-29 --yesExit codes
| Code | Meaning |
|---|---|
| 0 | success (including --dry-run) |
| 1 | API error, or a bad argument |
| 2 | unknown group or verb |
| 3 | write refused — --yes not given |
Global flags
| Flag | Short | Effect |
|---|---|---|
| --json | -j | emit the raw API payload instead of a table — pipe into jq |
| --yes | -y | authorise a write |
| --dry-run | -n | print the request a write would send, then exit |
| --verbose | -v | trace each HTTP call to stderr |
| --help | -h | help for the command's group |
Flags accept --flag value or --flag=value. Short booleans bundle (-jy).
-- stops flag parsing, so values that look like flags survive.
Clearing a nullable field
Fields the API models as nullable (door_code, check_in_time,
check_out_time, notes, special_requests, phone) take the literal string
null to clear them. Omitting the flag leaves the field untouched — the two are
not the same thing:
sw bookings patch <id> --door-code 4821 --yes # set
sw bookings patch <id> --door-code null --yes # clear, back to property default
sw bookings patch <id> --check-in-time 13:00 --yes # leaves door_code aloneCommands
Run sw help <group> for per-verb flags. Groups:
| Group | Verbs |
|---|---|
| bookings | list get patch cancel no-show resend payments refund |
| rooms | list get create patch delete |
| blocks | list create patch delete |
| guests | list get |
| day-sheet | show (bare sw day-sheet works) |
| stats | revenue |
| settings | get patch |
| policies | show |
| notifications | get patch test |
| keys | list create revoke |
| webhooks | list create delete |
| logs | requests events emails |
| parity | show |
Worked examples
# Today's arrivals and departures
sw day-sheet
# A specific date, as JSON
sw day-sheet --date 2026-08-27 --json
# Bookings in a window
sw bookings list --from 2026-08-01 --to 2026-09-01
# One booking in full
sw bookings get c8f67671-db39-4ccf-bebb-aeca566a9415
# Extend a stay by a night, previewing first
sw bookings patch <id> --check-out 2026-08-29 --dry-run
sw bookings patch <id> --check-out 2026-08-29 --yes
# Per-stay times and access code in one call
sw bookings patch <id> --check-in-time 13:00 --door-code 4821 --yes
# Weekly revenue, occupancy and ADR
sw stats revenue --granularity week --from 2026-07-01 --to 2026-09-01
# Block a room for maintenance
sw blocks create --room-id <uuid> --start 2026-09-01 --end 2026-09-05 \
--reason "deep clean" --yes
# Pipe into jq
sw bookings list --json | jq '.bookings[] | select(.door_code == null) | .reference'Interaction with the WRP bridge
Staywire mirrors WebRezPro, and staywire-wrp-shadow pushes SW-side edits back
to WRP. A sw bookings patch that changes dates is therefore not local — the
bridge's detector picks it up within one poll cycle (~5 min) and composes a
push_reservation_v2 modify against WRP. Consequences worth knowing:
- Date changes on a mapped booking propagate to WRP. Room moves are refused by the bridge composer (no room-number→type mapping yet) and stay SW-only.
- The bridge audits every push to
data/wrp-writeback.jsonlin the shadow repo, including dry runs. That file is the record of what actually reached WRP. sw parity showreads the parity registers — the surface of known SW↔WRP gaps.
sw --dry-run only gates the Staywire call. It has no effect on the bridge,
because the bridge reacts to committed SW state, not to your terminal.
Development
pnpm --filter @staywire/cli sw <args> # run from source via tsx
pnpm --filter @staywire/cli typecheck
pnpm --filter @staywire/cli test
pnpm --filter @staywire/cli buildCommands live in src/commands.ts as a flat registry. Help text is generated
from that registry, so adding a verb updates sw help automatically — there is
no second place to edit. src/commands.test.ts asserts the invariant that keeps
the safety model honest: every write verb sets mutates: true.
