tandem-editor 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +9 -2
- package/CHANGELOG.md +75 -313
- package/README.md +37 -28
- package/dist/channel/index.js +21 -5
- package/dist/channel/index.js.map +1 -1
- package/dist/cli/index.js +405 -33
- package/dist/cli/index.js.map +1 -1
- package/dist/client/assets/CoworkSettings-C9Dd4D9z.js +1 -0
- package/dist/client/assets/core-DhEqZVGG.js +1 -0
- package/dist/client/assets/index-Bz3WFCWw.css +1 -0
- package/dist/client/assets/index-CN_6DqdC.js +228 -0
- package/dist/client/assets/webview-BQBJMQvJ.js +1 -0
- package/dist/client/index.html +47 -1
- package/dist/monitor/index.js +34 -33
- package/dist/monitor/index.js.map +1 -1
- package/dist/server/index.js +3941 -3394
- package/dist/server/index.js.map +1 -1
- package/package.json +6 -7
- package/skills/tandem/SKILL.md +9 -9
- package/dist/client/assets/index-DLTxaDBk.js +0 -351
- package/dist/client/assets/webview-0tvvWtyc.js +0 -1
package/README.md
CHANGED
|
@@ -6,8 +6,6 @@ Have you ever been working on a piece of writing with an LLM and caught yourself
|
|
|
6
6
|
|
|
7
7
|
And because Tandem hooks into Claude as an MCP server, you're not stuck in some stripped-down document-editing silo. It's the full Claude — with all its knowledge, your conversation context, and every tool it has access to — just now it can also see and edit your document.
|
|
8
8
|
|
|
9
|
-

|
|
10
|
-
|
|
11
9
|
## Why Tandem?
|
|
12
10
|
|
|
13
11
|
- **No more copy-paste ping-pong.** Select text in the editor, and Claude reads your selection directly. Ask "what do you think of this?" or "make this more concise" — Claude knows exactly which text you mean.
|
|
@@ -29,7 +27,7 @@ Requires **Node.js 22+** ([download](https://nodejs.org)) and **Claude Code** (`
|
|
|
29
27
|
```bash
|
|
30
28
|
npm install -g tandem-editor
|
|
31
29
|
tandem setup # registers MCP tools + installs Claude Code skill
|
|
32
|
-
tandem # starts server + opens
|
|
30
|
+
tandem # starts server + opens editor
|
|
33
31
|
```
|
|
34
32
|
|
|
35
33
|
`tandem setup` auto-detects Claude Code and Claude Desktop, writes MCP configuration, and installs a skill (`~/.claude/skills/tandem/SKILL.md`) that teaches Claude how to use Tandem's tools effectively. Re-run after upgrading (`npm update -g tandem-editor && tandem setup`).
|
|
@@ -73,7 +71,7 @@ claude --dangerously-load-development-channels server:tandem-channel
|
|
|
73
71
|
|
|
74
72
|
This is the magic-sauce mode — and it's the one I'd recommend you run with. The channel shim pushes events (selections, annotations, chat) to Claude over SSE the moment they happen, so Tandem genuinely feels like there's another person on the other end of the document: someone watching what you highlight, reacting to edits you accept, and chiming in on a paragraph the instant you select it, the way a collaborator on a Google Doc would. The `--dangerously-load-development-channels` flag is an experimental Claude Code feature, which is why it isn't on by default — but turning it on is what makes the whole experience click.
|
|
75
73
|
|
|
76
|
-
**Recommended layout:** snap the Claude Code terminal to one side of your screen and the Tandem
|
|
74
|
+
**Recommended layout:** snap the Claude Code terminal to one side of your screen and the Tandem editor window to the other. You'll be flipping attention between them constantly, and having both visible is what makes the side-by-side-collaborator feeling land.
|
|
77
75
|
|
|
78
76
|
Then try:
|
|
79
77
|
|
|
@@ -81,11 +79,11 @@ Then try:
|
|
|
81
79
|
"Open sample/welcome.md and review it with me"
|
|
82
80
|
```
|
|
83
81
|
|
|
84
|
-
Claude calls `tandem_open`, the document appears in the
|
|
82
|
+
Claude calls `tandem_open`, the document appears in the editor, and you're ready to collaborate.
|
|
85
83
|
|
|
86
84
|
#### The core loop — no copy/paste
|
|
87
85
|
|
|
88
|
-
1. Highlight a paragraph in the
|
|
86
|
+
1. Highlight a paragraph in the editor.
|
|
89
87
|
2. With channels on, Claude often reacts before you even say anything. Otherwise, just type what you want in the terminal: *"what do you think of this paragraph?"* or *"rewrite this to be more concise"*.
|
|
90
88
|
3. Claude reads your selection directly from the shared Tandem state (via `activity.selectedText` on `tandem_checkInbox`). You never paste the passage into the terminal.
|
|
91
89
|
4. Claude replies in the Tandem chat sidebar (`tandem_reply`) or drops annotations on the document (`tandem_annotate` / `tandem_suggestEdit`), which you can accept, dismiss, or edit in the side panel.
|
|
@@ -119,7 +117,7 @@ Or check the raw health endpoint:
|
|
|
119
117
|
|
|
120
118
|
```bash
|
|
121
119
|
curl http://localhost:3479/health
|
|
122
|
-
# → {"status":"ok","version":"0.
|
|
120
|
+
# → {"status":"ok","version":"0.8.0","transport":"http","hasSession":false}
|
|
123
121
|
```
|
|
124
122
|
|
|
125
123
|
`hasSession` becomes `true` once Claude Code connects.
|
|
@@ -131,7 +129,7 @@ curl http://localhost:3479/health
|
|
|
131
129
|
git clone https://github.com/bloknayrb/tandem.git
|
|
132
130
|
cd tandem
|
|
133
131
|
npm install
|
|
134
|
-
npm run dev:standalone # starts server (:3478/:3479) +
|
|
132
|
+
npm run dev:standalone # starts server (:3478/:3479) + editor client (:5173)
|
|
135
133
|
```
|
|
136
134
|
|
|
137
135
|
Open http://localhost:5173 — you'll see `sample/welcome.md` loaded automatically on first run. The `.mcp.json` in the repo configures Claude Code automatically when run from this directory.
|
|
@@ -142,7 +140,7 @@ Open http://localhost:5173 — you'll see `sample/welcome.md` loaded automatical
|
|
|
142
140
|
|
|
143
141
|
You point at text, Claude sees it. Here's how that plays out day-to-day:
|
|
144
142
|
|
|
145
|
-
- **Open a document.** Ask Claude (`"let's work on notes.md in tandem"`), drag a file onto the
|
|
143
|
+
- **Open a document.** Ask Claude (`"let's work on notes.md in tandem"`), drag a file onto the editor, or click the **+** in the tab bar. `.md`, `.txt`, `.html`, and `.docx` (review-only) are supported.
|
|
146
144
|
- **Point at what you mean.** Select text in the editor and ask Claude about "this paragraph" in the terminal — or just wait for Claude to react if you have channels on. Claude reads your selection directly, no copy-paste needed. Hold the selection for about a second so it registers (dwell-time gating filters out incidental clicks).
|
|
147
145
|
- **Iterate on Claude's response.** Claude's suggestions appear as annotations in the side panel — accept, dismiss, edit, or ask follow-up questions. Each round refines the text without you ever leaving the document. Press **Ctrl+Shift+R** for keyboard review mode: **Tab** to navigate, **Y** accept, **N** dismiss, **E** edit, **Z** undo within a 10-second window.
|
|
148
146
|
- **Heads-down vs collaborative.** Toggle **Solo** mode when you want to write without interruptions — Tandem queues non-urgent annotations until you flip back to **Tandem** mode. Both `tandem_status` and `tandem_checkInbox` return the current mode so Claude adapts its behavior automatically.
|
|
@@ -154,20 +152,14 @@ Everything in Tandem is built around one idea: you work in the document, Claude
|
|
|
154
152
|
|
|
155
153
|
### Chat
|
|
156
154
|
|
|
157
|
-

|
|
158
|
-
|
|
159
155
|
Send messages to Claude alongside your document. Select text before sending to attach it as context — Claude sees exactly what you mean. Clicking an anchored selection later scrolls back to that passage.
|
|
160
156
|
|
|
161
157
|
### Annotations
|
|
162
158
|
|
|
163
|
-

|
|
164
|
-
|
|
165
159
|
This is how Claude's feedback shows up in the document. Claude adds highlights, comments, suggestions, and flags directly on the text. Suggestion cards show a visual diff — original text in red strikethrough, replacement in green. The side panel lists all annotations with filtering by type, author, and status. Accept, dismiss, or edit each one individually — or use bulk actions to process them in batches.
|
|
166
160
|
|
|
167
161
|
### Review Mode
|
|
168
162
|
|
|
169
|
-

|
|
170
|
-
|
|
171
163
|
Press **Ctrl+Shift+R** to enter keyboard review mode. Navigate with **Tab**, accept with **Y**, dismiss with **N**, examine with **E**. A 10-second undo window with a visual countdown lets you reverse accidental accepts. Shortcut hints appear below the Review button.
|
|
172
164
|
|
|
173
165
|
### More
|
|
@@ -183,42 +175,52 @@ Press **Ctrl+Shift+R** to enter keyboard review mode. Navigate with **Tab**, acc
|
|
|
183
175
|
- **Configurable display name** — set your name so Claude knows who's reviewing
|
|
184
176
|
- **Atomic file saves** — write to temp, then rename, preventing partial writes
|
|
185
177
|
- **E2E tested** — Playwright tests cover the annotation lifecycle end-to-end
|
|
178
|
+
- **Authorship text coloring** — blue for your edits, orange for Claude's, toggled per-document
|
|
179
|
+
- **Threaded annotation replies** — back-and-forth conversation on any annotation
|
|
180
|
+
- **Auto-save** — documents save on change; Ctrl+S for manual trigger
|
|
181
|
+
- **Settings popover** — Light/Dark/System theme, text size (S/M/L), reduce motion, display name (Ctrl+,)
|
|
182
|
+
- **Auth tokens for LAN exposure** — bind to `0.0.0.0` with auto-generated tokens; `tandem rotate-token` for rotation
|
|
183
|
+
- **Durable annotation persistence** — annotations survive server restarts independently of session files
|
|
184
|
+
- **Claude Code plugin** — `tandem mcp-stdio` + `tandem channel` bridge Tandem into Cowork and Claude Desktop
|
|
186
185
|
|
|
187
186
|
## Where Tandem is headed
|
|
188
187
|
|
|
189
|
-
|
|
188
|
+
Since the v0.4.0 desktop app launch, Tandem has added auth tokens for LAN exposure (v0.7.0), a Claude Code plugin bridge for Cowork and Claude Desktop (v0.6.0+), durable annotation persistence, settings with Light/Dark/System theming, authorship text coloring, and coordinate system correctness fixes with semantic token enforcement (v0.8.0). A few directions on the radar for later releases:
|
|
190
189
|
|
|
191
|
-
- **High-fidelity .docx round-trip** — current `.docx` support is review-only;
|
|
192
|
-
- **Claude Desktop parity** — the MCP server already works with Claude Desktop; polish and documentation for a first-class experience there is in the works.
|
|
190
|
+
- **High-fidelity .docx round-trip** — current `.docx` support is review-only; production export is planned so you can stay in Tandem through the final draft.
|
|
193
191
|
- **Exportable annotated documents** — PDF (and eventually `.docx`) with annotations baked in, so you can share reviewed drafts outside Tandem.
|
|
194
192
|
- **Code editing mode** — CodeMirror 6 surface for reviewing code the same way you review prose.
|
|
195
|
-
- **Standalone mode** — direct Anthropic API connection so Tandem can run without Claude Code in the loop, for users who want a pure
|
|
193
|
+
- **Standalone mode** — direct Anthropic API connection so Tandem can run without Claude Code in the loop, for users who want a pure standalone experience.
|
|
196
194
|
|
|
197
195
|
See the full [Roadmap](docs/roadmap.md) and [Known Limitations](docs/roadmap.md#known-limitations-v1) for the complete picture, including items that are explicitly out of scope for v1.
|
|
198
196
|
|
|
199
197
|
## Documentation
|
|
200
198
|
|
|
201
|
-
- **[User Guide](docs/user-guide.md)** — How to use Tandem:
|
|
202
|
-
- [MCP Tool Reference](docs/mcp-tools.md) —
|
|
199
|
+
- **[User Guide](docs/user-guide.md)** — How to use Tandem: editor UI, annotations, chat, review mode, keyboard shortcuts
|
|
200
|
+
- [MCP Tool Reference](docs/mcp-tools.md) — 28 MCP tools + channel API endpoints
|
|
203
201
|
- [Architecture](docs/architecture.md) — System design, data flows, coordinate systems, channel push
|
|
204
202
|
- [Workflows](docs/workflows.md) — Claude Code usage patterns: text iteration, cross-referencing, multi-model
|
|
205
203
|
- [Roadmap](docs/roadmap.md) — Phase 2+ roadmap, known issues, future extensions
|
|
206
|
-
- [Design Decisions](docs/decisions.md) — ADR-001 through ADR-
|
|
207
|
-
- [Lessons Learned](docs/lessons-learned.md) —
|
|
204
|
+
- [Design Decisions](docs/decisions.md) — ADR-001 through ADR-024
|
|
205
|
+
- [Lessons Learned](docs/lessons-learned.md) — 48 implementation lessons
|
|
208
206
|
|
|
209
207
|
## CLI Commands
|
|
210
208
|
|
|
211
209
|
| Command | What it does |
|
|
212
210
|
|---------|-------------|
|
|
213
|
-
| `tandem` | Start server and open
|
|
211
|
+
| `tandem` | Start server and open editor (global install) |
|
|
214
212
|
| `tandem setup` | Register MCP tools with Claude Code / Claude Desktop |
|
|
215
213
|
| `tandem setup --force` | Register to default paths regardless of auto-detection |
|
|
216
214
|
| `tandem --version` | Show installed version |
|
|
217
215
|
| `tandem --help` | Show usage |
|
|
216
|
+
| `tandem setup --with-channel-shim` | Also register the stdio channel shim |
|
|
217
|
+
| `tandem rotate-token` | Rotate auth token (60-second grace window) |
|
|
218
|
+
| `tandem mcp-stdio` | Run as stdio MCP server (proxy to local HTTP, for plugin bridge) |
|
|
219
|
+
| `tandem channel` | Run the channel shim (stdio MCP for plugin's tandem-channel entry) |
|
|
218
220
|
|
|
219
221
|
## MCP Configuration
|
|
220
222
|
|
|
221
|
-
Tandem registers two MCP connections: **HTTP** for document tools (
|
|
223
|
+
Tandem registers two MCP connections: **HTTP** for document tools (28 tools including annotation editing — always on), and a **channel shim** for real-time push notifications. The channel shim is what enables the live-collaborator experience described in [Connect Claude Code](#connect-claude-code) and is recommended; it activates when you start Claude Code with `--dangerously-load-development-channels server:tandem-channel`. If you'd rather not pass that experimental flag, the entry sits idle and everything still works through polling on the HTTP connection — you just lose spontaneous reactions.
|
|
222
224
|
|
|
223
225
|
**Global install** (`tandem setup`): Automatically writes both entries to `~/.claude/mcp_settings.json` (Claude Code) and/or `claude_desktop_config.json` (Claude Desktop) with absolute paths. No manual configuration needed.
|
|
224
226
|
|
|
@@ -254,6 +256,13 @@ All optional — defaults work out of the box.
|
|
|
254
256
|
| `TANDEM_TRANSPORT` | `http` | Transport mode (`http` or `stdio`) |
|
|
255
257
|
| `TANDEM_NO_SAMPLE` | unset | Set to `1` to skip auto-opening `sample/welcome.md` |
|
|
256
258
|
| `TANDEM_CLAUDE_CMD` | `claude` | Claude Code executable name (for `tandem setup` auto-detection) |
|
|
259
|
+
| `TANDEM_BIND_HOST` | `127.0.0.1` | Bind address for MCP HTTP (`0.0.0.0` for LAN) |
|
|
260
|
+
| `TANDEM_AUTH_TOKEN` | auto-generated | Override auth token (set by Tauri; manual use rare) |
|
|
261
|
+
| `TANDEM_ALLOW_UNAUTHENTICATED_LAN` | unset | Set to `1` to skip token requirement on LAN bind |
|
|
262
|
+
| `TANDEM_LAN_IP` | auto-detected | Explicit LAN IP for multi-homed machines |
|
|
263
|
+
| `TANDEM_REQUEST_TIMEOUT_MS` | `30000` | Per-request timeout in stdio bridge (ms) |
|
|
264
|
+
| `TANDEM_APP_DATA_DIR` | platform default | Override app-data root (sessions, auth-token, annotations) |
|
|
265
|
+
| `TANDEM_ANNOTATION_STORE` | unset | Set to `off` to disable durable annotation persistence |
|
|
257
266
|
|
|
258
267
|
See `.env.example` for a copy-paste template.
|
|
259
268
|
|
|
@@ -270,10 +279,10 @@ Tandem kills stale processes on :3478/:3479 at startup. If another app uses thos
|
|
|
270
279
|
**Channel shim fails to start**
|
|
271
280
|
The `tandem-channel` entry spawns a subprocess. For global installs, `tandem setup` writes absolute paths to the bundled `dist/channel/index.js` — re-run `tandem setup` after upgrading. For dev setup, if you see `MODULE_NOT_FOUND` with a production config (`node dist/channel/index.js`), run `npm run build`. The default dev config uses `npx tsx` and doesn't require a build step.
|
|
272
281
|
|
|
273
|
-
**
|
|
274
|
-
The
|
|
282
|
+
**Editor shows "Cannot reach the Tandem server"**
|
|
283
|
+
The editor connects to the server via WebSocket. For global installs, run `tandem` to start the server. For dev setup, use `npm run dev:standalone` (or `npm run dev:server`). The message appears after 3 seconds of failed connection.
|
|
275
284
|
|
|
276
|
-
**Empty
|
|
285
|
+
**Empty editor with no document**
|
|
277
286
|
On first run, `sample/welcome.md` auto-opens. If you've cleared sessions or deleted the sample file, click the **+** button in the tab bar or drop a file onto the editor.
|
|
278
287
|
|
|
279
288
|
## Development
|
package/dist/channel/index.js
CHANGED
|
@@ -17945,7 +17945,7 @@ async function authFetch(url, init) {
|
|
|
17945
17945
|
return fetch(url, init);
|
|
17946
17946
|
}
|
|
17947
17947
|
|
|
17948
|
-
// src/
|
|
17948
|
+
// src/shared/events/types.ts
|
|
17949
17949
|
var VALID_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
17950
17950
|
"annotation:created",
|
|
17951
17951
|
"annotation:accepted",
|
|
@@ -18005,6 +18005,7 @@ function formatEventContent(event) {
|
|
|
18005
18005
|
}
|
|
18006
18006
|
default: {
|
|
18007
18007
|
const _exhaustive = event;
|
|
18008
|
+
void _exhaustive;
|
|
18008
18009
|
return `Unknown event${doc}`;
|
|
18009
18010
|
}
|
|
18010
18011
|
}
|
|
@@ -18034,6 +18035,7 @@ function formatEventMeta(event) {
|
|
|
18034
18035
|
break;
|
|
18035
18036
|
default: {
|
|
18036
18037
|
const _exhaustive = event;
|
|
18038
|
+
void _exhaustive;
|
|
18037
18039
|
break;
|
|
18038
18040
|
}
|
|
18039
18041
|
}
|
|
@@ -18103,7 +18105,11 @@ async function connectAndStream(mcp, tandemUrl, lastEventId, onEventId) {
|
|
|
18103
18105
|
status: "idle",
|
|
18104
18106
|
active: false
|
|
18105
18107
|
})
|
|
18106
|
-
}).catch(() => {
|
|
18108
|
+
}).catch((err) => {
|
|
18109
|
+
console.error(
|
|
18110
|
+
"[Channel] clearAwareness failed (non-fatal):",
|
|
18111
|
+
err instanceof Error ? err.message : err
|
|
18112
|
+
);
|
|
18107
18113
|
});
|
|
18108
18114
|
}
|
|
18109
18115
|
function flushAwareness() {
|
|
@@ -18149,11 +18155,21 @@ async function connectAndStream(mcp, tandemUrl, lastEventId, onEventId) {
|
|
|
18149
18155
|
try {
|
|
18150
18156
|
event = parseTandemEvent(JSON.parse(data));
|
|
18151
18157
|
} catch {
|
|
18152
|
-
console.error(
|
|
18158
|
+
console.error(
|
|
18159
|
+
"[Channel] Malformed SSE event data (skipping), eventId=%s:",
|
|
18160
|
+
eventId,
|
|
18161
|
+
data.slice(0, 200)
|
|
18162
|
+
);
|
|
18163
|
+
if (eventId) onEventId(eventId);
|
|
18153
18164
|
continue;
|
|
18154
18165
|
}
|
|
18155
18166
|
if (!event) {
|
|
18156
|
-
console.error(
|
|
18167
|
+
console.error(
|
|
18168
|
+
"[Channel] Invalid SSE event structure (skipping), eventId=%s:",
|
|
18169
|
+
eventId,
|
|
18170
|
+
data.slice(0, 200)
|
|
18171
|
+
);
|
|
18172
|
+
if (eventId) onEventId(eventId);
|
|
18157
18173
|
continue;
|
|
18158
18174
|
}
|
|
18159
18175
|
if (event.type !== "chat:message") {
|
|
@@ -18164,7 +18180,6 @@ async function connectAndStream(mcp, tandemUrl, lastEventId, onEventId) {
|
|
|
18164
18180
|
continue;
|
|
18165
18181
|
}
|
|
18166
18182
|
}
|
|
18167
|
-
if (eventId) onEventId(eventId);
|
|
18168
18183
|
try {
|
|
18169
18184
|
await mcp.notification({
|
|
18170
18185
|
method: "notifications/claude/channel",
|
|
@@ -18177,6 +18192,7 @@ async function connectAndStream(mcp, tandemUrl, lastEventId, onEventId) {
|
|
|
18177
18192
|
console.error("[Channel] MCP notification failed (transport broken?):", err);
|
|
18178
18193
|
throw err;
|
|
18179
18194
|
}
|
|
18195
|
+
if (eventId) onEventId(eventId);
|
|
18180
18196
|
scheduleAwareness(event);
|
|
18181
18197
|
}
|
|
18182
18198
|
}
|