@playaos/cli
v0.1.0
Published
PlayaOS CLI — operate your Burning Man camp from the terminal or an AI agent
Maintainers
Readme
PlayaOS CLI
Operate your Burning Man camp from the terminal — or from an AI agent.
@playaos/cli is a zero-runtime-dependency TypeScript CLI (playaos) for querying a
Burning Man camp through the PlayaOS REST API — members,
applications, dues, shifts, and org config. It ships alongside an AI-agent skill pack
generated from the same source, so the same capabilities are available to humans at a
terminal and to LLM agents.
Every data command emits a single line of JSON and signals its outcome via exit code — built
to be driven programmatically. (The exception is playaos help / --help / -h, which
prints the plain-text usage string to stdout.)
Status: M0 (read-only). Write actions (approve applicant, shift signup, collect dues, send email) are planned for M1 behind a
PLAYAOS_ALLOW_WRITESgate.
Technology Stack
| Concern | Choice |
|---------|--------|
| Language | TypeScript (ESM, node20 target) |
| Runtime | Node.js >= 20 |
| Package manager | pnpm (frozen lockfile in CI) |
| Bundler | tsup — single dist/index.js with shebang |
| Tests | Vitest |
| Runtime dependencies | none (Node built-ins only: node:util, fetch, node:fs) |
| CI | GitHub Actions (self-hosted), Graphite CI optimizer |
Project Architecture
Two products are built from one source tree:
┌─────────────────────────┐
argv ───────► │ src/index.ts route() │ dispatch on argv[2]
└────────────┬────────────┘
▼
┌─────────────────────────┐
│ src/commands/<name>.ts │ thin: validate → fetch → filter → print
└──┬────────┬────────┬─────┘
▼ ▼ ▼
lib/args.ts lib/auth.ts lib/http.ts ──► PlayaOS REST API (/api/v1)
│ │ │
└────────┴────► lib/output.ts ──► JSON envelope + exit codePer-invocation flow in each command:
- Validate subcommand, then
parseFlags/parseStrict(src/lib/args.ts, strict) resolveAuth()(src/lib/auth.ts)apiFetch()(src/lib/http.ts)- Optional client-side filter (M0 stopgap)
printOk(data)on success;die(err)in the catch
The three lib/ helpers are the single choke-points for their concern:
lib/auth.ts— resolvesPLAYAOS_API_KEY+PLAYAOS_API_URL; redacts the key from any serialized output.lib/http.ts— the only code that knows the wire format (/api/v1prefix, Bearer auth,{ error, code }bodies,Retry-After). ThrowsApiError.lib/output.ts— owns the JSON envelope and the exit-code contract.
This repo intentionally does not depend on @playaos/api-client; http.ts/auth.ts
re-implement only the slices they need.
Skill pack
Two hand-edited skills live under skills/:
playaos— the full camp skill (members, applications, dues, shifts + the public gift layer). Needs a camp key (PLAYAOS_API_KEY).playaos-public— the no-key public gift-layer skill (requires_env: []): events, art, packing list + catalog, directory, marketplace search, plus the optionalPLAYAOS_USER_KEYwrites. Built for zero-setup distribution.
build/skill.ts parses their frontmatter, and build/export-hermes.ts /
build/export-openclaw.ts re-emit per-harness variants of both into dist/hermes/...
and dist/openclaw/.... The dist/ copies are generated — never edit them by hand.
Getting Started
Prerequisites
- Node.js >= 20
- pnpm
- A PlayaOS API key (
pk_live_*/pk_test_*) from https://playaos.app/developer
Install (as a tool)
npm install -g @playaos/cliConfigure
export PLAYAOS_API_KEY=pk_live_your_key_here
# optional — defaults to https://api.playaos.app
export PLAYAOS_API_URL=https://your-camp.api.playaos.appUse
playaos org get # camp config
playaos members list --status active # members by status / role
playaos members get <id> # one member
playaos applications list --status pending # applications by status / year
playaos dues list --status unpaid # dues rows
playaos shifts list --open # shifts with open slots
playaos help # full usageBuild from source
git clone https://github.com/playaos/ai-toolkit.git
cd ai-toolkit
pnpm install
pnpm build # → dist/index.js
pnpm testDistribution
Install a skill into an AI agent
Generate the per-harness bundles, then hand a skill folder to your agent:
pnpm export # → dist/hermes/... and dist/openclaw/...
pnpm pack:skills # → dist/bundles/<harness>-<skill>.tar.gzEach bundle is a self-contained SKILL.md + references/. Drop the folder into your
agent's skills directory:
| Agent | Skill | Bundle |
|-------|-------|--------|
| Hermes | camp (needs PLAYAOS_API_KEY) | dist/hermes/productivity/playaos |
| Hermes | public (no key) | dist/hermes/productivity/playaos-public |
| OpenClaw / ClawHub | camp | dist/openclaw/playaos |
| OpenClaw / ClawHub | public | dist/openclaw/playaos-public |
The -public bundle needs no API key — it works the moment the playaos CLI is
installed. At launch, the same bundles are attached to each GitHub release (below) so
users can install without cloning.
Publishing (maintainers)
Releases are cut by pushing a version tag; .github/workflows/release.yml does the rest:
git tag v0.1.0
git push origin v0.1.0The workflow re-runs typecheck/test/build/export, publishes @playaos/cli to npm
(--access public --provenance), and creates a GitHub release with the skill bundles
attached. It is dormant until a tag is pushed and requires one repo secret:
NPM_TOKEN— an npm automation token with publish rights to the@playaosscope.
Output Contract
Stable — agents depend on it. Success goes to stdout, failure to stderr:
{ "ok": true, "data": <array|object> }
{ "ok": false, "error": { "code": "<string>", "message": "<string>", "status"?: <number>, "retryAfter"?: <number> } }error.status and error.retryAfter are optional: status is omitted for usage and
network errors (no HTTP response), and retryAfter (seconds) is only set on rate_limit
(429) responses.
| Exit | Meaning | code | Action |
|------|---------|--------|--------|
| 0 | success | — | parse stdout JSON |
| 1 | network / generic API error | network / api_error | surface to user |
| 2 | usage (bad flags, missing key) | usage | fix command / set PLAYAOS_API_KEY |
| 3 | auth 401/403 | auth | check PLAYAOS_API_KEY |
| 4 | not found 404 | not_found | verify the ID |
| 5 | rate limit 429 | rate_limit | wait error.retryAfter s, retry once |
Project Structure
.
├── src/
│ ├── index.ts # entrypoint + route() + USAGE
│ ├── commands/ # org, members, applications, dues, shifts (+ *.test.ts)
│ └── lib/ # auth, http, args, output, errors (+ *.test.ts)
├── skills/playaos/
│ ├── SKILL.md # canonical agent skill (source of truth)
│ └── references/ # full command + flag reference
├── build/ # skill.ts + export-hermes.ts + export-openclaw.ts
├── docs/plans/ # design + milestone implementation notes
├── install/ # hermes.sh, openclaw.sh installers
├── .github/workflows/ci.yml
├── CLAUDE.md # guidance for AI coding agents
└── package.jsonKey Features
- Read-only camp queries — members, applications, dues, shifts, org config.
- JSON-only, exit-code-driven output — first-class for scripting and AI agents.
- Strict flag parsing — an unknown/valueless flag or stray positional is a usage error, never silently coerced.
- Secret-safe — the API key is redacted from all serialized output.
- Zero runtime dependencies — small, auditable, fast to install.
- Dual-harness skill pack — one canonical skill exported for Hermes and OpenClaw.
Development Workflow
pnpm dev # tsup --watch
pnpm typecheck # tsc --noEmit
pnpm test # vitest run
pnpm test:watch
pnpm build # bundle to dist/
pnpm export # regenerate skill pack (hermes + openclaw)Run a single test:
pnpm vitest run src/commands/dues.test.ts
pnpm vitest run -t "filters by status"CI gate (self-hosted, in order): typecheck → test → build → export. Reproduce locally by running those four. Branching uses Graphite-style stacks; the CI optimizer skips runs already covered by a parent PR.
Coding Standards
- ESM with
.jsimport specifiers in TypeScript source (from "./lib/auth.js") — required. - No
console.log— all output flows throughlib/output.ts. - Keep commands thin; concern ownership lives in the three
lib/choke-points. - M0 stopgaps carry Linear ticket refs in comments (client-side
dues --status&shifts --open→ PLA-777/778; no pagination → PLA-779). Keep these notes in sync across code, USAGE, andSKILL.md.
See CLAUDE.md for the full architecture and conventions reference.
Testing
Vitest, with tests co-located next to source (*.test.ts). Tests mock
fetch and assert on both the JSON body and the exit code. The entrypoint checks VITEST=1
so importing route in a test never fires the CLI against the runner's argv.
pnpm testKnown Limitations (M0)
dues list --statusandshifts list --openfilter client-side — the full list crosses the wire (the API does not yet expose these filters; PLA-777 / PLA-778).- No pagination — list commands return the entire set; no
--limit/--cursoryet (PLA-779). - Read-only — write actions land in M1 behind
PLAYAOS_ALLOW_WRITES.
Contributing
- Follow the 5-step command shape above and the Coding Standards.
- Adding a command: create
src/commands/<name>.ts, register it inroute()and theUSAGEstring insrc/index.ts, addsrc/commands/<name>.test.ts, and document it inskills/playaos/SKILL.md+references/commands.md. - Ensure the CI gate passes locally:
pnpm typecheck && pnpm test && pnpm build && pnpm export. - Use existing commands (e.g.
src/commands/dues.ts,src/commands/shifts.ts) as exemplars for structure, error handling, and client-side filtering.
License
MIT
