Plannotator has three components. Only the hook is required.

Components

ComponentRequiredWhat it does
HookYesLocal binary that intercepts ExitPlanMode, runs the review UI
Share PortalOptionalStatic site that renders shared plans. When you open a share link, this is what loads in your browser.
Paste ServiceOptionalStorage backend for the share portal. When a plan is too large for a URL, the paste service holds the compressed data and the portal fetches it on load.

How sharing works

Small plans are encoded entirely in the URL hash — the share portal reads the hash and renders the plan. No backend involved. The data remains private — it never leaves the URL.

Large plans don’t fit in a URL. When a user explicitly confirms short link creation, the plan is encrypted in the browser (AES-256-GCM) before being sent to the paste service, which stores only the ciphertext and returns a short ID. The decryption key is embedded in the URL fragment (#key=...) and never sent to the server — not even the paste service operator can read stored plans. When someone opens that link, the portal fetches the ciphertext, decrypts it client-side using the key from the URL, and renders the plan.

Without paste service: Sharing still works for plans that fit in a URL. Those plans stay completely private — the data lives only in the URL hash and never touches a server. Large plans show a warning that the URL may be truncated by messaging apps.

With paste service: Large plans get short, reliable URLs that work everywhere. Data is end-to-end encrypted and auto-deletes after the configured TTL.

1. Install the Hook

See Installation for hook setup instructions.

2. Deploy the Share Portal

The share portal is a static single-page application. It has no backend, no database, and makes no network requests beyond fetching paste data for short URLs.

Build

bun install
bun run build:portal

Output: apps/portal/dist/

Deploy

Upload the dist/ folder to any static hosting provider.

Nginx

server {
    listen 80;
    server_name plannotator.internal.example.com;
    root /var/www/plannotator;
    try_files $uri /index.html;
}

AWS S3 + CloudFront

aws s3 sync apps/portal/dist/ s3://your-bucket/ --delete

Configure the CloudFront distribution to return /index.html for 404s (SPA routing).

Vercel / Netlify / Cloudflare Pages

Point to the repository root:

  • Build command: bun run build:portal
  • Output directory: apps/portal/dist

3. Deploy the Paste Service

The paste service accepts compressed plan data and returns a short ID. A compressed plan goes in, a link to retrieve it comes out. Pastes auto-delete after the configured TTL. No database required.

The paste service is fully open source — the same codebase you’re looking at.

Run the binary

Download the paste service binary for your platform from GitHub Releases. Binaries are available for macOS (ARM64, x64), Linux (x64, ARM64), and Windows (x64).

chmod +x plannotator-paste-*
./plannotator-paste-darwin-arm64   # or whichever matches your platform

Pastes stored to ~/.plannotator/pastes/ by default.

Configuration

VariableDefaultDescription
PASTE_PORT19433Server port
PASTE_DATA_DIR~/.plannotator/pastesStorage directory
PASTE_TTL_DAYS7Auto-delete after N days
PASTE_MAX_SIZE524288Max payload size (512KB)
PASTE_ALLOWED_ORIGINS(see defaults)CORS allowed origins

4. Connect the Components

export PLANNOTATOR_SHARE_URL=https://your-portal.example.com
export PLANNOTATOR_PASTE_URL=https://your-paste.example.com

5. Verify

  1. Start a plan review in Claude Code or OpenCode
  2. Add annotations, click ExportShare
  3. Confirm the share URL starts with your configured domain
  4. If the plan is large, click Create short link when prompted
  5. Open the short URL — the plan should render correctly

Built by