# brandlit CLI (v1.0.0)

Dead-simple Node CLI for the Brandlit Studio HTTP API — sign in, create, save,
publish, and put a project on your own domain in six commands.

**Base:** https://brandlit.blacklabelbots.com  
**Auth:** cookie jar (`bl_session` / `bl_uid`) at `~/.config/brandlit/cookies.txt`  
**Bearer / machine tokens:** not available yet; the CLI never sends `Authorization`.  
**Deps:** zero. Node 18+ (20+ recommended) for built-in `fetch`.

## Install / run

The CLI ships with the Brandlit Studio source (`cli/bin/brandlit.js`). From a checkout:

```bash
node cli/bin/brandlit.js --help          # one-shot
npm link                                 # exposes `brandlit` on PATH
brandlit health                          # read-only check, no credentials
```

## 60-second happy path

```bash
brandlit connect login --open                          # device code → approve → bl_session saved
ID=$(brandlit projects create --name "Launch" | jq -r .project.id)
brandlit projects save "$ID" --file ./dist/index.html
brandlit projects publish "$ID" | jq -r .publicUrl
brandlit domain attach "$ID" --domain www.example.com  # BYO hostname, no purchase
# add CNAME www → <byoCnameTarget> at your DNS host, then:
brandlit domain verify "$ID" --domain www.example.com --wait --timeout 600
```

## Commands

| Command | HTTP | Notes |
|---------|------|-------|
| `health` | `GET /health` | no auth |
| `login --email --password` | `POST /api/auth/signin` | or `BRANDLIT_EMAIL` / `BRANDLIT_PASSWORD` |
| `whoami` | `GET /api/auth/me` | exit 3 when jar empty |
| `logout [--keep-jar]` | `POST /api/auth/signout` | clears jar by default |
| `connect login [--open] [--timeout S] [--interval S]` | `start` + `poll` loop | **one command**; exit 0 approved · 3 denied · 8 expired/timeout · 130 Ctrl-C |
| `connect start` | `GET /api/auth/grokbot/start` | raw device-code payload |
| `connect poll --code DEVICE` | `GET /api/auth/grokbot/poll` | single poll |
| `connect status` | `GET /api/auth/grokbot/status` | read-only |
| `projects [list]` | `GET /api/projects` | |
| `projects create --name N [--mode] [--template]` | `POST /api/projects` | exit 5 if free editor minutes used |
| `projects get <id> [--html-out F]` | `GET /api/projects/{id}` | |
| `projects save <id> --file F \| --html S [--ops-file F] [--name N] [--brand-kit F]` | `POST /api/projects/{id}/save` | `--file -` reads stdin |
| `projects publish <id> [--file F]` | `POST /api/projects/{id}/publish` | prints publicUrl / siteUrl / byoCnameTarget |
| `domain instructions [<id>]` | `GET /api/domain/instructions` | BYO CNAME targets |
| `domain attach <id> --domain HOST` | `POST /api/domain/attach` | binds a hostname you own; `verified:false` until DNS |
| `domain verify [<id>] --domain HOST [--wait]` | `POST /api/domain/verify` | exit 0 verified · 8 not yet |
| `grok --prompt "…"` | `POST /api/grok` | gate → exit 4 |
| `billing products` | `GET /api/billing/products` | read-only discovery |

`<id>` can also be passed as `--project` / `--id`. Every command has
`brandlit help <command>` with flags + examples.

### Intentionally not wrapped

- **Checkout / confirm** (`/api/billing/checkout`, `/confirm`) — no money movement from the CLI.
- **Domain purchase** (`/api/domain/buy`) — BYO attach + verify only.
- **Bearer / PAT** — no token endpoint exists yet; nothing invented.
- Deploys and pricing changes.

## Output contract

- **STDOUT is always JSON** — pipe to `jq`.
- **STDERR** carries human progress (spinner on TTY, de-duplicated lines otherwise). `-q` silences it. `NO_COLOR` disables color.
- On failure: stderr `error: <code> — <message>`, stdout `{ "ok": false, "code", "error", "httpStatus", … }`.
  `code` is the Worker's code when it sends one, otherwise derived from HTTP status
  (`bad_request`, `auth_required`, `forbidden`, `not_found`, `conflict`, …).

## Exit codes

| Code | Meaning |
|------|---------|
| 0 | ok |
| 1 | usage / bad args (incl. refused checkout / buy) |
| 2 | network |
| 3 | auth required / forbidden (not your project) / Connect denied |
| 4 | connect_grokbot / needs_key / platform gate |
| 5 | free / meter exhausted |
| 6 | not found |
| 7 | conflict |
| 8 | not done yet — domain not verified, Connect expired / timed out |
| 10 | other API error |
| 130 | interrupted (Ctrl-C) |

## Cookie jar

Netscape format, `chmod 600`, curl-compatible (`curl -b ~/.config/brandlit/cookies.txt …`).

- `--jar PATH` or `BRANDLIT_COOKIE_JAR`
- `--base URL` or `BRANDLIT_BASE` (non-prod / local `wrangler dev`)

Projects are owned by the jar identity: a guest `bl_uid` jar and a signed-in
`bl_session` jar see different projects. If `save`/`publish` returns
`forbidden` (exit 3), you're on the wrong identity.

## CI usage (until Bearer lands)

```bash
export BRANDLIT_COOKIE_JAR=$RUNNER_TEMP/brandlit.txt
# Restore a jar produced by `brandlit connect login` on a dev machine (store as a CI secret file),
# then:
brandlit whoami || exit 1
brandlit projects save "$ID" --file dist/index.html && brandlit projects publish "$ID"
```

Sessions expire. Machine Bearer tokens are planned but not available yet.

## Docs

- [Integrator overview](/docs/INTEGRATOR-README.md)
- [OpenAPI JSON](/openapi.json) · [YAML](/openapi.yaml)
- [curl cookbook](/docs/CURL-COOKBOOK.md)
