# Pond Docs Full Text

This file concatenates the public agent-targeted docs in source order so an agent can ingest the whole pond capsule contract with one fetch.

---

<!-- source: docs/llms.txt -->
<!-- raw: https://pond.run/docs/llms.txt -->

# Pond Docs

> Pond is an agent-native CLI and runtime for building small full-stack TypeScript apps called capsules. A capsule is one server file (schema, queries, mutations), one client file (Preact UI), and a shared/ dir, deployed to a hosted control plane that gives back a public URL and a one-time claim token.

The docs are plain Markdown so an agent can ingest them with one fetch. Prefer `/llms-full.txt` if you want the whole reference in a single request.

## For agents building a capsule

If you are an agent writing a capsule, treat the capsule directory as the whole app. Write the server contract, write the Preact client, run `pond dev`, inspect with `pond inspect` / `pond logs`, and deploy with `pond deploy --api <url>`. Do not leave the directory.

Hard constraints — non-negotiable:

- `server/index.ts` imports only from `pond/server`, plain relative TypeScript, and Node built-ins shipped with the runtime. No arbitrary npm packages.
- `client/index.tsx` imports only from `pond/client` and plain relative TypeScript. Use Tailwind classes for styling; the runtime injects Tailwind via CDN.
- `shared/` contains pure TypeScript shared by both sides. It must not read env, secrets, DOM APIs, Node APIs, or any pond runtime API.
- Every `server/index.ts` ends with `export default capsule({ ... })`.
- Every `client/index.tsx` exports a Preact component named `App`.
- Authorization is your responsibility inside handlers. Filter by `ctx.auth.userId`; re-check ownership on update / delete.

## Docs

- [Pond Server API Reference](https://pond.run/docs/api-reference.md): the surface of `pond/server` — `capsule()`, `table()`, `query()`, `mutation()`, `endpoint()`, column types, the `ctx.db` data API, lifecycle.
- [Pond Client API Reference](https://pond.run/docs/client-reference.md): the surface of `pond/client` — `useQuery`, `useMutation`, `useAuth`, `SignInWithGoogle`, `signOut`, plus the re-exported `render` and `h` from Preact.
- [Pond CLI Reference](https://pond.run/docs/cli-reference.md): every `pond` subcommand (`new`, `dev`, `deploy`, `host`, `db`, `logs`, `inspect`, `fork`, `claim`, `login`, `user`, `token`, `domains`, `env`, `auth`) with flags, when-to-use, and worked examples.
- [README](https://pond.run/docs/README.md): overall introduction, capsule example, CLI commands, in-browser IDE, hosted control plane, anonymous deploys, threat model.

## Examples

- [todo-style capsule](https://pond.run/docs/api-reference.md#capsule-def-capsuledefinition): the smallest useful pattern — one table, one query, one mutation, server-owned writes.

## Machine-readable

- [llms-full.txt](https://pond.run/llms-full.txt) — full reference concatenated in one file.

---

<!-- source: docs/api-reference.md -->
<!-- raw: https://pond.run/docs/api-reference.md -->

# Pond Server API Reference

**Use this as the quick contract when building a Pond capsule.** If you are an agent writing a capsule, this page is the authoritative list of the primitives you may call inside `server/index.ts`. Do not invent helpers that aren't here. Do not import arbitrary npm packages — capsule modules are bundled with esbuild and only `pond/server`, `pond/client`, plain relative TypeScript, and the Node built-ins that ship with the runtime are available.

This is the surface exported from `pond/server`. A capsule's `server/index.ts` calls into it to define schema, queries, mutations, and endpoints.

```ts
import { capsule, query, mutation, endpoint, table, string, number, boolean, json, text } from "pond/server"
```

---

## Column types

Returned by helpers and used inside `table()`.

### `string(): ColumnType`

A `TEXT` column.

### `number(): ColumnType`

A `REAL` column. (SQLite is flexible — both integers and floats round-trip.)

### `boolean(): ColumnType`

An `INTEGER` column. JS booleans are coerced to `0`/`1` on write and stay numeric on read; convert at the boundary if you need true booleans in your application code.

---

## Schema

### `table<T>(columns: T): T`

Identity function used as a marker. The shape of `columns` is what the runtime introspects to issue `CREATE TABLE`. Every table automatically gets:

- `id TEXT PRIMARY KEY` (UUID, generated by `insert()`)
- `createdAt TEXT DEFAULT (datetime('now'))`
- `updatedAt TEXT DEFAULT (datetime('now'))` (touched on every `update()`)

So a definition like:

```ts
schema: {
  messages: table({
    body: string(),
    author: string(),
  }),
}
```

produces a `messages(id, body, author, createdAt, updatedAt)` table.

**Validation.** Table names and column names must match `/^[A-Za-z_][A-Za-z0-9_]*$/`, be at most 64 chars, not begin with `_pond_`, and not be a SQLite reserved word.

---

## Capsule context

Every handler receives a `CapsuleContext` as its first argument.

```ts
interface CapsuleContext {
  auth: CapsuleAuth
  db: CapsuleDb
  env: Record<string, string>
  log: CapsuleLog
}
```

### `ctx.auth`

Identity of the caller.

```ts
interface CapsuleAuth {
  isGuest: boolean
  userId: string
  displayName?: string
  picture?: string
  email?: string
}
```

`isGuest: true` means the caller has no signed session — either no cookie, an invalid JWT, or an expired session. The runtime fills in a guest identity via the dev server's guest switcher or a default `{ userId: "guest", displayName: "Guest" }` in production.

Sign-in providers, all opt-in via env vars on `.env.pond.server`:

- **Google** — set `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REDIRECT_URI`. Users hit `/auth/google`. `userId` becomes `<google-sub>`.
- **GitHub** — set `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, optional `GITHUB_REDIRECT_URI`. Users hit `/auth/github`. `userId` becomes `github:<gh-id>`.
- **Email magic links** — no extra config required. Capsule client `POST`s `{ email }` to `/auth/email/request`, the runtime stores a one-time 15-minute token in `_pond_magic_links`, then prints the verification link to logs unless `RESEND_API_KEY` + `EMAIL_FROM` are set (in which case it ships the link via Resend). The link lands on `/auth/email/verify?token=…` and creates a session. `userId` becomes `email:<addr>`.

You can enable any combination — sessions are JWT-signed against `POND_SESSION_SECRET` so they survive provider swaps.

### `ctx.db`

Proxy with one property per schema table:

```ts
ctx.db.messages.insert({ body: "hi", author: "alice" })
ctx.db.messages.get(id)
ctx.db.messages.update(id, { body: "edited" })
ctx.db.messages.delete(id)
ctx.db.messages.where("author", "alice").orderBy("createdAt", "desc").limit(50).all()
ctx.db.messages.all()
```

Each `CapsuleDbTable` exposes:

| Method                             | Description                                              |
| ---------------------------------- | -------------------------------------------------------- |
| `where(column, value)`             | Chainable WHERE clause. Multiple calls AND together.     |
| `orderBy(column, "asc" \| "desc")` | Chainable ORDER BY. Multiple calls compose.              |
| `limit(n)`                         | LIMIT clause.                                            |
| `all()`                            | Executes the query. Returns `any[]`.                     |
| `get(id)`                          | SELECT by id. Returns the row or `undefined`.            |
| `insert(data)`                     | INSERT with a generated UUID. Returns the inserted row.  |
| `update(id, data)`                 | UPDATE by id; sets `updatedAt`. Returns the updated row. |
| `delete(id)`                       | DELETE by id.                                            |

All column references go through the same identifier validation as schema definitions.

### `ctx.env`

Plain object loaded from `.env.pond.server` (KEY=VALUE format, `#` comments). Includes a derived `GOOGLE_REDIRECT_URI` when not set explicitly.

### `ctx.ai`

Built-in LLM primitive. Routes to the first provider configured in `.env.pond.server`:

1. `ANTHROPIC_API_KEY` → Claude
2. `OPENAI_API_KEY` → OpenAI
3. `HERMES_BASE_URL` → a local OpenAI-compatible endpoint (Hermes / vLLM / Ollama)

Force a provider with `AI_PROVIDER=anthropic|openai|hermes`.

```ts
const text = await ctx.ai.complete({ prompt: "Summarize this:", system: "Be terse." })
for await (const chunk of ctx.ai.stream({ prompt: "...", model: "claude-sonnet-4-6" })) {
  // yields one full string today; streaming wire format will land per-provider.
}
```

### `ctx.blob`

Per-deploy key/value blob store. Files live under `<deploy-dir>/.pond/blobs/`. Metadata (key, contentType, size) is mirrored in the `_pond_blobs` table.

```ts
await ctx.blob.put("avatars/me.png", bytes, { contentType: "image/png" })
const got = await ctx.blob.get("avatars/me.png")
await ctx.blob.delete("avatars/me.png")
const all = await ctx.blob.list("avatars/")
```

Capsule users can read blobs via `GET /api/blob/<key>` (subject to whatever auth you wrap on top — there is no built-in ACL).

### `ctx.log`

```ts
ctx.log.info("user signed up", { userId: u.id })
ctx.log.error("payment failed", { reason: err.message })
```

In dev, entries stream over SSE on `/__pond/logs` and stay in memory (most recent 200). In prod (`pond start`), entries also append to `<deploy-dir>/.pond/logs.ndjson` with rotation at 5 MB.

---

## Handlers

### `query<TArgs, TResult>(handler: (ctx: CapsuleContext, ...args: TArgs) => TResult | Promise<TResult>): QueryHandler<TArgs, TResult>`

Wraps a function to mark it as a query. The return value is JSON-serialized. Mounted at two routes:

- `GET /api/query/:name` — for the no-arg case. Cacheable, shows up cleanly in the network panel.
- `POST /api/query/:name` with `{ args: [...] }` — for parameterized reads. Arguments are spread into the handler.

```ts
queries: {
  // No-arg read — hit via useQuery("messages").
  messages: query((ctx) => ctx.db.messages.orderBy("createdAt", "desc").all()),

  // Parameterized read — hit via useQuery("postById", id).
  postById: query((ctx, id: string) => ctx.db.posts.get(id)),

  // Multiple args work the same way — useQuery("search", keyword, limit).
  search: query((ctx, keyword: string, limit: number) =>
    ctx.db.posts.where("title", keyword).limit(limit).all()
  ),
}
```

On the client, `useQuery(name, ...args)` picks GET when called with no args and POSTs when args are passed. Args are part of the cache key, so changing them refetches automatically.

### `mutation<TArgs, TResult>(handler: (ctx: CapsuleContext, ...args: TArgs) => TResult | Promise<TResult>): MutationHandler<TArgs, TResult>`

Wraps a function to mark it as a mutation. Mounted at `POST /api/mutation/:name`. Arguments come from the JSON request body's `args` field (an array).

```ts
mutations: {
  sendMessage: mutation((ctx, body: string) =>
    ctx.db.messages.insert({ body, author: ctx.auth.userId })
  ),
}
```

### `endpoint(opts: { method: string; path: string }, handler: EndpointHandler): EndpointDefinition`

For custom HTTP routes — Stripe webhooks, REST APIs, etc. The handler receives the context and an `EndpointRequest`, and returns an `EndpointResponse`.

```ts
endpoints: {
  webhook: endpoint({ method: "POST", path: "/webhooks/stripe" }, async (ctx, req) => {
    const body = await req.text()
    // verify signature, dispatch...
    return json({ ok: true })
  }),
}
```

#### `EndpointRequest`

```ts
interface EndpointRequest {
  headers: Headers
  query: Record<string, string>
  json<T>(): Promise<T>
  text(): Promise<string>
  bytes(): Promise<ArrayBuffer>
}
```

#### Response helpers

- `json(body, init?)` → `application/json` with status 200 by default.
- `text(body, init?)` → `text/plain` with status 200 by default.

`init` is `{ status?: number; headers?: Record<string, string> }`.

---

## `capsule(def): CapsuleDefinition`

The top-level wrapper. Every `server/index.ts` exports the result as default.

```ts
export default capsule({
  schema: { ... },
  queries: { ... },
  mutations: { ... },
  endpoints: { ... },        // optional
  sockets: {                 // optional — WebSocket handlers
    chat: socket((ctx, ws) => {
      ws.on("message", (data) => ws.send(`heard: ${data}`))
    }),
  },
  allowedOrigins: ["..."],   // optional — additional CORS origins
  rateLimit: {               // optional — enforced before the handler runs
    addItem: { perMinute: 60, by: "user" },
    "/api/webhook": { perMinute: 1000, by: "ip" },
  },
  public: true,              // optional — opt into /gallery + pond fork
  title: "...",              // optional — public listing title
  description: "...",        // optional — public listing description
})
```

By default the capsule's HTTP endpoints reject cross-origin browser requests. List extra origins in `allowedOrigins` to opt them in.

### WebSockets

Each entry in `sockets` mounts at `/api/socket/<name>` on the same port as the rest of the capsule. The handler receives the same `ctx` your queries and mutations get (so `ctx.auth`, `ctx.db`, `ctx.ai`, `ctx.blob` all work) plus a `SocketLike`:

```ts
interface SocketLike {
  send(data: string): void
  close(code?: number, reason?: string): void
  on(event: "message", listener: (data: string) => void): void
  on(event: "close", listener: () => void): void
}
```

Clients connect at `wss://<deployId>.pond.run/api/socket/<name>`. The hosted control plane forwards the HTTP Upgrade to the deploy via a raw TCP pipe — no special config needed.

### Public capsules + fork

When you mark a capsule `public: true`, the host exposes:

- `GET /api/public-deploys` — listed at `/gallery` (the bare host renders a small JS client over this).
- `GET /api/public-deploys/:id/source` — unauthenticated source-tree export.

Anyone with the URL can run `pond fork https://<id>.pond.run` to scaffold a local copy ready for `pond dev`.

### Rate limits

`rateLimit[routeName]` matches query/mutation/endpoint names. `by: "user"` keys on the session cookie; `by: "ip"` (default) keys on the client IP. Buckets are rolling, in-process — restart resets them. Over-limit requests get a `429 Rate limited` with `Retry-After: 60`.

### Schema migrations

The runtime tracks tables in `_pond_migrations`. On boot:

- New tables are created with `CREATE TABLE IF NOT EXISTS …`.
- New columns are added via `ALTER TABLE ADD COLUMN`.
- Removing a column from your schema raises an error — to actually drop or rename a column you must run `pond db migrate --drop <table>.<col>` (or `--rename …`) explicitly. The runtime refuses destructive changes silently.

### Schema additive change example

```ts
// Before
schema: {
  items: table({ body: string() })
}
// After — adds `done` automatically the next time the capsule boots
schema: {
  items: table({ body: string(), done: boolean() })
}
```

### `/__pond/metrics`

Every running capsule exposes a Prometheus text-format snapshot at `/__pond/metrics`. Counters: `pond_route_requests_total`, `pond_route_errors_total`, `pond_route_duration_ms` (rolling p50/p95/p99). No auth — wrap with whatever proxy auth you use externally.

---

## Lifecycle

1. `pond dev` or `pond start` esbuilds `server/index.ts` into `.pond/server.mjs` (dev) or imports `.pond/deploy-bundle.mjs` (prod).
2. The runtime creates / opens SQLite at `.pond/data.db` with WAL mode.
3. For each table in `def.schema`, the runtime runs `CREATE TABLE IF NOT EXISTS …` (only the first time per table; tracked in `_pond_migrations`).
4. Routes get mounted:
   - `GET /api/query/<name>` for each entry in `def.queries`
   - `POST /api/mutation/<name>` for each entry in `def.mutations`
   - `<method> <path>` for each entry in `def.endpoints`
   - Plus the built-in `/auth/google`, `/auth/google/callback`, `/auth/me`, `/auth/signout`.
5. The runtime serves on the chosen port (default 3000).

There is no migration system beyond "create on first sight" right now. Schema changes that aren't additive require manual SQL against `.pond/data.db`.

---

## Related docs

- [`docs/client-reference.md`](./client-reference.md) — client-side hooks (`pond/client`).
- `README.md` — overall introduction, quickstart, hosted control plane, security model.
- `CONTRIBUTING.md` — local dev loop, where to add code, PR checklist.

---

<!-- source: docs/client-reference.md -->
<!-- raw: https://pond.run/docs/client-reference.md -->

# Pond Client API Reference

**Use this as the quick contract when building a Pond capsule client.** If you are an agent writing the UI, this page is the authoritative list of the hooks and components you may import from `pond/client`. Stick to the exports listed here — do not bring in React directly, do not pull in routing or query libraries from npm. Preact is the renderer; Tailwind classes are available without configuration.

This is the surface exported from `pond/client`. A capsule's `client/index.tsx` imports it to call queries, run mutations, and read auth state.

```ts
import { useQuery, useMutation, useAuth, SignInWithGoogle, signOut, render, h } from "pond/client"
```

The client is Preact-based. `render` and `h` are re-exported from Preact for convenience.

---

## `useQuery<T, TArgs>(name, ...args): { data, isLoading, error, refetch }`

Subscribes to a server query mounted at `/api/query/<name>`. Re-fetches whenever any mutation completes.

- Called with no args → `GET /api/query/<name>` (cacheable, visible as a plain GET in the network panel).
- Called with args → `POST /api/query/<name>` with `{ args: [...] }`. Args are spread into the server handler.

Args are part of the cache key — changing them refetches automatically.

```tsx
function MessageList() {
  const { data, isLoading, error, refetch } = useQuery<Message[]>("messages")
  if (isLoading) return <div>loading…</div>
  if (error) return <div>error: {error.message}</div>
  return (
    <ul>
      {data?.map((m) => (
        <li key={m.id}>{m.body}</li>
      ))}
    </ul>
  )
}

// Parameterized read — the server handler is `query((ctx, id: string) => ...)`.
function Post({ id }: { id: string }) {
  const { data: post } = useQuery<Post, [string]>("postById", id)
  return post ? <article>{post.body}</article> : null
}
```

### Return shape

| Field       | Type                  | Meaning                                                              |
| ----------- | --------------------- | -------------------------------------------------------------------- |
| `data`      | `T \| undefined`      | The most recent successful response, or undefined before first load. |
| `isLoading` | `boolean`             | True during the first load and during any refetch.                   |
| `error`     | `Error \| null`       | Set if the last fetch threw. Cleared on next successful refetch.     |
| `refetch`   | `() => Promise<void>` | Manually re-runs the query.                                          |

### Auto-refetch

Every component that calls `useQuery("foo")` subscribes to a shared listener set keyed by name. When any `useMutation` completes successfully, every active query refetches. This is intentionally aggressive — pond is for small apps where the network round-trip is cheap.

---

## `useMutation<TArgs, TResult>(name): [run, { isLoading, error }]`

Calls a server mutation at `POST /api/mutation/<name>`. Returns a tuple of the runner and the current state.

```tsx
function SendMessage() {
  const [send, { isLoading, error }] = useMutation<[string], Message>("sendMessage")
  return (
    <form
      onSubmit={async (e) => {
        e.preventDefault()
        const body = new FormData(e.currentTarget).get("body") as string
        await send(body)
      }}
    >
      <input name="body" />
      <button type="submit" disabled={isLoading}>
        send
      </button>
      {error && <div>{error.message}</div>}
    </form>
  )
}
```

The runner takes the same positional arguments the server mutation declares. Successful calls automatically refetch every active query.

---

## `useAuth(): AuthState`

Reads the current session via `GET /auth/me`.

```ts
interface AuthState {
  isLoading: boolean
  isGuest: boolean
  userId: string
  displayName?: string
  picture?: string
  email?: string
}
```

```tsx
function Header() {
  const auth = useAuth()
  if (auth.isLoading) return null
  if (auth.isGuest) return <SignInWithGoogle />
  return (
    <div>
      {auth.displayName} <button onClick={signOut}>sign out</button>
    </div>
  )
}
```

The hook listens for a `pond:auth-changed` window event and refetches when fired. `signOut()` dispatches that event after calling the server, so any `useAuth` consumer updates automatically.

---

## `SignInWithGoogle(props)`

A button that navigates to `/auth/google`. Pass any HTML button props to override styling or behavior; pass `onClick` to intercept (call `event.preventDefault()` to suppress the navigation).

```tsx
<SignInWithGoogle class="rounded px-3 py-2 bg-white text-black" />
```

Requires Google OAuth env vars on the server (`GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REDIRECT_URI`).

---

## `signOut(): Promise<void>`

Posts to `/auth/signout` and dispatches `pond:auth-changed` so active `useAuth` hooks refetch.

---

## `render`, `h`

Re-exported from `preact`. Use them to mount your root component if you need to do it manually. The bundler-generated HTML shell already mounts `<App />` for you, so most capsules don't need these directly.

---

## Related docs

- [`docs/api-reference.md`](./api-reference.md) — server-side API reference (`pond/server`).
- `README.md` — overall introduction and runtime model.

---

<!-- source: docs/cli-reference.md -->
<!-- raw: https://pond.run/docs/cli-reference.md -->

# pond CLI reference

Every `pond` subcommand, what it does, when to reach for it, and the flags that matter.

For the full list of any command's flags, run `pond <command> --help`. This page focuses on intent and common use.

---

## At a glance

| Command                             | What it does                                                            |
| ----------------------------------- | ----------------------------------------------------------------------- |
| [`pond new`](#pond-new)             | Scaffold a new capsule from a template or a free-form description       |
| [`pond dev`](#pond-dev)             | Start the local dev server with hot reload                              |
| [`pond deploy`](#pond-deploy)       | Ship the capsule — hosted on pond.run by default, `--local` for offline |
| [`pond start`](#pond-start)         | Run a previously-built local deploy bundle                              |
| [`pond inspect`](#pond-inspect)     | Peek at a running capsule's state (schema, sockets, ai, blob, env)      |
| [`pond logs`](#pond-logs)           | Stream a capsule's logs                                                 |
| [`pond db`](#pond-db)               | Inspect, dump, back up, and restore the capsule's SQLite                |
| [`pond fork`](#pond-fork)           | Clone a public capsule's source from a deploy URL                       |
| [`pond claim`](#pond-claim)         | Take ownership of an anonymous hosted deploy                            |
| [`pond signup`](#pond-signup)       | Friendly first-account flow — create a user + claim the current deploy  |
| [`pond host`](#pond-host)           | Run your own hosted control plane (the thing `pond deploy` deploys to)  |
| [`pond login`](#pond-login)         | Save a user API token for a control plane                               |
| [`pond dashboard`](#pond-dashboard) | Open the per-host project dashboard in your browser                     |
| [`pond user`](#pond-user)           | Create / list / promote users on a control plane (admin)                |
| [`pond token`](#pond-token)         | Rotate the saved user token                                             |
| [`pond domains`](#pond-domains)     | Attach a custom subdomain to a hosted deploy                            |
| [`pond env`](#pond-env)             | Set, unset, or list env vars on a hosted deploy                         |
| [`pond auth`](#pond-auth)           | Manage your local dev-server guest identity                             |

---

## pond new

Scaffold a new capsule directory. Accepts either a single slug (treated as a name) or a free-form description (treated as a prompt and slugified into a directory name).

**When to use:** starting a brand-new app. Combine with `--generate` if you want a locally-detected agent (hermes → claude → codex) to fill in the code from your prompt.

**Key flags:**

- `--template <name>` — start from a named template instead of the heuristic pick. Run `pond new --list-templates` to see them all.
- `--generate` — after scaffolding, invoke the first detected local agent to implement the prompt. With this flag a blank-canvas stub is written and the agent drives the design end-to-end.
- `--dir <name>` (alias `--name_flag`) — override the auto-derived directory name when passing a prompt.
- `--no-git` — skip `git init` in the new directory.

```sh
# Just a name → starts from the auto-picked template
pond new my-app

# A description → slugified to a dir, AGENTS.md captures the prompt
pond new "a kanban board with markdown cards"

# Same prompt, but an agent writes the code right after scaffolding
pond new "a habit tracker with streak counts" --generate

# Pick a specific template regardless of the prompt
pond new analytics-dash --template todo
```

---

## pond dev

Start the development server. Watches `server/index.ts`, `client/index.tsx`, `shared/`, and `.env.pond.server`, rebuilding on change. Serves the rendered HTML, the bundled JS, and the capsule's queries/mutations/sockets.

**When to use:** every time you're iterating on a capsule locally.

**Key flags:**

- `--port <n>` — preferred port (default `3000`). If it's busy the server walks forward up to 20 ports and tells you which one it landed on (added 0.2.5).

```sh
pond dev
# → pond dev server running at http://localhost:3000
```

---

## pond deploy

Build and ship the capsule. **Hosted by default** — bare `pond deploy` uploads to `https://pond.run`, prints the live URL, the IDE URL, and a one-time `claimToken` (saved to `.pond/deploy.json`).

The CLI picks the deploy target in this order:

1. `--local` → offline bundle (no upload). Run with [`pond start`](#pond-start).
2. `--api <url>` → use that control plane.
3. `.pond/deploy.json` already records an `apiUrl` from a previous deploy → redeploy to the same host. This is how `pond deploy` "remembers" which control plane this capsule lives on after the first deploy.
4. Otherwise → `https://pond.run` (anonymous). A `→ No deploy target set; uploading to https://pond.run …` notice prints before the upload so the behavior isn't invisible.

**Key flags:**

- `--local` — build a standalone bundle in `.pond/deploy-bundle.mjs` instead of uploading. Use for airgapped / self-host shipping.
- `--api <url>` — explicit control plane URL (e.g. `https://my-host.example.com`).
- `--token <value>` — user API token. Overrides `~/.pond/credentials.json` from `pond login`.
- `--push-env` — also upload `.env.pond.server` to the control plane (otherwise env stays local).
- `--public-inspect` — allow `/__pond/inspect` requests without a claim token (default: claim-token required).
- `--port <n>` — port baked into the local bundle (default `3000`, only meaningful with `--local`).

```sh
# Anonymous hosted deploy on pond.run — returns a live URL + claimToken
pond deploy

# Authenticated hosted deploy, env pushed
pond deploy --push-env

# Self-hosted control plane
pond deploy --api https://my-host.example.com

# Offline bundle for `pond start` / shipping yourself
pond deploy --local
```

After a hosted deploy, the URL + `claimToken` are saved to `.pond/deploy.json` (mode `0o600`). The token is one-time — claim or rotate immediately.

---

## pond start

Run a local deploy bundle previously produced by `pond deploy --local`.

**When to use:** smoke-testing a production build on your machine, or serving the bundle behind your own reverse proxy.

**Key flags:**

- `--port <n>` — port to bind (defaults to the `port` saved in `.pond/deploy.json`; falls back to `3000`).

```sh
pond deploy --local   # produces .pond/deploy-bundle.mjs
pond start            # serves it
PORT=8080 pond start  # env-var override
```

---

## pond inspect

Snapshot a running capsule: declared tables, registered sockets, AI providers, blob storage, env-var presence, runtime version.

**When to use:** confirming a deploy actually has the schema and config you expect, or debugging "is the env var set?" without rolling logs.

**Target resolution** (since 0.3.10):

1. `--local` → force `localhost:<port>`.
2. Explicit `target` positional (URL or `deployId`) → use it.
3. `.pond/deploy.json` exists with `url` + `claimToken` → **auto-target the hosted deploy**, prints `→ Inspecting <url>  (pass --local for the dev server)` to stderr.
4. Otherwise → fall back to `localhost:<port>`.

**Key flags:**

- `--port <n>` — localhost port (default `3000`).
- `--local` — force localhost even if `.pond/deploy.json` points at a remote.
- `<target>` (positional) — `deployId`, full URL, or omit to auto-target.

```sh
pond inspect                                  # auto: remote if deployed, else local
pond inspect --local                          # force local dev server
pond inspect https://abc.pond.run             # explicit remote
```

---

## pond logs

Stream stdout/stderr from a running capsule via Server-Sent Events.

**When to use:** watching live activity, or tailing a hosted deploy you just shipped.

**Target resolution:** identical to `pond inspect` — `--local` forces localhost, an explicit `--target` wins, otherwise auto-target the deploy in `.pond/deploy.json`, falling back to `localhost:<port>`.

```sh
pond logs                                     # auto-target the hosted deploy
pond logs --local                             # force local dev server
pond logs --target https://abc.pond.run       # explicit remote
```

A friendly hint fires if the chosen target refuses (`ECONNREFUSED`) — no more raw undici stack traces when the dev server isn't running.

---

## pond db

Inspect, dump, back up, and restore the capsule's SQLite database. Same target-resolution rules as `pond inspect` apply to every subcommand: `--local` to force localhost, explicit `--target`, or auto-target from `.pond/deploy.json`. Bare `pond db` now prints help cleanly (exit 0) instead of erroring with `No command specified`.

### `pond db list`

List table names declared in the schema.

```sh
pond db list
```

### `pond db dump [table]`

Dump rows. With no argument, dumps every table. With an argument, dumps that one.

```sh
pond db dump                # entire database
pond db dump habits         # one table
```

### `pond db backup --out <path>`

Snapshot the SQLite file to a local path (uses `VACUUM INTO` — safe to run on a live database).

```sh
pond db backup --out ./snap.sqlite
```

### `pond db restore --in <path>`

Upload a SQLite snapshot. The capsule writes it to `.pond/data.db.restored` — restart the process to swap it in.

```sh
pond db restore --in ./snap.sqlite
```

---

## pond fork

Clone a public capsule from a deploy URL. Pulls the source tree from `/api/deploys/<id>/source`, writes it to a new local directory, scaffolds `package.json` / `node_modules`, and you're ready for `pond dev`.

**When to use:** starting from someone else's capsule, or recovering source from a deploy you lost the local copy of.

**Key flags:**

- `<deploy>` (positional, required) — deploy URL like `https://abc.pond.run`, or a raw `deployId`.
- `--name <name>` — override the local directory name (default: derived from the capsule title or deployId).
- `--api <url>` — control-plane base URL. When omitted, the API base is derived from the deploy URL — but only `pond.run` / `*.pond.run` hosts are trusted by default. Pass `--api` explicitly to fork from any other control plane.
- `--no-git` — skip `git init`.
- `--allow-scripts` — permit forking a `package.json` that defines npm lifecycle scripts (`preinstall`/`install`/`postinstall`/`prepare`/`postprepare`). **Off by default**: those scripts run automatically on `npm install` and are an RCE vector if the upstream is hostile. The CLI refuses the fork and names the offending scripts unless this flag is passed. Since 0.3.11, the control plane also refuses to **accept** uploads that contain these scripts (POST/PUT `/api/deploys`, single-file PUT `/files/package.json`) — `--allow-scripts` only matters when forking from an older control plane or a self-hosted one that pre-dates the upload-side check.

```sh
pond fork https://abc.pond.run
pond fork https://abc.pond.run --name my-copy
```

---

## pond claim

Take ownership of an anonymous hosted deploy by presenting its `claimToken`, or reattach an already-owned deploy from a new machine. For the friendliest first-time path, use [`pond signup`](#pond-signup) — it wraps this command.

**Authorization rules:**

- **Anonymous deploy** (just created via `pond deploy`, not yet claimed): a valid `claimToken` is sufficient. Pair with `--signup <name>` to mint a new user in the same call, or with `Authorization: Bearer <token>` (an existing user account) to claim under that account.
- **Already-claimed deploy** (since 0.3.9): the `claimToken` alone is **not** sufficient to transfer ownership. The caller's bearer token must belong to the current owner or be an admin. Attempts by a different user are rejected with `403 "Deploy already owned by another account"` and audited as `deploy.claim_denied`. This closes the takeover path where a leaked `claimToken` could dispossess the original owner.

**When to use:** cross-machine ownership reattachment (the legitimate owner re-targeting from a new laptop), or explicit control over the claim step. For day-one "I just deployed and want an account," prefer `pond signup`.

**Key flags:**

- Reads `.pond/deploy.json` from the cwd for `deployId`, `apiUrl`, and `claimToken`.
- `--signup <name>` — create a new user with this username and claim in one shot (anonymous deploys only).
- `--api <url>` — override the API URL from `.pond/deploy.json`.

```sh
pond deploy
# → deployId: abc123…, claimToken: tk_…  (one-time disclosure; server stores only the hash)

pond claim                          # uses .pond/deploy.json (existing user, must be current owner)
pond claim --signup devgwardo       # claim + create user (anonymous deploys only)
```

Since 0.3.10, the control plane stores only `sha256(claimToken)` on disk — a backup leak no longer yields usable tokens. The plaintext lives only in your local `.pond/deploy.json` (mode `0o600`) and is returned once at create time.

---

## pond signup

Create an account on a pond control plane and claim the current anonymous deploy under it — in one command. Wraps [`pond claim --signup`](#pond-claim) with a name that matches the user's mental model.

**When to use:** day-one. You ran `pond new` and `pond deploy` and now you want an account that owns this deploy. This is the one-line shortcut.

**Key flags:**

- `<username>` (positional, required) — the username to create (matches `/^[a-z0-9_-]{1,32}$/i`).
- `--api <url>` — override the apiUrl from `.pond/deploy.json` (rarely needed — the deploy already knows where it lives).

```sh
# Three-command first-run flow
pond new my-app
pond deploy                # anonymous deploy on pond.run, gets a URL + claimToken
pond signup torrey         # account "torrey" created, this deploy claimed under it
```

If there's no `.pond/deploy.json` (you haven't deployed yet), `pond signup` refuses and points at `pond deploy`. Signup without an attached deploy is intentionally not supported — it would create a dangling account.

---

## pond host

Run your own pond control plane — the server that accepts `pond deploy`, builds bundles, hosts user workers, and serves anonymous deploys.

**When to use:** self-hosting pond instead of (or alongside) `pond.run`. Production: put it behind a TLS-terminating proxy and set `--public-base-url`.

**Key flags:**

- `--port <n>` — bind port (default `8787`).
- `--host <iface>` — interface to bind, default `127.0.0.1`. Use `0.0.0.0` to accept external traffic (only if you also have a TLS proxy).
- `--public-host <name>` — hostname used in returned deploy URLs (default `localhost`). Use this if your proxy hostname differs from `--host`.
- `--public-base-url <url>` — full external URL incl. scheme, e.g. `https://pond.example.com`. When set, deploy URLs use this base instead of `http://<public-host>:<port>`. Required when you're behind TLS.
- `--data-dir <path>` — where the control DB and deploy directories live (default `.pond-host`).
- `--anonymous-deploys` / `--no-anonymous-deploys` — toggle unauthenticated deploys (default: on).
- `--anonymous-grace <duration>` — how long an unclaimed deploy stays alive (`1h`, `30m`, `60s`). Default `1h`.
- `--anonymous-retention <duration>` — how long after termination an unclaimed deploy stays on disk. Default `7d`.
- `--anonymous-rate-per-hour <n>` — rolling-hour rate limit per IP for anonymous deploys. Default `5`.
- `--trust-proxy` — read client IPs from `x-forwarded-for` (you must terminate TLS yourself; otherwise this is spoofable). Also `POND_TRUST_PROXY_HEADERS=1`.
- `--abuse-email <addr>` — contact shown on the `/abuse` and `/security` pages.

```sh
# Local dev
pond host

# Production: bind all interfaces, public URL is HTTPS, abuse contact configured
pond host \
  --host 0.0.0.0 \
  --port 8787 \
  --public-base-url https://pond.example.com \
  --abuse-email abuse@example.com \
  --trust-proxy
```

On Node 22 LTS and later, anonymous deploys boot inside Node's permission model — `--allow-fs-read` / `--allow-fs-write` scoped to the deploy dir. On Node 20 the sandbox is disabled and `pond host` warns about it. (0.2.6 fixed the Node-24 spelling regression here.)

---

## pond login

Attach an existing user identity for a pond control plane. Saves the token to `~/.pond/credentials.json` so subsequent `pond deploy` calls don't need a `--token`.

**When to use:** you already have a token issued to you (from a previous `pond signup`, an admin minting it for you, or a self-hosted bootstrap). For first-time account creation, use [`pond signup`](#pond-signup) instead — it's a one-command path that doesn't require having a token in advance.

**Key flags:**

- `--api <url>` — control plane base URL. **Defaults to `https://pond.run`** (matches `pond deploy`).
- `--username <name>` — label for the saved credential. With `--token`, must match the token's actual user.
- `--token <value>` — attach an existing token (the common path).
- `--admin-token <value>` — admin token, used to create a new (non-admin) user on a self-hosted plane. Requires `--username`.

```sh
# Attach an existing token (most common path)
pond login --username devgwardo --token usr_xxxxxxxx

# Self-hosted control plane
pond login --api https://pond.example.com --username devgwardo --token usr_xxxxxxxx

# Self-hosted bootstrap (POND_HOST_TOKEN must be set, or pass --admin-token)
POND_HOST_TOKEN=<bootstrap-token> pond login --api https://pond.example.com --username admin
```

When you don't have a token yet, `pond login` errors with a three-way hint:

1. `pond signup <name>` (if you have an anonymous deploy in this directory),
2. `pond login --token <token> --username <name>` (if someone gave you a token),
3. `POND_HOST_TOKEN=…` or `--admin-token` (self-hosted bootstrap).

---

## pond dashboard

Open the per-host project dashboard in your browser. The dashboard is served by `pond host` at `<host>/dashboard` and lists every deploy owned by the signed-in user — project title, live URL, age, status (owned / shared / anonymous), plus per-deploy actions (Open IDE, Open live app, rotate claim token, delete). It's where you go after `pond deploy` to see "what have I shipped."

**When to use:** to see all your hosted projects in one place, jump into the IDE for any of them, or rotate a claim token after sharing a deploy.

**Resolution order** (so you rarely need to pass `--api`):

1. `--api <url>` if passed.
2. `apiUrl` from `.pond/deploy.json` in the current directory.
3. The only saved credential in `~/.pond/credentials.json` (if exactly one).
4. Otherwise: refuses with a list of `pond dashboard --api <url>` invocations, one per saved credential.

**Key flags:**

- `--api <url>` — pick a specific control plane.
- `--print-url` — print the URL instead of launching a browser. Use in headless / CI environments.

```sh
# Most common — opens whichever host this project deploys to
pond dashboard

# Pick a specific control plane
pond dashboard --api https://pond.run

# Headless / CI
pond dashboard --print-url --api https://pond.example.com
```

The dashboard authenticates with your account API token (the same one `pond login` saves to `~/.pond/credentials.json`). The first visit asks you to paste it; subsequent visits remember it in `localStorage`.

---

## pond user

Admin commands for users on a control plane.

### `pond user create <username> [--admin]`

Create a new user. Requires an admin token (loaded from `~/.pond/credentials.json` or `--token`).

```sh
pond user create alice
pond user create bob --admin
```

### `pond user list`

List users on the control plane.

```sh
pond user list
```

---

## pond token

Manage the saved user API token.

### `pond token rotate --api <url>`

Rotate the saved token for an API. The new token replaces the old in `~/.pond/credentials.json`.

```sh
pond token rotate --api https://pond.example.com
```

---

## pond domains

Manage custom subdomains for hosted deploys. Bare `pond domains` now prints help cleanly (exit 0) instead of erroring with `No command specified`.

### `pond domains list --api <url>`

List subdomains configured for the authenticated user.

### `pond domains add <subdomain> --deploy <deployId> --api <url>`

Attach a subdomain to a deploy. The subdomain becomes `https://<subdomain>.<your-host>`.

```sh
pond domains add my-app --deploy abc123 --api https://pond.example.com
```

### `pond domains remove <subdomain> --api <url>`

Detach a subdomain (the deploy keeps its `<deployId>.<host>` URL).

```sh
pond domains remove my-app --api https://pond.example.com
```

---

## pond env

Manage env vars on a hosted deploy. They're written to the deploy's `.env.pond.server` and re-read on the next worker boot. Bare `pond env` now prints help cleanly (exit 0) instead of erroring with `No command specified`.

### `pond env list [deployId] --api <url>`

List env-var names (values are not returned).

### `pond env set <deployId> KEY=value [KEY2=value2 …] --api <url>`

Set one or more env vars in a single call.

```sh
pond env set abc123 OPENAI_API_KEY=sk_… SENTRY_DSN=https://… --api https://pond.run
```

### `pond env unset <deployId> KEY [KEY2 …] --api <url>`

Delete env vars.

```sh
pond env unset abc123 OLD_FLAG --api https://pond.run
```

---

## pond auth

Manage the local dev-server guest identity. Useful only against `pond dev` — production deploys use real auth.

### `pond auth as --name <name>`

Set the current guest's display name so you can simulate a logged-in user without a real OAuth flow.

```sh
pond auth as --name devgwardo
```

---

## Environment variables

A few useful ones:

| Variable                   | Purpose                                                                                                                       |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `POND_SESSION_SECRET`      | Persistent session-cookie secret. Set this in `.env.pond.server` for production; without it sessions don't survive a restart. |
| `POND_TRUST_PROXY_HEADERS` | Same effect as `--trust-proxy` on `pond host`.                                                                                |
| `POND_PUBLIC_BASE_URL`     | Same effect as `--public-base-url` on `pond host`.                                                                            |

---

## See also

- [Server API reference](./api-reference.md) — `capsule()`, `table()`, `query()`, `mutation()`, `ctx.db`, lifecycle.
- [Client API reference](./client-reference.md) — `useQuery`, `useMutation`, `useAuth`, sign-in helpers.
- [Operations guide](./operations.md) — running `pond host` in production.
- [llms-full.txt](./llms-full.txt) — single-file dump of the agent-targeted docs.

---
