Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@ If you change the payload contract, update the code, docs, examples, and the Ope
### Diff handling
- `src/lib/diff/git-patch.ts` - patch parsing support for diff rendering

### Self-hosted UUID mode (optional server variant)
- `selfhosted/server.ts` - HTTP server with API routes and UUID page rendering
- `selfhosted/db.ts` - SQLite persistence (CRUD, TTL refresh, cleanup)
- `selfhosted/ttl.ts` - TTL constants and helpers
- `selfhosted/validate.ts` - payload validation for the API
- `selfhosted/tsconfig.json` - TypeScript config for server compilation
- `selfhosted/Dockerfile` - multi-stage Docker build
- `selfhosted/docker-compose.yml` - Docker Compose deployment

### Docs and external contract
- `README.md`
- `docs/architecture.md`
Expand All @@ -138,6 +147,7 @@ If you change the payload contract, update the code, docs, examples, and the Ope
- `docs/dependency-notes.md`
- `docs/testing.md`
- `skills/agent-render-linking/SKILL.md`
- `skills/selfhosted-agent-render/SKILL.md`

## Development commands

Expand Down
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,41 @@ Built for the OpenClaw ecosystem, `agent-render` focuses on fragment-based shari

## Principles

- Fully static export with Next.js App Router
- No backend, no database, no server-side persistence
- Fragment-based payloads (`#...`) so the server never receives artifact contents
- Fully static export with Next.js App Router (default product)
- Fragment-based payloads (`#...`) so the static host never receives artifact contents
- Public-safe naming and MIT-compatible dependencies
- Optional self-hosted mode for server-backed UUID links (see below)

## Self-Hosted UUID Mode (Optional)

In addition to the default static/fragment-based product, `agent-render` includes an optional self-hosted server mode that stores payloads in SQLite under UUID keys.

**When to use it:**
- Payloads exceed the ~8 KB fragment budget
- Links are shared on platforms that mangle long URLs
- You want short, stable links like `https://host/{uuid}`
- Agent-driven workflows that create and manage artifacts programmatically

**What it provides:**
- REST API for creating, reading, updating, and deleting artifacts
- UUID-based viewer links that render the same UI as fragment links
- 24-hour sliding TTL with automatic expiry
- SQLite storage — no external database required
- Docker Compose and daemon deployment options

**Quick start:**

```bash
npm run build
npm run selfhosted:dev
```

Then create an artifact via the API and visit `http://localhost:3000/{uuid}`.

See `docs/deployment.md` for Docker Compose, daemon, and auth setup options.
See `skills/selfhosted-agent-render/SKILL.md` for agent workflow guidance.

The self-hosted mode is an add-on. The existing static fragment-based viewer remains the default product and continues to work as-is.

## Local Development

Expand Down Expand Up @@ -79,9 +110,10 @@ The shell keeps first load lean and defers renderer-heavy code until needed. The

- `docs/architecture.md` - architecture and tradeoffs
- `docs/payload-format.md` - fragment protocol, limits, and examples
- `docs/deployment.md` - deployment notes
- `docs/deployment.md` - deployment notes (including self-hosted mode)
- `docs/dependency-notes.md` - major dependency and license notes
- `docs/testing.md` - test commands, screenshot workflow, and CI notes
- `skills/selfhosted-agent-render/SKILL.md` - self-hosted UUID mode skill for agents

## Zero Retention

Expand Down
46 changes: 46 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,49 @@ The static host does not receive fragment contents as part of the request, but t
- GitHub Pages-compatible `basePath` and `assetPrefix`
- `.nojekyll` included for Pages compatibility
- Fragment size budget enforced before render

## Self-hosted UUID mode (optional)

The repository includes an optional self-hosted server mode in `selfhosted/` that provides UUID-based artifact links backed by SQLite. This is a separate deployment mode — the static export remains the default product.

### How it works

The self-hosted server is a standalone Node.js HTTP server that:

1. Serves the pre-built `out/` static files (the same frontend as the static product)
2. Exposes a REST API at `/api/artifacts` for CRUD operations on stored payloads
3. Handles `GET /:uuid` requests by looking up the payload in SQLite, injecting it into the viewer page via `window.__AGENT_RENDER_PAYLOAD__`, and serving the result

The `ViewerShell` component checks for `window.__AGENT_RENDER_PAYLOAD__` on mount. When present, it uses the injected payload string instead of reading `window.location.hash`. This feeds into the same decode → normalize → render pipeline, so all viewer features (copy, download, print-to-PDF, diff modes, artifact switching) work identically.

### Storage

SQLite with a single `artifacts` table:

- `id` (UUID v4 primary key)
- `payload` (the agent-render payload string)
- `created_at`, `updated_at`, `last_viewed_at` (ISO timestamps)
- `expires_at` (sliding TTL, refreshed on each successful read)

### TTL

Artifacts use a 24-hour sliding TTL. Each successful read (API or viewer) extends `expires_at` by 24 hours. Expired entries are lazily deleted on read and can also be batch-cleaned via `POST /api/cleanup`.

### Separation from static mode

The self-hosted mode does not alter the static export pipeline:

- `next.config.ts` remains `output: "export"`
- The frontend change is a single mount-time check for an injected payload — a no-op in static/fragment mode
- All existing fragment-based functionality, tests, and deployments are unaffected
- Self-hosted code lives entirely in `selfhosted/` and is not bundled into the static export

### Key files

- `selfhosted/server.ts` — HTTP server with API routes and UUID page rendering
- `selfhosted/db.ts` — SQLite persistence (CRUD, TTL refresh, cleanup)
- `selfhosted/ttl.ts` — TTL constants and helpers
- `selfhosted/validate.ts` — Payload validation for the API
- `selfhosted/Dockerfile` — Multi-stage Docker build
- `selfhosted/docker-compose.yml` — Docker Compose deployment
- `selfhosted/tsconfig.json` — TypeScript config for server-side compilation
2 changes: 2 additions & 0 deletions docs/dependency-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- `lz-string` - MIT
- `fflate` - MIT
- `mermaid` - MIT
- `better-sqlite3` - MIT (self-hosted mode only)

## Notes

Expand All @@ -32,6 +33,7 @@
- `papaparse` plus `@tanstack/react-table` keeps CSV parsing and rendering readable without coupling to a heavyweight data-grid framework.
- `fflate` provides portable deflate/inflate support across iOS Safari and Android Chromium without relying on browser-specific compression streams.
- `mermaid` renders diagram definitions (flowcharts, sequence diagrams, etc.) to SVG client-side. Dynamically imported within the markdown renderer so it does not affect initial bundle size.
- `better-sqlite3` provides synchronous SQLite access for the optional self-hosted server mode. Only used by `selfhosted/` code and not bundled into the static frontend export.

## Notable removals

Expand Down
95 changes: 95 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,98 @@ Cloudflare Pages works well with the current project shape.
- Environment variable: set `NEXT_PUBLIC_BASE_PATH` only if you intentionally deploy under a subpath

If you deploy at the domain root on Cloudflare Pages, leave `NEXT_PUBLIC_BASE_PATH` unset.

## Self-hosted UUID mode (optional)

The repository includes an optional self-hosted server that stores artifact payloads in SQLite and serves them under UUID links. This is a separate deployment from the static export and is intended for power users and agents.

### Quick start

```bash
npm ci
npm run build
npm run selfhosted:dev
```

The server starts on port 3000. Create artifacts via `POST /api/artifacts` and view them at `http://localhost:3000/{uuid}`.

### Environment variables

| Variable | Default | Description |
| --------- | --------------------------- | ------------------------------ |
| `PORT` | `3000` | Server listen port |
| `HOST` | `0.0.0.0` | Server bind address |
| `DB_PATH` | `./data/agent-render.db` | SQLite database file path |
| `OUT_DIR` | `out` | Path to the static build output|

### Docker Compose

```bash
cd selfhosted
docker compose up -d
```

This builds the frontend, compiles the server, and starts it with a persistent SQLite volume.

To rebuild after code changes:

```bash
docker compose up -d --build
```

### Daemon / systemd

Build the server:

```bash
npm run selfhosted:build
```

Then create a systemd unit:

```ini
[Unit]
Description=agent-render self-hosted server
After=network.target

[Service]
ExecStart=/usr/bin/node /path/to/agent-render/selfhosted/dist/server.js
Environment=PORT=3000
Environment=DB_PATH=/var/lib/agent-render/agent-render.db
Restart=on-failure
User=agent-render
WorkingDirectory=/path/to/agent-render

[Install]
WantedBy=multi-user.target
```

### pm2

```bash
npm run selfhosted:build
pm2 start selfhosted/dist/server.js --name agent-render
```

### Storage

The server uses SQLite with WAL mode. The database file is created automatically at the path specified by `DB_PATH`. The parent directory is created if it does not exist.

Artifacts have a 24-hour sliding TTL. Each successful view extends the expiry. Expired entries are lazily cleaned on read and can be batch-removed via `POST /api/cleanup`.

### Auth and access control

The self-hosted server does not include built-in authentication. Options for protecting it:

- **Public**: No additional configuration. Fine for non-sensitive artifacts.
- **Cloudflare Tunnel + Zero Trust**: Expose the server through a Cloudflare Tunnel and add Access policies for authentication. This is the recommended approach for remote access with SSO.
- **Reverse proxy**: Place behind nginx, Caddy, or Traefik with HTTP basic auth, OAuth2 proxy, or mTLS.
- **Local only**: Set `HOST=127.0.0.1` to bind to localhost only.

### Same-machine deployment

The simplest pattern is running the server on the same machine as the agent. The agent creates artifacts via `http://localhost:3000/api/artifacts` and returns viewer links. No network exposure required unless you want remote access.

### Relationship to static mode

The self-hosted mode is an add-on. The existing static export (`npm run build` → `out/`) remains the default product. The self-hosted server serves those same static files plus the API and UUID routes. The two modes can coexist in the same repository without conflict.
12 changes: 12 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ The suite is intentionally split by responsibility:
- component tests protect selector/disclosure UI contracts
- unit tests protect transport codecs, envelope validation, diff parsing, and language inference

## Self-hosted mode tests

The self-hosted server has its own test suite under `tests/selfhosted/`:

- `db.test.ts` — CRUD operations, TTL refresh, expiry cleanup
- `validate.test.ts` — Payload validation rules
- `ttl.test.ts` — TTL computation and expiry checks

These tests use `// @vitest-environment node` to run with Node.js instead of jsdom, since they depend on `better-sqlite3` (a native module).

They run as part of the standard `npm run test` command.

## CI

The repository includes `.github/workflows/test.yml`, which installs Playwright browsers and runs `npm run test:ci` on pushes, pull requests, and manual dispatch.
Loading
Loading