agentproto app
agentproto app install <appDir|url|file.agentapp|appId> [--ref <ref>] [--subdir <path>] [--data-dir <path>]
agentproto app resync <appId>
agentproto app list
agentproto app catalog [--refresh] [--json]
agentproto app uninstall <appId> [--json]
agentproto app update [<appId>|--all] [--dry-run] [--json]
agentproto app store [--print]
agentproto app pack <appDir> [--out <path.agentapp>] [--release] [--json]
agentproto app unpack <file.agentapp> [--dir <outDir>] [--json]
agentproto app serve [appDir] [--port <n>] [--app <appId>] [--json]
[--remote-mcp-url <url>] [--remote-mcp-auth <token>]
[--remote-app-id <appId>]
agentproto app build <appDir> [--json]
agentproto app dev <appDir> [--port <n>] [--json] [-- <viteArgs...>]
agentproto app init <template> [dir]
agentproto app validate [dir] [--json]This page covers the
agentproto appCLI verb (install/serve/pack/build/ dev). For what a bundled agent can actually reach at runtime —app_run, and which tool ids anAGENT.mdcan declare — see Which tools can an app agent call?.
Bundle an agentproto app folder (one carrying a valid .agentproto/APP.md)
into a single self-contained .agentapp tar.gz — the "APK for agentproto
apps" — and unpack that bundle back into a folder, verifying an aggregate
SHA-256 before restoring. Entirely local and dependency-free (system tar):
no daemon, no network. build and dev compile/run an app's optional ui/
source project (Vite + TypeScript) into/against the static
.agentproto/ui/ that serve, pack, and the MCP-Apps panel actually
consume — see Optional ui/ source project
below.
An app folder is any directory with .agentproto/APP.md plus the agents
(.agentproto/agents/<id>/AGENT.md), workflows
(.agentproto/workflows/<id>/WORKFLOW.md), and optional UI
(.agentproto/ui/) it references — the shape defineApp().emit(dir)
(@agentproto/app-kit) produces. The bundle walks the WHOLE folder
(including .agentproto/ and any loose workspace files, but skipping any
node_modules/ or .git/ directory at any depth — a ui/ source tree ships
both and neither belongs in the shipped app), so unpacking restores the
exact tree and relative paths that readAppRefs / app_install depend on
survive the round-trip. The APP.md package block narrows what ships, and
pack --release drops dev-only files and the build step (see pack below).
Subverbs
install <appDir|url|file.agentapp|appId> [--ref <ref>] [--subdir <path>] [--data-dir <path>]
Register the app (its id from .agentproto/APP.md) → <appDir> mapping in
~/.agentproto/apps.json, the same file the daemon's app_install writes, so
agentproto app serve --app <id> and the daemon can resolve it. Idempotent:
re-running for the same id updates the entry.
If APP.md's ui block declares a build ({ command, cwd?, sources? }),
app_install builds the UI bundle first when it's missing or older than the
newest matching sources file — see ui.build
below. No ui.build and a missing ui.path fails install with a clear
error naming the path, same as before this existed.
| Flag | Default | Description |
|---|---|---|
--data-dir <path> | see below | Where the app's durable data — everything app_data_read / app_data_write / app_data_list touch — lives. Absolute, ~-relative, or relative to <appDir>. This is what keeps multi-GB generated output out of the app's source tree. |
The registered data dir is resolved, in order: --data-dir → the entry's
previously registered data dir (a bare re-install never moves an app's data)
→ the APP.md data.dir frontmatter hint (relative to <appDir>) →
<appDir>/data. It is stored absolute; the daemon's app_install {dir, dataDir} follows the same precedence. A git URL or .agentapp install,
whose app dir is replaced on every reinstall, falls back to
~/.agentproto/app-data/<url-encoded appId> instead of <appDir>/data
(existing installs keep their recorded data dir).
How paths resolve against it (the daemon's rule, packages/runtime/src/app-data.ts):
- An app-relative path resolves under the data dir.
- Under the default layout (
<appDir>/data) a leadingdata/is the legacy spelling from when the plane was anchored at<appDir>and is dropped —data/trips/x.jsonandtrips/x.jsonname the same file. - If a path (or its top-level folder) does not exist under the data dir but
does under
<appDir>, it resolves there — files written by a pre-data-dir install keep working, andapp_data_listmerges both views. Move the folder into the data dir and the fallback stops applying.
Installing from a git URL or a .agentapp
A git URL (https://…, git@…, file://…) or a .agentapp (an https://
/ file:// URL, or a local path) is installed by the running daemon
(app_install {url, ref?, subdir?} / {file}), so start the daemon first:
- git: shallow clone into
<daemon state dir>/apps/<repo>[-<subdir>](~/.agentproto/apps/…, never your cwd).--refpicks a branch or tag,--subdirthe app's path inside the repo. The installed commit is pinned assource: { kind: "git", url, ref?, sha, subdir? }. .agentapp: downloaded (30 s timeout, 200 MB cap) or read, digest verified exactly likeunpack, then installed under<state dir>/apps/<id>. Pinned assource: { kind: "agentapp", url, sha256, version }. A digest mismatch refuses the install and leaves any previous install untouched.
Re-installing replaces the app dir atomically and keeps the app's data dir.
app_list / app_status show source.
Installing by app id from the catalog
An @scope/name argument that is not an existing path is looked up in
the daemon's app_catalog (the default catalog is
https://agentproto.sh/catalog/v1/apps.json). The entry's pinned source is
installed: {url, sha256} for a bundle, {url, ref, subdir, sha} for git,
plus the catalog URL so update follows that catalog. A git entry still
never runs ui.build without --allow-build. An existing path always wins;
an unknown id fails and points at agentproto app catalog.
agentproto app install @agentik/session-chatresync <appId>
Ask the daemon to re-check a git / .agentapp install against its source
(git ls-remote vs the pinned sha; re-download vs the pinned sha256).
Prints { "changed": false }, or reinstalls and prints
{ "changed": true, "from": …, "to": … }. Apps installed from a local
directory have no source and error.
list
Print every registered app as id -> dir, each followed by its data dir
(entries written before the field existed show <dir>/data). With nothing
installed it prints No apps installed. Browse: agentproto app store (or: agentproto app catalog).
catalog [--refresh] [--json]
List the daemon's app_catalog: app id, version, tier, installed or not,
update available, and which catalog source it came from, followed by any
source warnings (unreachable source, stale cache). --refresh bypasses the
daemon's 5-minute cache of remote sources. Needs the daemon.
uninstall <appId> [--json]
Remove an installed app's record from the daemon (app_uninstall). Its data
dir is kept.
update [<appId>|--all] [--dry-run] [--json]
Without an app id, list the available updates (app_updates). With an app id
(or --all), apply them (app_resync, which installs the current catalog
entry with its digest verified). --dry-run only lists.
store [--print]
Open the daemon's App Store panel (<daemon url>/store) in the default
browser; --print only prints the URL. See the
App Store guide.
serve [appDir] [--port <n>] [--app <appId>] [--json]
Serve an agentproto app's UI as a standalone webapp with a working
window.McpApp bridge wired to the daemon's /mcp endpoint. The same
HTML dashboard that renders inside an MCP-Apps panel now runs in a plain
browser tab with full MCP connectivity.
The UI root is resolved from APP.md frontmatter: when ui.path is
declared (e.g. ui.path: ui/index.html), the directory containing that
file is used as the UI root; when ui is absent, the legacy
.agentproto/ui/ directory is used. When ui.build is declared, the
bundle is built first if missing or stale (same as install, above); a
missing resolved UI root with no ui.build (or a failing build) is a
hard exit-2 error naming the path/command, not a silent 404.
ui.build — don't commit the generated bundle
ui:
path: .agentproto/ui/index.html
build:
command: pnpm run build
cwd: ui
sources:
- ui/src/**
- ui/index.html
- ui/vite.config.tscommand is a shell command line run with cwd cwd (default: the app
dir); sources (default ["src/**"], relative to cwd) is compared by
mtime against ui.path to decide staleness. app_install, this command,
the daemon's GET /apps/:appId/ui, and the MCP panel all resolve it
through the same ensureAppUiBuilt (@agentproto/runtime/app-ui-build),
single-flight per bundle path so concurrent first requests build once.
Output is captured to ~/.agentproto/logs/app-ui-build/<app>-<hash>.log (outside the app dir); a failing build
surfaces that log's tail in the error instead of a bare 404. This is
distinct from agentproto app build, which only
knows the <appDir>/ui/ Vite-project convention and must be run by hand —
ui.build is declarative (any command, any layout) and runs automatically.
| Flag | Default | Description |
|---|---|---|
appDir | current directory | Directory holding .agentproto/APP.md. Ignored in remote mode (see below). |
--app <appId> | unset | Serve an installed app by its registered id (from ~/.agentproto/apps.json, written by install) instead of a directory path. Mutually exclusive with appDir. |
--port <n> | PORT env, then ui.port in APP.md, else OS-assigned | Port to bind. Resolution order: explicit --port > PORT env var > APP.md ui.port > OS-assigned. A declared ui.port that is already taken falls back to auto-assign; an explicit --port that is taken is a hard error. Not read in remote mode (no APP.md) — there --port or PORT env or auto-assign applies. |
--remote-mcp-url <url> | unset | Streamable-HTTP MCP endpoint of a remote server (e.g. https://api.example.com/mcp). Setting this enables remote mode (see below). Env: AGENTPROTO_REMOTE_MCP_URL. |
--remote-mcp-auth <token> | unset | Bearer token sent as the Authorization header on every MCP request to the remote server. Env: AGENTPROTO_REMOTE_MCP_AUTH. |
--remote-app-id <appId> | unset | The MCP-Apps app id to render in remote mode, or a full ui://… resource URI. A bare id is fetched as ui://<appId>. Env: AGENTPROTO_REMOTE_APP_ID. |
--json | false | Print { url, appDir, daemonMcpUrl } (or, in remote mode, { url, mode: "remote", appId, resourceUri, daemonMcpUrl }) on stdout instead of a human summary. |
Start the daemon first (agentproto serve); the bridge proxies tool calls to
http://127.0.0.1:<daemon.port>/mcp.
app serve also exposes a same-origin file-upload endpoint for app UIs:
POST /__agentproto/upload?filename=<name> with the raw file bytes as the
body. Files land in <appDir>/inbox/ with a sanitized, collision-avoided
name, and the endpoint returns { path, bytes }. Uploads are capped at 200 MB.
Remote mode (--remote-mcp-url set): instead of serving a local app dir
and proxying tool calls to the local daemon, app serve connects its MCP
client to a REMOTE MCP server and renders one of ITS MCP-Apps ui://
resources as a browser tab. There is no local app directory in this mode —
the HTML is fetched over MCP (readResource) and no ui.tools allowlist
applies: every tool the remote server exposes is forwarded, with the
loopback-only bind as the safety gate.
pack <appDir> [--out <path.agentapp>] [--release] [--json]
Reads <appDir>/.agentproto/APP.md, walks the entire app dir, computes an
aggregate SHA-256 over every file, writes a manifest.json at the bundle
root, and emits a gzipped tar archive containing manifest.json plus the
app folder's contents (not a wrapping folder). Extraction therefore yields
manifest.json, .agentproto/, and the loose files at top level.
| Flag | Default | Description |
|---|---|---|
--out <path> | <safeId>-<version>.agentapp in cwd | The output .agentapp path. When omitted, derives a filesystem-safe filename from the app id and version (e.g. @agentproto/job-application-kit v0.1.0 → agentproto-job-application-kit-0.1.0.agentapp). With --entry, a directory means "into this dir", and a path ending in .agentapp is used as the bundle path itself. |
--release | false | Build the publishable bundle (see below). |
--entry | false | Requires --release. Also write a validated catalog entry next to the bundle (see below). |
--asset-url <url> | GitHub Releases URL | With --entry: the published bundle URL recorded in the entry's source.url. |
--publisher <name> | none | With --entry: the entry's publisher field (APP.md store.publisher wins). |
--media-base-url <url> | https://raw.githubusercontent.com/agentproto/apps/main/media/<appId>/<version> | With --entry: base URL of the store listing's local media (APP.md store: icon/screenshots), copied to media/<appId>/<version>/ next to the entry. See Store listing. |
--json | false | Print the generated manifest.json on stdout instead of a human summary (with --entry: the bundle path, entry file path, and the entry itself). |
Fails with exit code 2 if <appDir> has no .agentproto/APP.md.
What ships. Every file except node_modules/ and .git/, filtered by the
optional APP.md package block:
package:
include: [".agentproto/**"] # when set, only matching files ship
exclude: ["notes/**"] # matching files are dropped
stripBuild: true # drop ui.build from the packed APP.mdGlobs are /-separated paths relative to the app dir: ** spans
directories, * stays inside one segment (no braces, no negation).
.agentproto/APP.md, the ui.path entry and the files in its sibling
assets/ directory always ship.
--release is the mode for a bundle you publish:
- Runs
ui.buildfirst when the UI bundle is missing or stale (same check asapp build/ the daemon). - Adds the default excludes to
package.exclude(a declaredexcludeadds to these, it never removes them):- dev trees:
ui/**,docs/**,data/**,scripts/**, and at the app root onlydev/**,test/**,tests/**(so an agent nameddevortestunder.agentproto/agents/still ships); - tests anywhere:
**/__tests__/**,**/*.test.*,**/*.spec.*; - repo docs at the root:
README.md,CHANGELOG.md,CONTRIBUTING.md(LICENSEandNOTICEship); - tooling:
.github/**,.vscode/**,.idea/**,.gitignore,.editorconfig,tsconfig*.json,vitest.config.*,jest.config.*,eslint.config.*,.eslintrc*,.prettierrc*; - artifacts:
**/*.log,**/*.map,**/.DS_Store,**/.env,**/.env.*.
- dev trees:
- Fails (
missing-ui, exit1) if the declaredui.pathis not in the bundle after the build. - Removes
ui.buildfrom the packed APP.md (unlesspackage.stripBuild: false), so an install never runs a build command. The SHA-256 covers the rewritten APP.md, so always publish the digest of the release pack.
--entry writes the publishing pipeline's other half, a catalog entry
for the bundle just packed:
- Written next to the bundle as
<slug>-<version>.entry.json, where<slug>is the last segment of the appId without its scope (@agentik/session-chat->session-chat). - A validated
AppCatalogEntrywithtier: "bundle"andlicense: {kind: "free"}:appId/name/descriptioncome from APP.md, as do the optionalcategory,icon, andplacement; APP.md must declare aversionor packing fails.sourcecarries the bundle's manifestsha256(exactly whatunpack/app_install {sha256}verify), the bundle's bytesize,version, and theurl:--asset-urlif given, else the GitHub Releases asset URL of the publicagentproto/appsrepo, tag<slug>@<version>(@encoded%40), asset<slug>-<version>.agentapp. - Feed the entries to
agentproto catalog buildto produce the publishedapps.json; see distribute-an-app.
unpack <file.agentapp> [--dir <outDir>] [--json]
Extracts the bundle to a temp dir, reads and validates manifest.json
(required, format: agentapp/v1), recomputes the aggregate SHA-256 over the
listed files and compares it to the manifest — a mismatch means the bundle
is corrupted and the command fails (exit 1) without restoring. On success
it copies the app contents (excluding manifest.json, which is a bundle
artifact, not part of the app) into the destination.
| Flag | Default | Description |
|---|---|---|
--dir <dir> | <safeId>-<version> in cwd | Dest folder to restore into. When omitted, derives <safeId>-<version> from the manifest. |
--json | false | Print a machine-readable verification summary on stdout. |
serve [appDir] [--port <n>] [--json]
Serves an agentproto app's UI as a standalone webapp with a working
window.McpApp bridge, so the same UI that renders inside an MCP-Apps panel
runs in a plain browser tab with full MCP connectivity. The UI root is resolved
from APP.md frontmatter ui.path (directory containing the entry file), falling
back to the legacy .agentproto/ui/. Port resolution:
--port > PORT env var > the app's declared ui.port (APP.md frontmatter) > an
OS-assigned free port. Requires the daemon (agentproto serve) to be
running — the bridge forwards tool calls to its /mcp endpoint.
build <appDir> [--json]
Builds <appDir>/ui/ (the optional Vite UI source project — see
Optional ui/ source project) into
<appDir>/.agentproto/ui/, the static output serve/pack/the MCP-Apps
panel consume.
- No
ui/package.json, or one with noscripts.build→ no-op success (exit0): the app is a hand-written static UI with nothing to compile. Human output:no ui build step — static UI passthrough.--json:{"built":false,"reason":"no-ui-project"}(missingui/package.json) or{"built":false,"reason":"no-build-script"}(present but noscripts.build). - Otherwise runs
<pm> run buildwith cwd<appDir>/ui, stdio inherited. The package manager is detected from a lockfile —pnpm-lock.yaml→ pnpm,package-lock.json→ npm,yarn.lock→ yarn — checked in<appDir>first, then<appDir>/ui, defaulting to pnpm. - A non-zero build exit is a hard failure (exit
1). After a successful build,agentproto app buildverifies<appDir>/.agentproto/ui/index.htmlexists; if the ui project'svite.config.tsdoesn't emit there (outDir: "../.agentproto/ui"), that's also exit1, with a hint. --jsonsuccess:{"built":true,"uiDir":"<abs .agentproto/ui path>"}.
Fails with exit code 2 if <appDir> has no .agentproto/APP.md.
dev <appDir> [--port <n>] [--json] [-- <viteArgs...>]
Runs <appDir>/ui/'s own dev server (<pm> run dev, same package-manager
detection as build) with a live window.McpApp bridge — the app serve
experience but with Vite's HMR instead of a static build. Requires
<appDir>/ui/package.json to declare a scripts.dev; a hand-written static
UI has nothing to hot-reload — use agentproto app serve instead (exit 2
otherwise).
Two servers run: the ui project's own dev server (whatever port it picks)
and a bridge-only HTTP server this command owns — POST /__agentproto/tool-call plus OPTIONS/CORS, since the browser talks to the
Vite dev origin, a different port than the bridge. --port sets the
bridge server's port (default: OS-assigned). The dev server child is spawned
with AGENTPROTO_BRIDGE_URL=http://127.0.0.1:<bridgePort> in its
environment, so a scaffolded vite.config.ts can proxy /__agentproto to
it. Extra args after -- are forwarded to <pm> run dev.
When APP.md declares ui.port (the same frontmatter serve reads) and no
-- <viteArgs> were passed at all, dev appends --port <declared> to the
<pm> run dev invocation itself, so the ui dev server's own URL is stable
and matches the app's declared surface — this is unrelated to the --port
flag above, which only controls the bridge server. Passing any explicit
viteArgs disables the hint entirely: you're steering the dev server
directly, so nothing gets merged on top of your flags.
Ctrl-C or the dev server child exiting tears both servers down; app dev
exits with the child's exit code. --json prints
{"bridgeUrl":"...","appDir":"..."} on one line before handing the
terminal to the dev server.
init <template> [dir]
Scaffold [dir] (default: the current directory) from <template> — the
same scaffoldApp operation pnpm create agentproto-app <dir> drives, so
no second package install is needed. Refuses a non-empty target directory
(exit 2, reason target-not-empty); an unknown template is also exit 2.
Templates:
| Template | Shape |
|---|---|
react-ts | Vite + TanStack Router/Query ui/ source project + .agentproto/ shell (the create-agentproto-app default) |
vanilla | minimal .agentproto/ shell + static UI |
book | the book-app trame (category book, library stub, install skill) |
trame | the minimal AIP app trame — see below |
The trame template emits everything validate (below) knows how to
check, mirroring the book-factory app layout:
<dir>/.agentproto/APP.md (id from the dir slug, one agent, one workflow,
ui.tools incl. app_state_get/app_state_list,
verify.command: "node scripts/verify.mjs")
<dir>/.agentproto/agents/<slug>-agent/AGENT.md
<dir>/.agentproto/workflows/<slug>-flow/WORKFLOW.md
<dir>/.agentproto/workflows/<slug>-flow/prompts/run.md
<dir>/.agentproto/ui/index.html (single file, window.McpApp-aware stage board)
<dir>/gates/example.mjs (exit 0 + one-line JSON report)
<dir>/scripts/verify.mjs (runs the gates, prints {ok, findings})
<dir>/data/DATA.md (the data-plane key dictionary)
<dir>/tests/gate.test.mjs (node --test suite)The workflow ships ONE kind: agent step (harness-pinned: model,
effort, role, promptFile — the file wins over the inline prompt)
followed by ONE kind: gate step running node gates/example.mjs from the
app root. A gate's cwd and args[] accept a LEADING $… run-time ref
($input|$item|$steps.<id>|$index) with trailing text — e.g.
cwd: $input.bookDir or args: ["$input.bookDir/knowledge"]; a relative
resolved cwd (incl. .) resolves against the run's own cwd.
validate [dir] [--json]
Check [dir] (default: the current directory) against the app loader.
Exit 0 iff ALL of:
loadAppHandle(@agentproto/app-kit) succeeds —APP.mdparses, everyAGENT.mdloads, and thedefineAppattachment invariant holds.- Every declared workflow loads via
@agentproto/workflow-loader— harness blocks,promptFileresolution, andkind: gatesteps are validated there. - Every
ui.toolsentry is a tool the daemon registers or anyapp_*tool. The daemon list isDAEMON_TOOL_NAMES(@agentproto/runtime/daemon-tool-names), generated from a real gateway'stools/listwith every optional surface wired (workflows, pairing/devices, llm-endpoint, ...) and kept exact by a runtime test, so it follows the daemon of the same release. A tool added in a newer daemon needs a CLI from that release. - When APP.md declares
data.dir,<dir>/<data.dir>/DATA.mdexists (the data plane must ship its key dictionary — seedata/DATA.md). - When APP.md declares
verify.command, it is run — argv-split on whitespace, NO shell, cwd = the app dir, 10-minute cap. Its stdout is printed verbatim and its exit code propagated: with no other findings,validateexits with the verify command's own exit code.
Findings print one per line as [error] <scope>: <message> on stderr.
--json prints { ok, findings: [{scope, level, message}] } on stdout;
exit 0 iff ok.
Optional ui/ source project
An app's .agentproto/ui/ is always the thing actually served — a plain
index.html + assets, hand-written or built. An app MAY additionally carry
a ui/ source project (a Vite + TypeScript project) that builds into
.agentproto/ui/:
vite.config.tssetsoutDir: "../.agentproto/ui",emptyOutDir: true, andbase: "./"(relative asset paths, since the daemon /app serve/ the MCP-Apps panel serve static files with no rewrite rules and may serve from a subpath).- The router (if any) uses hash history (
createHashHistory) for the same reason: oneindex.html+ hash routes works from any subpath, and evenfile://, with no per-route HTML emission needed from the static host. - No
ui/directory, or aui/without apackage.jsonbuild script, means the app is a hand-written static UI —app buildno-ops successfully andapp devisn't available (useapp serve).
create-agentproto-app scaffolds this shape (Vite + TanStack Router +
TanStack Query + @agentproto/app-client) end to end.
The manifest.json
{
"format": "agentapp/v1",
"id": "@agentproto/job-application-kit",
"name": "Job Application Kit",
"version": "0.1.0",
"description": "…",
"agents": ["job-scout", "job-tailor"],
"workflows": ["job-hunt"],
"ui": ["index.html"],
"files": [".agentproto/APP.md", "base-cv.json", "dossiers/…"],
"fileCount": 29,
"totalSize": 123456,
"sha256": "abc123…",
"createdAt": "2026-08-11T…",
"agentprotoVersion": ">=0.1.0"
}files are relative paths (sorted); ui is an array of bundled UI
filenames when the app declares one. sha256 is an aggregate over the
concatenated bytes of every bundled file in files order (excluding
manifest.json itself), which is exactly what unpack recomputes to
verify integrity.
Stage board
app serve and app dev both respond to
GET /agentproto/stageboard.js with a dependency-free vanilla ES module
(content-type: text/javascript, no-store caching — never stale in dev)
that renders the app's state ledger as a stage board. There is nothing to
build or bundle: import it from any served UI page and mount it on an
element.
<div id="stages"></div>
<script type="module">
import { mountStageBoard } from "/agentproto/stageboard.js";
window.McpApp.connect().then((bridge) => {
mountStageBoard(document.getElementById("stages"), {
appId: "@you/your-app", // the installed app id
callTool: bridge.callTool, // the window.McpApp bridge
refreshMs: 15000, // auto-refresh (optional)
// onValidate: () => …, // optional hook for a Validate button
// onApprove: (approval) => …, // optional custom approval handler
});
});
</script>What it renders:
- One column per stage in first-seen order (the folded snapshot's key order), one row per item — or a single stage-level row for ledgers that never used items.
- Per status chips using the daemon's vocabulary (
pending,running,gated-failed,blocked,done,approved). - A collapsible per-row gate detail: last
gate-reportfindings (frompayload.report.findingswhen present, else the raw payload), attempt count, and theappRunIdof the most recent run that touched the row. - A visible "last updated" timestamp and a Refresh button; auto-refresh
every
refreshMs(default 15s). - A Validate button when you pass
onValidate; otherwise the app'sverify.commandis shown ifapp_statusexposes one, and the button is hidden when neither exists. - An Approvals block listing
app_status.awaitingApprovals[](when that field is present) with an Approve button per entry. Approving askswhovia a prompt input and callsworkflow_escalation_resolve; if that tool is not in the app'sui.toolsallowlist, the board degrades gracefully (feature-detected from the 403). PassonApproveto handle approvals yourself instead.
All styles are scoped under .ap-stageboard and driven by CSS variables —
theme it by overriding e.g. --ap-stageboard-accent, --ap-stageboard-line,
--ap-stageboard-good, --ap-stageboard-bad, --ap-stageboard-warn on an
ancestor element.
The pure fold behind the render (toRows(snapshot, events)) is exported
from the module too, so tests and other renderers can reuse it without a
DOM.
Note: the
create-agentproto-apptrame template does not yet ship this snippet — until it does, copy the<script type="module">block above into your app's UI by hand.
Examples
# Register an app, keeping its generated output on a big external volume
agentproto app install ./tripsmith --data-dir /Volumes/big/tripsmith-data
# Pack a real app into the current directory
agentproto app pack ./job-application-kit
# Pack with an explicit output path and JSON manifest
agentproto app pack ./job-application-kit --out kit.agentapp --json
# Unpack into a chosen folder (verifies SHA first)
agentproto app unpack kit.agentapp --dir ./my-app
# An unpacked folder is itself packable again (round-trip stable)
agentproto app pack ./my-app
# Scaffold the minimal app trame, then prove it is sound
agentproto app init trame ./my-app
agentproto app validate ./my-app --json
# Build a ui/ source project into .agentproto/ui/ (no-ops for a static UI)
agentproto app build ./my-app
# Run the ui/ dev server with a live MCP bridge (daemon must be running)
agentproto app dev ./my-app
# Forward extra args to the underlying dev server
agentproto app dev ./my-app -- --host 0.0.0.0