agent-whiteboard 1.2.0 → 1.3.1

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +123 -83
  3. package/bin/cli.js +11 -2
  4. package/bin/install-skills.js +108 -0
  5. package/dist/client/assets/{abnfDiagram-VCTEODGH-Dj8sd_5g.js → abnfDiagram-VCTEODGH-Bwh5lUgu.js} +1 -1
  6. package/dist/client/assets/{architectureDiagram-5GKGNRK7-B1QyVL0F.js → architectureDiagram-5GKGNRK7-B_ecYkha.js} +1 -1
  7. package/dist/client/assets/{blockDiagram-I7D4REHJ-CmU2SSFe.js → blockDiagram-I7D4REHJ-yKkzC4XQ.js} +1 -1
  8. package/dist/client/assets/{c4Diagram-7LVT6UL2-DWmhp77c.js → c4Diagram-7LVT6UL2-lHAqFGmA.js} +1 -1
  9. package/dist/client/assets/channel-B2NgRQfI.js +1 -0
  10. package/dist/client/assets/{chunk-2Q5K7J3B-CQLo8Sbv.js → chunk-2Q5K7J3B-B7zuI4_k.js} +1 -1
  11. package/dist/client/assets/{chunk-5VM5RSS4-fJTWmTTt.js → chunk-5VM5RSS4-jBsRKGwk.js} +1 -1
  12. package/dist/client/assets/{chunk-F27PBJKO-DqzEf5gi.js → chunk-F27PBJKO-CGIsMCIr.js} +1 -1
  13. package/dist/client/assets/{chunk-IMKFNOWR-CMamLTz3.js → chunk-IMKFNOWR-WXoBIg3r.js} +1 -1
  14. package/dist/client/assets/{chunk-JWPE2WC7-tyYPyD43.js → chunk-JWPE2WC7-DVxbj6OP.js} +1 -1
  15. package/dist/client/assets/{chunk-POPQ4Y6H-XdY4rO_V.js → chunk-POPQ4Y6H-B6_LAnue.js} +1 -1
  16. package/dist/client/assets/{chunk-SVP7TREG-COvqzqaO.js → chunk-SVP7TREG-DPtBhcA2.js} +1 -1
  17. package/dist/client/assets/{chunk-TICWLB2K-BsJSFuPC.js → chunk-TICWLB2K-BpTvKQOE.js} +1 -1
  18. package/dist/client/assets/{chunk-XXDRQBXY-HfVrYBJb.js → chunk-XXDRQBXY-ChPDJQUM.js} +1 -1
  19. package/dist/client/assets/classDiagram-ZZMXUADV-0PCo-FZi.js +1 -0
  20. package/dist/client/assets/classDiagram-v2-VYDZK3BY-0PCo-FZi.js +1 -0
  21. package/dist/client/assets/{cose-bilkent-JH36ORCC-UXGdwSjS.js → cose-bilkent-JH36ORCC-CNOS6l2h.js} +1 -1
  22. package/dist/client/assets/{cynefin-OW5HDTMX-PWlPYnpn.js → cynefin-OW5HDTMX-yFRC4RbL.js} +1 -1
  23. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-CYE5H1wR.js → cynefinDiagram-5FMLGOSQ-A1B_XDwz.js} +1 -1
  24. package/dist/client/assets/{dagre-GXQ25YYZ-DaknBOKq.js → dagre-GXQ25YYZ-CsGQlESG.js} +1 -1
  25. package/dist/client/assets/{diagram-S7CK7UJ4-Dz7rZHxF.js → diagram-S7CK7UJ4-oi40Oovb.js} +1 -1
  26. package/dist/client/assets/{diagram-UQ7AKVKN-DzCi6tiS.js → diagram-UQ7AKVKN-fXq7BGyX.js} +1 -1
  27. package/dist/client/assets/{diagram-VSXAHHWV-TlUzhXgv.js → diagram-VSXAHHWV-DGsF5-Gn.js} +1 -1
  28. package/dist/client/assets/{diagram-VX7I27RA-Ds7jdzK7.js → diagram-VX7I27RA-C2kM3ZeN.js} +1 -1
  29. package/dist/client/assets/{diagram-Z3DM3KII-C--JD7cf.js → diagram-Z3DM3KII-B-4vwwDz.js} +1 -1
  30. package/dist/client/assets/{ebnfDiagram-PWID7BFC-Dw5OM-dL.js → ebnfDiagram-PWID7BFC-IQpqM8Er.js} +1 -1
  31. package/dist/client/assets/{erDiagram-RLTQ6QDP-DXalBHjA.js → erDiagram-RLTQ6QDP-DZUhIRFv.js} +1 -1
  32. package/dist/client/assets/{flowDiagram-HODETNUW-DcI3UqyE.js → flowDiagram-HODETNUW-BogyNcsK.js} +1 -1
  33. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-DyrkIo6c.js → ganttDiagram-EL5Y4UJY-DQrF2qTY.js} +1 -1
  34. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-fJKfGRcg.js → gitGraphDiagram-WWUBYQGX-DCjdibDV.js} +1 -1
  35. package/dist/client/assets/{index-CIhkXT_z.js → index-CwpL9TCq.js} +9 -9
  36. package/dist/client/assets/{infoDiagram-27XIBGKW-Cijiqswb.js → infoDiagram-27XIBGKW-B9fQanxS.js} +1 -1
  37. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-C1WqWU2l.js → ishikawaDiagram-5VMMS53U-C2Vylmfx.js} +1 -1
  38. package/dist/client/assets/{journeyDiagram-3NMN7TZE-7j05mbLR.js → journeyDiagram-3NMN7TZE-D89LoCjM.js} +1 -1
  39. package/dist/client/assets/{kanban-definition-UXKFOSKX-D5mGDfs_.js → kanban-definition-UXKFOSKX-PEjEU0rc.js} +1 -1
  40. package/dist/client/assets/{layout-KEin06-H.js → layout-BFcmmT4m.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-CLvSDmrA.js → mermaid.core-DKnSsFxv.js} +5 -5
  42. package/dist/client/assets/{mindmap-definition-YA3MSWOX-BsCqKd_d.js → mindmap-definition-YA3MSWOX-B7LiyX5g.js} +1 -1
  43. package/dist/client/assets/{pegDiagram-XKGWAZYB-Du5lDU3W.js → pegDiagram-XKGWAZYB-BnJUZrSw.js} +1 -1
  44. package/dist/client/assets/{pieDiagram-E7YTZNPT-hjv5Ao6B.js → pieDiagram-E7YTZNPT-z_3XFa-J.js} +1 -1
  45. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-DNon8LWK.js → quadrantDiagram-AXDQQJYC-B-iV8Ep2.js} +1 -1
  46. package/dist/client/assets/{railroadDiagram-O6MQD6OU-yNZz2zxo.js → railroadDiagram-O6MQD6OU-DWcuukQV.js} +1 -1
  47. package/dist/client/assets/{requirementDiagram-BXWQKSXE-CZiiEUHW.js → requirementDiagram-BXWQKSXE-BL29FbE8.js} +1 -1
  48. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-D-u8ZQUV.js → sankeyDiagram-P5KCCOFB-DncoVK5c.js} +1 -1
  49. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BxjTf3q9.js → sequenceDiagram-WJ2MYXX4-4_9ywdq-.js} +1 -1
  50. package/dist/client/assets/{sizeCapture-INFHLROL-Dt4Wg8C5.js → sizeCapture-INFHLROL-DgpEl-H1.js} +1 -1
  51. package/dist/client/assets/{stateDiagram-D77RDMKH-B8BM6dln.js → stateDiagram-D77RDMKH-D3uAsi-s.js} +1 -1
  52. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BEkx2fLW.js +1 -0
  53. package/dist/client/assets/{swimlanes-42K2YHIH-eGWrsWAA.js → swimlanes-42K2YHIH-DchNJuY0.js} +1 -1
  54. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BHXAFRx6.js +8 -0
  55. package/dist/client/assets/{timeline-definition-24CTP7MA-CAM-3Qj9.js → timeline-definition-24CTP7MA-KWfqhWGR.js} +1 -1
  56. package/dist/client/assets/{vennDiagram-4TSXK5OY-CuFEwwD0.js → vennDiagram-4TSXK5OY-YJTdSPKH.js} +1 -1
  57. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DI5yCVh0.js → wardleyDiagram-VM6X3IG4-CSN6rLUa.js} +1 -1
  58. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-BI0Bn-OQ.js → xychartDiagram-S5SC5T6Z-DqPq1Pcp.js} +1 -1
  59. package/dist/client/index.html +1 -1
  60. package/package.json +3 -1
  61. package/skills/agent-whiteboard/SKILL.md +45 -0
  62. package/skills/whiteboard-analysis/SKILL.md +32 -0
  63. package/skills/whiteboard-assessment/SKILL.md +51 -0
  64. package/skills/whiteboard-design/SKILL.md +29 -0
  65. package/skills/whiteboard-teaching/SKILL.md +39 -0
  66. package/dist/client/assets/channel-C9ni1Via.js +0 -1
  67. package/dist/client/assets/classDiagram-ZZMXUADV-DktNEiGC.js +0 -1
  68. package/dist/client/assets/classDiagram-v2-VYDZK3BY-DktNEiGC.js +0 -1
  69. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-MJauX84T.js +0 -1
  70. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BYFu8tyL.js +0 -8
package/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## 1.3.1 — 2026-11-07
2
+
3
+ Patch: documentation only.
4
+
5
+ - **README reordered for users:** quickstart, MCP registration, agent skills, use cases and tools now come first; Environment variables and REST fallback follow; Development, Tests and Manual showcase are at the end. Added a "Jump to" line, moved the `/mcp` enable step into MCP registration, reworded the `.mcp.json` note for non-contributors, and listed `npm run check` under Tests.
6
+
7
+ ## 1.3.0 — 2026-11-07
8
+
9
+ **Milestone v1.9 — Agent Skills & `install-skills` CLI (Sprint 92) complete.** Adds ready-made agent skills and a one-command installer (FR34 in `01`, F51–F54/NF58 in `03`).
10
+
11
+ - **Agent skills (F51):** five skills in `skills/` teach an agent when and how to use the tools — `agent-whiteboard` (core rules), `whiteboard-teaching`, `whiteboard-assessment`, `whiteboard-analysis`, `whiteboard-design`. Shipped in the npm package (`files` now includes `skills`, F54).
12
+ - **`install-skills` subcommand (F52):** `npx agent-whiteboard install-skills` copies them into `~/.claude/skills` (`--project` → `./.claude/skills`, `--dir <path>`, `--only a,b`, `--force`, `--list`). Existing skills are never overwritten without `--force`. `bin/cli.js` dispatches it before loading the server, so plain `npx agent-whiteboard` is unchanged.
13
+ - **README (F53):** new Use cases (workflows per context) and Agent skills sections.
14
+ - **Dev tooling:** `npm run check` (typecheck → lint → test → build) mirrors CI locally; ESLint ignores the gitignored `video/` folder. `test_skills.md` documents the manual skills test plan.
15
+ - Tests: `installSkills.test.ts` (10 tests); 755 unit tests passing; `tsc`/`svelte-check`/`eslint`/`npm run build` clean.
16
+
17
+ ## 1.2.2 — 2026-11-07
18
+
19
+ Patch: test-only fix, no behavioural change.
20
+
21
+ - **Fix flaky CI test:** `import-zip.test.ts` "cleans up its temp directory" counted `agent-whiteboard-import-*` folders in the shared system tmpdir, which other test files (separate workers) also populate, so the before/after comparison raced. The test now uses a private `TMPDIR` and asserts it ends empty.
22
+
23
+ ## 1.2.1 — 2026-11-07
24
+
25
+ Patch: lint fix, no behavioural change.
26
+
27
+ - **Fix CI lint crash:** `Quiz.svelte` used a reactive arrow function (`$: resultFor = ...`) that made `svelte/no-reactive-functions` crash in its autofix under ESLint 10, failing `npm run lint` in CI. Replaced with a reactive `resultById` map.
28
+
1
29
  ## 1.2.0 — 2026-11-06
2
30
 
3
31
  **Milestone v1.8 — Quiz Support (Sprints 89–91) complete.** Adds interactive quizzes for reviewing the user's knowledge (FR33 in `01`, F44–F50/NF57 in `03`).
package/README.md CHANGED
@@ -18,6 +18,8 @@ The browser also has a History panel for browsing, reloading, deleting, and expo
18
18
 
19
19
  <img src="docs/screenshots/history-panel.png" width="700" alt="History panel listing past snapshots grouped by workspace">
20
20
 
21
+ **Jump to:** [Quickstart](#quickstart) · [MCP registration](#mcp-registration) · [Agent skills](#agent-skills) · [Use cases](#use-cases) · [MCP tools](#mcp-tools) · [Environment variables](#environment-variables) · [Development](#development) *(contributors)*
22
+
21
23
  ## How it works
22
24
 
23
25
  ```
@@ -54,38 +56,98 @@ By default the server only binds to loopback addresses (`localhost`/`127.0.0.1`/
54
56
 
55
57
  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.
56
58
 
57
- ## Development
59
+ ## MCP registration
58
60
 
59
- Running from source (contributing, or testing an unreleased change) instead of `npx agent-whiteboard`:
61
+ Add a `.mcp.json` to your project root (this repository already has one) and Claude Code picks it up automatically:
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "agent-whiteboard": {
67
+ "type": "sse",
68
+ "url": "http://localhost:3000/mcp"
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ The server must be running before Claude Code connects. The first time, run `/mcp` in Claude Code, select `agent-whiteboard` and enable it; the tools are available immediately. If you override `PORT`, update the URL above to match. See [Environment variables](#environment-variables) for the full list of overridable settings.
75
+
76
+ ## Agent skills
77
+
78
+ The `skills/` folder ships ready-made [agent skills](https://docs.claude.com/en/docs/claude-code/skills) that teach an agent *when and how* to use the tools above. Start with `agent-whiteboard`; the others build on it.
79
+
80
+ | Skill | Covers |
81
+ |-------------------------|-------------------------------------------------------------------|
82
+ | `agent-whiteboard` | Core rules: workspaces, render types, pacing, step-frames, export |
83
+ | `whiteboard-teaching` | Explainers, build-ups, worked examples, guided lessons, branching |
84
+ | `whiteboard-assessment` | Inline and deferred quizzes, remediation, spaced review |
85
+ | `whiteboard-analysis` | Charts, exploration loops, findings walkthroughs, derivations |
86
+ | `whiteboard-design` | Architecture sessions, option comparison, codebase explanation |
87
+
88
+ Install them with one command (no server is started):
60
89
 
61
90
  ```bash
62
- npm install
63
- npm run dev
91
+ npx agent-whiteboard install-skills # all skills → ~/.claude/skills (every project)
92
+ npx agent-whiteboard install-skills --project # → ./.claude/skills (this project only)
93
+ npx agent-whiteboard install-skills --only agent-whiteboard,whiteboard-teaching
94
+ npx agent-whiteboard install-skills --force # overwrite skills that already exist
95
+ npx agent-whiteboard install-skills --list # show the bundled skills
64
96
  ```
65
97
 
66
- This starts the Node server (`:3000`), the Vite dev server (`:5173`), and opens the browser tab automatically.
98
+ Existing skill folders are left untouched unless you pass `--force`. `--dir <path>` installs into any other folder (for agents that keep skills elsewhere). Prefer to do it by hand? Copy the folders from [`skills/`](skills/) yourself. Restart your agent session afterwards so it picks the skills up.
99
+
100
+ ## Use cases
101
+
102
+ Agent Whiteboard is domain-agnostic; these are the workflows it is built for. Each links the tools involved.
103
+
104
+ | Context | Workflow | Tools |
105
+ |---------------------------|-------------------------------------------------------|-------------------------------------------------------|
106
+ | **Teaching & training** | Concept explainer (one visual, then pause) | `render` → `wait_done` |
107
+ | | Progressive build-up of a diagram | step-frames + `wait_done` per frame |
108
+ | | Worked example (math / algorithm), one step per frame | `katex` / `mermaid` step-frames, `step` / `seek` |
109
+ | | Learner-driven branching on a topic map | `wait_click` with `node_actions` |
110
+ | **Assessment** | Inline self-check quiz (scored in the browser) | `render` quiz with a key → `wait_quiz` |
111
+ | | Deferred quiz graded by the agent | `render` quiz without a key → `wait_quiz` |
112
+ | | Teach → quiz → remediate; spaced review | quizzes + `list_snapshots` |
113
+ | **Analysis & data** | Iterative data exploration, version history | `vega-lite` + `wait_done`, History panel |
114
+ | | Findings walkthrough / report | step-frames (`html` + `vega-lite`) + `export_html` |
115
+ | | Statistics / math derivation with an illustration | `katex` + `vega-lite` |
116
+ | **Design & architecture** | Iterative system design, click to drill down | `mermaid` + `wait_click` |
117
+ | | Option comparison and decision | step-frames + `html` table |
118
+ | | Explaining a codebase or protocol; onboarding pack | sequence diagrams, step-frames, quiz, `export_html` |
119
+ | **Sharing** | Export a lesson or analysis; share a workspace | `export_html`; zip export/import in the History panel |
67
120
 
68
- Enable the MCP server in Claude Code the first time:
121
+ ## Example agent flow
69
122
 
70
123
  ```
71
- /mcp
124
+ render(type="mermaid", payload="graph TD; A --> B", options={ workspace: "my-lesson", title: "System overview" })
125
+ → wait_done() # agent pauses; user clicks Done when ready
126
+ → render(type="katex", payload="E = mc^2", options={ workspace: "my-lesson" })
127
+ → wait_done()
72
128
  ```
73
129
 
74
- Select `agent-whiteboard` and enable it. The tools will be available immediately.
130
+ 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):
75
131
 
76
- ## Environment variables
132
+ ```
133
+ init_step_frames(frame_type="mermaid", workspace="my-lesson", title="TCP handshake")
134
+ → append_frame(id, payload="graph LR; Client -- SYN --> Server", label="1. SYN")
135
+ → wait_done() # user reviews frame 1, clicks Done
136
+ → append_frame(id, payload="graph LR; Client -- SYN --> Server; Server -- SYN-ACK --> Client", label="2. SYN-ACK")
137
+ → wait_done() # user reviews frame 2, clicks Done
138
+ → append_frame(id, payload="...", label="3. ACK")
139
+ → commit_step_frames(id)
140
+ ```
77
141
 
78
- | Variable | Default | Description |
79
- |----------------------------|-----------------------|-------------|
80
- | `PORT` | `3000` | Server port — serves the REST/MCP/WebSocket API, and (via `npx agent-whiteboard`) the built client too. |
81
- | `HOST` | `localhost` | Bind address. Must resolve to a loopback address (`localhost`, `127.0.0.1`, `::1`) — the server refuses to start otherwise unless `ALLOW_NON_LOOPBACK` is set. See [Trust model](#trust-model). |
82
- | `ALLOW_NON_LOOPBACK` | unset | Set to `1` to allow binding a non-loopback `HOST`. Not recommended — there is no authentication on any endpoint. |
83
- | `WHITEBOARD_SNAPSHOTS_DIR` | `~/.agent-whiteboard` | Snapshot storage location. |
84
- | `CHANNEL_PORT` | `3001` | Port for the stdio-channel HTTP relay used by Claude Code's channel forwarding (only relevant if Claude Code was started with channels enabled). |
142
+ `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.
85
143
 
86
- **Port conflicts:** if `:3000` (or `:3001`/`:5173`, see below) is already in use on your machine, set `PORT` (and `CHANNEL_PORT` if needed) to a free port before starting.
144
+ Knowledge check — render a quiz and wait for the user to submit it (see [Quizzes](#quizzes)):
87
145
 
88
- > Dev-only (`npm run dev`, not `npx agent-whiteboard`): the Vite client dev server always runs on `:5173`, hardcoded in `client/vite.config.ts` — not currently configurable via an environment variable.
146
+ ```
147
+ render(type="quiz", payload='{"questions":[{"id":"q1","text":"What does SYN start?","kind":"single","options":[{"id":"a","text":"A handshake"},{"id":"b","text":"A teardown"}]}]}', options={ workspace: "my-lesson" })
148
+ → wait_quiz() # no answer key in the payload → deferred: the answers come back to the agent
149
+ → evaluate result.answers, explain the right answers, and score them
150
+ ```
89
151
 
90
152
  ## MCP tools
91
153
 
@@ -93,14 +155,14 @@ Select `agent-whiteboard` and enable it. The tools will be available immediately
93
155
 
94
156
  Push content to the whiteboard canvas. Replaces whatever is currently on screen.
95
157
 
96
- | `type` | `payload` |
97
- |---|---|
98
- | `"mermaid"` | Mermaid diagram source. Must begin with a valid keyword (`graph`, `flowchart`, `sequenceDiagram`, `classDiagram`, `erDiagram`, `gantt`, `pie`, `mindmap`). |
99
- | `"svg"` | Inline SVG markup. |
100
- | `"html"` | HTML/CSS fragment. Sanitized via DOMPurify — inline styles only; `<script>` and `<style>` tags are stripped. |
101
- | `"katex"` | LaTeX string, rendered in display mode. |
102
- | `"vega-lite"` | Vega-Lite JSON spec (must be valid JSON string). |
103
- | `"quiz"` | JSON quiz — see [Quizzes](#quizzes). Valid in `render()` only (not in `slideshow()` or step-frames). |
158
+ | `type` | `payload` |
159
+ |---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
160
+ | `"mermaid"` | Mermaid diagram source. Must begin with a valid keyword (`graph`, `flowchart`, `sequenceDiagram`, `classDiagram`, `erDiagram`, `gantt`, `pie`, `mindmap`). |
161
+ | `"svg"` | Inline SVG markup. |
162
+ | `"html"` | HTML/CSS fragment. Sanitized via DOMPurify — inline styles only; `<script>` and `<style>` tags are stripped. |
163
+ | `"katex"` | LaTeX string, rendered in display mode. |
164
+ | `"vega-lite"` | Vega-Lite JSON spec (must be valid JSON string). |
165
+ | `"quiz"` | JSON quiz — see [Quizzes](#quizzes). Valid in `render()` only (not in `slideshow()` or step-frames). |
104
166
 
105
167
  `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).
106
168
 
@@ -251,36 +313,33 @@ There is no one-shot way to render a multi-frame sequence — `render()` is sing
251
313
  }
252
314
  ```
253
315
 
254
- ## Example agent flow
316
+ ## History & snapshots
255
317
 
256
- ```
257
- render(type="mermaid", payload="graph TD; A --> B", options={ workspace: "my-lesson", title: "System overview" })
258
- → wait_done() # agent pauses; user clicks Done when ready
259
- → render(type="katex", payload="E = mc^2", options={ workspace: "my-lesson" })
260
- → wait_done()
261
- ```
318
+ 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 a delete button that opens a 2-step modal (`client/src/DeleteExportModal.svelte`) for deleting an entire workspace or a chosen subset of snapshots. Destructive actions require a second confirming click.
262
319
 
263
- 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):
320
+ A single **Import / export** button (opposing-arrows icon) opens a small keyboard-accessible menu with two entries: **Export…** opens the same modal for exporting a workspace or subset of snapshots as self-contained HTML (the `export_html()` tool / `POST /export-html` endpoint described above) or as a portable `.zip` (`POST /export-zip`), and **Import…** opens a dialog for loading such a `.zip` into this or another whiteboard instance (`POST /import`, merging or creating a new workspace). Dropping a `.zip` anywhere on the page also starts an import.
264
321
 
265
- ```
266
- init_step_frames(frame_type="mermaid", workspace="my-lesson", title="TCP handshake")
267
- → append_frame(id, payload="graph LR; Client -- SYN --> Server", label="1. SYN")
268
- → wait_done() # user reviews frame 1, clicks Done
269
- → append_frame(id, payload="graph LR; Client -- SYN --> Server; Server -- SYN-ACK --> Client", label="2. SYN-ACK")
270
- → wait_done() # user reviews frame 2, clicks Done
271
- → append_frame(id, payload="...", label="3. ACK")
272
- → commit_step_frames(id)
273
- ```
322
+ | Step 1 — choose a workspace | Step 2 — delete requires a second confirming click |
323
+ |----------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
324
+ | <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"> |
274
325
 
275
- `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.
326
+ | Step 2 — export a subset of snapshots | The resulting self-contained HTML, opened directly in a browser |
327
+ |----------------------------------------------------------------------------------------------------------------|---|
328
+ | <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"> |
276
329
 
277
- Knowledge check — render a quiz and wait for the user to submit it (see [Quizzes](#quizzes)):
330
+ ## Environment variables
278
331
 
279
- ```
280
- render(type="quiz", payload='{"questions":[{"id":"q1","text":"What does SYN start?","kind":"single","options":[{"id":"a","text":"A handshake"},{"id":"b","text":"A teardown"}]}]}', options={ workspace: "my-lesson" })
281
- → wait_quiz() # no answer key in the payload → deferred: the answers come back to the agent
282
- → evaluate result.answers, explain the right answers, and score them
283
- ```
332
+ | Variable | Default | Description |
333
+ |-----------------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
334
+ | `PORT` | `3000` | Server port — serves the REST/MCP/WebSocket API, and (via `npx agent-whiteboard`) the built client too. |
335
+ | `HOST` | `localhost` | Bind address. Must resolve to a loopback address (`localhost`, `127.0.0.1`, `::1`) — the server refuses to start otherwise unless `ALLOW_NON_LOOPBACK` is set. See [Trust model](#trust-model). |
336
+ | `ALLOW_NON_LOOPBACK` | unset | Set to `1` to allow binding a non-loopback `HOST`. Not recommended — there is no authentication on any endpoint. |
337
+ | `WHITEBOARD_SNAPSHOTS_DIR` | `~/.agent-whiteboard` | Snapshot storage location. |
338
+ | `CHANNEL_PORT` | `3001` | Port for the stdio-channel HTTP relay used by Claude Code's channel forwarding (only relevant if Claude Code was started with channels enabled). |
339
+
340
+ **Port conflicts:** if `:3000` (or `:3001`/`:5173`, see below) is already in use on your machine, set `PORT` (and `CHANNEL_PORT` if needed) to a free port before starting.
341
+
342
+ > Dev-only (`npm run dev`, not `npx agent-whiteboard`): the Vite client dev server always runs on `:5173`, hardcoded in `client/vite.config.ts` — not currently configurable via an environment variable.
284
343
 
285
344
  ## REST fallback
286
345
 
@@ -315,19 +374,24 @@ All tools have HTTP equivalents for scripting or testing without an MCP client:
315
374
 
316
375
  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).
317
376
 
318
- ## History & snapshots
377
+ ## Development
319
378
 
320
- 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 a delete button that opens a 2-step modal (`client/src/DeleteExportModal.svelte`) for deleting an entire workspace or a chosen subset of snapshots. Destructive actions require a second confirming click.
379
+ Running from source (contributing, or testing an unreleased change) instead of `npx agent-whiteboard`:
321
380
 
322
- A single **Import / export** button (opposing-arrows icon) opens a small keyboard-accessible menu with two entries: **Export…** opens the same modal for exporting a workspace or subset of snapshots as self-contained HTML (the `export_html()` tool / `POST /export-html` endpoint described above) or as a portable `.zip` (`POST /export-zip`), and **Import…** opens a dialog for loading such a `.zip` into this or another whiteboard instance (`POST /import`, merging or creating a new workspace). Dropping a `.zip` anywhere on the page also starts an import.
381
+ ```bash
382
+ npm install
383
+ npm run dev
384
+ ```
323
385
 
324
- | Step 1 — choose a workspace | Step 2 — delete requires a second confirming click |
325
- |----------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
326
- | <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"> |
386
+ This starts the Node server (`:3000`), the Vite dev server (`:5173`), and opens the browser tab automatically.
327
387
 
328
- | Step 2 — export a subset of snapshots | The resulting self-contained HTML, opened directly in a browser |
329
- |----------------------------------------------------------------------------------------------------------------|---|
330
- | <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"> |
388
+ ## Tests
389
+
390
+ ```bash
391
+ npm run check # everything CI runs: typecheck → lint → unit tests → build
392
+ npm test # Vitest unit/integration tests (server + client)
393
+ npm run test:e2e # Playwright end-to-end tests (requires npm run dev:test first)
394
+ ```
331
395
 
332
396
  ## Manual showcase
333
397
 
@@ -358,30 +422,6 @@ Section flags (combinable):
358
422
  --intro-delay <ms> How long each intro card stays up (default: 4000)
359
423
  ```
360
424
 
361
- ## Tests
362
-
363
- ```bash
364
- npm test # Vitest unit/integration tests (server)
365
- npm run test:e2e # Playwright end-to-end tests (requires npm run dev:test first)
366
- ```
367
-
368
- ## MCP registration
369
-
370
- `.mcp.json` is committed to the repo root and picked up automatically by Claude Code:
371
-
372
- ```json
373
- {
374
- "mcpServers": {
375
- "agent-whiteboard": {
376
- "type": "sse",
377
- "url": "http://localhost:3000/mcp"
378
- }
379
- }
380
- }
381
- ```
382
-
383
- The server must be running before Claude Code connects. If you override `PORT`, update the URL above to match. See [Environment variables](#environment-variables) for the full list of overridable settings.
384
-
385
425
  ## License
386
426
 
387
427
  [PolyForm Noncommercial License 1.0.0](LICENSE) — free to use, modify, and redistribute for any non-commercial purpose. Commercial use isn't covered by this license; if you want to use this commercially, reach out at bobpuley@gmail.com to discuss a license.
package/bin/cli.js CHANGED
@@ -10,8 +10,17 @@
10
10
 
11
11
  import { dirname, join } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
- import open from "open";
14
- import { startServer } from "../dist/server/index.js";
13
+
14
+ // Subcommand: `agent-whiteboard install-skills ...` — copies the bundled agent
15
+ // skills and exits, without loading the server. Dispatched before the server
16
+ // imports below (loaded dynamically) so it stays fast and side-effect free.
17
+ if (process.argv[2] === "install-skills") {
18
+ const { runInstallSkills } = await import("./install-skills.js");
19
+ process.exit(runInstallSkills(process.argv.slice(3)));
20
+ }
21
+
22
+ const { default: open } = await import("open");
23
+ const { startServer } = await import("../dist/server/index.js");
15
24
 
16
25
  const here = dirname(fileURLToPath(import.meta.url));
17
26
  const staticRoot = join(here, "..", "dist", "client");
@@ -0,0 +1,108 @@
1
+ // `agent-whiteboard install-skills` — copies the bundled agent skills into a
2
+ // Claude Code skills folder. Plain ESM JS, no dependencies, so it works from
3
+ // the npx cache without touching the server or the "open" package.
4
+
5
+ import { cpSync, existsSync, mkdirSync, readdirSync, statSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { dirname, join, resolve } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ const here = dirname(fileURLToPath(import.meta.url));
11
+ export const BUNDLED_SKILLS_DIR = join(here, "..", "skills");
12
+
13
+ export const USAGE = `Usage: agent-whiteboard install-skills [options]
14
+
15
+ Copies the bundled agent skills into a Claude Code skills folder.
16
+
17
+ Options:
18
+ --project Install into ./.claude/skills (current project) instead of ~/.claude/skills
19
+ --dir <path> Install into <path> (overrides --project)
20
+ --only <a,b,c> Install only the named skills (comma-separated)
21
+ --force Overwrite skills that already exist
22
+ --list List the bundled skills and exit
23
+ -h, --help Show this help`;
24
+
25
+ export function listBundledSkills(srcDir = BUNDLED_SKILLS_DIR) {
26
+ if (!existsSync(srcDir)) return [];
27
+ return readdirSync(srcDir)
28
+ .filter((n) => statSync(join(srcDir, n)).isDirectory() && existsSync(join(srcDir, n, "SKILL.md")))
29
+ .sort();
30
+ }
31
+
32
+ export function parseArgs(argv) {
33
+ const opts = { project: false, dir: null, only: null, force: false, list: false, help: false };
34
+ for (let i = 0; i < argv.length; i++) {
35
+ const a = argv[i];
36
+ if (a === "--project") opts.project = true;
37
+ else if (a === "--force") opts.force = true;
38
+ else if (a === "--list") opts.list = true;
39
+ else if (a === "-h" || a === "--help") opts.help = true;
40
+ else if (a === "--dir" || a === "--only") {
41
+ const v = argv[++i];
42
+ if (!v || v.startsWith("--")) throw new Error(`${a} requires a value`);
43
+ if (a === "--dir") opts.dir = v;
44
+ else opts.only = v.split(",").map((s) => s.trim()).filter(Boolean);
45
+ } else throw new Error(`unknown option: ${a}`);
46
+ }
47
+ return opts;
48
+ }
49
+
50
+ /**
51
+ * Copy skills. Returns { dest, installed, skipped } (skipped = already present, no --force).
52
+ * Throws on unknown --only names.
53
+ */
54
+ export function installSkills({ srcDir = BUNDLED_SKILLS_DIR, dest, only = null, force = false }) {
55
+ const available = listBundledSkills(srcDir);
56
+ const names = only ?? available;
57
+ const unknown = names.filter((n) => !available.includes(n));
58
+ if (unknown.length) {
59
+ throw new Error(`unknown skill(s): ${unknown.join(", ")} (available: ${available.join(", ")})`);
60
+ }
61
+ mkdirSync(dest, { recursive: true });
62
+ const installed = [];
63
+ const skipped = [];
64
+ for (const name of names) {
65
+ const target = join(dest, name);
66
+ if (existsSync(target) && !force) {
67
+ skipped.push(name);
68
+ continue;
69
+ }
70
+ cpSync(join(srcDir, name), target, { recursive: true, force: true });
71
+ installed.push(name);
72
+ }
73
+ return { dest, installed, skipped };
74
+ }
75
+
76
+ /** CLI entry. Returns the process exit code. */
77
+ export function runInstallSkills(argv, { log = console.log, err = console.error } = {}) {
78
+ let opts;
79
+ try {
80
+ opts = parseArgs(argv);
81
+ } catch (e) {
82
+ err(`${e.message}\n\n${USAGE}`);
83
+ return 2;
84
+ }
85
+ if (opts.help) {
86
+ log(USAGE);
87
+ return 0;
88
+ }
89
+ if (opts.list) {
90
+ for (const n of listBundledSkills()) log(n);
91
+ return 0;
92
+ }
93
+ const dest = opts.dir
94
+ ? resolve(opts.dir)
95
+ : opts.project
96
+ ? resolve(process.cwd(), ".claude", "skills")
97
+ : join(homedir(), ".claude", "skills");
98
+ try {
99
+ const { installed, skipped } = installSkills({ dest, only: opts.only, force: opts.force });
100
+ for (const n of installed) log(`installed ${n}`);
101
+ for (const n of skipped) log(`skipped ${n} (already exists; use --force to overwrite)`);
102
+ log(`\nSkills folder: ${dest}`);
103
+ return 0;
104
+ } catch (e) {
105
+ err(`error: ${e.message}`);
106
+ return 1;
107
+ }
108
+ }
@@ -1 +1 @@
1
- import{g as p,r as u,d as a}from"./chunk-SVP7TREG-COvqzqaO.js";import{p as f}from"./chunk-JWPE2WC7-tyYPyD43.js";import{_ as n,l as o}from"./mermaid.core-CLvSDmrA.js";import{M as c,b as d}from"./cynefin-OW5HDTMX-PWlPYnpn.js";import"./index-CIhkXT_z.js";import"./step-BEQzeS8V.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},F={parser:R,db:a,renderer:u,styles:p};export{F as diagram};
1
+ import{g as p,r as u,d as a}from"./chunk-SVP7TREG-DPtBhcA2.js";import{p as f}from"./chunk-JWPE2WC7-DVxbj6OP.js";import{_ as n,l as o}from"./mermaid.core-DKnSsFxv.js";import{M as c,b as d}from"./cynefin-OW5HDTMX-yFRC4RbL.js";import"./index-CwpL9TCq.js";import"./step-BEQzeS8V.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},F={parser:R,db:a,renderer:u,styles:p};export{F as diagram};