agent-whiteboard 1.0.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/CHANGELOG.md +470 -0
- package/LICENSE +21 -0
- package/README.md +313 -0
- package/bin/cli.js +30 -0
- package/dist/client/apple-touch-icon.png +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-0cdd387c.woff2 +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-30da91e8.woff +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-68534840.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-07d8e303.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-1ae6bd74.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-de7701e4.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-3398dd02.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-5d53e70a.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-ed0b7437.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-74444efd.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-9163df9c.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-9be7ceb8.woff +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-1e6f9579.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-51814d27.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-5e28753b.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-0f60d1b8.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-138ac28d.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-c76c5d69.woff +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-70ee1f64.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-99cd42a3.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-a6f7ec0d.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-0d85ae7c.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-97479ca6.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-f1d6ef86.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-c2342cd8.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-c6368d87.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-d0332f52.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-850c0af5.woff +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-dc47344d.woff2 +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-f9377ab0.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-08ce98e5.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-7af58c5e.woff2 +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-8a8d2445.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-1ece03f7.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-e99ae511.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-ece03cfd.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-00b26ac8.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-3931dd81.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-91ee6750.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-11e4dc8a.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-68e8c73e.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-f36ea897.ttf +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-036d4e95.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-1c67f068.ttf +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-d96cdf2b.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-6b47c401.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-95b6d2f1.ttf +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-c943cc98.woff +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-2014c523.woff +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-a6b2099f.ttf +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-d04c5421.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-500e04d5.ttf +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-6ab6b62e.woff +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-99f9c675.woff +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-a4af7d41.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-c647367d.ttf +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-71d517d6.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-e14fed02.woff +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-f01f3e87.ttf +0 -0
- package/dist/client/assets/arc-4691b008.js +1 -0
- package/dist/client/assets/architecture-7EHR7CIX-e08620e8.js +1 -0
- package/dist/client/assets/architectureDiagram-3BPJPVTR-9e2f2dbd.js +36 -0
- package/dist/client/assets/blockDiagram-GPEHLZMM-d9dd3f7a.js +132 -0
- package/dist/client/assets/bootstrap.min-21652813.js +6 -0
- package/dist/client/assets/c4Diagram-AAUBKEIU-4ed97d79.js +10 -0
- package/dist/client/assets/channel-9d07a35d.js +1 -0
- package/dist/client/assets/chunk-2J33WTMH-8049adc8.js +1 -0
- package/dist/client/assets/chunk-4BX2VUAB-fd7396be.js +1 -0
- package/dist/client/assets/chunk-55IACEB6-ae1ac93c.js +1 -0
- package/dist/client/assets/chunk-727SXJPM-7e3658d9.js +206 -0
- package/dist/client/assets/chunk-AQP2D5EJ-114f64b0.js +231 -0
- package/dist/client/assets/chunk-FMBD7UC4-f2647c5e.js +15 -0
- package/dist/client/assets/chunk-ND2GUHAM-0a2fcd8c.js +1 -0
- package/dist/client/assets/chunk-QZHKN3VN-d00b3f33.js +1 -0
- package/dist/client/assets/classDiagram-4FO5ZUOK-ebb5cc4a.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-ebb5cc4a.js +1 -0
- package/dist/client/assets/colors-98f238a0.js +1 -0
- package/dist/client/assets/cose-bilkent-S5V4N54A-1f808fd7.js +1 -0
- package/dist/client/assets/cytoscape.esm-1491e259.js +331 -0
- package/dist/client/assets/dagre-BM42HDAG-e8ec1c9d.js +4 -0
- package/dist/client/assets/defaultLocale-a8476f12.js +1 -0
- package/dist/client/assets/diagram-2AECGRRQ-83b385bf.js +43 -0
- package/dist/client/assets/diagram-5GNKFQAL-d9406c6a.js +10 -0
- package/dist/client/assets/diagram-KO2AKTUF-3db5b95a.js +3 -0
- package/dist/client/assets/diagram-LMA3HP47-fbd90397.js +24 -0
- package/dist/client/assets/diagram-OG6HWLK6-3c697534.js +24 -0
- package/dist/client/assets/embed-2b4adf34.js +201 -0
- package/dist/client/assets/erDiagram-TEJ5UH35-7f41448f.js +85 -0
- package/dist/client/assets/eventmodeling-FCH6USID-e6d9019e.js +1 -0
- package/dist/client/assets/flowDiagram-I6XJVG4X-38f05cc7.js +162 -0
- package/dist/client/assets/ganttDiagram-6RSMTGT7-39287f5a.js +292 -0
- package/dist/client/assets/gitGraph-WXDBUCRP-037e225a.js +1 -0
- package/dist/client/assets/gitGraphDiagram-PVQCEYII-3c2e3ef7.js +106 -0
- package/dist/client/assets/graph-f3dfaf53.js +1 -0
- package/dist/client/assets/index-4abf3da0.js +5 -0
- package/dist/client/assets/index-a1656aec.js +1 -0
- package/dist/client/assets/index-d674a6a4.css +1 -0
- package/dist/client/assets/info-J43DQDTF-51cd3390.js +1 -0
- package/dist/client/assets/infoDiagram-5YYISTIA-ef2826b7.js +2 -0
- package/dist/client/assets/init-a5b10ee5.js +1 -0
- package/dist/client/assets/ishikawaDiagram-YF4QCWOH-d0b8a2e0.js +70 -0
- package/dist/client/assets/journeyDiagram-JHISSGLW-586ffcf8.js +139 -0
- package/dist/client/assets/kanban-definition-UN3LZRKU-9e58e14f.js +89 -0
- package/dist/client/assets/katex-0cba7652.js +257 -0
- package/dist/client/assets/katex-4c9ea27f.js +257 -0
- package/dist/client/assets/layout-3a4eff80.js +1 -0
- package/dist/client/assets/linear-fed00341.js +1 -0
- package/dist/client/assets/mermaid-parser.core-7ee09942.js +173 -0
- package/dist/client/assets/mermaid.core-9fc8f6d2.js +306 -0
- package/dist/client/assets/mindmap-definition-RKZ34NQL-a40dd09f.js +96 -0
- package/dist/client/assets/ordinal-db88ae6f.js +1 -0
- package/dist/client/assets/packet-YPE3B663-f55b568a.js +1 -0
- package/dist/client/assets/pie-LRSECV5Y-88c1a5e6.js +1 -0
- package/dist/client/assets/pieDiagram-4H26LBE5-51aae5ea.js +30 -0
- package/dist/client/assets/quadrantDiagram-W4KKPZXB-bc4686f9.js +7 -0
- package/dist/client/assets/radar-GUYGQ44K-6ede145d.js +1 -0
- package/dist/client/assets/range-6bc2a9a0.js +1 -0
- package/dist/client/assets/requirementDiagram-4Y6WPE33-8466f5a1.js +84 -0
- package/dist/client/assets/sankeyDiagram-5OEKKPKP-fd54c1f6.js +40 -0
- package/dist/client/assets/sequenceDiagram-3UESZ5HK-baa63c66.js +162 -0
- package/dist/client/assets/stateDiagram-AJRCARHV-bb4f0d0b.js +1 -0
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-9479cd12.js +1 -0
- package/dist/client/assets/step-171ad54a.js +1 -0
- package/dist/client/assets/time-043b3464.js +1 -0
- package/dist/client/assets/timeline-definition-PNZ67QCA-f64b58bd.js +120 -0
- package/dist/client/assets/treeView-BLDUP644-73ba1a83.js +1 -0
- package/dist/client/assets/treemap-LRROVOQU-98e47821.js +1 -0
- package/dist/client/assets/vennDiagram-CIIHVFJN-a410b1f7.js +34 -0
- package/dist/client/assets/wardley-L42UT6IY-a27badb9.js +1 -0
- package/dist/client/assets/wardleyDiagram-YWT4CUSO-527ffe52.js +78 -0
- package/dist/client/assets/xychartDiagram-2RQKCTM6-42b5914b.js +7 -0
- package/dist/client/favicon-16x16.png +0 -0
- package/dist/client/favicon-32x32.png +0 -0
- package/dist/client/favicon.ico +0 -0
- package/dist/client/icon-192.png +0 -0
- package/dist/client/icon-512.png +0 -0
- package/dist/client/index.html +18 -0
- package/dist/server/app.js +429 -0
- package/dist/server/channel.js +42 -0
- package/dist/server/events.js +90 -0
- package/dist/server/export-html.js +461 -0
- package/dist/server/index.js +79 -0
- package/dist/server/interaction.js +135 -0
- package/dist/server/mcp.js +421 -0
- package/dist/server/paths.js +11 -0
- package/dist/server/persist.js +56 -0
- package/dist/server/presentation.js +5 -0
- package/dist/server/render-core.js +213 -0
- package/dist/server/session.js +103 -0
- package/dist/server/slideshow.js +83 -0
- package/dist/server/snapshot-reader.js +190 -0
- package/dist/server/snapshot-writer.js +117 -0
- package/dist/server/snapshot.js +47 -0
- package/dist/server/step-frames-builder.js +70 -0
- package/dist/server/validate.js +140 -0
- package/dist/server/viewport-cache.js +69 -0
- package/dist/server/ws.js +71 -0
- package/package.json +92 -0
package/README.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# Agent Whiteboard
|
|
2
|
+
|
|
3
|
+
A domain-agnostic interactive whiteboard for AI teacher agents. An AI agent renders diagrams, math, charts, and HTML to a local browser tab via MCP tools — and can pause to wait for the user to signal they're ready to continue.
|
|
4
|
+
|
|
5
|
+
<img src="docs/screenshots/step-frames.gif" width="700" alt="Step-through sequence advancing through a TCP three-way handshake">
|
|
6
|
+
|
|
7
|
+
The browser also has a History panel for browsing, reloading, deleting, and exporting past snapshots:
|
|
8
|
+
|
|
9
|
+
<img src="docs/screenshots/history-panel.png" width="700" alt="History panel listing past snapshots grouped by workspace">
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
[Claude Code agent]
|
|
15
|
+
│
|
|
16
|
+
└── 13 MCP tools — render, step-frames builder, slideshow, wait_click/wait_done, snapshot export (see below)
|
|
17
|
+
│
|
|
18
|
+
▼
|
|
19
|
+
[Node.js server :3000] — MCP over SSE + REST fallback + WebSocket
|
|
20
|
+
│
|
|
21
|
+
▼
|
|
22
|
+
[Browser tab :5173] — Svelte SPA, auto-opens on start, with a History panel for browsing/deleting/exporting past snapshots
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The server exposes MCP tools over SSE at `http://localhost:3000/mcp`. Claude Code connects automatically when `.mcp.json` is present in the project root.
|
|
26
|
+
|
|
27
|
+
Every `render()`/commit call is persisted as a snapshot on disk (`~/.agent-whiteboard/<workspace>/`), so a lesson's diagram history survives past the current canvas state — see [History & snapshots](#history--snapshots).
|
|
28
|
+
|
|
29
|
+
## Requirements
|
|
30
|
+
|
|
31
|
+
- Node.js ≥ 18
|
|
32
|
+
- Claude Code (CLI or IDE extension)
|
|
33
|
+
|
|
34
|
+
## Quickstart
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx agent-whiteboard
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This starts the server on `:3000` (serving the pre-built client — no separate dev server) and opens a browser tab automatically. Add `.mcp.json` (see [MCP registration](#mcp-registration)) so Claude Code connects to it.
|
|
41
|
+
|
|
42
|
+
By default the server only binds to loopback addresses (`localhost`/`127.0.0.1`/`::1`) — see [Trust model](#trust-model) below.
|
|
43
|
+
|
|
44
|
+
### Trust model
|
|
45
|
+
|
|
46
|
+
This is a **single-user, local-only** tool: there is no authentication on any endpoint. That's an accepted trade-off, not an oversight — the server refuses to start if `HOST` is set to anything other than a loopback address, unless you explicitly set `ALLOW_NON_LOOPBACK=1` (not recommended; anyone who can reach the port can render arbitrary content, read/delete your snapshot history, and exfiltrate exported data). Don't expose this server beyond your own machine.
|
|
47
|
+
|
|
48
|
+
## Development
|
|
49
|
+
|
|
50
|
+
Running from source (contributing, or testing an unreleased change) instead of `npx agent-whiteboard`:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install
|
|
54
|
+
npm run dev
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This starts the Node server (`:3000`), the Vite dev server (`:5173`), and opens the browser tab automatically.
|
|
58
|
+
|
|
59
|
+
Enable the MCP server in Claude Code the first time:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
/mcp
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Select `agent-whiteboard` and enable it. The tools will be available immediately.
|
|
66
|
+
|
|
67
|
+
## MCP tools
|
|
68
|
+
|
|
69
|
+
### `render(type, payload[, options])`
|
|
70
|
+
|
|
71
|
+
Push content to the whiteboard canvas. Replaces whatever is currently on screen.
|
|
72
|
+
|
|
73
|
+
| `type` | `payload` |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `"mermaid"` | Mermaid diagram source. Must begin with a valid keyword (`graph`, `flowchart`, `sequenceDiagram`, `classDiagram`, `erDiagram`, `gantt`, `pie`, `mindmap`). |
|
|
76
|
+
| `"svg"` | Inline SVG markup. |
|
|
77
|
+
| `"html"` | HTML/CSS fragment. Sanitized via DOMPurify — inline styles only; `<script>` and `<style>` tags are stripped. |
|
|
78
|
+
| `"katex"` | LaTeX string, rendered in display mode. |
|
|
79
|
+
| `"vega-lite"` | Vega-Lite JSON spec (must be valid JSON string). |
|
|
80
|
+
|
|
81
|
+
`render()` is single-frame only. For a step-through sequence (multiple frames navigable via `step()`/`seek()`), use [`init_step_frames`/`append_frame`/`commit_step_frames`](#init_step_framesframe_type-workspace-title) instead — see [Step-frames sequences](#step-frames-sequences).
|
|
82
|
+
|
|
83
|
+
`options`:
|
|
84
|
+
- `workspace` — **required.** Workspace name for snapshot routing (alphanumeric, dashes, underscores, dots, spaces — no path separators). Snapshots are written to `~/.agent-whiteboard/<workspace>/`. Missing or invalid workspace returns `{ "ok": false, "error": "..." }` without rendering.
|
|
85
|
+
- `title` — string label displayed above the canvas for this render call.
|
|
86
|
+
|
|
87
|
+
**Returns:** `{ "ok": true, "id": "<uuid>" }` — the UUID of the snapshot written for this call (`id` is omitted if the snapshot write fails, which is non-fatal). Error: `{ "ok": false, "error": "..." }`
|
|
88
|
+
|
|
89
|
+
### `step(direction)`
|
|
90
|
+
|
|
91
|
+
Advance (`"next"`) or rewind (`"prev"`) the step cursor for a loaded step-frames sequence.
|
|
92
|
+
|
|
93
|
+
**Returns:** `{ "ok": true, "current_frame": N, "total_frames": M }` or `{ "ok": false, "error": "..." }`
|
|
94
|
+
|
|
95
|
+
### `seek(frame)`
|
|
96
|
+
|
|
97
|
+
Jump the step-frame cursor to an arbitrary frame index without repeated `step()` calls.
|
|
98
|
+
|
|
99
|
+
**Returns:** `{ "ok": true, "current_frame": N, "total_frames": M }` or `{ "ok": false, "error": "..." }`
|
|
100
|
+
|
|
101
|
+
### `clear()`
|
|
102
|
+
|
|
103
|
+
Reset the canvas to a blank state.
|
|
104
|
+
|
|
105
|
+
**Returns:** `{ "ok": true }`
|
|
106
|
+
|
|
107
|
+
### `export([id])`
|
|
108
|
+
|
|
109
|
+
Return a canvas source payload.
|
|
110
|
+
|
|
111
|
+
- **Without `id`:** returns the current canvas state verbatim (last `render()` payload). For `step-frames`, returns the full original frames JSON. Returns an empty string if the canvas is blank.
|
|
112
|
+
- **With `id` (UUID):** scans all snapshot files for the one whose `id` field matches (written from v0.11 onward) and returns its payload. Old snapshots without an `id` field are not addressable this way.
|
|
113
|
+
|
|
114
|
+
**Returns:** `{ "ok": true, "data": "<source>" }` (without id: always succeeds; with id: `{ "ok": false, "error": "graph not found" }` if no match)
|
|
115
|
+
|
|
116
|
+
### `slideshow(slides, delay_ms)`
|
|
117
|
+
|
|
118
|
+
Load a playlist and auto-advance it on a server-side timer.
|
|
119
|
+
|
|
120
|
+
- `slides` — array of `{ type, payload, title? }` objects (same types as `render()`).
|
|
121
|
+
- `delay_ms` — interval in milliseconds between slides.
|
|
122
|
+
|
|
123
|
+
A new call cancels any running slideshow. Use `slideshow_stop()` to stop early.
|
|
124
|
+
|
|
125
|
+
**Returns:** `{ "ok": true }` or `{ "ok": false, "error": "..." }`
|
|
126
|
+
|
|
127
|
+
### `slideshow_stop()`
|
|
128
|
+
|
|
129
|
+
Cancel the running slideshow. The last rendered slide stays on screen. No-op if nothing is running.
|
|
130
|
+
|
|
131
|
+
**Returns:** `{ "ok": true }`
|
|
132
|
+
|
|
133
|
+
### `wait_done()`
|
|
134
|
+
|
|
135
|
+
Block until the user clicks the **Done** button in the browser tab, then return. Use this after `render()` when you want the user to review a diagram before the agent continues. Times out after 10 minutes.
|
|
136
|
+
|
|
137
|
+
**Returns:** `{ "ok": true }`
|
|
138
|
+
|
|
139
|
+
<img src="docs/screenshots/done-button.png" width="700" alt="Done button visible in the controls panel while wait_done() is armed">
|
|
140
|
+
|
|
141
|
+
### `wait_click([node_actions])`
|
|
142
|
+
|
|
143
|
+
Arm the browser for a single node or edge click on the current Mermaid diagram. Nodes are highlighted with a blue outline and pointer cursor. Applies reliably to `graph`/`flowchart` diagrams; other types are best-effort.
|
|
144
|
+
|
|
145
|
+
`node_actions` (optional) — a map of node ID → string array. When provided, clicking a mapped node shows an inline popup menu listing the action strings; the selected item is returned in the `action` field. Nodes not in the map produce a plain click (`action: null`). Edge clicks are always plain.
|
|
146
|
+
|
|
147
|
+
Only one `wait_click()` may be pending at a time — a second `wait_click()` call, or an arming `wait_done()` call, supersedes it. Times out after 10 minutes.
|
|
148
|
+
|
|
149
|
+
**Returns:** `{ "ok": true, "type": "node"|"edge", "id": "<id>", "label": "<label>", "action": "<selected>" | null }` on click (`action` is the chosen menu item string, or `null` for a plain click); `{ "ok": true, "type": "timeout" }` on plain inactivity timeout; `{ "ok": true, "type": "superseded" }` when cancelled by a new arm.
|
|
150
|
+
|
|
151
|
+
| Armed — clickable nodes get a blue outline | After clicking a node with `node_actions` registered |
|
|
152
|
+
|---------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
|
|
153
|
+
| <img src="docs/screenshots/clickable-nodes.png" width="480" alt="wait_click() armed: nodes outlined in blue"> | <img src="docs/screenshots/agent-actions.png" width="480" alt="Popup menu showing agent-registered actions for the clicked node"> |
|
|
154
|
+
|
|
155
|
+
### `init_step_frames(frame_type, workspace, title?)`
|
|
156
|
+
|
|
157
|
+
Begin a step-frames sequence — this is the only way to create a multi-frame, step-through sequence (`render()` is single-frame only). Creates an empty builder in server memory, pushes a 0-frame placeholder to the browser, and returns a builder ID. Protocol: `init_step_frames()` → `append_frame()` × N → `commit_step_frames()`. The builder expires after 30 minutes of inactivity.
|
|
158
|
+
|
|
159
|
+
Each `append_frame()` call is validated and broadcast individually — interleave `wait_done()` after each one for a paced, user-acknowledged reveal (see [Example agent flow](#example-agent-flow)).
|
|
160
|
+
|
|
161
|
+
**Returns:** `{ "ok": true, "id": "<uuid>" }` or `{ "ok": false, "error": "..." }`
|
|
162
|
+
|
|
163
|
+
### `append_frame(id, payload, label?, type?)`
|
|
164
|
+
|
|
165
|
+
Append one frame to an in-progress builder. `payload` is validated against `type ?? frame_type` (same rules as `render()`). `type` (optional) overrides the sequence's `frame_type` for this one frame — lets a sequence mix content types (e.g. a mermaid frame followed by a katex frame). Each valid append immediately broadcasts the accumulated sequence to the browser, positioned at the latest frame — the user watches the step-through grow one frame at a time. Invalid payloads are rejected without touching prior frames or browser state.
|
|
166
|
+
|
|
167
|
+
**Returns:** `{ "ok": true, "frame_count": N }` or `{ "ok": false, "error": "..." }`
|
|
168
|
+
|
|
169
|
+
### `commit_step_frames(id, node_to_frame?)`
|
|
170
|
+
|
|
171
|
+
Finalize an in-progress builder: assembles the full step-frames JSON, writes a snapshot, updates canvas state (so `export()` and `step()`/`seek()` work on it), and deletes the builder entry. The browser already shows the sequence from `append_frame()` live previews.
|
|
172
|
+
|
|
173
|
+
`node_to_frame` (optional) — map of node ID → frame index. When set, clicking a mapped node in the browser jumps directly to its frame via `POST /seek` — no `wait_click()` call needed. Disabled for the duration of any `wait_click()` call, then automatically re-enabled once that call resolves or times out — no need to build and commit a new sequence just to restore it.
|
|
174
|
+
|
|
175
|
+
**Returns:** `{ "ok": true, "id": "<uuid>" }` or `{ "ok": false, "error": "..." }`
|
|
176
|
+
|
|
177
|
+
### `list_snapshots(workspace)`
|
|
178
|
+
|
|
179
|
+
List the snapshots stored for a workspace (`id`, `timestamp`, `type`, optional `title`), newest-first, so an agent can discover what's available to export via `export_html()` without going through the browser History panel. Returns an empty array if the workspace has no snapshots.
|
|
180
|
+
|
|
181
|
+
**Returns:** `{ "ok": true, "snapshots": [...] }`
|
|
182
|
+
|
|
183
|
+
### `export_html(workspace, ids)`
|
|
184
|
+
|
|
185
|
+
Export one or more snapshots (by UUID, discovered via `list_snapshots()`) to a single self-contained HTML document — the agent-facing equivalent of the browser History panel's "Export selected". Mermaid diagrams render client-side in the exported file; KaTeX/Vega-Lite/SVG/HTML render server-side. Dependencies (Mermaid, Bootstrap, KaTeX) are linked from a CDN (pinned version + SRI hash), which keeps the assembled HTML small enough to return **inline** as a string in the tool response — this tool has no filesystem access and never writes to disk. The resulting file needs network access to render correctly when opened. For a fully offline-capable export, or to save the file to a specific path, use `POST /export-html` with `{ "mode": "offline" }` instead (see REST fallback below).
|
|
186
|
+
|
|
187
|
+
**Returns:** `{ "ok": true, "path": "<absolute path>" }` or `{ "ok": false, "error": "..." }`
|
|
188
|
+
|
|
189
|
+
## Step-frames sequences
|
|
190
|
+
|
|
191
|
+
There is no one-shot way to render a multi-frame sequence — `render()` is single-frame only. Build one via `init_step_frames()` → `append_frame()` × N → `commit_step_frames()` (see above). Once committed, `export()` returns the assembled sequence in this shape:
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"frame_type": "mermaid",
|
|
196
|
+
"frames": [
|
|
197
|
+
{ "label": "Step 1", "payload": "graph TD; A" },
|
|
198
|
+
{ "label": "Step 2", "payload": "graph TD; A --> B" },
|
|
199
|
+
{ "label": "Step 3", "payload": "graph TD; A --> B --> C" }
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Example agent flow
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
render(type="mermaid", payload="graph TD; A --> B", options={ workspace: "my-lesson", title: "System overview" })
|
|
208
|
+
→ wait_done() # agent pauses; user clicks Done when ready
|
|
209
|
+
→ render(type="katex", payload="E = mc^2", options={ workspace: "my-lesson" })
|
|
210
|
+
→ wait_done()
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Paced, user-acknowledged reveal — interleave `wait_done()` between `append_frame()` calls so each frame waits for the user before the next one appears (useful for large or complex step-frames sequences the agent is building on the fly):
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
init_step_frames(frame_type="mermaid", workspace="my-lesson", title="TCP handshake")
|
|
217
|
+
→ append_frame(id, payload="graph LR; Client -- SYN --> Server", label="1. SYN")
|
|
218
|
+
→ wait_done() # user reviews frame 1, clicks Done
|
|
219
|
+
→ append_frame(id, payload="graph LR; Client -- SYN --> Server; Server -- SYN-ACK --> Client", label="2. SYN-ACK")
|
|
220
|
+
→ wait_done() # user reviews frame 2, clicks Done
|
|
221
|
+
→ append_frame(id, payload="...", label="3. ACK")
|
|
222
|
+
→ commit_step_frames(id)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`wait_done()` isn't coupled to step-frames — it just blocks on the Done button — so this pattern works with any number of frames and needs no dedicated tool support.
|
|
226
|
+
|
|
227
|
+
## REST fallback
|
|
228
|
+
|
|
229
|
+
All tools have HTTP equivalents for scripting or testing without an MCP client:
|
|
230
|
+
|
|
231
|
+
| Endpoint | Body / Params |
|
|
232
|
+
|---|---|
|
|
233
|
+
| `POST /render` | `{ "type": "...", "payload": "...", "options": { "workspace": "...", "title": "..." } }` — `workspace` required. Returns `{ "ok": true, "id": "<uuid>" }` on success. |
|
|
234
|
+
| `POST /step` | `{ "direction": "next" \| "prev" }` |
|
|
235
|
+
| `POST /clear` | — |
|
|
236
|
+
| `GET /export` | No params: returns current canvas. `?id=<uuid>`: returns matching snapshot payload (404 if not found). |
|
|
237
|
+
| `POST /slideshow` | `{ "slides": [...], "delay_ms": N }` |
|
|
238
|
+
| `POST /slideshow/stop` | — |
|
|
239
|
+
| `POST /seek` | `{ "frame": N }` |
|
|
240
|
+
| `POST /user-done` | — (simulates the Done button click) |
|
|
241
|
+
| `POST /wait-done` | — (long-polls until Done is signalled) |
|
|
242
|
+
| `POST /node-click` | `{ "type": "node"\|"edge", "id": "...", "label": "..." }` (sent by browser) |
|
|
243
|
+
| `POST /wait-click` | `{ "node_actions": { "<id>": ["action1", ...] } }` (optional; long-polls until a node/edge click is signalled) |
|
|
244
|
+
| `POST /step-frames/init` | `{ "frame_type": "...", "workspace": "...", "title": "..." }` — starts an incremental builder, returns `{ "ok": true, "id": "<uuid>" }` |
|
|
245
|
+
| `POST /step-frames/:id/frame` | `{ "payload": "...", "label": "...", "type": "..." }` (`type` optional, overrides the sequence's `frame_type`) — appends a frame, returns `{ "ok": true, "frame_count": N }` |
|
|
246
|
+
| `POST /step-frames/:id/commit` | `{ "node_to_frame": { "<id>": N, ... } }` (optional body) — finalizes the builder, returns `{ "ok": true, "id": "<uuid>" }` |
|
|
247
|
+
| `GET /snapshots` | `?workspace=` **required**, no fallback — lists that workspace's snapshots (REST equivalent of the MCP `list_snapshots` tool) |
|
|
248
|
+
| `GET /snapshots/all` | Lists every workspace and its snapshot count, current workspace flagged with `isCurrent: true` |
|
|
249
|
+
| `POST /snapshots/load` | `{ "filename": "..._screen.json", "workspace": "..." (optional) }` — loads a past snapshot onto the canvas (write-silent, no new snapshot is created) |
|
|
250
|
+
| `POST /snapshots/delete-files` | `{ "workspace": "...", "filenames": ["..._screen.json", ...] }` — deletes specific snapshot files |
|
|
251
|
+
| `POST /snapshots/delete-workspace` | `{ "workspace": "..." }` — deletes an entire workspace's snapshot folder |
|
|
252
|
+
| `POST /export-html` | `{ "items": [{ "workspace": "...", "id": "..." }, ...], "mode": "cdn" \| "offline" }` (`mode` optional, defaults to `"cdn"`) — returns the assembled HTML file as a download, not JSON |
|
|
253
|
+
|
|
254
|
+
All endpoints return the same JSON shapes as the MCP tools, except `POST /export-html`, which streams the HTML file directly (`Content-Disposition: attachment`). `mode: "cdn"` (default) links Mermaid/Bootstrap/KaTeX from a CDN; `mode: "offline"` embeds them for offline reading, at the cost of a much larger file (the Mermaid bundle alone is ~3.3MB).
|
|
255
|
+
|
|
256
|
+
## History & snapshots
|
|
257
|
+
|
|
258
|
+
Every `render()` / `commit_step_frames()` call writes a JSON snapshot to `~/.agent-whiteboard/<workspace>/` (override with the `WHITEBOARD_SNAPSHOTS_DIR` environment variable). The browser's right-side controls panel has a history toggle that opens a panel for browsing and loading past snapshots grouped by workspace, plus delete and export icon buttons that open a 2-step modal (`client/src/DeleteExportModal.svelte`) for deleting or exporting an entire workspace or a chosen subset of snapshots. Destructive actions require a second confirming click. Exports are produced by the `export_html()` tool / `POST /export-html` endpoint described above.
|
|
259
|
+
|
|
260
|
+
| Step 1 — choose a workspace | Step 2 — delete requires a second confirming click |
|
|
261
|
+
|----------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
|
|
262
|
+
| <img src="docs/screenshots/delete-workspace-picker.png" width="380" alt="Delete/Export modal step 1: workspace picker with snapshot counts"> | <img src="docs/screenshots/delete-confirmation.png" width="380" alt="Delete confirmation: button relabels to Click again to confirm"> |
|
|
263
|
+
|
|
264
|
+
| Step 2 — export a subset of snapshots | The resulting self-contained HTML, opened directly in a browser |
|
|
265
|
+
|----------------------------------------------------------------------------------------------------------------|---|
|
|
266
|
+
| <img src="docs/screenshots/export-select-item.png" width="380" alt="Selecting individual snapshots to export"> | <img src="docs/screenshots/export-outputhtml.png" width="480" alt="Exported HTML file opened offline, showing the table of contents and rendered diagrams"> |
|
|
267
|
+
|
|
268
|
+
## Manual showcase
|
|
269
|
+
|
|
270
|
+
Exercises every renderer and interactive feature (requires `npm run dev` running):
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
node tests/human_driven/showcase.js # Sections 1–8: renderer slideshow + seek
|
|
274
|
+
node tests/human_driven/showcase.js -x # Section 12: export-by-id (v0.11)
|
|
275
|
+
node tests/human_driven/showcase.js -a # All sections
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Section flags (combinable):
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
-s, --standard Sections 1–8: renderer slideshow + seek demo (default)
|
|
282
|
+
-i, --interactive Section 9: node click drill-down + Done button
|
|
283
|
+
-u, --popup Section 10: node_actions popup menu
|
|
284
|
+
-e, --edge Section 11: edge click demo
|
|
285
|
+
-x, --exportid Section 12: export by graph ID (v0.11)
|
|
286
|
+
-a, --all All sections
|
|
287
|
+
-p, --port <port> Server port (default: 3000)
|
|
288
|
+
-d, --delay <ms> Delay between slides (default: 5000)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Tests
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
npm test # Vitest unit/integration tests (server)
|
|
295
|
+
npm run test:e2e # Playwright end-to-end tests (requires npm run dev:test first)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## MCP registration
|
|
299
|
+
|
|
300
|
+
`.mcp.json` is committed to the repo root and picked up automatically by Claude Code:
|
|
301
|
+
|
|
302
|
+
```json
|
|
303
|
+
{
|
|
304
|
+
"mcpServers": {
|
|
305
|
+
"agent-whiteboard": {
|
|
306
|
+
"type": "sse",
|
|
307
|
+
"url": "http://localhost:3000/mcp"
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
The server must be running before Claude Code connects. The port is overridable via the `PORT` environment variable, and the bind address via `HOST` (both default to `localhost`/`3000`). Snapshot storage location is overridable via `WHITEBOARD_SNAPSHOTS_DIR` (defaults to `~/.agent-whiteboard`).
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Production entrypoint — what `npx agent-whiteboard` actually runs.
|
|
3
|
+
//
|
|
4
|
+
// Deliberately plain ESM JS (no build step for this file) so it can be listed
|
|
5
|
+
// directly in package.json's "bin" field. Resolves dist/client relative to
|
|
6
|
+
// its own location (not process.cwd()), since npx invokes this from whatever
|
|
7
|
+
// directory the user happens to be in. Imports only the compiled server and
|
|
8
|
+
// the "open" dependency — never tsx/vite/concurrently/wait-on, none of which
|
|
9
|
+
// are installed for a consumer of the published package.
|
|
10
|
+
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import open from "open";
|
|
14
|
+
import { startServer } from "../dist/server/index.js";
|
|
15
|
+
|
|
16
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const staticRoot = join(here, "..", "dist", "client");
|
|
18
|
+
|
|
19
|
+
const PORT = process.env.PORT ?? "3000";
|
|
20
|
+
const HOST = process.env.HOST ?? "localhost";
|
|
21
|
+
|
|
22
|
+
startServer({
|
|
23
|
+
staticRoot,
|
|
24
|
+
onReady: () => {
|
|
25
|
+
open(`http://${HOST}:${PORT}`).catch(() => {
|
|
26
|
+
// Non-fatal — the URL is already printed by startServer(); the user
|
|
27
|
+
// can open it manually if the platform has no default browser handler.
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{w as ln,p as an,a as G,s as j,b as z,h as un,e as g,t as tn,d as K,m as _,f as rn,g as o,i as sn,j as on,k as fn}from"./step-171ad54a.js";function cn(l){return l.innerRadius}function gn(l){return l.outerRadius}function yn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,E,q,v,A,B,a){var I=E-l,i=q-h,n=B-v,d=a-A,u=d*I-n*i;if(!(u*u<g))return u=(n*(h-A)-d*(l-v))/u,[l+u*I,h+u*i]}function V(l,h,E,q,v,A,B){var a=l-E,I=h-q,i=(B?A:-A)/K(a*a+I*I),n=i*I,d=-i*a,u=l+n,s=h+d,f=E+n,c=q+d,C=(u+f)/2,t=(s+c)/2,m=f-u,y=c-s,R=m*m+y*y,T=v-A,P=u*c-f*s,O=(y<0?-1:1)*K(fn(0,T*T*R-P*P)),S=(P*y-m*O)/R,k=(-P*m-y*O)/R,w=(P*y+m*O)/R,p=(-P*m+y*O)/R,x=S-C,e=k-t,r=w-C,F=p-t;return x*x+e*e>r*r+F*F&&(S=w,k=p),{cx:S,cy:k,x01:-n,y01:-d,x11:S*(v/T-1),y11:k*(v/T-1)}}function hn(){var l=cn,h=gn,E=z(0),q=null,v=yn,A=dn,B=mn,a=null,I=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,C=rn(c-f),t=c>f;if(a||(a=n=I()),s<u&&(d=s,s=u,u=d),!(s>g))a.moveTo(0,0);else if(C>tn-g)a.moveTo(s*G(f),s*j(f)),a.arc(0,0,s,f,c,!t),u>g&&(a.moveTo(u*G(c),u*j(c)),a.arc(0,0,u,c,f,t));else{var m=f,y=c,R=f,T=c,P=C,O=C,S=B.apply(this,arguments)/2,k=S>g&&(q?+q.apply(this,arguments):K(u*u+s*s)),w=_(rn(s-u)/2,+E.apply(this,arguments)),p=w,x=w,e,r;if(k>g){var F=sn(k/u*j(S)),L=sn(k/s*j(S));(P-=F*2)>g?(F*=t?1:-1,R+=F,T-=F):(P=0,R=T=(f+c)/2),(O-=L*2)>g?(L*=t?1:-1,m+=L,y-=L):(O=0,m=y=(f+c)/2)}var H=s*G(m),J=s*j(m),M=u*G(T),N=u*j(T);if(w>g){var Q=s*G(y),U=s*j(y),W=u*G(R),X=u*j(R),D;if(C<an)if(D=pn(H,J,W,X,Q,U,M,N)){var Y=H-D[0],Z=J-D[1],$=Q-D[0],b=U-D[1],nn=1/j(on((Y*$+Z*b)/(K(Y*Y+Z*Z)*K($*$+b*b)))/2),en=K(D[0]*D[0]+D[1]*D[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}O>g?x>g?(e=V(W,X,H,J,s,x,t),r=V(Q,U,M,N,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(H,J),a.arc(0,0,s,m,y,!t)):a.moveTo(H,J),!(u>g)||!(P>g)?a.lineTo(M,N):p>g?(e=V(M,N,Q,U,u,-p,t),r=V(H,J,W,X,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[G(d)*n,j(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:z(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:z(+n),i):h},i.cornerRadius=function(n){return arguments.length?(E=typeof n=="function"?n:z(+n),i):E},i.padRadius=function(n){return arguments.length?(q=n==null?null:typeof n=="function"?n:z(+n),i):q},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:z(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:z(+n),i):A},i.padAngle=function(n){return arguments.length?(B=typeof n=="function"?n:z(+n),i):B},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{A as c,f as i}from"./mermaid-parser.core-7ee09942.js";import"./index-4abf3da0.js";export{c as ArchitectureModule,i as createArchitectureServices};
|