# Seer — publishing HTML bundles as an agent

Seer is a personal preview host for self-contained HTML bundles. You (an AI agent)
zip up a page you built, `PUT` it here with a bearer token, and Seer returns a stable,
versioned URL a human can open in a browser. Re-uploading the same slug creates a new
version and live-reloads any viewer that already has the page open. This is the place
to put richer output than a chat reply can carry — dashboards, small apps, interactive
reports — instead of pasting a wall of code.

You need two things, which the human has given you (typically as environment
variables): the base URL of this Seer instance (`https://seer.build`) and an API token
(referred to below as `$API_TOKEN`). Keep the token secret; it is the only write
credential.

## 1. Build the zip

- The zip must contain a root `index.html` (at the top level of the archive, not
  inside a subdirectory). That is what loads at the bundle URL.
- Use **relative** asset paths (`./style.css`, `assets/app.js`, `img/logo.png`).
  Absolute paths like `/style.css` will not resolve, because the bundle is served
  under `/b/<slug>/`.
- Nested directories are fine. Directory requests fall back to their `index.html`.
- Prefer a self-contained bundle (inline or bundled JS/CSS, or assets shipped inside
  the zip). External network requests are the human's browser's problem, not Seer's.
- Zip the **contents** of your build directory, not the directory itself, so
  `index.html` lands at the root of the archive:

```sh
# from inside the build directory:
zip -r ../bundle.zip .
```

Default size limit is 50 MB. Unsafe zip entries (absolute paths, `..`, null bytes)
are rejected.

## 2. Upload it

Pick a slug matching `[a-z0-9][a-z0-9-]{0,63}` (lowercase letters, digits, hyphens;
must start with a letter or digit; up to 64 characters). Send the zip as the raw
request body with `--data-binary` (not multipart):

```sh
curl -X PUT --data-binary @bundle.zip \
  -H "Authorization: Bearer $API_TOKEN" \
  https://seer.build/api/bundles/<slug>
```

`PUT` and `POST` behave identically. Each successful call creates the next version
for that slug.

## 3. Read the response

A successful upload returns `200` with JSON:

```json
{
  "slug": "<slug>",
  "version": 1,
  "url": "https://seer.build/b/<slug>/",
  "versionUrl": "https://seer.build/b/<slug>/v/1/",
  "bytes": 2048,
  "files": 3,
  "hasIndexHtml": true
}
```

- `url` is the **latest** URL: it always shows the newest version and live-reloads.
  Hand this one to the human in most cases.
- `versionUrl` is a **pinned** URL for this exact version; it never changes and does
  not live-reload. Use it when you want to reference a specific build permanently.
- Check `hasIndexHtml`: if it is `false`, you forgot the root `index.html` and the
  bundle URL will 404. Re-zip and re-upload.

Error responses are JSON with an `error` field. Notable statuses: `400` (invalid
slug, empty body, or bad zip), `401` (invalid or missing token), `413` (zip exceeds
the size limit).

## 4. Iterating

Upload the same slug again to publish a new version. Any browser tab already open on
the latest `url` reloads itself automatically. You do not need to send a new link —
the old one keeps working and updates in place.

## 5. Listing what is published

```sh
curl -H "Authorization: Bearer $API_TOKEN" https://seer.build/api/bundles
```

Returns every bundle with its full version history (slugs, versions, sizes,
timestamps).

## Sharing and viewing

Bundle URLs (`/b/<slug>/`) are **public** — anyone with the link can open it in a
browser, no sign-in required. Hand the `url` to whoever should see it, or open it
yourself. You can also fetch it back to verify the rendered page: a GET on the
bundle URL returns the served `index.html` (the latest URL has the live-reload
script injected before `</body>`).

Only the write side and the inventory are private: uploading needs the API token,
and the list of every bundle (`GET /api/bundles`) needs the token too. Individual
bundle links do not.
