agent-whiteboard 1.1.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 (82) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +163 -78
  3. package/bin/cli.js +11 -2
  4. package/bin/install-skills.js +108 -0
  5. package/dist/client/assets/{abnfDiagram-VCTEODGH-CcuBIZ5h.js → abnfDiagram-VCTEODGH-Bwh5lUgu.js} +1 -1
  6. package/dist/client/assets/{architectureDiagram-5GKGNRK7-CpMRoWIY.js → architectureDiagram-5GKGNRK7-B_ecYkha.js} +1 -1
  7. package/dist/client/assets/{blockDiagram-I7D4REHJ-Dee0m4lz.js → blockDiagram-I7D4REHJ-yKkzC4XQ.js} +1 -1
  8. package/dist/client/assets/{c4Diagram-7LVT6UL2-D_qs3YTM.js → c4Diagram-7LVT6UL2-lHAqFGmA.js} +1 -1
  9. package/dist/client/assets/channel-B2NgRQfI.js +1 -0
  10. package/dist/client/assets/{chunk-2Q5K7J3B-CBwQ-uNF.js → chunk-2Q5K7J3B-B7zuI4_k.js} +1 -1
  11. package/dist/client/assets/{chunk-5VM5RSS4-yGvVHapR.js → chunk-5VM5RSS4-jBsRKGwk.js} +1 -1
  12. package/dist/client/assets/{chunk-F27PBJKO-BzFUf98n.js → chunk-F27PBJKO-CGIsMCIr.js} +1 -1
  13. package/dist/client/assets/{chunk-IMKFNOWR-D-Zj1GaL.js → chunk-IMKFNOWR-WXoBIg3r.js} +1 -1
  14. package/dist/client/assets/{chunk-JWPE2WC7-C8Aj4MVR.js → chunk-JWPE2WC7-DVxbj6OP.js} +1 -1
  15. package/dist/client/assets/{chunk-POPQ4Y6H-DZP2a-yV.js → chunk-POPQ4Y6H-B6_LAnue.js} +1 -1
  16. package/dist/client/assets/{chunk-SVP7TREG-COihS1MQ.js → chunk-SVP7TREG-DPtBhcA2.js} +1 -1
  17. package/dist/client/assets/{chunk-TICWLB2K-DNZTRqyJ.js → chunk-TICWLB2K-BpTvKQOE.js} +1 -1
  18. package/dist/client/assets/{chunk-XXDRQBXY-BC_9L4PF.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-DrJ8c1cV.js → cose-bilkent-JH36ORCC-CNOS6l2h.js} +1 -1
  22. package/dist/client/assets/{cynefin-OW5HDTMX-CMVAR4fd.js → cynefin-OW5HDTMX-yFRC4RbL.js} +1 -1
  23. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-CdAQ0Lst.js → cynefinDiagram-5FMLGOSQ-A1B_XDwz.js} +1 -1
  24. package/dist/client/assets/{dagre-GXQ25YYZ-CybK0L_u.js → dagre-GXQ25YYZ-CsGQlESG.js} +1 -1
  25. package/dist/client/assets/{diagram-S7CK7UJ4-CHjTv5Sr.js → diagram-S7CK7UJ4-oi40Oovb.js} +1 -1
  26. package/dist/client/assets/{diagram-UQ7AKVKN-DfypPsGR.js → diagram-UQ7AKVKN-fXq7BGyX.js} +1 -1
  27. package/dist/client/assets/{diagram-VSXAHHWV-DHsmmCug.js → diagram-VSXAHHWV-DGsF5-Gn.js} +1 -1
  28. package/dist/client/assets/{diagram-VX7I27RA-CHZoTi9k.js → diagram-VX7I27RA-C2kM3ZeN.js} +1 -1
  29. package/dist/client/assets/{diagram-Z3DM3KII-Ceb2h13y.js → diagram-Z3DM3KII-B-4vwwDz.js} +1 -1
  30. package/dist/client/assets/{ebnfDiagram-PWID7BFC-BlZ247wB.js → ebnfDiagram-PWID7BFC-IQpqM8Er.js} +1 -1
  31. package/dist/client/assets/{erDiagram-RLTQ6QDP-BQdw-U-S.js → erDiagram-RLTQ6QDP-DZUhIRFv.js} +1 -1
  32. package/dist/client/assets/{flowDiagram-HODETNUW-DjUWHA94.js → flowDiagram-HODETNUW-BogyNcsK.js} +1 -1
  33. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-CXu2s5vq.js → ganttDiagram-EL5Y4UJY-DQrF2qTY.js} +1 -1
  34. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-CwlsB28k.js → gitGraphDiagram-WWUBYQGX-DCjdibDV.js} +1 -1
  35. package/dist/client/assets/index-CwpL9TCq.js +19 -0
  36. package/dist/client/assets/index-DEiH2AE7.css +1 -0
  37. package/dist/client/assets/{infoDiagram-27XIBGKW-C7nvTbsQ.js → infoDiagram-27XIBGKW-B9fQanxS.js} +1 -1
  38. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-DGFXG874.js → ishikawaDiagram-5VMMS53U-C2Vylmfx.js} +1 -1
  39. package/dist/client/assets/{journeyDiagram-3NMN7TZE-BDdRMcmq.js → journeyDiagram-3NMN7TZE-D89LoCjM.js} +1 -1
  40. package/dist/client/assets/{kanban-definition-UXKFOSKX-C3fnvfeO.js → kanban-definition-UXKFOSKX-PEjEU0rc.js} +1 -1
  41. package/dist/client/assets/{layout-BfegUiqt.js → layout-BFcmmT4m.js} +1 -1
  42. package/dist/client/assets/{mermaid.core-DioArgDk.js → mermaid.core-DKnSsFxv.js} +5 -5
  43. package/dist/client/assets/{mindmap-definition-YA3MSWOX-CFCbAUC0.js → mindmap-definition-YA3MSWOX-B7LiyX5g.js} +1 -1
  44. package/dist/client/assets/{pegDiagram-XKGWAZYB-B1ULJH6S.js → pegDiagram-XKGWAZYB-BnJUZrSw.js} +1 -1
  45. package/dist/client/assets/{pieDiagram-E7YTZNPT-B9H3HosU.js → pieDiagram-E7YTZNPT-z_3XFa-J.js} +1 -1
  46. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-eWc3HHkM.js → quadrantDiagram-AXDQQJYC-B-iV8Ep2.js} +1 -1
  47. package/dist/client/assets/{railroadDiagram-O6MQD6OU-DT7c-KwW.js → railroadDiagram-O6MQD6OU-DWcuukQV.js} +1 -1
  48. package/dist/client/assets/{requirementDiagram-BXWQKSXE-Dk1yVWV5.js → requirementDiagram-BXWQKSXE-BL29FbE8.js} +1 -1
  49. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-B5a__hiX.js → sankeyDiagram-P5KCCOFB-DncoVK5c.js} +1 -1
  50. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-CkCIByRS.js → sequenceDiagram-WJ2MYXX4-4_9ywdq-.js} +1 -1
  51. package/dist/client/assets/{sizeCapture-INFHLROL-CnZIW94r.js → sizeCapture-INFHLROL-DgpEl-H1.js} +1 -1
  52. package/dist/client/assets/{stateDiagram-D77RDMKH-89Sfaoz9.js → stateDiagram-D77RDMKH-D3uAsi-s.js} +1 -1
  53. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BEkx2fLW.js +1 -0
  54. package/dist/client/assets/{swimlanes-42K2YHIH-C7m88XIh.js → swimlanes-42K2YHIH-DchNJuY0.js} +1 -1
  55. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BHXAFRx6.js +8 -0
  56. package/dist/client/assets/{timeline-definition-24CTP7MA-BHKHgHq9.js → timeline-definition-24CTP7MA-KWfqhWGR.js} +1 -1
  57. package/dist/client/assets/{vennDiagram-4TSXK5OY-BqKaRdWu.js → vennDiagram-4TSXK5OY-YJTdSPKH.js} +1 -1
  58. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-XiDVRZV5.js → wardleyDiagram-VM6X3IG4-CSN6rLUa.js} +1 -1
  59. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-CxP4dIVd.js → xychartDiagram-S5SC5T6Z-DqPq1Pcp.js} +1 -1
  60. package/dist/client/index.html +2 -2
  61. package/dist/server/app.js +2 -0
  62. package/dist/server/export-html.js +4 -0
  63. package/dist/server/mcp.js +19 -4
  64. package/dist/server/quiz-submission.js +116 -0
  65. package/dist/server/quiz.js +110 -0
  66. package/dist/server/render-core.js +3 -2
  67. package/dist/server/routes/quiz.js +12 -0
  68. package/dist/server/routes/render.js +4 -4
  69. package/dist/server/validate.js +14 -0
  70. package/package.json +3 -1
  71. package/skills/agent-whiteboard/SKILL.md +45 -0
  72. package/skills/whiteboard-analysis/SKILL.md +32 -0
  73. package/skills/whiteboard-assessment/SKILL.md +51 -0
  74. package/skills/whiteboard-design/SKILL.md +29 -0
  75. package/skills/whiteboard-teaching/SKILL.md +39 -0
  76. package/dist/client/assets/channel-9qggtmY_.js +0 -1
  77. package/dist/client/assets/classDiagram-ZZMXUADV-BUnGzX-0.js +0 -1
  78. package/dist/client/assets/classDiagram-v2-VYDZK3BY-BUnGzX-0.js +0 -1
  79. package/dist/client/assets/index-CFelVSRu.js +0 -19
  80. package/dist/client/assets/index-GF8Uj64S.css +0 -1
  81. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CP--cdl-.js +0 -1
  82. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DmG2scPu.js +0 -8
package/CHANGELOG.md CHANGED
@@ -1,3 +1,52 @@
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
+
29
+ ## 1.2.0 — 2026-11-06
30
+
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`).
32
+
33
+ - **`quiz` render type (F44, F45):** `render({ type: "quiz", ... })` takes a JSON payload of single- and multiple-choice questions, validated by the new `server/quiz.ts` (unique ids, option counts, weight sums). Mode is derived from the answer key: every question keyed → inline, none → deferred, a mix is rejected. Valid in `render()` only, not in slideshows or step-frames.
34
+ - **Scoring (F47):** per question `all_or_nothing` (exact correct set) or `partial` (multiple-choice only; weighted correct options summing to 1, wrong picks earn nothing), with optional `points`.
35
+ - **Quiz renderer + inline evaluation (F44, F46):** new `Quiz.svelte` with radio/checkbox controls and an in-quiz Submit, disabled until every question is answered. Inline quizzes show per-question results and the total score after Submit.
36
+ - **Deferred evaluation (F48, F49):** Submit posts to the new `POST /quiz/submit`; the new `wait_quiz` MCP tool / `POST /wait-quiz` long-poll returns the questions and answers (plus the client-computed score for inline quizzes). 10-minute timeout, submit-before-wait retention, and superseding like `wait_click`.
37
+ - **History & export (F50):** quizzes are snapshotted as questions only and reload fresh; HTML export replaces a quiz with an explanatory note and never leaks its questions or answer key.
38
+ - **Dev/tooling:** `/quiz` added to the Vite dev proxy; README documents the quiz format, `wait_quiz` and the new endpoints; showcase Section 15 (`-q`) demonstrates inline and deferred quizzes.
39
+ - Full suite: 745 unit tests passing; `tsc`/`svelte-check`/`eslint`/`npm run build` clean.
40
+
41
+ ## 1.1.1 — 2026-11-06
42
+
43
+ **Milestone v1.7 — Unified Import/Export Button (Sprints 87–88) complete.** Pure UI consolidation; no server, API, MCP or snapshot-format changes.
44
+
45
+ - **Showcase page (F43):** `mockup/import-export-button-options.html` presented icon/tooltip/menu-label candidates; chosen: opposing-arrows icon, "Import / export" tooltip, "Export…" / "Import…" labels.
46
+ - **Single toolbar button (F41):** the separate Export and Import buttons are replaced by one `ImportExportMenu.svelte` button opening a popover with "Export…" (existing export modal) and "Import…" (existing `ImportModal`). Delete and drag-and-drop `.zip` import are unchanged.
47
+ - **ARIA menu behaviour (F42):** `aria-haspopup`/`aria-expanded`, `role="menuitem"`, arrow/Escape handling, focus return to the button, outside-click and Tab dismissal.
48
+ - **Tests (NF56):** new `ImportExportMenu` unit tests plus updated `App` tests.
49
+
1
50
  ## 1.1.0 — 2026-09-07
2
51
 
3
52
  **Milestone v1.6 — Workspace Export/Import for Sharing (Sprints 85–86) complete.** Adds a full workspace sharing round-trip: export a workspace as a portable `.zip` and import it into another whiteboard instance (FR31 in `01`, F34–F40/NF55 in `03`). The first milestone to ship real new user-facing functionality since 1.0 — correctly a minor bump per the versioning rule in `05`.
package/README.md CHANGED
@@ -8,7 +8,7 @@ The original goal was simple: instead of explaining everything through long text
8
8
 
9
9
  As the project evolved, it became clear that the same interaction model is useful far beyond education. Whether an agent is explaining a software architecture, walking through a network protocol, debugging a problem, presenting data, or documenting a system, the ability to communicate visually is just as valuable. Agent Whiteboard is therefore intentionally domain-agnostic.
10
10
 
11
- Through a set of MCP tools, an AI agent can render Mermaid diagrams, SVG, HTML, LaTeX, and Vega-Lite charts, build incremental multi-step explanations, pause until the user is ready to continue, react to clicks on diagrams, and keep a persistent history of snapshots that can later be reloaded or exported.
11
+ Through a set of MCP tools, an AI agent can render Mermaid diagrams, SVG, HTML, LaTeX, Vega-Lite charts, and interactive quizzes, build incremental multi-step explanations, pause until the user is ready to continue, react to clicks on diagrams, and keep a persistent history of snapshots that can later be reloaded or exported.
12
12
 
13
13
  Unlike collaborative whiteboard applications, Agent Whiteboard is not a canvas that humans edit directly. The whiteboard is controlled by the AI agent, making it a lightweight visual communication surface between the agent and its user. Everything runs locally and integrates naturally with Claude Code through MCP.
14
14
 
@@ -18,18 +18,20 @@ 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
  ```
24
26
  [Claude Code agent]
25
27
  │
26
- └── 13 MCP tools — render, step-frames builder, slideshow, wait_click/wait_done, snapshot export (see below)
28
+ └── 15 MCP tools — render, step-frames builder, slideshow, wait_click/wait_done/wait_quiz, snapshot export (see below)
27
29
  │
28
30
  ▼
29
31
  [Node.js server :3000] — MCP over SSE + REST fallback + WebSocket
30
32
  │
31
33
  ▼
32
- [Browser tab :5173] — Svelte SPA, auto-opens on start, with a History panel for browsing/deleting/exporting past snapshots
34
+ [Browser tab :5173] — Svelte SPA, auto-opens on start, with a History panel for browsing/deleting/exporting/importing past snapshots
33
35
  ```
34
36
 
35
37
  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.
@@ -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,13 +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). |
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). |
103
166
 
104
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).
105
168
 
@@ -161,6 +224,32 @@ Block until the user clicks the **Done** button in the browser tab, then return.
161
224
 
162
225
  <img src="docs/screenshots/done-button.png" width="700" alt="Done button visible in the controls panel while wait_done() is armed">
163
226
 
227
+ ### `wait_quiz()`
228
+
229
+ Block until the user clicks **Submit** on the quiz currently on screen (rendered with `render({ type: "quiz", ... })`), then return the questions and the user's answers. A submission made before you call `wait_quiz()` is kept and returned immediately, as long as that quiz is still on screen. Times out after 10 minutes; a second `wait_quiz()` supersedes a pending one.
230
+
231
+ **Returns:** `{ "ok": true, "type": "submitted", "questions": {...}, "answers": [{ "questionId": "q1", "selected": ["a"] }] }` — plus `"score": { "earned", "possible" }` for an inline quiz. Also `{ "ok": true, "type": "timeout" | "superseded" }`, or `{ "ok": false, "error": "..." }` if no quiz is on the canvas.
232
+
233
+ ### Quizzes
234
+
235
+ A quiz is a JSON payload of single- or multiple-choice questions:
236
+
237
+ ```json
238
+ { "title": "Check-in",
239
+ "questions": [
240
+ { "id": "q1", "text": "Which verb is idempotent?", "kind": "single",
241
+ "options": [{ "id": "a", "text": "PUT", "correct": true }, { "id": "b", "text": "POST" }] },
242
+ { "id": "q2", "text": "Pick the primes", "kind": "multiple", "scoring": "partial", "points": 2,
243
+ "options": [{ "id": "a", "text": "2", "correct": true, "weight": 0.5 }, { "id": "b", "text": "3", "correct": true, "weight": 0.5 }, { "id": "c", "text": "4" }] }
244
+ ] }
245
+ ```
246
+
247
+ - **Inline evaluation** — mark `correct: true` on the right option(s) of *every* question. The browser scores the quiz when the user clicks Submit and shows per-question results plus the total (the answer key is visible to anyone inspecting the page, so use deferred mode for anything you don't want revealed).
248
+ - **Deferred evaluation** — mark none. Submit sends the answers to the agent, which receives them via `wait_quiz()` and evaluates them itself.
249
+ - A payload where only some questions have a key is rejected.
250
+ - **Scoring** (per question): `"all_or_nothing"` (default) earns `points` (default 1) only for the exact correct set. `"partial"` (multiple-choice only) gives every correct option a `weight`; the weights must sum to 1, the score is the sum of the weights of the correct options selected, and wrong picks earn nothing.
251
+ - Quizzes are saved to history (questions only — answers and scores are never persisted) and reload as a fresh quiz. They are not included in HTML exports.
252
+
164
253
  ### `wait_click([node_actions])`
165
254
 
166
255
  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.
@@ -224,28 +313,33 @@ There is no one-shot way to render a multi-frame sequence — `render()` is sing
224
313
  }
225
314
  ```
226
315
 
227
- ## Example agent flow
316
+ ## History & snapshots
228
317
 
229
- ```
230
- render(type="mermaid", payload="graph TD; A --> B", options={ workspace: "my-lesson", title: "System overview" })
231
- → wait_done() # agent pauses; user clicks Done when ready
232
- → render(type="katex", payload="E = mc^2", options={ workspace: "my-lesson" })
233
- → wait_done()
234
- ```
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.
235
319
 
236
- 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.
237
321
 
238
- ```
239
- init_step_frames(frame_type="mermaid", workspace="my-lesson", title="TCP handshake")
240
- → append_frame(id, payload="graph LR; Client -- SYN --> Server", label="1. SYN")
241
- → wait_done() # user reviews frame 1, clicks Done
242
- → append_frame(id, payload="graph LR; Client -- SYN --> Server; Server -- SYN-ACK --> Client", label="2. SYN-ACK")
243
- → wait_done() # user reviews frame 2, clicks Done
244
- → append_frame(id, payload="...", label="3. ACK")
245
- → commit_step_frames(id)
246
- ```
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"> |
247
325
 
248
- `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"> |
329
+
330
+ ## Environment variables
331
+
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.
249
343
 
250
344
  ## REST fallback
251
345
 
@@ -262,6 +356,8 @@ All tools have HTTP equivalents for scripting or testing without an MCP client:
262
356
  | `POST /seek` | `{ "frame": N }` |
263
357
  | `POST /user-done` | — (simulates the Done button click) |
264
358
  | `POST /wait-done` | — (long-polls until Done is signalled) |
359
+ | `POST /quiz/submit` | `{ "answers": [{ "questionId": "...", "selected": ["optionId", ...] }], "score": { "earned": N, "possible": N } (optional, inline quizzes only) }` (sent by browser) — 400 if no quiz is on screen or the answers don't match it |
360
+ | `POST /wait-quiz` | — (long-polls until the quiz is submitted; REST twin of `wait_quiz`) |
265
361
  | `POST /node-click` | `{ "type": "node"\|"edge", "id": "...", "label": "..." }` (sent by browser) |
266
362
  | `POST /wait-click` | `{ "node_actions": { "<id>": ["action1", ...] } }` (optional; long-polls until a node/edge click is signalled) |
267
363
  | `POST /step-frames/init` | `{ "frame_type": "...", "workspace": "...", "title": "..." }` — starts an incremental builder, returns `{ "ok": true, "id": "<uuid>" }` |
@@ -273,20 +369,29 @@ All tools have HTTP equivalents for scripting or testing without an MCP client:
273
369
  | `POST /snapshots/delete-files` | `{ "workspace": "...", "filenames": ["..._screen.json", ...] }` — deletes specific snapshot files |
274
370
  | `POST /snapshots/delete-workspace` | `{ "workspace": "..." }` — deletes an entire workspace's snapshot folder |
275
371
  | `POST /export-html` | `{ "items": [{ "workspace": "...", "id": "..." }, ...], "mode": "cdn" \| "offline" }` (`mode` optional, defaults to `"cdn"`) — returns the assembled HTML file as a download, not JSON |
372
+ | `POST /export-zip` | `{ "items": [{ "workspace": "...", "id": "..." }, ...] }` — returns a portable `.zip` (the selected snapshots' raw JSON plus a `manifest.json`) as a download, for sharing a workspace |
373
+ | `POST /import` | multipart form: `file` (a `.zip` from `/export-zip`), `mode` (`"create"` \| `"merge"`, default `"create"`), `targetWorkspace` — imports snapshots, deduplicating by snapshot `id`/`timestamp`; 50MB cap |
276
374
 
277
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).
278
376
 
279
- ## History & snapshots
377
+ ## Development
280
378
 
281
- 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.
379
+ Running from source (contributing, or testing an unreleased change) instead of `npx agent-whiteboard`:
282
380
 
283
- | Step 1 — choose a workspace | Step 2 — delete requires a second confirming click |
284
- |----------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
285
- | <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"> |
381
+ ```bash
382
+ npm install
383
+ npm run dev
384
+ ```
286
385
 
287
- | Step 2 — export a subset of snapshots | The resulting self-contained HTML, opened directly in a browser |
288
- |----------------------------------------------------------------------------------------------------------------|---|
289
- | <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"> |
386
+ This starts the Node server (`:3000`), the Vite dev server (`:5173`), and opens the browser tab automatically.
387
+
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
+ ```
290
395
 
291
396
  ## Manual showcase
292
397
 
@@ -295,6 +400,7 @@ Exercises every renderer and interactive feature (requires `npm run dev` running
295
400
  ```bash
296
401
  node tests/human_driven/showcase.js # Sections 1–8: renderer slideshow + seek
297
402
  node tests/human_driven/showcase.js -x # Section 12: export-by-id (v0.11)
403
+ node tests/human_driven/showcase.js -q # Section 15: quiz, inline + deferred (v1.8)
298
404
  node tests/human_driven/showcase.js -a # All sections
299
405
  ```
300
406
 
@@ -306,6 +412,9 @@ Section flags (combinable):
306
412
  -u, --popup Section 10: node_actions popup menu
307
413
  -e, --edge Section 11: edge click demo
308
414
  -x, --exportid Section 12: export by graph ID (v0.11)
415
+ -c, --incremental Section 13: incremental step-frames creation (init/append/commit)
416
+ -n, --nodetoframe Section 14: node_to_frame autonomous navigation
417
+ -q, --quiz Section 15: quiz — inline scoring + deferred wait_quiz (v1.8)
309
418
  -a, --all All sections
310
419
  -p, --port <port> Server port (default: 3000)
311
420
  -d, --delay <ms> Delay between slides (default: 5000)
@@ -313,30 +422,6 @@ Section flags (combinable):
313
422
  --intro-delay <ms> How long each intro card stays up (default: 4000)
314
423
  ```
315
424
 
316
- ## Tests
317
-
318
- ```bash
319
- npm test # Vitest unit/integration tests (server)
320
- npm run test:e2e # Playwright end-to-end tests (requires npm run dev:test first)
321
- ```
322
-
323
- ## MCP registration
324
-
325
- `.mcp.json` is committed to the repo root and picked up automatically by Claude Code:
326
-
327
- ```json
328
- {
329
- "mcpServers": {
330
- "agent-whiteboard": {
331
- "type": "sse",
332
- "url": "http://localhost:3000/mcp"
333
- }
334
- }
335
- }
336
- ```
337
-
338
- 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.
339
-
340
425
  ## License
341
426
 
342
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-COihS1MQ.js";import{p as f}from"./chunk-JWPE2WC7-C8Aj4MVR.js";import{_ as n,l as o}from"./mermaid.core-DioArgDk.js";import{M as c,b as d}from"./cynefin-OW5HDTMX-CMVAR4fd.js";import"./index-CFelVSRu.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};