pageshelf · diff
v1.0.0 to v1.0.0
71 added, 40 removed. Audit B to B.
---
name: pageshelf
- description: Use when publishing agent-generated HTML artifacts, long plans, research reports, PR explainers, or interactive review pages with the pageshelf CLI. Covers secure local/Tailscale serving, session handling, URL generation, and common pitfalls.
+ description: Use when publishing agent-generated HTML artifacts, long plans, research reports, PR explainers, or interactive review pages with the pageshelf CLI. Covers private local/Tailscale serving, default interactive review/annotation UX, session handling, URL generation, and common pitfalls.
version: 1.0.0
author: Hermes Agent
license: MIT
metadata:
hermes:
tags: [html-artifacts, tailscale, cli, reports, agent-output, security]
related_skills: [writing-plans, requesting-code-review]
---
# Pageshelf
## Overview
- Pageshelf is a small Go CLI and secure static artifact server for agent-generated HTML. Use it when a response would be too long or visually dense for chat, Markdown, Telegram, or a PR comment. Instead of dumping a giant plan into the conversation, create an HTML artifact, put it into a pageshelf session, and return the generated URL.
+ Pageshelf is a small Go CLI and private static artifact server for agent-generated HTML. Use it when a response would be too long or visually dense for chat, Markdown, Telegram, or a PR comment. Instead of dumping a giant plan into the conversation, create an HTML artifact, put it into a pageshelf session, and return the generated URL.
The default workflow is intentionally low-friction for agents:
```bash
pageshelf put report.html
```
- If no session is supplied, pageshelf creates one automatically and prints a tokenized `/a/<session>/index.html` URL. For sharing across the user's private network, run the server with Tailscale integration:
+ If no session is supplied, pageshelf creates one automatically and prints a clean `/a/<session>/index.html` URL. For sharing across the user's private network, run the server with Tailscale integration:
```bash
pageshelf serve --tailscale
```
Pageshelf is not a general public hosting platform. Treat it as a private artifact shelf for local or tailnet use.
+ ## Development Note
+
+ Pageshelf's built-in annotation UI is a normal in-repo Vite/TypeScript web app at `internal/runtime/annotation`, managed with aube. The app source is `index.html`, `src/main.ts`, supporting `src/*.ts` modules, `src/light.css`, and `src/shadow.css`; `make runtime-build` runs the Vite build into `internal/runtime/annotation/dist/` and regenerates bundled Go asset constants in `internal/server/annotation_runtime_generated.go`. Run it before testing or committing runtime changes so the binary serves the current `/_pageshelf/runtime/annotation/annotation.js` and `annotation.css` assets.
+
## When to Use
Use pageshelf when:
- A plan, report, diff explanation, architecture review, or research synthesis is too long for chat.
- - The user needs tables, SVG diagrams, color-coded sections, tabs, or interactive controls.
+ - The user needs tables, SVG diagrams, color-coded sections, tabs, interactive controls, or mobile-friendly review/annotation UI.
- Telegram or another messaging surface cannot render the information well.
- A coding/review agent needs to attach a browsable HTML explainer to a task or PR.
- You want the user to skim a visual artifact and keep the chat response short.
Do not use pageshelf when:
- The output is short enough to read directly in chat.
- The artifact contains secrets, private tokens, credentials, or raw sensitive data.
- The user needs a long-lived public website.
- You need server-side application behavior. Pageshelf serves static files only.
## Quick Start
- ### 1. Start the server locally
+ ### 1. Ensure a server is running
+ Prefer reusing the existing Pageshelf server. Do **not** start a fresh `pageshelf serve` every time you publish an artifact.
+
+ First check the expected endpoint:
+
```bash
- pageshelf serve
+ curl -fsS http://127.0.0.1:8787/healthz >/dev/null || pageshelf serve
```
Default bind:
```text
127.0.0.1:8787
```
+ For Tailscale sharing, check the tailnet endpoint before starting another server:
+
+ ```bash
+ TSIP=$(tailscale ip -4 | head -1)
+ curl -fsS "http://$TSIP:8787/healthz" >/dev/null || pageshelf serve --tailscale
+ ```
+
+ If the server is already managed by systemd, prefer checking/restarting the service rather than spawning ad-hoc processes.
+
### 2. Publish one HTML file
```bash
pageshelf put index.html
```
Typical output:
```text
session: 20260510-0945-artifact-ifw1wa
- url: http://127.0.0.1:8787/a/20260510-0945-artifact-ifw1wa/index.html?t=psr_...
+ url: http://127.0.0.1:8787/a/20260510-0945-artifact-ifw1wa/index.html
```
### 3. Use Tailscale for private network sharing
Start the server on the detected Tailscale IP:
```bash
pageshelf serve --tailscale
```
Generate Tailscale URLs from `put`:
```bash
pageshelf put --tailscale index.html
```
Or later:
```bash
pageshelf url --tailscale 20260510-0945-artifact-ifw1wa
```
## Core Commands
### `serve`
```bash
pageshelf serve
pageshelf serve --tailscale
pageshelf serve --host 127.0.0.1 --port 8787
pageshelf serve --host 0.0.0.0 --unsafe-public-bind
```
Behavior:
- Defaults to `127.0.0.1:8787`.
- `--tailscale` binds to a detected `100.64.0.0/10` Tailscale address.
- Public wildcard binds such as `0.0.0.0` and `::` require `--unsafe-public-bind`.
- Server has read/write/header timeouts and graceful shutdown.
Prefer `--tailscale` over `0.0.0.0` for private sharing.
### `put`
Publish files into a session. If `--session` is omitted, pageshelf creates a new session.
```bash
pageshelf put index.html
pageshelf put report.md
pageshelf put --raw source.md
pageshelf put index.html assets/
pageshelf put -s pr-review index.html
pageshelf put -s pr-review ./artifact/
```
From stdin:
```bash
cat report.html | pageshelf put --stdin --name index.html
```
Inline content:
```bash
pageshelf put --content '<!doctype html><h1>Report</h1>' --name index.html
```
Machine-readable output:
```bash
pageshelf put --json index.html
```
Generate URL for a non-default server endpoint:
```bash
pageshelf put --host 100.88.12.34 --port 8787 index.html
pageshelf put --tailscale index.html
pageshelf put --base-url http://agent-box:8787 index.html
```
With `--tailscale`, pageshelf prefers the local MagicDNS name when available, e.g. `http://omarchy-1.tailaf73.ts.net:8787/...`, and falls back to the raw `100.x.y.z` Tailscale IP.
URL selection from `put`:
- A single file returns that file's URL, e.g. `pageshelf put foo.html` returns `/foo.html`.
- Markdown files render to HTML by default: `report.md` returns `/report.html`, `index.md` returns `/index.html`.
- Use `--raw` when you need to serve the Markdown file itself, e.g. `pageshelf put --raw report.md` returns `/report.md`.
- `--stdin --name index.html` and `--content ... --name index.html` return `/index.html`.
- `--stdin --name report.md` and `--content ... --name report.md` render Markdown unless `--raw` is supplied.
- Multi-file/directory uploads prefer `index.html` if present; otherwise they return the first added file.
Interactive HTML:
```bash
- pageshelf put --interactive index.html
+ pageshelf put index.html
```
- Only use `--interactive` when the artifact needs local JavaScript for sliders, tabs, copy buttons, animations, or custom editors.
+ HTML artifacts are interactive by default. `--interactive` is deprecated and only accepted as a compatibility no-op; do not add it to new examples. Served HTML gets Pageshelf's built-in annotation runtime by default; use `pageshelf put --no-annotations ...` when a session should opt out. The runtime provides a mobile-friendly Review button, Shadow DOM bottom sheet/desktop panel, annotations stored in same-origin `localStorage` for the current URL path, text-selection anchors with quote/prefix/suffix/heading/path metadata, element picking, edit/delete/resolve controls, **Copy annotations**, **Copy JSON**, and **Export JSON**. Treat same-origin artifacts in a session as trusted because browser storage is same-origin. There is no automatic sending; users explicitly copy/paste notes when they want to hand them off.
### `session`
```bash
pageshelf session create rate-limiter
pageshelf session create rate-limiter --tag plan --tag backend --ttl 14d
pageshelf session create rate-limiter --expires-at 2026-06-01 --json
pageshelf session info rate-limiter
pageshelf session info rate-limiter --json
pageshelf session meta rate-limiter --tag pr-review --ttl 7d
pageshelf session meta rate-limiter --clear-tags --ttl 0
pageshelf session rm rate-limiter
pageshelf gc --dry-run
pageshelf gc
```
Most agent workflows do not need `session create`; `put` can auto-create sessions. New sessions get `created_at`, `updated_at`, and `expires_at` metadata automatically; default retention is 14 days. Add tags during creation with `--tag`, replace them later with `session meta --tag ...`, and remove expired sessions with `pageshelf gc`. Use `--ttl 0` only when the artifact should not expire automatically.
### `list`, `files`, and `url`
```bash
pageshelf list
pageshelf list --json
pageshelf files <session>
pageshelf files <session> --json
pageshelf url <session>
pageshelf url <session> diff.html
pageshelf url --tailscale <session>
pageshelf url --base-url http://agent-box:8787 <session>
```
Use `url` when you already added files and need to regenerate a share link.
## Agent Workflow Patterns
### Long planning output
1. Write a concise chat summary.
2. Generate `index.html` with the full plan, diagrams, code snippets, and risk sections.
3. Publish it:
```bash
pageshelf put --tailscale index.html
```
4. Return the URL and a 3-5 bullet summary in chat.
+
+ ### Mobile-first review/annotation artifacts
+
+ For plans, PR explainers, and annotated diffs, make the HTML feel like a selection-first review surface:
+
+ - single-column readable layout on phones
+ - large tap targets for findings, checklist rows, and code/diff locations
+ - visible annotation/review panel by default when the artifact is for review
+ - generic **Copy annotations**, **Copy JSON**, and **Export JSON** actions for manual handoff or tool ingestion
+ - no automatic submission back to Hermes or another agent; the user explicitly pastes copied annotations
+
### PR explainer
Create a self-contained HTML file with:
- high-level summary
- annotated diff snippets
- severity-colored findings
- data/control-flow diagrams
- test checklist
Then publish:
```bash
pageshelf put --tailscale pr-review.html
```
If the filename is not `index.html`, either name it explicitly while writing or call:
```bash
pageshelf url --tailscale <session> pr-review.html
```
### Multi-file artifact
If HTML references local assets, put the whole directory:
```bash
pageshelf put --tailscale ./artifact/
```
Inside the HTML, use relative links:
```html
<img src="./assets/flow.svg" alt="Flow diagram">
<link rel="stylesheet" href="./style.css">
```
Pageshelf serves them under the same session route.
### Existing session append
When `put` creates a session, capture the returned session ID. Add follow-up files with `-s`:
```bash
pageshelf put --json index.html
pageshelf put -s 20260510-0945-artifact-ifw1wa data.json diagram.svg
```
Avoid relying on hidden current-session state. Pageshelf intentionally makes session choice explicit after the first auto-create.
## Security Model
Pageshelf is designed for private local/tailnet artifact serving, not public internet hosting.
Key protections:
- - tokenized read URLs
- - token hash stored in manifest
- - local token file stored separately
- default localhost bind
- explicit Tailscale bind
- wildcard bind requires `--unsafe-public-bind`
- path traversal prevention
- symlink rejection on input
- - restrictive CSP
+ - tight private-artifact CSP that allows local inline annotation UI but blocks network sends
- `Cache-Control: no-store`
- `X-Content-Type-Options: nosniff`
- `Referrer-Policy: no-referrer`
- `X-Frame-Options: DENY`
- ### Safe vs Interactive CSP
-
- Default safe mode disables scripts:
-
- ```http
- script-src 'none'
- connect-src 'none'
- ```
+ ### CSP for Private Interactive Artifacts
- Interactive mode permits inline JavaScript but still blocks network fetches:
+ Pageshelf assumes artifacts are generated by the user or their agent for private review, but network sends remain blocked. Default served HTML CSP:
```http
+ default-src 'none'
script-src 'self' 'unsafe-inline'
+ style-src 'self' 'unsafe-inline'
+ img-src 'self' data: blob:
+ font-src 'self' data:
connect-src 'none'
+ object-src 'none'
+ base-uri 'none'
+ frame-ancestors 'none'
+ form-action 'none'
+ worker-src 'none'
+ child-src 'none'
+ frame-src 'none'
```
- Use `--interactive` only for artifacts that need browser-side behavior.
+ With `--no-annotations`, Pageshelf skips runtime injection and serves HTML with `script-src 'none'` and `connect-src 'none'`. Keep assets local and avoid remote trackers/CDNs. Artifact URLs are capability-free; private serving depends on localhost/Tailscale binding, path traversal checks, symlink rejection, no-store headers, safe CSP, and secret scanning.
### Sensitive data rule
- Do not publish secrets, credentials, raw tokens, private keys, production `.env` files, or confidential dumps. Tokenized Tailscale URLs are convenient, not a replacement for data classification.
+ Do not publish secrets, credentials, raw tokens, private keys, production `.env` files, or confidential dumps. Pageshelf URLs do not contain read tokens; local/Tailscale serving is not a replacement for data classification.
## HTML Authoring Guidance
For agent-created pages:
- Make the HTML self-contained where practical.
- Prefer inline CSS for portability.
- Use SVG for architecture and sequence diagrams.
- Use relative asset paths for multi-file artifacts.
- Include a short executive summary at the top.
- Add a sticky table of contents for long reports.
- - Include copy buttons only when `--interactive` will be used.
+ - Include copy buttons freely; HTML is interactive by default. For review pages, add a generic **Copy annotations** button that copies selected locations plus comments, not Hermes-specific prompts.
- Avoid external CDNs; CSP and tailnet usage make local assets more reliable.
- Do not embed third-party analytics, trackers, fonts, or remote scripts.
For Telegram handoff, keep the chat reply short:
```text
詳細HTML作った:
<url>
中身:
- architecture diagram
- security checklist
- implementation tasks
```
## Common Pitfalls
- 1. **Forgetting the server endpoint in generated URLs.**
+ 1. **Starting a new server for every `put`.**
+ `put` only writes into the data directory and prints a URL; it does not require a fresh server. Check `/healthz` on the intended local/Tailscale endpoint first and reuse the running systemd/service process when available. Spawning ad-hoc servers leads to port drift (`8791`, `8792`, `8796`, ...), stale links, and confusion.
+
+ 2. **Forgetting the server endpoint in generated URLs.**
If the server runs with `--tailscale`, generate URLs with `pageshelf put --tailscale ...` or `pageshelf url --tailscale ...`.
- 2. **Expecting `/a/<session>/...` to work without a token.**
- Artifact URLs require `?t=psr_...`.
+ 3. **Assuming the URL itself is secret.**
+ Artifact URLs are clean and capability-free. Rely on localhost/Tailscale/private bind assumptions and do not publish sensitive content.
- 3. **Putting a directory with absolute asset links.**
+ 4. **Putting a directory with absolute asset links.**
HTML should use relative paths like `./assets/diagram.svg`, not `/tmp/artifact/assets/diagram.svg`.
- 4. **Using `--interactive` casually.**
- It allows inline JavaScript. Use safe mode unless the page needs client-side behavior.
+ 5. **Treating `--interactive` as required.**
+ It is deprecated/no-op. Publish normal HTML and design the artifact to be interactive by default when useful.
- 5. **Publishing secrets because the server is “only Tailscale.”**
+ 6. **Publishing secrets because the server is “only Tailscale.”**
Tailnet access is still access. Redact or summarize sensitive content first.
- 6. **Binding to `0.0.0.0` out of habit.**
+ 7. **Binding to `0.0.0.0` out of habit.**
Use `--tailscale` for private sharing. Only use `--unsafe-public-bind` when you understand the exposure.
- 7. **Losing the session ID from auto-create output.**
+ 8. **Losing the session ID from auto-create output.**
Use `--json` when an agent needs to parse and reuse the session ID.
- 8. **Expecting pageshelf to host an app backend.**
+ 9. **Expecting pageshelf to host an app backend.**
It serves static files. Interactive artifacts must run fully in the browser.
## Verification Checklist
After publishing an artifact:
- [ ] `pageshelf serve` or `pageshelf serve --tailscale` is running.
- - [ ] The URL path uses `/a/<session>/<file>` and includes `?t=psr_...`.
+ - [ ] The URL path uses clean `/a/<session>/<file>` form, with no token query.
- [ ] The artifact opens successfully in a browser.
- - [ ] Wrong or missing token returns `401`.
- - [ ] Safe artifacts do not require JavaScript.
- - [ ] Interactive artifacts were published with `--interactive` intentionally.
+ - [ ] Mobile review controls are reachable and readable.
+ - [ ] Copy annotations is generic and copies locations plus comments for manual paste.
- [ ] No secrets or raw credentials are present in the HTML or asset files.
- [ ] Multi-file assets load via relative paths.
- [ ] Chat response includes only the short summary plus URL.
## One-Shot Recipes
### Publish a long report to Tailscale
```bash
pageshelf serve --tailscale
pageshelf put --tailscale report.html
```
### Publish a generated HTML string from an agent
```bash
cat <<'HTML' | pageshelf put --stdin --name index.html --tailscale
<!doctype html>
<html>
<head><meta charset="utf-8"><title>Artifact</title></head>
<body><h1>Artifact</h1></body>
</html>
HTML
```
### Publish an interactive tuning UI
```bash
- pageshelf put --interactive --tailscale index.html assets/
+ pageshelf put --tailscale index.html assets/
```
### Regenerate a link for a different server address
```bash
pageshelf url --base-url http://100.88.12.34:8787 <session> index.html
```
### Inspect and clean up
```bash
pageshelf list
pageshelf files <session>
pageshelf session rm <session>
```