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
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{
"name": "pagent",
"source": "./",
"description": "Generative UI for terminal-bound AI agents — render interactive browser UIs (forms, pickers, dashboards, confirmations) via the A2UI protocol. Ships a stdio MCP and skill, with hosted rendering at pagent.vercel.app or self-hosted via PAGENT_URL.",
"description": "Generative UI for terminal-bound AI agents — render interactive browser UIs (forms, pickers, dashboards, confirmations) via the A2UI protocol. Ships a stdio MCP and skill, with hosted rendering at pagent.link or self-hosted via PAGENT_URL.",
"category": "productivity",
"tags": ["generative-ui", "agentic-ui", "a2ui", "mcp"]
}
Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "pagent",
"version": "0.0.1",
"description": "Generative UI for terminal-bound AI agents — render interactive browser UIs (forms, pickers, dashboards, confirmations) via the A2UI protocol. Ships a stdio MCP and skill, with hosted rendering at pagent.vercel.app or self-hosted via PAGENT_URL.",
"description": "Generative UI for terminal-bound AI agents — render interactive browser UIs (forms, pickers, dashboards, confirmations) via the A2UI protocol. Ships a stdio MCP and skill, with hosted rendering at pagent.link or self-hosted via PAGENT_URL.",
"author": "Alexandro T. Netto",
"homepage": "https://github.com/blockful/pagent",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
# VITE_API_URL is required by `vite build` (see apps/web/vite.config.ts).
# CI uses a placeholder; production deploys set the real value in Vercel.
env:
VITE_API_URL: https://pagent.up.railway.app
VITE_API_URL: https://api.pagent.link
run: npm run build:web

- name: Verify MCP bundle is up to date
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/vercel-alias-pagent.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: Pin pagent.vercel.app to latest production
name: Pin pagent.link to latest production

# Re-alias pagent.vercel.app to whichever pagent project deployment Vercel
# has just promoted to production. Required because the bare pagent.vercel.app
# subdomain is owned by another team's project at the project-domain layer
# Re-alias pagent.link to whichever pagent project deployment Vercel
# has just promoted to production. Required because the bare pagent.link
# domain is owned by another team's project at the project-domain layer
# (Vercel rejects adding it as our project's auto-tracking domain), so we
# can serve from it via deployment aliases but it does NOT auto-update on
# new prod deploys. This workflow does the re-alias for us.
Expand All @@ -29,7 +29,7 @@ jobs:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_PROJECT_ID: prj_JhOxWsbBLRb1Sdo19Thbz4eHOyj7
VERCEL_TEAM_ID: team_zDJQ6yrYdOu79gJBmUIBVYCV
ALIAS: pagent.vercel.app
ALIAS: pagent.link
steps:
- name: Skip if VERCEL_TOKEN is unset
id: gate
Expand Down
4 changes: 2 additions & 2 deletions PRD.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# PRD — Agent UI Session
# PRD — Pagent

## Problem

Expand Down Expand Up @@ -36,7 +36,7 @@ guarantee built into V0.
sequenceDiagram
autonumber
participant A as Agent (via MCP)
participant S as agent-ui-session service
participant S as pagent service
participant U as User (browser)

A->>S: POST /new { spec }
Expand Down
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

Hosted UI rendering for terminal-bound AI agents. The agent emits an A2UI surface to this service, prints a short URL, and reads the user's interactions back via API.

- **Live API:** https://pagent.up.railway.app
- **Live renderer:** https://pagent.vercel.app
- **Live API:** https://api.pagent.link
- **Live renderer:** https://pagent.link

See [PRD.md](./PRD.md) for the design and [HANDOFF.md](./docs/HANDOFF.md) for build context.

Expand Down Expand Up @@ -91,7 +91,7 @@ Each app validates its environment at boot/build with Zod and fails loudly on mi
| | `OTEL_EXPORTER_OTLP_*` | optional | OpenTelemetry exporter config. Leave `OTEL_EXPORTER_OTLP_ENDPOINT` unset to disable tracing. |
| **web** ([`.env.example`](apps/web/.env.example)) | `VITE_API_URL` | **`vite build`** | Valid URL. Inlined at build time and embedded in CSP. `vite dev` allows it unset (uses Vite proxy). |
| | `API_PORT` / `CLIENT_PORT` | optional (dev only) | Valid port (1–65535). Defaults `8787` / `8788`. |
| **mcp** ([`.env.example`](apps/mcp/.env.example)) | `PAGENT_URL` | optional | Valid URL when set. Default `https://pagent.up.railway.app`. |
| **mcp** ([`.env.example`](apps/mcp/.env.example)) | `PAGENT_URL` | optional | Valid URL when set. Default `https://api.pagent.link`. |

When validation fails, the process logs the offending field and exits with a non-zero code — CI catches misconfigured deploys (`build:web` runs in CI with a placeholder `VITE_API_URL`) before they ship.

Expand Down Expand Up @@ -120,17 +120,17 @@ You should see `pagent` listed with `show_ui` and `check_result` tools. The plug

> "Use the pagent skill to ask me my favorite color via a UI form."

The agent calls `show_ui`, prints a URL (hosted at `https://pagent.vercel.app`), you submit, and the conversation continues.
The agent calls `show_ui`, prints a URL (hosted at `https://pagent.link`), you submit, and the conversation continues.

**Point at a different service?** Set `PAGENT_URL` before launching Claude. By default the MCP talks to `https://pagent.up.railway.app`.
**Point at a different service?** Set `PAGENT_URL` before launching Claude. By default the MCP talks to `https://api.pagent.link`.

## Use it from any MCP client (HTTP transport)

Beyond the Claude Code plugin, pagent's MCP also speaks the streamable HTTP transport — so any MCP-capable client (Codex, OpenCode, Cursor, Cline, Continue, etc.) can connect with one command and zero local install:

```bash
# Claude Code, without the plugin (HTTP MCP, scoped to the current project)
claude mcp add --scope project --transport http pagent "https://pagent.up.railway.app/mcp"
claude mcp add --scope project --transport http pagent "https://api.pagent.link/mcp"
```

For other clients, drop this into whichever `mcp.json` / config file they read:
Expand All @@ -140,7 +140,7 @@ For other clients, drop this into whichever `mcp.json` / config file they read:
"mcpServers": {
"pagent": {
"type": "http",
"url": "https://pagent.up.railway.app/mcp"
"url": "https://api.pagent.link/mcp"
}
}
}
Expand Down Expand Up @@ -196,7 +196,7 @@ To bypass in an emergency: `git push --no-verify` (don't make this a habit).
1. Create a new Railway service from this repo.
2. Set **Root Directory** to `apps/api` so Railway picks up the railway.json.
3. Set environment variables (see `apps/api/.env.example`):
- `PUBLIC_URL` — the Vercel URL of `apps/web` (e.g. `https://pagent.vercel.app`). Used in `show_ui` responses. **Required in production.** Boot fails loudly if missing.
- `PUBLIC_URL` — the Vercel URL of `apps/web` (e.g. `https://pagent.link`). Used in `show_ui` responses. **Required in production.** Boot fails loudly if missing.
- `ALLOWED_ORIGINS` — comma-separated origins allowed to call the API (set to your Vercel URL). **Required in production.** API boot fails loudly if missing.
- `PORT` — Railway sets this automatically; the server reads it.
- `PAGE_TTL_MS` — optional; default 30 minutes.
Expand All @@ -213,7 +213,7 @@ The `/health` endpoint is configured as the healthcheck path. Returns 200 only w
1. Create a new Vercel project from this repo.
2. Set **Root Directory** to `apps/web` so vercel.json is picked up.
3. Set environment variables (see `apps/web/.env.example`):
- `VITE_API_URL` — the Railway URL of `apps/api` (e.g. `https://pagent.up.railway.app`). Inlined at build time, so a redeploy is needed if this changes. **Required for `vite build`** — the build fails loudly if missing or malformed (prevents shipping a bundle that silently calls relative paths).
- `VITE_API_URL` — the Railway URL of `apps/api` (e.g. `https://api.pagent.link`). Inlined at build time, so a redeploy is needed if this changes. **Required for `vite build`** — the build fails loudly if missing or malformed (prevents shipping a bundle that silently calls relative paths).
4. Deploy. Vercel runs `npm install` from the monorepo root (workspace install) and `npm run build:web`, outputting `apps/web/dist/`.

`vite dev` (i.e. `npm run dev`) does not require `VITE_API_URL` — it falls back to Vite's proxy for same-origin paths, so local development works zero-config.
Expand Down Expand Up @@ -266,7 +266,7 @@ refactor. The response shape is exactly what is shown above.
Quick smoke from the terminal:

```bash
curl -sf https://pagent.up.railway.app/health
curl -sf https://api.pagent.link/health
```

### Logs and traces
Expand Down Expand Up @@ -299,13 +299,13 @@ in Grafana from the trace and log streams.

### Common failure modes

| Symptom | Likely cause | Where to look | First response |
| --------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /health` → 503 | Postgres unreachable | Supabase status page; Railway DB env vars | Check Supabase dashboard. If the DB is up but the env var was rotated, restore `DATABASE_URL` in Railway and redeploy. |
| Spike of 429s on `POST /new` | Per-IP rate limit hit (default 30 req / 60 s) | Railway logs — group by client IP | Legit spike: bump `RATE_LIMIT_MAX` in Railway env and restart (no redeploy needed). Abuse: block at the network edge. |
| 413 on `POST /new` | Request body > 256 KB | Log field `error: payload_too_large` | If a real use case, raise `MAX_BODY_BYTES` in `apps/api/app.ts` (code change + redeploy). Otherwise it's spam; ignore. |
| CORS errors in the browser console at `pagent.vercel.app` | `ALLOWED_ORIGINS` does not include the renderer's origin | Browser DevTools → Network → failing preflight | Add the missing origin to `ALLOWED_ORIGINS` in Railway env and restart the service. |
| Boot failure with `ZodError` in Railway logs | A required env var is missing | Railway logs (the process exits before it binds) | Read the Zod validation error — it names the missing field. Usually `PUBLIC_URL` or `ALLOWED_ORIGINS`. Set it in Railway, then redeploy. |
| Symptom | Likely cause | Where to look | First response |
| --------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /health` → 503 | Postgres unreachable | Supabase status page; Railway DB env vars | Check Supabase dashboard. If the DB is up but the env var was rotated, restore `DATABASE_URL` in Railway and redeploy. |
| Spike of 429s on `POST /new` | Per-IP rate limit hit (default 30 req / 60 s) | Railway logs — group by client IP | Legit spike: bump `RATE_LIMIT_MAX` in Railway env and restart (no redeploy needed). Abuse: block at the network edge. |
| 413 on `POST /new` | Request body > 256 KB | Log field `error: payload_too_large` | If a real use case, raise `MAX_BODY_BYTES` in `apps/api/app.ts` (code change + redeploy). Otherwise it's spam; ignore. |
| CORS errors in the browser console at `pagent.link` | `ALLOWED_ORIGINS` does not include the renderer's origin | Browser DevTools → Network → failing preflight | Add the missing origin to `ALLOWED_ORIGINS` in Railway env and restart the service. |
| Boot failure with `ZodError` in Railway logs | A required env var is missing | Railway logs (the process exits before it binds) | Read the Zod validation error — it names the missing field. Usually `PUBLIC_URL` or `ALLOWED_ORIGINS`. Set it in Railway, then redeploy. |

### Rollback

Expand All @@ -328,7 +328,7 @@ git push --force-with-lease
Watch CI go green, then verify:

```bash
curl -sf https://pagent.up.railway.app/health && echo ok
curl -sf https://api.pagent.link/health && echo ok
```

### Operational tunables
Expand Down
4 changes: 2 additions & 2 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ PORT=8787

# The public URL where users open pages in their browser.
# In dev: http://localhost:8788 (the Vite renderer, NOT the API on 8787).
# In prod: your Vercel deployment URL (e.g. https://pagent.vercel.app).
# In prod: your Vercel deployment URL (e.g. https://pagent.link).
# Required when NODE_ENV=production — boot will fail loudly otherwise.
PUBLIC_URL=http://localhost:8788

# How long pages live, in milliseconds (default 30 minutes).
PAGE_TTL_MS=1800000

# Comma-separated list of origins allowed to call the API. Leave unset for dev (allows all).
# Required when NODE_ENV=production — boot will fail loudly otherwise. Set to your Vercel URL (e.g. https://pagent.vercel.app).
# Required when NODE_ENV=production — boot will fail loudly otherwise. Set to your Vercel URL (e.g. https://pagent.link).
ALLOWED_ORIGINS=

# Supabase Postgres connection string (Session pooler, port 5432).
Expand Down
4 changes: 2 additions & 2 deletions apps/api/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ app.use(
// has no frames to embed and DENY is more restrictive.
xFrameOptions: 'DENY',
// Browsers default Cross-Origin-Resource-Policy to same-origin which would
// block the renderer at pagent.vercel.app from reading API responses at
// pagent.up.railway.app. CORS already gates cross-origin reads explicitly.
// block the renderer at pagent.link from reading API responses at
// api.pagent.link. CORS already gates cross-origin reads explicitly.
crossOriginResourcePolicy: 'cross-origin',
// Defaults are fine for everything else (HSTS, X-Content-Type-Options
// nosniff, Referrer-Policy no-referrer, etc.)
Expand Down
6 changes: 3 additions & 3 deletions apps/api/schemas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ describe('envSchema', () => {
const r = envSchema.safeParse({
DATABASE_URL: 'x',
NODE_ENV: 'production',
ALLOWED_ORIGINS: 'https://pagent.vercel.app',
PUBLIC_URL: 'https://pagent.vercel.app',
ALLOWED_ORIGINS: 'https://pagent.link',
PUBLIC_URL: 'https://pagent.link',
});
expect(r.success).toBe(true);
});
Expand Down Expand Up @@ -246,7 +246,7 @@ describe('envSchema', () => {
DATABASE_URL: 'x',
NODE_ENV: 'production',
ALLOWED_ORIGINS: 'https://a.com',
PUBLIC_URL: 'https://pagent.vercel.app',
PUBLIC_URL: 'https://pagent.link',
});
expect(r.success).toBe(true);
});
Expand Down
4 changes: 2 additions & 2 deletions apps/api/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ export const envSchema = z.preprocess(
code: 'custom',
path: ['ALLOWED_ORIGINS'],
message:
'ALLOWED_ORIGINS is required in production. Set it to a comma-separated list of origins permitted to call the API (e.g. https://pagent.vercel.app).',
'ALLOWED_ORIGINS is required in production. Set it to a comma-separated list of origins permitted to call the API (e.g. https://pagent.link).',
});
}
if (cfg.NODE_ENV === 'production' && !cfg.PUBLIC_URL) {
ctx.addIssue({
code: 'custom',
path: ['PUBLIC_URL'],
message:
'PUBLIC_URL is required in production. Set it to the renderer URL (e.g. https://pagent.vercel.app).',
'PUBLIC_URL is required in production. Set it to the renderer URL (e.g. https://pagent.link).',
});
}
}),
Expand Down
2 changes: 1 addition & 1 deletion apps/mcp/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# URL of the pagent REST API the MCP server talks to.
# Optional. Defaults to https://pagent.up.railway.app (the hosted production API).
# Optional. Defaults to https://api.pagent.link (the hosted production API).
# Override for local dev:
# PAGENT_URL=http://localhost:8787 claude
# Must be a valid URL — boot fails loudly otherwise.
Expand Down
Loading
Loading