# Building on Coline: instructions for coding agents

Coline is a team workspace. Its SDK builds two things from one `app.config.ts`:

- **Sites**: a public website at `name.coline.space` (or a custom domain) whose data lives in a Coline workspace. No account is needed to build or deploy one.
- **Apps**: tools, screens, file types, schedules and integrations installed into a workspace. Pushing one needs the user signed in with `npx coline-app login`.

If the user asks for a website, a landing page, a form, a waitlist, a guestbook, a poll or any small public web app, build a **site**.

## Prerequisites

- Node.js 20 or later, with npm.
- Nothing else for a site: no account, login, key or global install.

## Build a site

```bash
npx create-coline-app my-site --template site
cd my-site && npm install
npm run check        # types, manifest and bundles; uploads nothing
npm test             # the template's tests, against the same rules Coline enforces
npm run dev          # http://127.0.0.1:3000, in-memory data
```

Read the generated `AGENTS.md` in the project. It is the full contract.

Files:

- `app.config.ts`: the app. `surfaces.site` declares the page (`entry`), static files (`assets`), routes with their `<head>`, visitors (sign-in and access), uploads, endpoints and which tools visitors may run. `collections` declares who can read, create, update and delete each collection, and a zod `schema` for its records.
- `site/main.tsx`: the React page. Talk to Coline only through `@colineapp/sdk/site/react` (hooks) or `@colineapp/sdk/site`.
- `site/app.css`: `@import "tailwindcss";` Tailwind is compiled for you.
- `site/public/`: favicon and images, served at the site root.

Build the page from Coline's components, `@colineapp/ui` (the template already does). They match Coline and switch between light and dark with the visitor's device. Don't hand-roll buttons, inputs, cards or dialogs:

```tsx
import { Button, Card, CardContent, ColineSiteProvider, EmptyState, Field, FieldLabel, Input } from "@colineapp/ui";
import "@colineapp/ui/styles.css"; // then "./app.css": @import "tailwindcss"; @import "@colineapp/ui/theme.css";
createRoot(root).render(<ColineSiteProvider><App /></ColineSiteProvider>);
```

A site looks like Coline; its one design choice is an accent color:

- Use the components as they ship: pick a `variant` and `size`, and use `className` on them only for layout (margin, width, flex, grid). Don't change their colors, borders, radius, padding or fonts, in classes or in CSS.
- No custom fonts (Geist ships with the library), no palette of your own, no fixed colors. Style your own layout with the token classes (`bg-card`, `text-muted-foreground`, `border-border`, `text-destructive`).
- Use `Tabs` or `SegmentedControl` for views and navigation.
- Accent: `<ColineSiteProvider accent="#0f766e">` recolors selection, radio choices, the selected date, steps, info badges and the first chart series, in light and dark. Use it in your layout with `bg-action` / `text-action`.

`npm run check` warns when a site breaks these. Component list: https://docs.coline.app/sites/design.md

In the page, use hooks typed from the app:

```tsx
import { createSiteHooks, SignInButton, useSession, useUpload } from "@colineapp/sdk/site/react";
import type app from "../app.config"; // import type: keeps server code out of the page

const { useCollection, usePaginatedCollection, useTool } = createSiteHooks<typeof app>();
const notes = useCollection("notes", { orderBy: "createdAt", order: "desc" }); // live, typed
await notes.insert({ text: "Hi" });
const add = useTool("my-site.add_note"); // add.run(input) is type-checked
```

`Router`, `Routes`, `Route`, `Link`, `useParams` and `useLocation` route on the client; list every path in `surfaces.site.routes` too.

What a site can do:

| Need | Use |
| --- | --- |
| Accounts | `visitors: { signIn: true, providers: ["google", "coline"] }` and `SignInButton` |
| Members only | `visitors.access: "signedIn"`, or `"approved"` for team-approved visitors |
| Per-user data | a collection with `read: "owner"`, `create: "visitor"` |
| Validation | `schema` on the collection; tool `input` schemas |
| Server logic | `defineTool`, listed in `surfaces.site.tools` |
| File uploads | `surfaces.site.uploads` and `useUpload()` (needs a claim) |
| Webhooks, JSON APIs, feeds | `defineEndpoint` and `surfaces.site.endpoints` (needs a claim) |
| Live updates | `useCollection` (built in) |
| Browser AI agents | `surfaces.site.agentTools: true` |

Rules that save a failed deploy:

- `key` is lowercase kebab-case and globally unique. Every tool name starts with `<key>.`.
- Declare every permission you use. A site needs `site.serve`; collections and uploads need `storage.app`; visitor-callable tools need `ai.tools`; endpoints need `endpoints.receive`.
- Collection rules are `public`, `visitor`, `signedIn`, `owner` or `team` (the default: invisible to visitors). `create` can't be `owner`.
- The page can only call its own site. `fetch` to other origins is blocked by the page's content policy. Call outside services from a site tool with `network.external` instead.
- Writes that need checks go through a tool listed in `surfaces.site.tools`. Tools run as the app, never above the `write` effect for visitors, and see the visitor as `ctx.actor`.
- Never build passwords or your own login. Use sign-in. Sites deployed without an account are rejected if they contain password fields.
- Visitors can't use key-value storage or bulk writes. Use records, or do it in a tool.
- Validate input in tools and check ownership there. Hiding a button is not an access check.

Locally, `npm run dev` enforces the same rules. Open `/?as=alice` to be a signed-in visitor named alice, `/?as=guest` for a new guest.

## Deploy

```bash
npx coline-app deploy --json
```

Prints `{ ok: true, data: { url, siteId, claimUrl, expiresAt, ... } }`. Without a sign-in the site is live for 72 hours after its last deploy. **Give the user the `claimUrl`**: they open it, sign in and pick a workspace to keep the site. Redeploy from the same folder to update the same site; `.coline/site.json` holds its deploy token.

To keep the site in the user's workspace, sign in once:

```bash
npx coline-app login   # waits until the user approves in their browser
```

It opens a browser, or, with no browser (SSH, a sandbox), prints a link and a code. **Show the user the link and code** and wait: they approve and pick a workspace. Then `deploy` goes straight into that workspace and the site never expires; it also claims a site this folder deployed before, keeping its address. `--name my-site` picks `my-site.coline.space`. `npx coline-app whoami --json` shows the signed-in workspace. In CI, `COLINE_API_KEY` (a workspace key with `apps.write`) replaces the sign-in.

Check a deployed site:

```bash
npx coline-app inspect --json     # address, state, version, limits, usage, routes
npx coline-app logs --json        # tool runs with the real error text
npx coline-app db dump --json     # records in every collection
npx coline-app db export --out data.json
npx coline-app storage list --json
npx coline-app sites list --json  # needs the sign-in
npx coline-app sites delete --yes
```

`logs`, `db dump` and `db export` take `--port 3000` to read the local dev server instead.

Exit codes: `0` ok, `1` error, `2` needs sign-in or a claim, `3` over a limit. Errors carry a stable `code` and a `fix`:

| Code | Fix |
| --- | --- |
| `APP_KEY_TAKEN` | If the user's own unclaimed site holds the key, run `npx coline-app claim` (signed in) from its folder; otherwise change `key` and rename the tools |
| `SITE_MANIFEST_INVALID` | Fix the field the message names |
| `SITE_VERSION_EXISTS` | Bump `version` in package.json |
| `SITE_CONTENT_BLOCKED` | Remove password fields; use sign-in |
| `SITE_DEPLOY_TOKEN_INVALID` | The site expired or was claimed; delete `.coline/site.json` or run `npx coline-app login` |
| `NOT_SIGNED_IN`, `LOGIN_EXPIRED` | Run `npx coline-app login` |
| `OAUTH_WORKSPACE_MISMATCH` | The site is in another workspace; log in again and pick it |
| `SITE_DEPLOY_TOO_LARGE` | Over 30 MB; host large media elsewhere |

At runtime: `record_forbidden` (collection rule), `record_invalid` (schema), `sign_in_required` / `approval_required` (access mode), `unavailable_to_visitors` (move the call into a tool).

## Limits

An unclaimed site: 10,000 requests and 1,000 tool calls a day, 25 MB of records, 50 MB of stored assets (each deploy uploads up to 30 MB, about 20 MB of it assets), no uploads, endpoints, secrets, outside websites or AI until claimed. Claiming turns on what the site declares. Claimed sites on the free plan keep the same daily limits and get 1 GB of uploads per workspace; Workspace Pro raises them, gives each site 100 GB of uploads and adds custom domains. Everything is included in the plan.

## Build an app for a workspace

```bash
npx create-coline-app my-app            # or --template editor | integration | external-backend
cd my-app && npm install && npm run check
npx coline-app preview                  # local preview, no credentials
npx coline-app login                    # once; the user approves in a browser
npx coline-app push
```

Never ask for or invent API keys; `login` is how the user grants access.

## Docs

- Sites: https://docs.coline.app/sites.md
- Data and rules: https://docs.coline.app/sites/data.md
- Design and components: https://docs.coline.app/sites/design.md
- Visitors, sign-in and access: https://docs.coline.app/sites/visitors.md
- Files and uploads: https://docs.coline.app/sites/files.md
- Endpoints and browser agents: https://docs.coline.app/sites/endpoints.md
- Deploy, claim and the CLI: https://docs.coline.app/sites/deploy.md
- Limits: https://docs.coline.app/sites/limits.md
- Build an app: https://docs.coline.app/sdk.md
- SDK reference: https://docs.coline.app/sdk/reference.md
- Every page: https://docs.coline.app/llms.txt
