Install
$ agentstack add skill-aaryan-kapoor-surface-surface ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →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
surface actions— drain your inbox: clicks that arrived while you were gone. Handle each, thensurface ack.- Read
SURFACE.mdif present — which surfaces this project maintains, which state keys to update when. surface list— never create a duplicate; update the existing card.- 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 sets. 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 setuponce. 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. Usesurface codex statusto diagnose the bridge. If setup is unavailable, fall back to a one-shotsurface wait. - Claude Code: arm it with the
Monitortool (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-shotsurface wait --idis fine only for a single fire-and-forget answer. - Other harnesses: per-line watchdog →
--follow; wake-on-exit only → one-shotwait, re-arm after each; always-on daemon → a--webhookbinding. Recipes:surface wait --help, anddocs/interaction/delivery-ladder.mdin 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);
--allrestores machine-wide consumption,--projecttargets another repo,--no-ackmakes 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.idas an idempotency key. - Offline (clicks land while you're gone)?
surface bind --action --run ''/--webhookis 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 alreadytrue, **stop and ask the user in chat** ("wake me on clicks? each wake runsunattended / spends a headless session") and wait for their reply — **never setenabled: trueyourself 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'strue. To revoke:unbindand setenabledback tofalse`. - 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=ENCODEDfor web PDFs. - Pass
--agentfor attribution and--idfor recurring cards. - Remote/non-loopback callers — CI, scripts, another box, not just agents — point
SURFACE_URLat the reachable host and setSURFACE_SESSION, then run the same CLI (surface set,notify); mint/audit/revoke the bearer withauth session issue --role system --label·list·revoke.surface --help/surface --helpare 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
- Source: Aaryan-Kapoor/Surface
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.