pageshelf · v1.0.0 · 2026-06-07 · sha256 0fe163a18b1a6deb

pageshelf v1.0.0B

Immutable. This exact content is served forever at /api/v1/blob/0fe163a18b1a6deb.

---
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 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 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 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, 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. 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
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
```

### 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 --tailscale --allow-image-src https://i.gyazo.com
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.
- `--allow-image-src <origin>` appends explicit image origins to the served artifact CSP, for example `--allow-image-src https://i.gyazo.com`. Repeat the flag for multiple origins.
- 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 index.html
```

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:

- default localhost bind
- explicit Tailscale bind
- wildcard bind requires `--unsafe-public-bind`
- path traversal prevention
- symlink rejection on input
- 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`

### CSP for Private Interactive Artifacts

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'
```

With `--no-annotations`, Pageshelf skips runtime injection and serves HTML with `script-src 'none'` and `connect-src 'none'`. Use `pageshelf serve --allow-image-src <origin>` to append explicit remote image origins to `img-src`, for example `https://i.gyazo.com`. Values must be origins; paths, query strings, fragments, wildcards, whitespace, and semicolons are rejected. This only relaxes image loading; `connect-src 'none'` remains unchanged. 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. 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 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. **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 ...`.

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.

4. **Putting a directory with absolute asset links.**
   HTML should use relative paths like `./assets/diagram.svg`, not `/tmp/artifact/assets/diagram.svg`.

5. **Treating `--interactive` as required.**
   It is deprecated/no-op. Publish normal HTML and design the artifact to be interactive by default when useful.

6. **Publishing secrets because the server is “only Tailscale.”**
   Tailnet access is still access. Redact or summarize sensitive content first.

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.

8. **Losing the session ID from auto-create output.**
   Use `--json` when an agent needs to parse and reuse the session ID.

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 clean `/a/<session>/<file>` form, with no token query.
- [ ] The artifact opens successfully in a browser.
- [ ] 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 --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>
```