Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
153 commits
Select commit Hold shift + click to select a range
5631286
Add agent collaborators design spec
alcor Aug 30, 2026
d1a65a3
Add connect UI section to agent collaborators spec
alcor Aug 30, 2026
c803d89
Rename worker to vapor and route vapor.fyi as custom domain
alcor Aug 30, 2026
3588d03
Add agent collaborators implementation plan
alcor Aug 30, 2026
2d6a7fe
Trigger CI
alcor Aug 30, 2026
cdb4bdd
Add shared agent protocol module
alcor Aug 30, 2026
12efca4
Add Yjs markdown block layer with content-hash anchors
alcor Aug 30, 2026
1d96511
Reuse critic-constants delimiters and nest overlapping marks
alcor Aug 30, 2026
5fae152
Serve documents at the root path
alcor Aug 30, 2026
34f2e01
Add per-document agent token roster
alcor Aug 30, 2026
2773142
Clear agent_tokens on document expiry
alcor Aug 30, 2026
73cf40c
Add agent read and mutation RPCs with anchor checks
alcor Aug 30, 2026
6cc81d2
Fix agentReplace silently no-opping delete on inverted to<from range
alcor Aug 30, 2026
2bbc906
Add performance engine for paced agent edits
alcor Aug 30, 2026
bdb10cf
Fix stale index/position and mid-typing eviction duplication in perfo…
alcor Aug 30, 2026
3b71b2f
Add synthetic agent presence to awareness
alcor Aug 30, 2026
743c87b
Add document events with mention detection and long-poll
alcor Aug 30, 2026
a7b82b6
Fix thread_reply over-firing, missing rate limits, and error code ove…
alcor Aug 30, 2026
539eab4
Serve MCP at /mcp backed by DocumentAgent RPCs
alcor Aug 30, 2026
a351ac3
Add raw markdown export and MCP help page
alcor Aug 30, 2026
7b8a56f
Fix unescaped MCP help origin and add nosniff to markdown export
alcor Aug 30, 2026
e583b55
Add invite agent dialog and roster management
alcor Aug 30, 2026
3628c0e
Fix invite agent dialog accessibility
alcor Aug 30, 2026
4c9e0d0
Redirect secondary domains to vapor.fyi
alcor Aug 30, 2026
95349a5
Update README and CLAUDE.md for the vapor fork
alcor Aug 30, 2026
dd57c88
Redirect pre-rename /docs/:id links to the root path
alcor Aug 30, 2026
597a6f9
Enforce the reserved root-slug list
alcor Aug 30, 2026
422d163
Make agent writes survive unsupported markup and corrupt rows
alcor Aug 30, 2026
60454da
Fix typed @mentions, agentless digests, and event fan-out
alcor Aug 30, 2026
8a55385
Guard create_document input and cap the agent roster
alcor Aug 30, 2026
54f5efe
Validate before charging rate limit, and refresh last_seen_at for any…
alcor Aug 30, 2026
0607be0
Merge branch 'alcor/deploy-vapor-fyi' into alcor/agent-collaborators
alcor Aug 30, 2026
c64788f
Broadcast agent-origin updates to connected clients
alcor Aug 30, 2026
077e0b7
Rename user-visible strings from mist to vapor
alcor Aug 30, 2026
ae4c574
Rename theme and frontmatter keys from mist to vapor
alcor Aug 30, 2026
d5126e1
Spec anonymous agents: tokenless MCP with auto-enrolled identity
alcor Aug 30, 2026
e37b9a3
Allow tokenless MCP sessions with auto-enrolled anonymous agents
alcor Aug 30, 2026
fd3a556
Derive create_document's minted agent name from clientInfo too
alcor Aug 30, 2026
dcbdb45
Ignore .superpowers scratch directory
alcor Aug 30, 2026
c80c2a0
Add identity phase design spec
alcor Aug 31, 2026
d34f7a5
Make /mcp the authenticated door, /mcp/anonymous the tokenless one
alcor Aug 31, 2026
e6dd4aa
Retire per-doc tokens in identity spec; identity becomes the credential
alcor Aug 31, 2026
c027aa0
Add identity phase implementation plan
alcor Aug 31, 2026
c3a9450
Port session and Google auth core from subpixel
alcor Aug 31, 2026
6806a90
Add anonymous animal identities with Noto Emoji presence
alcor Aug 31, 2026
9aebefd
Add global Registry durable object for profiles and OAuth state
alcor Aug 31, 2026
b2da398
Add auth routes for Google sign-in sessions
alcor Aug 31, 2026
b50f673
Add OAuth 2.1 authorization server for MCP clients
alcor Aug 31, 2026
a7a7193
Replace per-doc tokens with verified identity; two MCP doors; web sig…
alcor Aug 31, 2026
b37475a
Document the identity model and two MCP doors
alcor Aug 31, 2026
a6dcb1b
Set GOOGLE_CLIENT_ID var for production sign-in
alcor Aug 31, 2026
b0e87d4
Add CIMD client support; fix sign-in presence, attribution, avatars, …
alcor Aug 31, 2026
7adf305
Add /privacy and /terms pages
alcor Aug 31, 2026
5ef93c9
Vary anonymous identities beyond Anonymous with a persistent adjective
alcor Aug 31, 2026
65496d9
Fix duplicate comment threads; attribute agents as their owner's Agent
alcor Aug 31, 2026
cd5b205
Label anonymous agents Agentic <Animal>
alcor Aug 31, 2026
c84e2c3
Use the owner's first name for signed-in agent labels
alcor Aug 31, 2026
cf51825
Merge pull request #2 from arfct/alcor/agent-collaborators
alcor Aug 31, 2026
94755fe
Rewrite README around what vapor is now
alcor Aug 31, 2026
9b6ccfb
Use neutral example identities in code, tests, and specs
alcor Aug 31, 2026
46968ad
Rewrite README in document voice
alcor Aug 31, 2026
8533d9a
Merge pull request #3 from arfct/alcor/agent-collaborators
alcor Aug 31, 2026
2e210c7
Cut README back to plain facts
alcor Aug 31, 2026
67e2e2b
Merge pull request #4 from arfct/alcor/readme-voice
alcor Aug 31, 2026
392b0ff
Redesign toolbar menus, comment cards, and mark underlines
alcor Aug 31, 2026
907014d
Add vector logo as primary favicon
alcor Aug 31, 2026
774a4c7
Simplify the comment rail and fold Preview into the mode menu
alcor Aug 31, 2026
ce89399
Scroll the highlighted phrase into view instead of quoting it
alcor Aug 31, 2026
eb0ab20
Merge pull request #5 from arfct/alcor/ui-refresh
alcor Aug 31, 2026
ca951d2
Plan the notes-app import: WYSIWYG, toolbar, UI kit, styles, roadmap
alcor Aug 31, 2026
0b7a2fc
Fold data-model evaluation into the WYSIWYG plan
alcor Aug 31, 2026
aaf3907
Add Base UI component kit; migrate menus off Radix
alcor Aug 31, 2026
1320c9b
Import the notes app's editor type scale and block styles
alcor Aug 31, 2026
c79d7c4
WYSIWYG editing: rich nodes in the CRDT, markdown at the boundary
alcor Aug 31, 2026
0d115ca
Add the formatting toolbar
alcor Aug 31, 2026
ae97340
Declare secret-backed Env keys so typegen matches CI
alcor Aug 31, 2026
2ec5e36
Plan sleeping tabs and DO duration cost control
alcor Aug 31, 2026
63e2c77
Free-tier safeguards: sleeping tabs and DO wake hygiene
alcor Aug 31, 2026
4473d24
Plan the MCP Events polyfill
alcor Sep 1, 2026
76bcde7
Add DO usage guardrail script; mark plan phases shipped
alcor Sep 1, 2026
db991c2
Declare node globals for the usage script
alcor Sep 1, 2026
e286080
Reorder the header; expose the dev server on the tailnet
alcor Sep 1, 2026
bf6a0bc
Drop the chevrons from the Edit and Share triggers
alcor Sep 1, 2026
b4a5898
Merge pull request #6 from arfct/alcor/notes-import-plans
alcor Sep 1, 2026
cadb2a7
Merge pull request #7 from arfct/alcor/sleeping-tabs-plan
alcor Sep 1, 2026
ad70f85
Read the analytics token from CLOUDFLARE_ANALYTICS_TOKEN
alcor Sep 1, 2026
c716e54
Make webhook policy safe for set-and-forget subscribers
alcor Sep 1, 2026
d47d6dd
Merge main (WYSIWYG engine, safeguards) into the events branch
alcor Sep 1, 2026
94595f9
Implement the MCP Events polyfill
alcor Sep 1, 2026
a68036c
Add the vapor-to-routine mention relay
alcor Sep 1, 2026
4c70c23
Merge pull request #8 from arfct/alcor/mcp-events-polyfill-plan
alcor Sep 1, 2026
47fbf1f
UI polish: header start-editing button, avatar initials, icon and cop…
alcor Sep 1, 2026
37ba2b2
Merge pull request #9 from arfct/fixes/ui-polish
alcor Sep 1, 2026
9ce239c
Add Claude Code plugin and drafting skill
alcor Sep 1, 2026
7c1aae0
Merge pull request #10 from arfct/feat/claude-skill
alcor Sep 1, 2026
3d25064
Default the plugin's MCP connection to signed-in (#11)
alcor Sep 1, 2026
498a987
Redesign homepage as a left-aligned document (#12)
alcor Sep 1, 2026
bcad3a4
Homepage: top bar, Create a document heading, 99-hour blurb (#13)
alcor Sep 1, 2026
20d4984
Header reorder, Invite an agent in Share menu, comment styling (#14)
alcor Sep 1, 2026
72be7df
Homepage as a live document, comment rail polish, agent attribution (…
alcor Sep 2, 2026
2900a70
Skill: share links liberally, watch for ten minutes, archive; mobile …
alcor Sep 2, 2026
3da9164
Header: 48px icon toolbar, + menu, connection dot, VAPOR wordmark (#18)
alcor Sep 2, 2026
ba14600
Mobile web: one layout at every width (#19)
alcor Sep 2, 2026
2213be9
Homepage tour: shorter copy, agent reply (#20)
alcor Sep 2, 2026
6abfc14
Agent instructions block (A-199) (#21)
alcor Sep 2, 2026
f169dc1
Code blocks: syntax highlighting and language selector (A-197) (#22)
alcor Sep 2, 2026
f3e6eaa
Task lists (A-198, part 1) (#23)
alcor Sep 2, 2026
f9234b6
Tables (A-198, part 2) (#27)
alcor Sep 2, 2026
39ed255
Keyboard shortcuts and smart link paste (A-196) (#25)
alcor Sep 2, 2026
ee1e9b6
Suggest mode: tracked inline formatting, blocked structure (A-201) (#26)
alcor Sep 2, 2026
4fc60f6
Editor UI fixes: title flow, placeholder presets, mobile commenting, …
alcor Sep 2, 2026
5f1a579
Side rail, anchored comments, face pile, and a new tour (#29)
alcor Sep 4, 2026
fa86fec
Add version history and attachments plans (#41)
artifact-agent Sep 5, 2026
c0910ae
Mobile chrome: root scroll, one right-aligned toolbar, keyboard-safe …
alcor Sep 5, 2026
414e3db
Tour dialogs: app links, New document and Invite an agent, document r…
alcor Sep 6, 2026
22208f8
Version history with restore (#45)
alcor Sep 6, 2026
c678e84
Attachments: files in R2 as inline blocks (#46)
alcor Sep 6, 2026
2e1ebde
Tour title fades in last, straight to its blurred rest state (#47)
alcor Sep 6, 2026
5f467c6
Comment click selects, floating resolve tools dropped, header polish …
alcor Sep 6, 2026
3a381cd
Agent setup pass: every client, attachments, skill packaging, self-de…
alcor Sep 6, 2026
13894b0
Wake my agent: identity-wide mention delivery to a routine or webhook…
alcor Sep 6, 2026
b4ae397
Invite dialog: client tabs with marks, as-you/anonymously scope, wake…
alcor Sep 6, 2026
6981867
Add @mentions and slash commands with inline completion (#53)
artifact-agent Sep 6, 2026
b3eb3a3
Invite dialog: personally/anonymously and app/CLI pulldowns, copy ico…
alcor Sep 6, 2026
5635102
Portable instances: nothing tied to vapor.fyi, self-hosting guide (#56)
artifact-agent Sep 6, 2026
beb5a44
Reference instance: drop www aliases from REDIRECT_HOSTS (#57)
alcor Sep 6, 2026
76c9b1a
Agent identity: Google-sub principals, short uids, mention tokens wit…
alcor Sep 6, 2026
006da0d
vapor skill: revise a draft's document in place, as a patch (#61)
alcor Sep 6, 2026
d4a5c3a
Hexagon agents in the face pile, animals for anonymous agents, LM Stu…
alcor Sep 6, 2026
8a08239
Sign in with Apple as a second identity provider (#64)
artifact-agent Sep 6, 2026
783af23
Reference instance: enable Sign in with Apple (#65)
alcor Sep 6, 2026
442fc5e
Sign in with Apple: load the JS toolkit from its real URL (#66)
alcor Sep 6, 2026
81b6979
Events between agents: tag agent transactions with the actor, filter …
artifact-agent Sep 7, 2026
4c312f1
DocumentAgent hibernation: clear the awareness interval, move timers …
artifact-agent Sep 7, 2026
43e7f20
Title slugs in document URLs, and shared links that unfurl as the doc…
alcor Sep 7, 2026
4eb547e
MCP comments anchor to the quoted span; resolve, edit, delete over MC…
artifact-agent Sep 9, 2026
d3962b7
Comment editor: stop inheriting the body editor's padding and type (#75)
artifact-agent Sep 9, 2026
749a07c
Face pile shows the viewer's own face, last and set apart (#76)
artifact-agent Sep 9, 2026
5e30cbb
Face pile: centre the faces in the pill (#77)
artifact-agent Sep 9, 2026
6a7315b
OAuth: validate before spending the code, replay window, loopback por…
artifact-agent Sep 9, 2026
c60c380
MCP surface: markdown-copied quotes match, one presence entry per per…
artifact-agent Sep 9, 2026
3a1fed7
Standing instructions: attributed, and handed to agents as untrusted …
artifact-agent Sep 9, 2026
a6fa742
Caret labels on one line; own comments show without a reload (#94)
artifact-agent Sep 9, 2026
4131ea0
Document lifetime in reads, a document.expiring event, and list_docum…
artifact-agent Sep 11, 2026
f932047
Range replace: verify every anchor in the range, charge for what is a…
artifact-agent Sep 11, 2026
a49db6d
Personal access tokens for headless and fleet agents (#97)
artifact-agent Sep 11, 2026
a09bffd
Registry: restore the brace lost merging #95 and #97 (#98)
artifact-agent Sep 11, 2026
dd4498e
Undo and redo that work: hotkeys, Format menu buttons, and two fixes …
artifact-agent Sep 11, 2026
c1f66c7
Send to Kindle and reMarkable, and EPUB export (#101)
artifact-agent Sep 11, 2026
87119f8
ChatGPT plugin-directory readiness (#103) (#104)
artifact-agent Sep 13, 2026
e3f1fc0
EPUB set in the page's type; a printable page for Save as PDF (#102)
artifact-agent Sep 13, 2026
4e0d3b6
Output schemas on every tool; OpenID Connect discovery (#103) (#105)
artifact-agent Sep 13, 2026
56ed8b4
Add ChatGPT app submission metadata
alcor Sep 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agents/skills/vapor/SKILL.md
15 changes: 15 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "vapor",
"owner": {
"name": "Nicholas Jitkoff",
"url": "https://github.com/arfct"
},
"description": "vapor \u2014 live markdown documents people and agents review together",
"plugins": [
{
"name": "vapor",
"source": "./plugin",
"description": "Draft plans and documents on vapor \u2014 live markdown people and agents review together, exported to the repo before the doc expires."
}
]
}
33 changes: 32 additions & 1 deletion .dev.vars.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
# Fathom analytics (optional — omit to disable)
# Local development variables. Copy to .dev.vars (git-ignored); everything
# is optional and the app runs anonymous-only with none of them set.

# Identity (docs/self-hosting.md, "Sign-in providers"). Set either, both,
# or neither; with neither the instance is anonymous-only.
# GOOGLE_CLIENT_ID is the public OAuth client id of a Google "Web
# application" credential whose authorized JavaScript origins include
# http://localhost:5173. APPLE_CLIENT_ID is a Sign in with Apple Services ID
# whose return URLs include http://localhost:5173/auth/apple — note Apple
# only allows https return URLs, so Apple sign-in is usually tested on a
# deployed preview rather than on localhost. SESSION_SECRET signs session
# JWTs; any long random string works locally:
# openssl rand -base64 32
GOOGLE_CLIENT_ID=
APPLE_CLIENT_ID=
SESSION_SECRET=

# Instance identity (app/shared/site.ts). Normally left unset locally: the
# request's own origin (http://localhost:5173) is used everywhere.
# PUBLIC_ORIGIN=
# REDIRECT_HOSTS=
# OPERATOR_NAME=
# SOURCE_URL=

# Send to Kindle by email (docs/self-hosting.md). Both or neither.
# RESEND_API_KEY=
# SEND_FROM_EMAIL=

# Domain verification for listing the MCP server in ChatGPT's plugin directory (docs/self-hosting.md)
# OPENAI_APPS_CHALLENGE=

# Fathom analytics (omit to disable)
VITE_FATHOM_SITE_ID=
VITE_FATHOM_DOMAINS=
50 changes: 50 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Deploys to Cloudflare Workers from GitHub. Off until you add two repository
# secrets (Settings → Secrets and variables → Actions):
#
# CLOUDFLARE_API_TOKEN an API token with the "Edit Cloudflare Workers"
# template (plus R2 write if you let it create the bucket)
# CLOUDFLARE_ACCOUNT_ID from the Workers overview page's sidebar
#
# Optional repository *variable* WRANGLER_CONFIG points the build at another
# wrangler config (the reference instance uses deploy/vapor.fyi.jsonc). With
# the secrets absent the job runs and skips, so a fork without them stays green.
name: Deploy

on:
push:
branches: [main]
workflow_dispatch:

concurrency:
group: deploy
cancel-in-progress: false

jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
WRANGLER_CONFIG: ${{ vars.WRANGLER_CONFIG }}
steps:
- name: Check for Cloudflare credentials
id: creds
run: |
if [ -n "$CLOUDFLARE_API_TOKEN" ] && [ -n "$CLOUDFLARE_ACCOUNT_ID" ]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "No CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID secrets; skipping deploy."
echo "present=false" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@v4
if: steps.creds.outputs.present == 'true'
- uses: actions/setup-node@v4
if: steps.creds.outputs.present == 'true'
with:
node-version: 22
cache: npm
- run: npm ci
if: steps.creds.outputs.present == 'true'
- run: npm run deploy
if: steps.creds.outputs.present == 'true'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ worker-configuration.d.ts

# Coverage
/coverage/
.superpowers/
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
125 changes: 102 additions & 23 deletions CLAUDE.md

Large diffs are not rendered by default.

142 changes: 81 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,95 +1,115 @@
# mist
# vapor

Collaborative markdown editor. A cross between GitHub Gist and Google Docs — share and do multiplayer editing on markdown documents, quickly.
You paste a draft into chat and now there are two copies, both going stale. vapor gives the draft one URL instead: a live markdown document anyone can open and edit, people and AI agents side by side, each with a cursor. It deletes itself after 99 hours.

Everything is public by URL. Documents persist live with no save button. Multiple users see each other's cursors in real time.
vapor is a single Cloudflare Worker you can run yourself: see [Running your own vapor](docs/self-hosting.md). The reference instance is [vapor.fyi](https://vapor.fyi); the examples below use it, and every command works the same against your own origin. A fork of [mist](https://github.com/inanimate-tech/mist).

## Features
## Documents

- **Real-time multiplayer editing** via TipTap + Yjs, backed by Cloudflare Durable Objects
- **Live markdown formatting** — inline styles render as you type, with formatting characters shown in grey
- **Suggest mode** — track changes using CriticMarkup (additions, deletions, comments, highlights)
- **Threaded comments** with highlight anchoring
- **Preview mode** — rendered markdown with click, hover, or keypress toggle
- **CLI upload** — `curl https://your-domain/new -T file.md`
- **Drag and drop** `.md` files to create new documents
- **Dark/light/auto themes**
- **Documents auto-expire** after 99 hours
Anyone with the URL can read and edit. Live markdown with track changes (CriticMarkup), comments anchored to highlights, and a rendered preview. No accounts required, no save button, nothing kept past 99 hours—export before then.

## Tech stack
```bash
curl https://vapor.fyi/new -T notes.md # create from a file; the URL carries the title: /my-notes-<id>
curl https://vapor.fyi/<id>.md # raw markdown back (with or without the slug)
```

- [Cloudflare Workers](https://developers.cloudflare.com/workers/) + [Durable Objects](https://developers.cloudflare.com/durable-objects/) (backend + persistence)
- [Cloudflare Agents SDK](https://developers.cloudflare.com/agents/) (real-time WebSocket agent)
- [React Router 7](https://reactrouter.com/) (SSR)
- [TipTap 3](https://tiptap.dev/) (editor)
- [Yjs](https://yjs.dev/) (CRDT for multiplayer)
- [Tailwind CSS 4](https://tailwindcss.com/) (styling)
- TypeScript, Vitest
## Reading it elsewhere

## Getting started
Every document is also an EPUB at `/<id>.epub`, with tracked changes accepted, comments left out, and images embedded, set in the same sans type as the page; `/<id>/print` is the same copy as a printable page for Save as PDF. Share → Send to device sends it: to a **Kindle** by email once you have saved your Send-to-Kindle address (the instance needs an email sender; see the self-hosting guide), or to a **reMarkable** through its cloud after a one-time pairing code. Download EPUB works for anyone, signed in or not.

### Prerequisites
## People and agents

- Node.js 22+ (see `.nvmrc`)
- A Cloudflare account (free tier works)
Sign-in (Google or Apple) is optional and only changes attribution:

### Setup
| | Human | Agent |
|---|---|---|
| Anonymous | Curious Ladybug 🐞 | Agentic Butterfly 🦋 |
| Signed in | Ada Lovelace | Ada's Claude |

```bash
git clone https://github.com/inanimate-tech/mist.git
cd mist
npm install
```
Your anonymous animal lives in localStorage and follows you between documents. Sign in and your name takes over, earlier comments included. People are circles; agents are hexagons carrying the mark of the client they connected from, in their owner's colour. Your email never appears in a document: mentions carry a short public id, and `@` completion shows names.

## Connecting an agent

### Development
vapor is an [MCP](https://modelcontextprotocol.io) server. Two ways in:

```bash
npm run dev
# signed in: stable identity, write access if you grant it
claude mcp add --transport http vapor https://vapor.fyi/mcp

# anonymous: no setup, suggest and comment only
claude mcp add --transport http vapor https://vapor.fyi/mcp/anonymous
```

### Deploy
The same URL works in claude.ai, ChatGPT (developer mode), Codex CLI, Cursor, Gemini CLI, and VS Code. Every instance serves its own guide at `/mcp` (and `/llms.txt` for agents) with the snippet for each client, and every document's Share → Invite an agent dialog has the same.

Set your Cloudflare account ID via environment variable:
Agents get suggest and comment by default; full write is a separate grant on the consent screen. For a headless machine or a fleet of harnesses, a signed-in person can mint a personal access token (Share → Invite an agent → Other) and send it as a bearer instead of doing the OAuth dance per install. Their edits type in at human pace with a visible cursor (`pace: "instant"` skips the show). Each document's Agents panel lists who's enrolled, with revoke.

Tools: `read_document` · `insert` · `replace` · `suggest` · `comment` · `reply` · `resolve_thread` · `edit_comment` · `delete_comment` · `attach` · `create_document` · `list_documents` · `join` · `leave` · `events_poll` · `events_subscribe`. Reads carry `created_at` and `expires_at`, and `document.expiring` fires six hours before a document deletes itself. A comment with a `quote` attaches to that text exactly like one made in the browser. Attachments (images inline, other files as a chip) need the signed-in endpoint with write. Mention the agent in a document (type `@` and pick it), or reply in one of its threads, and the agent hears about it: by polling `events_poll` for a while after sharing a link, or through a signed webhook from `events_subscribe`.

A fenced block whose language is `agent` carries standing guidance for agents. People see it as a labelled panel in the editor; each block records who last edited it, and `read_document` returns it as `instructions` framed as untrusted document content — it shapes how an agent works in that document, never what it may do outside it.

To have a mention wake an agent that isn't running anywhere, sign in and set a wake target once under Share → Invite an agent, in the Claude tab (routine) or the Other tab (webhook): a [Claude Code routine](https://code.claude.com/docs/en/routines)'s fire URL and token, or an HTTPS webhook. Every mention of your agent, and every reply in its threads, in any document it is on, fires it. The canonical routine prompt is at the end of the `/mcp` guide. Design in [the wake plan](docs/plans/2026-09-06-agent-wake-plan.md).

## The drafting habit

The vapor plugin for Claude Code bundles the MCP connection with a skill that changes where drafts live: plans and proposals go up as vapor docs instead of chat walls, Claude answers comments over MCP, and the settled document is exported to the repo before the 99-hour cliff. The bundled connection is the reference instance's signed-in endpoint — the first tool call prompts a Google sign-in and consent screen.

```bash
export CLOUDFLARE_ACCOUNT_ID=your-account-id
npm run deploy
claude plugin marketplace add arfct/vapor
claude plugin install vapor@vapor
```

### Optional: Analytics

To enable [Fathom](https://usefathom.com/) analytics, set these environment variables (or add to `.dev.vars`):
Just the skill, no plugin: every instance serves it at `/skill.md`, addressed to that instance (source in [`plugin/skills/vapor/SKILL.md`](plugin/skills/vapor/SKILL.md)):

```bash
curl -s https://vapor.fyi/skill.md --create-dirs -o ~/.claude/skills/vapor/SKILL.md
```
VITE_FATHOM_SITE_ID=your-site-id
VITE_FATHOM_DOMAINS=your-domain.com

The skill is in the [Agent Skills](https://agentskills.io) format, so the same file works elsewhere. Codex CLI, Cursor, and GitHub Copilot all read `~/.agents/skills/`; Gemini CLI takes the whole thing, connection included, as an extension:

```bash
curl -s https://vapor.fyi/skill.md --create-dirs -o ~/.agents/skills/vapor/SKILL.md
gemini extensions install https://github.com/arfct/vapor
```

### Commands
In a repository, `.agents/skills/vapor/SKILL.md` (a symlink here) gives every contributor's agent the workflow. `gemini-extension.json` and `skills/` at the root exist for the Gemini install and point at the same file. Running a fork and want the plugin to connect to it instead? [Shipping a plugin for your instance](docs/self-hosting.md#shipping-a-plugin-for-your-instance).

## Run your own

```bash
npm run dev # Local development server
npm run build # Production build
npm run deploy # Build and deploy to Cloudflare Workers
npm run typecheck # TypeScript type checking
npm run lint # ESLint
npm run test # Vitest with coverage
npm run test:watch # Vitest in watch mode
git clone https://github.com/arfct/vapor && cd vapor
npm install
npm run dev # http://localhost:5173, everything emulated locally
npx wrangler login
npx wrangler r2 bucket create vapor-attachments
npm run deploy # → https://vapor.<you>.workers.dev
```

## Project structure
That is a working instance. A custom domain, Google sign-in, the optional instance variables (`PUBLIC_ORIGIN`, `REDIRECT_HOSTS`, `OPERATOR_NAME`, `SOURCE_URL`), deploying from GitHub Actions, and keeping a separate production config are all in [docs/self-hosting.md](docs/self-hosting.md). Nothing in the code names a host: an instance describes itself from the URL it is served at.

## How it's built

Each document is one Cloudflare Durable Object holding the [Yjs](https://yjs.dev/) doc, agent roster, and event log. [TipTap](https://tiptap.dev/) and [React Router 7](https://reactrouter.com/) on the front, the [Agents SDK](https://developers.cloudflare.com/agents/) underneath, and a dependency-free auth stack (Google sign-in, OAuth 2.1 with PKCE and CIMD) ported from [subpixel](https://subpixel.app).

```
agents/ Durable Object agents (server-side document state)
app/
components/ UI components
lib/ Editor logic, utilities, CriticMarkup, Yjs provider
routes/ File-based routing
shared/ Types and constants shared between client and server
workers/ Cloudflare Worker entry point
tests/ Test suite
agents/ Durable Objects: DocumentAgent, VaporMcp, Registry
app/ React Router app
workers/ Worker entry, routes, OAuth server
deploy/ Per-instance wrangler configs (the reference instance's lives here)
tests/ Unit + integration
```

## Developing

Node 22+ (`.nvmrc`).

```bash
npm install
npm run dev # local server
npm run test # also: typecheck, lint
npm run deploy # needs CLOUDFLARE_ACCOUNT_ID
```

## Licence
Sign-in needs `SESSION_SECRET` (a Workers secret) and `GOOGLE_CLIENT_ID` and/or `APPLE_CLIENT_ID` (wrangler vars); all optional in development. See `.dev.vars.example`. Design docs live in [docs/plans/](docs/plans/); the architecture in [docs/technical-architecture.md](docs/technical-architecture.md).

[MIT](LICENSE)
[Privacy](https://vapor.fyi/privacy) · [Terms](https://vapor.fyi/terms) · [MIT](LICENSE)
Loading