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
@@ -0,0 +1,110 @@
1
+ // Quiz payload parsing + validation (F44, F45, F47 in docs/03 §10).
2
+ //
3
+ // A quiz is a single-frame `render()` payload: JSON describing questions with
4
+ // predefined options. Mode is derived from the answer key (F45): every
5
+ // question has a `correct` option → "inline" (client scores); none do →
6
+ // "deferred" (the agent scores via wait_quiz); a mix is rejected.
7
+ const WEIGHT_EPSILON = 1e-6;
8
+ function isRecord(v) {
9
+ return typeof v === "object" && v !== null && !Array.isArray(v);
10
+ }
11
+ function fail(error) {
12
+ return { ok: false, error: `invalid quiz payload: ${error}` };
13
+ }
14
+ export function parseQuizPayload(payload) {
15
+ let raw;
16
+ try {
17
+ raw = JSON.parse(payload);
18
+ }
19
+ catch {
20
+ return fail("payload must be valid JSON");
21
+ }
22
+ if (!isRecord(raw))
23
+ return fail("payload must be a JSON object");
24
+ if (raw.title !== undefined && typeof raw.title !== "string")
25
+ return fail("title must be a string");
26
+ if (!Array.isArray(raw.questions) || raw.questions.length === 0) {
27
+ return fail("questions must be a non-empty array");
28
+ }
29
+ const questionIds = new Set();
30
+ let keyedCount = 0;
31
+ for (const [qi, q] of raw.questions.entries()) {
32
+ const where = `questions[${qi}]`;
33
+ if (!isRecord(q))
34
+ return fail(`${where} must be an object`);
35
+ if (typeof q.id !== "string" || q.id === "")
36
+ return fail(`${where}.id must be a non-empty string`);
37
+ if (questionIds.has(q.id))
38
+ return fail(`duplicate question id "${q.id}"`);
39
+ questionIds.add(q.id);
40
+ if (typeof q.text !== "string" || q.text === "")
41
+ return fail(`${where}.text must be a non-empty string`);
42
+ if (q.kind !== "single" && q.kind !== "multiple")
43
+ return fail(`${where}.kind must be "single" or "multiple"`);
44
+ if (q.scoring !== undefined && q.scoring !== "all_or_nothing" && q.scoring !== "partial") {
45
+ return fail(`${where}.scoring must be "all_or_nothing" or "partial"`);
46
+ }
47
+ if (q.scoring === "partial" && q.kind !== "multiple") {
48
+ return fail(`${where}: partial scoring is only valid for kind "multiple"`);
49
+ }
50
+ if (q.points !== undefined && (typeof q.points !== "number" || !Number.isFinite(q.points) || q.points <= 0)) {
51
+ return fail(`${where}.points must be a positive number`);
52
+ }
53
+ if (!Array.isArray(q.options) || q.options.length < 2) {
54
+ return fail(`${where}.options must be an array of at least 2 options`);
55
+ }
56
+ const optionIds = new Set();
57
+ let correctCount = 0;
58
+ let weightSum = 0;
59
+ let weightedCount = 0;
60
+ for (const [oi, o] of q.options.entries()) {
61
+ const ow = `${where}.options[${oi}]`;
62
+ if (!isRecord(o))
63
+ return fail(`${ow} must be an object`);
64
+ if (typeof o.id !== "string" || o.id === "")
65
+ return fail(`${ow}.id must be a non-empty string`);
66
+ if (optionIds.has(o.id))
67
+ return fail(`duplicate option id "${o.id}" in question "${q.id}"`);
68
+ optionIds.add(o.id);
69
+ if (typeof o.text !== "string" || o.text === "")
70
+ return fail(`${ow}.text must be a non-empty string`);
71
+ if (o.correct !== undefined && typeof o.correct !== "boolean")
72
+ return fail(`${ow}.correct must be a boolean`);
73
+ if (o.weight !== undefined) {
74
+ if (typeof o.weight !== "number" || !Number.isFinite(o.weight) || o.weight <= 0) {
75
+ return fail(`${ow}.weight must be a positive number`);
76
+ }
77
+ if (o.correct !== true)
78
+ return fail(`${ow}: weight is only valid on a correct option`);
79
+ if (q.scoring !== "partial")
80
+ return fail(`${ow}: weight requires scoring "partial"`);
81
+ weightSum += o.weight;
82
+ weightedCount++;
83
+ }
84
+ if (o.correct === true)
85
+ correctCount++;
86
+ }
87
+ if (correctCount > 0)
88
+ keyedCount++;
89
+ if (correctCount > 0 && q.kind === "single" && correctCount !== 1) {
90
+ return fail(`question "${q.id}" is single-choice but has ${correctCount} correct options`);
91
+ }
92
+ if (correctCount > 0 && q.scoring === "partial") {
93
+ if (weightedCount !== correctCount) {
94
+ return fail(`question "${q.id}": every correct option needs a weight under partial scoring`);
95
+ }
96
+ if (Math.abs(weightSum - 1) > WEIGHT_EPSILON) {
97
+ return fail(`question "${q.id}": correct option weights must sum to 1 (got ${weightSum})`);
98
+ }
99
+ }
100
+ }
101
+ if (keyedCount !== 0 && keyedCount !== raw.questions.length) {
102
+ return fail("answer key is mixed: either every question has a correct option (inline) or none does (deferred)");
103
+ }
104
+ return { ok: true, quiz: raw, mode: keyedCount === 0 ? "deferred" : "inline" };
105
+ }
106
+ /** Returns null on success, an error string on failure (validateFrame convention). */
107
+ export function validateQuizPayload(payload) {
108
+ const result = parseQuizPayload(payload);
109
+ return result.ok ? null : result.error;
110
+ }
@@ -5,7 +5,7 @@ import { cancelSlideshow } from "./slideshow.js";
5
5
  import { broadcastReplace, broadcastStepFrames } from "./ws.js";
6
6
  import { generateSnapshotId } from "./snapshot-writer.js";
7
7
  import { assembleStepFramesPayload, persistContent } from "./persist.js";
8
- import { validateFrame } from "./validate.js";
8
+ import { validateFrame, validateRenderPayload } from "./validate.js";
9
9
  import { getCanvas, isStepSequence, seekStepFrame, setCanvas, setLastWorkspace, setStepFrames, stepCursor } from "./session.js";
10
10
  import { appendFrame, commitBuilder, createBuilder } from "./step-frames-builder.js";
11
11
  import { getViewport } from "./viewport-cache.js";
@@ -115,7 +115,8 @@ export function commitStepFramesResult(id, nodeToFrame) {
115
115
  */
116
116
  export async function applyLoadedSnapshotResult(frames, workspace, title, nodeToFrame, snapshotId, rawPayload) {
117
117
  for (const frame of frames) {
118
- const validationError = await validateFrame(frame);
118
+ // A quiz is a single-frame render() type (F44): valid on its own, never inside a multi-frame sequence.
119
+ const validationError = frames.length === 1 ? await validateRenderPayload(frame.type, frame.payload) : await validateFrame(frame);
119
120
  if (validationError) {
120
121
  return { ok: false, error: validationError };
121
122
  }
@@ -0,0 +1,12 @@
1
+ import { submitQuiz, waitForQuiz } from "../quiz-submission.js";
2
+ export function registerQuizRoutes(app) {
3
+ // Browser → server: the user's answers (and, for an inline quiz, the
4
+ // client-computed score). Resolves a pending wait_quiz, or is retained for the next one.
5
+ app.post("/quiz/submit", async (c) => {
6
+ const body = await c.req.json().catch(() => ({}));
7
+ const result = submitQuiz(body.answers, body.score);
8
+ return c.json(result, result.ok ? 200 : 400);
9
+ });
10
+ // REST twin of the MCP wait_quiz tool.
11
+ app.post("/wait-quiz", async (c) => c.json(await waitForQuiz()));
12
+ }
@@ -1,7 +1,7 @@
1
1
  import { signalClick, signalDone, waitForClick, waitForDone } from "../interaction.js";
2
2
  import { clearCanvas } from "../session.js";
3
3
  import { broadcast } from "../ws.js";
4
- import { FRAME_TYPES, nodeActionsSchema, nodeToFrameSchema, validateFrame } from "../validate.js";
4
+ import { FRAME_TYPES, RENDER_TYPES, nodeActionsSchema, nodeToFrameSchema, validateRenderPayload } from "../validate.js";
5
5
  import { cancelSlideshow } from "../slideshow.js";
6
6
  import { appendFrameAndBroadcast, commitRenderResult, commitStepFramesResult, initStepFramesResult, seekAndBroadcast, stepAndBroadcast, validateWorkspaceInput, } from "../render-core.js";
7
7
  import { setViewport } from "../viewport-cache.js";
@@ -17,13 +17,13 @@ export function registerRenderRoutes(app) {
17
17
  if (typeof body.payload !== "string") {
18
18
  return c.json({ ok: false, error: "payload must be a string" }, 400);
19
19
  }
20
- if (!FRAME_TYPES.includes(body.type)) {
21
- return c.json({ ok: false, error: `type must be one of: ${FRAME_TYPES.join(", ")}` }, 400);
20
+ if (!RENDER_TYPES.includes(body.type)) {
21
+ return c.json({ ok: false, error: `type must be one of: ${RENDER_TYPES.join(", ")}` }, 400);
22
22
  }
23
23
  const type = body.type;
24
24
  const { payload } = body;
25
25
  const title = body.options?.title;
26
- const validationError = await validateFrame({ type, payload });
26
+ const validationError = await validateRenderPayload(type, payload);
27
27
  if (validationError) {
28
28
  return c.json({ ok: false, error: validationError });
29
29
  }
@@ -1,6 +1,7 @@
1
1
  // Server-side payload validation helpers.
2
2
  import mermaid from "mermaid";
3
3
  import { z } from "zod";
4
+ import { validateQuizPayload } from "./quiz.js";
4
5
  /**
5
6
  * Shared shape schemas for `node_actions` (wait_click) and `node_to_frame`
6
7
  * (commit_step_frames) — the single implementation both MCP's zod
@@ -138,3 +139,16 @@ export async function validateFrame(frame) {
138
139
  }
139
140
  return null;
140
141
  }
142
+ /**
143
+ * Types accepted by render() — the frame types plus "quiz" (F44). Quiz is
144
+ * deliberately NOT a FRAME_TYPE: it is a single interactive page, not
145
+ * something that makes sense inside a slideshow or step-frames sequence
146
+ * (wait_quiz hands back one submission), so validateFrame() keeps rejecting it.
147
+ */
148
+ export const RENDER_TYPES = [...FRAME_TYPES, "quiz"];
149
+ /** The one validator for render() payloads, shared by POST /render and the MCP `render` tool. */
150
+ export async function validateRenderPayload(type, payload) {
151
+ if (type === "quiz")
152
+ return validateQuizPayload(payload);
153
+ return validateFrame({ type, payload });
154
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-whiteboard",
3
- "version": "1.1.0",
3
+ "version": "1.3.1",
4
4
  "description": "A domain-agnostic interactive whiteboard for AI teacher agents — render diagrams, math, charts, and HTML to a local browser tab via MCP tools.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "bin",
11
11
  "dist",
12
+ "skills",
12
13
  "README.md",
13
14
  "LICENSE",
14
15
  "CHANGELOG.md"
@@ -49,6 +50,7 @@
49
50
  "test:e2e": "playwright test",
50
51
  "lint": "eslint .",
51
52
  "lint:fix": "eslint . --fix",
53
+ "check": "npm run typecheck && npm run lint && npm test && npm run build",
52
54
  "prepublishOnly": "npm run build"
53
55
  },
54
56
  "dependencies": {
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: agent-whiteboard
3
+ description: Core usage of the Agent Whiteboard MCP server — render diagrams, math, charts, HTML and quizzes to a local browser tab, pace the user with wait_done/wait_click, build step-by-step sequences, and export or share the result. Use whenever an explanation, analysis or design discussion would be clearer visually than as text, or when the user mentions the whiteboard.
4
+ ---
5
+
6
+ # Agent Whiteboard — core
7
+
8
+ The whiteboard is a browser tab you control through MCP tools. The user does not draw on it; you draw, they react (Done button, clicks, quiz Submit). Use it when a picture, formula, chart or interactive check beats a paragraph. If the tools are missing, the server is not running or not registered: tell the user to run `npx agent-whiteboard` and add it to `.mcp.json` (see the project README).
9
+
10
+ ## Rules that always apply
11
+ 1. **`workspace` is required** on `render`, `init_step_frames`, `list_snapshots`, `export_html`. Pick one short name per topic or session (`tcp-lesson`, `q3-sales`). Reuse it so the history stays grouped. Letters, digits, `-`, `_`, `.`, spaces only.
12
+ 2. **`render` replaces the canvas** and is single-frame. For a multi-frame sequence use `init_step_frames` → `append_frame` × N → `commit_step_frames`.
13
+ 3. **Never end a turn assuming the user has looked.** After showing something they must read, call `wait_done` (blocks until they click Done, 10-minute timeout). Say what to look at in chat *before* blocking.
14
+ 4. **Keep chat and canvas complementary.** Canvas = the visual; chat = the narration. Don't paste the diagram source into chat.
15
+ 5. Every `ok:false` response has an `error` string. Read it, fix the payload, retry once; don't loop blindly.
16
+
17
+ ## Choosing a render type
18
+ | Need | `type` | Notes |
19
+ |------------------------------------------------|-------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
20
+ | Flow, sequence, class, ER, gantt, pie, mindmap | `mermaid` | Must start with a valid keyword (`graph`, `flowchart`, `sequenceDiagram`, `classDiagram`, `erDiagram`, `gantt`, `pie`, `mindmap`). Only `graph`/`flowchart` are reliably clickable. |
21
+ | Formulas | `katex` | LaTeX, display mode. |
22
+ | Data charts | `vega-lite` | JSON spec as a string; inline `data.values`. |
23
+ | Custom layout, tables, callouts | `html` | Sanitized: inline styles only, no `<script>`/`<style>`. |
24
+ | Hand-made illustrations | `svg` | Inline markup. |
25
+ | Knowledge check | `quiz` | See the `whiteboard-assessment` skill. `render()` only — not in slideshows or step-frames. |
26
+
27
+ Keep each frame to one idea. Prefer 5–9 mermaid nodes; split big diagrams into frames.
28
+
29
+ ## Pacing and interaction tools
30
+ - `wait_done` — user reviewed; continue. The default pacing tool.
31
+ - `wait_click([node_actions])` — user clicks a node/edge of the current mermaid diagram; returns `{id, label, action}`. `node_actions` maps node id → menu strings (e.g. `{"Cache": ["Explain", "Show failure"]}`). A new `wait_click` or `wait_done` supersedes a pending one.
32
+ - `wait_quiz` — user submits the quiz on screen; returns questions and answers.
33
+ - `step(next|prev)` / `seek(frame)` — move through a committed step-frames sequence.
34
+ - `slideshow(slides, delay_ms)` / `slideshow_stop` — unattended auto-advance (demos, ambient display). Not for teaching: the user can't pace it.
35
+
36
+ ## Step-frames pattern
37
+ `init_step_frames(frame_type, workspace, title)` returns a builder id. Each `append_frame(id, payload, label)` is shown immediately, so the user watches it grow; interleave `wait_done` for a paced reveal. `type` on a frame overrides the sequence type (mix mermaid and katex). `commit_step_frames(id, node_to_frame)` finalizes and saves; `node_to_frame` lets the user click a node to jump to its frame. Builders expire after 30 minutes idle.
38
+
39
+ ## History, export, sharing
40
+ - Every render/commit is saved under `~/.agent-whiteboard/<workspace>/`. `list_snapshots(workspace)` shows what exists; `export(id)` returns a snapshot's source.
41
+ - `export_html(workspace, ids)` returns one self-contained HTML document (CDN-linked). Quizzes are replaced by a note. For offline files or a saved path use `POST /export-html` with `{"mode":"offline"}`.
42
+ - Sharing a whole workspace with someone else: the browser's Import/export menu (zip). Tell the user; there is no MCP tool for it.
43
+
44
+ ## Specialised skills
45
+ `whiteboard-teaching` (lessons, worked examples), `whiteboard-assessment` (quizzes), `whiteboard-analysis` (data and findings), `whiteboard-design` (architecture and decisions).
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: whiteboard-analysis
3
+ description: Present and explore data and findings on the Agent Whiteboard — Vega-Lite charts, iterative data exploration, findings walkthroughs and statistics or math derivations. Use when the user wants to analyse, visualise, compare or explain data or results. Builds on the agent-whiteboard skill.
4
+ ---
5
+
6
+ # Analysis with the whiteboard
7
+
8
+ Read `agent-whiteboard` first. The charts are Vega-Lite specs (`type="vega-lite"`, payload = JSON string).
9
+
10
+ ## Chart basics
11
+ - Put data inline in `data.values`; keep it to a few hundred rows (summarise first if larger — aggregate in your own tooling, then chart the result).
12
+ - Always set axis titles, units and a chart `title`. Choose the mark for the question: line = trend, bar = comparison, point = relationship, area/stack = composition.
13
+ - Fix colour meaning across a session (the same series, the same colour) so successive charts compare visually.
14
+ - The payload must be valid JSON; a failed render returns `ok:false` with the reason.
15
+
16
+ ## Workflow A — Data exploration loop
17
+ 1. Render the first chart with a clear `title`; say in chat what it shows.
18
+ 2. `wait_done`, then take the user's follow-up ("split by region", "log scale", "exclude 2020") and re-render. Each render is saved, so earlier versions stay in History for comparison.
19
+ 3. Summarise what changed between versions in one sentence each time.
20
+ Keep the same `workspace` throughout (`<dataset>-exploration`).
21
+
22
+ ## Workflow B — Findings walkthrough
23
+ Build a story with step-frames (`init_step_frames` → `append_frame` × N → `commit_step_frames`), mixing types per frame: `html` question/context → `vega-lite` evidence → `html` interpretation → `html` conclusion/next steps. Label frames "1. Question", "2. Evidence", … and `wait_done` between them when presenting live. End with `export_html(workspace, ids)` if the user wants a shareable report.
24
+
25
+ ## Workflow C — Statistics or math derivation
26
+ Pair a `katex` frame (the formula or derivation step) with a `vega-lite` frame illustrating it (distribution, regression line, confidence band). One step per frame. State assumptions in an `html` frame first.
27
+
28
+ ## Workflow D — Compare two datasets or scenarios
29
+ Two charts side by side use a single `vega-lite` spec with `hconcat`/`vconcat` or `facet`, not two renders. For before/after, use two step-frames with identical axes so toggling with `step` shows the change.
30
+
31
+ ## Honesty
32
+ State sample size, time range and caveats on the canvas (`html` frame or chart subtitle), not only in chat. Don't truncate axes to exaggerate a difference unless you say so.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: whiteboard-assessment
3
+ description: Run quizzes on the Agent Whiteboard — inline self-check quizzes scored in the browser, deferred quizzes graded by the agent via wait_quiz, teach-quiz-remediate loops and spaced review. Use when the user wants to be tested, to review knowledge, or after teaching a topic. Builds on the agent-whiteboard skill.
4
+ ---
5
+
6
+ # Assessment with quizzes
7
+
8
+ Read `agent-whiteboard` first. Quizzes are `render(type="quiz")` payloads; they cannot go in slideshows or step-frames.
9
+
10
+ ## Payload
11
+ ```json
12
+ { "title": "Check-in",
13
+ "questions": [
14
+ { "id": "q1", "text": "Which verb is idempotent?", "kind": "single",
15
+ "options": [{"id":"a","text":"PUT","correct":true},{"id":"b","text":"POST"}] },
16
+ { "id": "q2", "text": "Pick the primes", "kind": "multiple", "scoring": "partial", "points": 2,
17
+ "options": [{"id":"a","text":"2","correct":true,"weight":0.5},
18
+ {"id":"b","text":"3","correct":true,"weight":0.5},
19
+ {"id":"c","text":"4"}] } ] }
20
+ ```
21
+ - Ids unique within the quiz / question. `kind`: `single` or `multiple`.
22
+ - `scoring`: `all_or_nothing` (default; exact correct set earns `points`, default 1) or `partial` (multiple only; correct options carry `weight`s summing to 1; wrong picks earn nothing).
23
+ - **Mode is decided by the key:** every question has a `correct` option → **inline**; none has → **deferred**; a mix is rejected.
24
+
25
+ ## Choose the mode
26
+ | Inline (key in payload) | Deferred (no key) |
27
+ |---------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------|
28
+ | Instant self-check, learner sees per-question result and total | You see the answers and grade them with judgment |
29
+ | Key is visible in the page source — fine for self-study, wrong for anything you want hidden | Answers stay hidden; best for formal checks, nuanced "best answer" questions, or when you want to explain per answer |
30
+
31
+ ## Workflow 1 — Inline self-check
32
+ `render` quiz with a key → tell the user to submit → `wait_quiz`. The result carries `answers` and `score {earned, possible}` (client-computed). Comment on the misses; don't re-score.
33
+
34
+ ## Workflow 2 — Deferred, agent-graded
35
+ `render` quiz without a key → `wait_quiz` → returned `questions` + `answers` → grade yourself against what you taught. Report the score in chat, explain each wrong answer briefly, and optionally render a summary (`html` table) of right/wrong.
36
+
37
+ ## Workflow 3 — Teach, quiz, remediate
38
+ After a lesson: 3–6 questions covering the lesson's key ideas (each tied to a frame you showed). Grade. For each miss, `seek` to or re-render the relevant visual and re-explain differently, then ask one fresh question on that point. Don't repeat the same wording.
39
+
40
+ ## Workflow 4 — Spaced review
41
+ Use `list_snapshots(workspace)` to see earlier lessons (quiz snapshots reload as fresh quizzes). Re-quiz on earlier material with new questions rather than identical ones. Mention progress against the previous score only if you recorded it in chat.
42
+
43
+ ## Writing good questions
44
+ - One concept per question; 3–5 options; plausible distractors drawn from real misconceptions.
45
+ - Avoid "all of the above" and negatives; keep option lengths similar.
46
+ - Use `multiple` + `partial` only when a set of facts is genuinely independent.
47
+
48
+ ## Gotchas
49
+ - A submission made before you call `wait_quiz` is kept as long as that same quiz is still on screen. Re-rendering discards it.
50
+ - `wait_quiz` times out after 10 minutes (`type: "timeout"`) and returns `ok:false` if no quiz is showing.
51
+ - Answers and scores are never saved to history, and quizzes are omitted from HTML export.
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: whiteboard-design
3
+ description: Run design and architecture sessions on the Agent Whiteboard — system diagrams refined iteratively, option comparisons the user decides between by clicking, codebase explanations, and onboarding material. Use when discussing architecture, flows, trade-offs or explaining how a system works. Builds on the agent-whiteboard skill.
4
+ ---
5
+
6
+ # Design and architecture with the whiteboard
7
+
8
+ Read `agent-whiteboard` first. Mermaid is the main tool: `flowchart`/`graph` for structure and clicks, `sequenceDiagram` for interactions, `classDiagram`/`erDiagram` for models. State machines: draw them as a `flowchart` (the validator only accepts the keywords listed in the core skill).
9
+
10
+ ## Workflow A — Iterative system design
11
+ 1. Render a high-level `flowchart` (5–9 boxes). `wait_click` with `node_actions` for the components worth deepening, e.g. `{"API": ["Expand", "Alternatives"], "DB": ["Expand", "Scaling"]}`.
12
+ 2. On a click, render the zoomed diagram of that component (or a step-frames drill-down). Keep a breadcrumb in the `title` ("System › API").
13
+ 3. Return to the overview with the explored nodes marked, and repeat until the user is satisfied.
14
+ Record decisions in chat as they are made.
15
+
16
+ ## Workflow B — Option comparison and decision
17
+ Show 2–3 alternatives as separate frames in one step-frames sequence (same structure and axes so differences pop), followed by an `html` frame with a short comparison table (criteria × options, plain inline styles). Then ask for a decision with `wait_click` on a mermaid node per option, or simply ask in chat. Finish by rendering the chosen design and the decision rationale.
18
+
19
+ ## Workflow C — Explain existing code or a protocol
20
+ Read the code first, then draw: component/flow diagram → sequence diagram for the key scenario → state or error paths. Step-frames with `wait_done` between frames work well for request lifecycles. Use real names from the code in node labels so the diagram maps to files.
21
+
22
+ ## Workflow D — Onboarding pack
23
+ Compose a short slideshow of step-frames sequences (what the system is → how a request flows → where to find things), end with a quiz via `whiteboard-assessment`, and `export_html` the workspace so newcomers can read it without the tool.
24
+
25
+ ## Diagram hygiene
26
+ - Label edges with the action or protocol, not just arrows.
27
+ - Group with `subgraph` rather than adding nodes; keep one direction (`LR` or `TD`) per diagram.
28
+ - Mark external systems and the part under discussion with different shapes or styles, consistently across frames.
29
+ - Mermaid syntax errors come back as `ok:false`; fix and retry rather than simplifying the content away.
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: whiteboard-teaching
3
+ description: Teach or train a user with the Agent Whiteboard — concept explainers, progressive build-ups, worked examples, guided lessons and learner-driven branching. Use when the user wants to learn, be tutored, onboarded or trained on a topic and visuals would help. Builds on the agent-whiteboard skill.
4
+ ---
5
+
6
+ # Teaching with the whiteboard
7
+
8
+ Read `agent-whiteboard` first for the tool rules (workspace, `wait_done`, render types). This skill is the lesson craft.
9
+
10
+ ## Before you start
11
+ Ask at most two things if unknown: the learner's current level and what they want to be able to do afterwards. Pick one workspace per lesson (`<topic>-lesson`).
12
+
13
+ ## Workflow A — Concept explainer (default loop)
14
+ 1. `render` one visual for one idea, with a `title`.
15
+ 2. Narrate in 2–4 sentences: what to look at, what it means.
16
+ 3. `wait_done`.
17
+ 4. Check understanding in one line ("What would happen if the server dropped the SYN?"), then continue or re-explain with a different visual.
18
+
19
+ ## Workflow B — Progressive build-up
20
+ Use when the final picture is too big to show at once.
21
+ 1. `init_step_frames(frame_type, workspace, title)`.
22
+ 2. `append_frame` the simplest version, labelled "1. …". Narrate. `wait_done`.
23
+ 3. Each next frame adds *one* element to the previous one (copy the earlier source, add a line) so the learner sees only the delta.
24
+ 4. `commit_step_frames` at the end so the whole sequence can be replayed with `step`/`seek`. Add `node_to_frame` so they can click a node to revisit its frame.
25
+
26
+ ## Workflow C — Worked example (math or algorithm)
27
+ One `katex` or `mermaid` frame per step: state before, operation, state after. Label frames by the operation ("Swap 3 and 5"). Pause with `wait_done` every 1–2 steps; for long derivations ask "want to try the next step yourself?" and wait in chat before revealing it.
28
+
29
+ ## Workflow D — Guided lesson
30
+ Outline in chat (3–6 sections), then per section run A or B. Finish with a recap diagram that connects the sections, then a quiz (`whiteboard-assessment`). Keep the lesson in one workspace so it can be exported with `export_html`.
31
+
32
+ ## Workflow E — Learner-driven branching
33
+ Render a mermaid map of the topic and call `wait_click` with `node_actions` such as `{"Cache": ["Explain", "Quiz me"]}`. Act on the returned `id`/`action`: explain that node, then re-render the map with visited nodes styled differently and arm `wait_click` again. Stop when the learner says so or all nodes are covered.
34
+
35
+ ## Style
36
+ - One idea per frame; label every step; no more than ~9 nodes.
37
+ - Reuse the same colours/shapes for the same concept across frames.
38
+ - Ask before moving on if the learner seems lost; `seek` back to the relevant frame instead of re-rendering.
39
+ - Don't dump the source of a diagram into chat.
@@ -1 +0,0 @@
1
- import{a0 as o,a1 as n}from"./mermaid.core-DioArgDk.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-TICWLB2K-DNZTRqyJ.js";import{_ as i}from"./mermaid.core-DioArgDk.js";import"./chunk-5VM5RSS4-yGvVHapR.js";import"./chunk-XXDRQBXY-BC_9L4PF.js";import"./chunk-POPQ4Y6H-DZP2a-yV.js";import"./chunk-F27PBJKO-BzFUf98n.js";import"./index-CFelVSRu.js";import"./step-BEQzeS8V.js";var f={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{f as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-TICWLB2K-DNZTRqyJ.js";import{_ as i}from"./mermaid.core-DioArgDk.js";import"./chunk-5VM5RSS4-yGvVHapR.js";import"./chunk-XXDRQBXY-BC_9L4PF.js";import"./chunk-POPQ4Y6H-DZP2a-yV.js";import"./chunk-F27PBJKO-BzFUf98n.js";import"./index-CFelVSRu.js";import"./step-BEQzeS8V.js";var f={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{f as diagram};