# Surface

> Surface-native display for AI agents, driven by the `surface` CLI. Use when the user says "surface this", "show me X", or "put it on my display/screen"; wants a live interactive UI, chart, or tool they act on; needs a question answerable from any device; or asks you to react to what they click — even while you're offline.

- **Type:** Skill
- **Install:** `agentstack add skill-aaryan-kapoor-surface-surface`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Aaryan-Kapoor](https://agentstack.voostack.com/s/aaryan-kapoor)
- **Installs:** 0
- **Category:** [Databases](https://agentstack.voostack.com/c/databases)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Aaryan-Kapoor](https://github.com/Aaryan-Kapoor)
- **Source:** https://github.com/Aaryan-Kapoor/Surface

## Install

```sh
agentstack add skill-aaryan-kapoor-surface-surface
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Surface

Surface is the user's universal display. When the user says "surface this", "show me X", "put Y on my display", or "ask me when it's done", drive the `surface` CLI — a thin client over a local service (`127.0.0.1:3000`). Hot paths get verbs; everything else, including every custom template, goes through `create --template`. The table below tells you *when*; `surface  --help` is authoritative for flags.

## Surface-native

A live display the user *acts on*, not a chat transcript or file viewer — **two-way and current**: the user answers, clicks, or watches a value change, and you react. **Pick by shape before writing HTML** — match the source to its verb: markdown → `doc --toc`, video/URL → `video`, PDF/image → `present`, a file you keep editing → `link`, a yes/no or pick-one → `ask --options`, a scrolling log → `create --template stream`. **Decompose a multi-source request first** — a "presenter view", a "home screen", "a card next to it" is *several* surfaces (one verb each: `present` the PDF, `video` the clip, a bound chart…), composed with `slot`, never one HTML blob with tabs. Hand-build interactive HTML (`create --content -` — charts, maps, tools, anything in the shape of the bundled demo gallery — `surface seed-demos` to see it) **only when no verb fits**; rendering markdown as HTML or faking a decision card with buttons is the classic miss — the verb is shorter, hot-reloads, and renders natively everywhere. Dynamism earns its place when it adds a decision, a live value, or a visual relationship text can't carry — never less interactive than the task wants, never more.

## Session start

1. `surface actions` — drain your inbox: clicks that arrived while you were gone. Handle each, then `surface ack `.
2. Read `SURFACE.md` if present — which surfaces this project maintains, which state keys to update when.
3. `surface list` — never create a duplicate; update the existing card.
4. Re-arm your action terminal for any interactive surfaces you own (see the delivery ladder). Terminals die with the session; the surfaces don't.

## Commands

| Verb | When to use |
|---|---|
| `create ` | Build a surface: ad-hoc HTML (`--content -`) or a template (`--template `). The default *only* for a custom interactive/visual shape — otherwise pick a verb above. |
| `ask ` | Ask the user — `--options a,b` pick-one, `--freetext` typed answer, `--wait` blocks; `--on ` targets one screen (else everywhere). Attach context, don't ask blind. |
| `append ` | Append to a running `stream` surface (pipe with `-`). |
| `video` · `doc` · `present` | YouTube/web video · repo markdown (`--toc`, hot-reloads) · one-shot snapshot of a **local** PDF/image (web PDF → `/proxy/pdf`). |
| `link ` | Serve a project file live from disk; `touch ` after each edit — your hot-reload target. |
| `set` · `patch` · `state` | Live state — change a value without rewriting HTML (see the two-way loop). |
| `list` · `read` · `update` · `versions` · `rollback` · `delete` | Artifact lifecycle. `update` revises a card; `rollback ` restores an earlier version (don't re-type old values); `delete` removes one. |
| `template list/show/create` | Inspect templates; promote a UI you've built twice (`create  --from `). |
| `wait` · `actions` · `ack` · `bind` · `bindings` · `unbind` | React to clicks — see the delivery ladder. `wait --id  --event state_patch` (or `stream_append`) wakes you on a peer's post — don't poll. |
| `reply` · `notify` · `open` · `exec` · `theme` | Talk back / drive the display. `theme` sets the **global** look — colors, background, fonts, raw CSS (not per-surface styling); `notify`/`open` take `--on `; `exec` pokes live JS into a surface. |
| `set board  '{...}'` | Shared fleet dashboard at id `board`; key by your `--agent` (`'{"status":…,"project":…}'`). Render dashboards **bound to board's keys** (`data-surface-bind`), don't invent a registry; post when you start/finish/block. |
| `slot renderer/home/overlay` | `renderer` = whole homescreen launcher (gets injected `window.__surfaces`/`navigate(id)`); `home` = widget; `overlay` = floating layer (e.g. a DND pill). The user's space — only when asked. |
| `status` · `stream` · `devices` | Presence (who's connected/awake — check before `--on`); tail every event; paired screens. |
| `init` · `sync` | Scaffold `.surface/` + `SURFACE.md`; reconcile project manifests across machines. |
| `pair` · `auth` | Pair a new screen; mint/revoke remote `SURFACE_SESSION` bearers. |
| `seed-demos` · `clear-demos` | Built-in demo gallery — the fast "show me what Surface can do" tour; `clear-demos` hides it again (don't `delete` them one by one; `seed-demos` revives). |

## The two-way loop

A surface that only renders is half-built: **state flows out, actions flow back.** Never regenerate HTML to change a value — every surface has a JSON state doc. `surface set   ` writes one key (dotted keys ok); **`surface patch  '{...}'` writes many at once** (deep-merge; pipe JSON with `-`) — prefer it over a chain of `set`s. State flows out bound in markup with `data-surface-bind` / `data-surface-show`, re-rendered live on every screen, and **persists across sessions** (`surface state ` reads it back — don't blindly re-seed values that are already there). Actions flow back with `Surface.action("name", {...})`. For a multi-step interaction, keep intermediate clicks local with `Surface.stage(key, value)` and fire one action at the commit with `Surface.commit("name")` — so you wake **once, on the user's actual intent**, not per click. **State is a claim, not an animation** — never patch a status, progress value, or "running…" for work you didn't actually execute or observe; if you substitute a cheaper check (a probe instead of a re-run), the surface must say so, not render the run you skipped.

## Delivery ladder — reacting to clicks

Each action wakes you. **Default outside Codex: arm a live action terminal** (`surface wait --follow`) the moment you put up an interactive surface — **once**, and keep it running for the whole interaction. It drains the pending inbox on connect, shows "agent listening", prints one JSON line per action, and **auto-acks each action it hands you** (`--no-ack` to keep them pending) — so only the `actions` inbox-drain needs a manual `ack`.

- **Codex CLI:** run `surface codex setup` once. Thereafter Codex-created surfaces flow back into their exact live session without a waiter; dead-session wakes remain consent-gated and fail closed on approvals. Use `surface codex status` to diagnose the bridge. If setup is unavailable, fall back to a one-shot `surface wait`.
- **Claude Code:** arm it with the **`Monitor` tool** (`persistent: true`), not a backgrounded shell. For anything two-way or ongoing, Monitor is the rule: a one-shot shell only wakes when its process *exits*, so it catches the first action and sleeps through the rest, leaving the surface unguarded. A backgrounded one-shot `surface wait --id ` is fine *only* for a single fire-and-forget answer.
- **Other harnesses:** per-line watchdog → `--follow`; wake-on-exit only → one-shot `wait`, re-arm after each; always-on daemon → a `--webhook` binding. Recipes: `surface wait --help`, and `docs/interaction/delivery-ladder.md` in the Surface repo.
- **The terminal dies with your session — re-arm on return** (the inbox drain covers everything clicked while you were gone).
- **One click, one agent.** The action event reaches every listener, but each waiter must *claim* an action before printing it and only one claim wins — so several sessions can safely hold terminals at once without doubling the work. A waiter is scoped to **its own project** (the git root it started in); `--all` restores machine-wide consumption, `--project ` targets another repo, `--no-ack` makes a pure observer that claims nothing.
- **Delivery is handoff, not completion.** Surface records an action handled once the line has left the CLI; it cannot know whether you finished the work. Treat `action.id` as an idempotency key.
- **Offline (clicks land while you're gone)?** `surface bind  --action  --run ''`/`--webhook` is the answer — fires when no waiter claims within a five-second first refusal — so an idle or wedged terminal can no longer black-hole your wake bindings; never hand-roll a server, daemon, or systemd unit for this. **A bind runs `` (or wakes a headless session) on the user's machine and quota while they're away with no one in the loop — so a recorded yes is a hard prerequisite, and the user wanting the feature is not that yes.** Before your first bind in a project, read `.surface/config.json → bindings.enabled`; if it isn't already `true`, **stop and ask the user in chat** ("wake me on clicks? each wake runs `` unattended / spends a headless session") and wait for their reply — **never set `enabled: true` yourself to unblock your task**: the request that created this work (even an urgent "make it fire while I'm away") is *not* that yes; only a separate, explicit user confirmation is. Record *their* answer there, then bind only once it's `true`. To revoke: `unbind ` and set `enabled` back to `false`.
- Unhandled clicks always wait in the inbox — nothing is lost.

## Conventions

- Surfaces are **self-contained** — inline CSS/JS, no CDNs — so they render offline and screenshot headlessly.
- Most sites block iframes — use embed URLs (`open.spotify.com/embed/...`), or `/proxy/pdf?url=ENCODED` for web PDFs.
- Pass `--agent ` for attribution and `--id ` for recurring cards.
- Remote/non-loopback callers — CI, scripts, another box, **not just agents** — point `SURFACE_URL` at the reachable host and set `SURFACE_SESSION`, then run the same CLI (`surface set`, `notify`); mint/audit/revoke the bearer with `auth session issue --role system --label ` · `list` · `revoke`. `surface --help` / `surface  --help` are authoritative.

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Aaryan-Kapoor](https://github.com/Aaryan-Kapoor)
- **Source:** [Aaryan-Kapoor/Surface](https://github.com/Aaryan-Kapoor/Surface)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-aaryan-kapoor-surface-surface
- Seller: https://agentstack.voostack.com/s/aaryan-kapoor
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
