Plannotator has three components. Only the hook is required.
Components
| Component | Required | What it does |
|---|---|---|
| Hook | Yes | Local binary that intercepts ExitPlanMode, runs the review UI |
| Share Portal | Optional | Static site that renders shared plans. When you open a share link, this is what loads in your browser. |
| Paste Service | Optional | Storage 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
| Variable | Default | Description |
|---|---|---|
PASTE_PORT | 19433 | Server port |
PASTE_DATA_DIR | ~/.plannotator/pastes | Storage directory |
PASTE_TTL_DAYS | 7 | Auto-delete after N days |
PASTE_MAX_SIZE | 524288 | Max 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
- Start a plan review in Claude Code or OpenCode
- Add annotations, click Export → Share
- Confirm the share URL starts with your configured domain
- If the plan is large, click Create short link when prompted
- Open the short URL — the plan should render correctly