agent-whiteboard 1.2.0 → 1.3.2

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 +34 -0
  2. package/README.md +125 -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,37 @@
1
+ ## 1.3.2 — 2026-11-08
2
+
3
+ Patch: documentation only.
4
+
5
+ - **README npm badge:** added an npm version badge linking to the package page, so the npmjs.com page and the repo point at each other.
6
+
7
+ ## 1.3.1 — 2026-11-07
8
+
9
+ Patch: documentation only.
10
+
11
+ - **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.
12
+
13
+ ## 1.3.0 — 2026-11-07
14
+
15
+ **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`).
16
+
17
+ - **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).
18
+ - **`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.
19
+ - **README (F53):** new Use cases (workflows per context) and Agent skills sections.
20
+ - **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.
21
+ - Tests: `installSkills.test.ts` (10 tests); 755 unit tests passing; `tsc`/`svelte-check`/`eslint`/`npm run build` clean.
22
+
23
+ ## 1.2.2 — 2026-11-07
24
+
25
+ Patch: test-only fix, no behavioural change.
26
+
27
+ - **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.
28
+
29
+ ## 1.2.1 — 2026-11-07
30
+
31
+ Patch: lint fix, no behavioural change.
32
+
33
+ - **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.
34
+
1
35
  ## 1.2.0 — 2026-11-06
2
36
 
3
37
  **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
@@ -1,5 +1,7 @@
1
1
  # Agent Whiteboard
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/agent-whiteboard)](https://www.npmjs.com/package/agent-whiteboard)
4
+
3
5
  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
6
 
5
7
  Agent Whiteboard started as a tool for AI tutoring agents.
@@ -18,6 +20,8 @@ The browser also has a History panel for browsing, reloading, deleting, and expo
18
20
 
19
21
  <img src="docs/screenshots/history-panel.png" width="700" alt="History panel listing past snapshots grouped by workspace">
20
22
 
23
+ **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)*
24
+
21
25
  ## How it works
22
26
 
23
27
  ```
@@ -54,38 +58,98 @@ By default the server only binds to loopback addresses (`localhost`/`127.0.0.1`/
54
58
 
55
59
  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
60
 
57
- ## Development
61
+ ## MCP registration
58
62
 
59
- Running from source (contributing, or testing an unreleased change) instead of `npx agent-whiteboard`:
63
+ Add a `.mcp.json` to your project root (this repository already has one) and Claude Code picks it up automatically:
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "agent-whiteboard": {
69
+ "type": "sse",
70
+ "url": "http://localhost:3000/mcp"
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ 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.
77
+
78
+ ## Agent skills
79
+
80
+ 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.
81
+
82
+ | Skill | Covers |
83
+ |-------------------------|-------------------------------------------------------------------|
84
+ | `agent-whiteboard` | Core rules: workspaces, render types, pacing, step-frames, export |
85
+ | `whiteboard-teaching` | Explainers, build-ups, worked examples, guided lessons, branching |
86
+ | `whiteboard-assessment` | Inline and deferred quizzes, remediation, spaced review |
87
+ | `whiteboard-analysis` | Charts, exploration loops, findings walkthroughs, derivations |
88
+ | `whiteboard-design` | Architecture sessions, option comparison, codebase explanation |
89
+
90
+ Install them with one command (no server is started):
60
91
 
61
92
  ```bash
62
- npm install
63
- npm run dev
93
+ npx agent-whiteboard install-skills # all skills → ~/.claude/skills (every project)
94
+ npx agent-whiteboard install-skills --project # → ./.claude/skills (this project only)
95
+ npx agent-whiteboard install-skills --only agent-whiteboard,whiteboard-teaching
96
+ npx agent-whiteboard install-skills --force # overwrite skills that already exist
97
+ npx agent-whiteboard install-skills --list # show the bundled skills
64
98
  ```
65
99
 
66
- This starts the Node server (`:3000`), the Vite dev server (`:5173`), and opens the browser tab automatically.
100
+ 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.
101
+
102
+ ## Use cases
103
+
104
+ Agent Whiteboard is domain-agnostic; these are the workflows it is built for. Each links the tools involved.
105
+
106
+ | Context | Workflow | Tools |
107
+ |---------------------------|-------------------------------------------------------|-------------------------------------------------------|
108
+ | **Teaching & training** | Concept explainer (one visual, then pause) | `render` → `wait_done` |
109
+ | | Progressive build-up of a diagram | step-frames + `wait_done` per frame |
110
+ | | Worked example (math / algorithm), one step per frame | `katex` / `mermaid` step-frames, `step` / `seek` |
111
+ | | Learner-driven branching on a topic map | `wait_click` with `node_actions` |
112
+ | **Assessment** | Inline self-check quiz (scored in the browser) | `render` quiz with a key → `wait_quiz` |
113
+ | | Deferred quiz graded by the agent | `render` quiz without a key → `wait_quiz` |
114
+ | | Teach → quiz → remediate; spaced review | quizzes + `list_snapshots` |
115
+ | **Analysis & data** | Iterative data exploration, version history | `vega-lite` + `wait_done`, History panel |
116
+ | | Findings walkthrough / report | step-frames (`html` + `vega-lite`) + `export_html` |
117
+ | | Statistics / math derivation with an illustration | `katex` + `vega-lite` |
118
+ | **Design & architecture** | Iterative system design, click to drill down | `mermaid` + `wait_click` |
119
+ | | Option comparison and decision | step-frames + `html` table |
120
+ | | Explaining a codebase or protocol; onboarding pack | sequence diagrams, step-frames, quiz, `export_html` |
121
+ | **Sharing** | Export a lesson or analysis; share a workspace | `export_html`; zip export/import in the History panel |
67
122
 
68
- Enable the MCP server in Claude Code the first time:
123
+ ## Example agent flow
69
124
 
70
125
  ```
71
- /mcp
126
+ render(type="mermaid", payload="graph TD; A --> B", options={ workspace: "my-lesson", title: "System overview" })
127
+ → wait_done() # agent pauses; user clicks Done when ready
128
+ → render(type="katex", payload="E = mc^2", options={ workspace: "my-lesson" })
129
+ → wait_done()
72
130
  ```
73
131
 
74
- Select `agent-whiteboard` and enable it. The tools will be available immediately.
132
+ 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
133
 
76
- ## Environment variables
134
+ ```
135
+ init_step_frames(frame_type="mermaid", workspace="my-lesson", title="TCP handshake")
136
+ → append_frame(id, payload="graph LR; Client -- SYN --> Server", label="1. SYN")
137
+ → wait_done() # user reviews frame 1, clicks Done
138
+ → append_frame(id, payload="graph LR; Client -- SYN --> Server; Server -- SYN-ACK --> Client", label="2. SYN-ACK")
139
+ → wait_done() # user reviews frame 2, clicks Done
140
+ → append_frame(id, payload="...", label="3. ACK")
141
+ → commit_step_frames(id)
142
+ ```
77
143
 
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). |
144
+ `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
145
 
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.
146
+ Knowledge check — render a quiz and wait for the user to submit it (see [Quizzes](#quizzes)):
87
147
 
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.
148
+ ```
149
+ 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" })
150
+ → wait_quiz() # no answer key in the payload → deferred: the answers come back to the agent
151
+ → evaluate result.answers, explain the right answers, and score them
152
+ ```
89
153
 
90
154
  ## MCP tools
91
155
 
@@ -93,14 +157,14 @@ Select `agent-whiteboard` and enable it. The tools will be available immediately
93
157
 
94
158
  Push content to the whiteboard canvas. Replaces whatever is currently on screen.
95
159
 
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). |
160
+ | `type` | `payload` |
161
+ |---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
162
+ | `"mermaid"` | Mermaid diagram source. Must begin with a valid keyword (`graph`, `flowchart`, `sequenceDiagram`, `classDiagram`, `erDiagram`, `gantt`, `pie`, `mindmap`). |
163
+ | `"svg"` | Inline SVG markup. |
164
+ | `"html"` | HTML/CSS fragment. Sanitized via DOMPurify — inline styles only; `<script>` and `<style>` tags are stripped. |
165
+ | `"katex"` | LaTeX string, rendered in display mode. |
166
+ | `"vega-lite"` | Vega-Lite JSON spec (must be valid JSON string). |
167
+ | `"quiz"` | JSON quiz — see [Quizzes](#quizzes). Valid in `render()` only (not in `slideshow()` or step-frames). |
104
168
 
105
169
  `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
170
 
@@ -251,36 +315,33 @@ There is no one-shot way to render a multi-frame sequence — `render()` is sing
251
315
  }
252
316
  ```
253
317
 
254
- ## Example agent flow
318
+ ## History & snapshots
255
319
 
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
- ```
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.
262
321
 
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):
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.
264
323
 
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
- ```
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"> |
274
327
 
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.
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"> |
276
331
 
277
- Knowledge check — render a quiz and wait for the user to submit it (see [Quizzes](#quizzes)):
332
+ ## Environment variables
278
333
 
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
- ```
334
+ | Variable | Default | Description |
335
+ |-----------------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
336
+ | `PORT` | `3000` | Server port — serves the REST/MCP/WebSocket API, and (via `npx agent-whiteboard`) the built client too. |
337
+ | `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). |
338
+ | `ALLOW_NON_LOOPBACK` | unset | Set to `1` to allow binding a non-loopback `HOST`. Not recommended — there is no authentication on any endpoint. |
339
+ | `WHITEBOARD_SNAPSHOTS_DIR` | `~/.agent-whiteboard` | Snapshot storage location. |
340
+ | `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). |
341
+
342
+ **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.
343
+
344
+ > 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
345
 
285
346
  ## REST fallback
286
347
 
@@ -315,19 +376,24 @@ All tools have HTTP equivalents for scripting or testing without an MCP client:
315
376
 
316
377
  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
378
 
318
- ## History & snapshots
379
+ ## Development
319
380
 
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.
381
+ Running from source (contributing, or testing an unreleased change) instead of `npx agent-whiteboard`:
321
382
 
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.
383
+ ```bash
384
+ npm install
385
+ npm run dev
386
+ ```
323
387
 
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"> |
388
+ This starts the Node server (`:3000`), the Vite dev server (`:5173`), and opens the browser tab automatically.
327
389
 
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"> |
390
+ ## Tests
391
+
392
+ ```bash
393
+ npm run check # everything CI runs: typecheck → lint → unit tests → build
394
+ npm test # Vitest unit/integration tests (server + client)
395
+ npm run test:e2e # Playwright end-to-end tests (requires npm run dev:test first)
396
+ ```
331
397
 
332
398
  ## Manual showcase
333
399
 
@@ -358,30 +424,6 @@ Section flags (combinable):
358
424
  --intro-delay <ms> How long each intro card stays up (default: 4000)
359
425
  ```
360
426
 
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
427
  ## License
386
428
 
387
429
  [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};