# Deploying with an agent

Read this first when an autonomous coding agent needs the deploy flow for a Cloudbed capsule.

## Fast path

Fresh project:

```sh
npx cloudbed new my-app --template todo
cd my-app
npx cloudbed deploy --json
```

Existing project:

```sh
cd existing-app
npx cloudbed init . --template todo --json
npx cloudbed deploy --json
```

`cloudbed init [dir] [--template todo|guestbook]` scaffolds into an existing
directory, including a non-empty one. It never overwrites files, merges the
`cloudbed` dependency into an existing `package.json`, and supports `--json`.

With no auth, `cloudbed deploy --json` creates an anonymous deploy. No account is
required. The response includes a live URL.

```json
{
  "deployId": "dep_...",
  "url": "https://example.cloudbed.app",
  "name": "todo",
  "mode": "anonymous",
  "claimed": false,
  "createdAt": "2026-07-02T00:00:00.000Z",
  "updatedAt": "2026-07-02T00:00:00.000Z",
  "expiresAt": "2026-07-09T00:00:00.000Z",
  "claimToken": "cb_claim_...",
  "claimUrl": "https://dashboard.cloudbed.dev/claim?token=...",
  "inspectToken": "cb_inspect_..."
}
```

Save these fields:

| Field | Why |
|---|---|
| `claimToken` | One-time secret returned only on anonymous create. Needed to claim the deploy. |
| `inspectToken` | Secret returned only on create. Needed by `cloudbed inspect` and private inspect routes. |
| `deployId` | Stable deploy id. Needed to update, inspect by id, or recover from lost local context. |
| `url` | Live app URL. Use it for verification and handoff. |

The CLI auto-persists deploy state to `.cloudbed/deploy.json` with `api`,
`deployId`, `claimToken`, and `inspectToken`. Keep `.cloudbed/` gitignored. If a
deploy is owned or claimed, the CLI writes `cloudbed.json`; commit that file so
future deploys from the directory update the same app.

## Auth for agents

Use `CLOUDBED_TOKEN` for non-interactive auth. It is a bearer token, checked
before `~/.cloudbed/auth.json`, and works for `deploy`, `claim`, `whoami`,
`token`, `domains`, and `inspect`.

```sh
export CLOUDBED_TOKEN="cb_..."
cloudbed whoami --json
```

To validate and persist a token for later commands:

```sh
cloudbed login --token "$CLOUDBED_TOKEN"
```

To mint tokens for automation:

```sh
cloudbed token create --name agent-ci --json
cloudbed token create --name agent-deploy --deploy --json
```

`--deploy` scopes the token to the current directory's deploy. The token value is
shown once; store it as a secret.

| Mode | What works | What is blocked |
|---|---|---|
| Anonymous | Live URL with no account; redeploy from the same local state. | No outbound `fetch`, no server env, expires 7 days after the last update. |
| Claimed/owned | No expiry, outbound `fetch`, `.env.cloudbed.server` sync on deploy, owner operations. | Requires owner credentials. |

## State files

| File | Commit? | Contents | Agent rule |
|---|---:|---|---|
| `cloudbed.json` | Yes | Committed deploy binding, including `deployId`. | Keep it when the project should update the same owned deploy. |
| `.cloudbed/deploy.json` | No | Local deploy secrets: `api`, `deployId`, `claimToken`, `inspectToken`. | Treat as secret; require `.cloudbed/` in `.gitignore`. |
| `~/.cloudbed/auth.json` | No | User/API login profiles by API origin. | Prefer `CLOUDBED_TOKEN` in automation; `login --token` writes here. |

## Error handling

Run commands with `--json`. On failure, JSON commands print an error envelope to
stderr and exit nonzero:

```json
{"error":{"code":"invalid_artifact","message":"artifact.format must be \"cloudbed.capsule.v1\"."}}
```

Branch on `error.code`, not the message.

| Code | Meaning | Agent action |
|---|---|---|
| `unknown_op` | Protocol operation is not recognized. | Fix the client/protocol version or operation name; do not retry unchanged. |
| `unknown_query` | Query name is not in the deployed artifact. | Inspect the manifest, fix the query name, or redeploy the server. |
| `unknown_mutation` | Mutation name is not in the deployed artifact. | Inspect the manifest, fix the mutation name, or redeploy the server. |
| `unknown_endpoint` | HTTP endpoint is not in the deployed artifact. | Fix the method/path or redeploy with that endpoint. |
| `handler_error` | Capsule server code threw during a query, mutation, or endpoint. | Inspect logs, fix server code, redeploy. |
| `quota_exceeded` | A deploy quota was exceeded. | Claim if anonymous, reduce usage/data, or wait for daily counters; avoid tight retries. |
| `rate_limited` | A platform rate limit was hit. | Back off, wait, reuse an existing deploy, or use account auth. |
| `invalid_message` | Command input, request body, or protocol message is malformed. | Fix arguments or JSON shape; do not retry unchanged. |
| `invalid_artifact` | Deploy artifact or env payload is invalid. | Rebuild, fix schema/bundles/endpoint paths, then deploy again. |
| `not_found` | Deploy, route, token, domain, or session was not found. | Check ids and local state; recreate or redeploy if stale. |
| `unauthorized` | Bearer, claim, or inspect credential is missing or invalid. | Set `CLOUDBED_TOKEN`, login, refresh the token, or restore `.cloudbed/deploy.json`. |
| `forbidden` | The credential is valid but not allowed for the operation. | Claim the deploy, use owner credentials, or stop the unsupported action. |
| `payload_too_large` | Bundle, env, request body, or row value is too large. | Shrink the payload/env/data and redeploy. |
| `internal` | Platform or auth service failed internally. | Retry with backoff; report if it persists. |
| `not_configured` | The platform lacks configuration for the requested feature. | Skip that feature or target a configured platform. |
| `upstream_error` | An upstream provider failed or rejected the request. | Retry with backoff; verify DNS/provider inputs. |

## Limits

Storage and handler caps apply to every deploy. Daily counters apply only to
anonymous deploys and disappear on claim.

| Limit | Value |
|---|---|
| Deploy payload (bundled server + client + optional SSR) | 4 MB |
| State size | 1 MB |
| State rows | 16,384 |
| Serialized row size | 64 KB |
| Rows returned per query | 1,000 |
| HTTP endpoint request body | 2 MB |
| Handler wall-clock time | 10 s |
| Log retention | 1,000 entries / 256 KB |
| Endpoint requests / day (anonymous only) | 10,000 |
| Mutations / day (anonymous only) | 1,000 |
| Anonymous deploys per IP / day | 50 |
| Server env (claimed only) | 64 keys, 16 KB per value, 64 KB total |

## Verify it worked

```sh
deploy_json="$(cloudbed deploy --json)"
url="$(node -e 'console.log(JSON.parse(process.argv[1]).url)' "$deploy_json")"
curl -fsS "$url"
cloudbed inspect --json
```

For `capsule({ ssr: true })` apps, `curl -fsS "$url"` includes the rendered app
markup and the embedded `__cloudbed_state__` snapshot. For CSR apps it still
returns the boot shell, so use the inspect manifest and app-specific HTTP or WS
checks for deeper verification.

`cloudbed logs --json` shows what the server code actually did (`ctx.log`
entries; add `--follow` to stream while you exercise the app). If the new
deploy is broken, `cloudbed releases --json` + `cloudbed rollback` restore the
previous release in one command — then fix and redeploy.
