pmx-canvas 0.8.2 → 0.9.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 (338) hide show
  1. package/.github/extensions/pmx-canvas/runtime.json +1 -1
  2. package/.github/extensions/pmx-canvas/tools.json +601 -22
  3. package/CHANGELOG.md +59 -0
  4. package/Readme.md +131 -532
  5. package/dist/canvas/global.css +2724 -476
  6. package/dist/canvas/index.js +168 -107
  7. package/dist/canvas/mcp-app.html +2899 -596
  8. package/dist/json-render/index.js +20 -20
  9. package/dist/types/client/canvas/BarHint.d.ts +12 -2
  10. package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
  11. package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
  12. package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
  13. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  14. package/dist/types/client/canvas/CommandBar.d.ts +3 -4
  15. package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
  16. package/dist/types/client/canvas/ContextChip.d.ts +3 -0
  17. package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
  18. package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
  19. package/dist/types/client/canvas/EmptyState.d.ts +4 -4
  20. package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
  21. package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
  22. package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
  23. package/dist/types/client/canvas/HomeView.d.ts +9 -7
  24. package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
  25. package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
  26. package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
  27. package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
  28. package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
  29. package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
  30. package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
  31. package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
  32. package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
  33. package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
  34. package/dist/types/client/canvas/canvas-area.d.ts +1 -1
  35. package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
  36. package/dist/types/client/canvas/graph-model.d.ts +37 -0
  37. package/dist/types/client/canvas/memory-layout.d.ts +23 -0
  38. package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
  39. package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
  40. package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
  41. package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
  42. package/dist/types/client/icons.d.ts +0 -10
  43. package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
  44. package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
  45. package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
  46. package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
  47. package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
  48. package/dist/types/client/state/boards-store.d.ts +81 -9
  49. package/dist/types/client/state/canvas-store.d.ts +14 -1
  50. package/dist/types/client/state/context-chip-store.d.ts +22 -0
  51. package/dist/types/client/state/context-status-store.d.ts +35 -0
  52. package/dist/types/client/state/human-store.d.ts +2 -1
  53. package/dist/types/client/state/intent-bridge.d.ts +11 -8
  54. package/dist/types/client/state/knowledge-store.d.ts +22 -0
  55. package/dist/types/client/state/near-pin-store.d.ts +44 -1
  56. package/dist/types/client/state/presence-store.d.ts +4 -4
  57. package/dist/types/client/state/presentation.d.ts +28 -0
  58. package/dist/types/client/state/relation-store.d.ts +10 -0
  59. package/dist/types/client/state/session-store.d.ts +41 -2
  60. package/dist/types/client/state/workspace-settings.d.ts +7 -0
  61. package/dist/types/client/types.d.ts +13 -1
  62. package/dist/types/client/utils/keyboard.d.ts +2 -0
  63. package/dist/types/client/utils/time-ago.d.ts +4 -0
  64. package/dist/types/mcp/context-uri-template.d.ts +3 -1
  65. package/dist/types/server/agent-presence.d.ts +10 -3
  66. package/dist/types/server/ax-context.d.ts +1 -1
  67. package/dist/types/server/canvas-db.d.ts +30 -7
  68. package/dist/types/server/canvas-operations.d.ts +24 -2
  69. package/dist/types/server/canvas-provenance.d.ts +22 -2
  70. package/dist/types/server/canvas-serialization.d.ts +2 -0
  71. package/dist/types/server/canvas-state.d.ts +93 -15
  72. package/dist/types/server/canvas-validation.d.ts +1 -1
  73. package/dist/types/server/context-brief.d.ts +94 -6
  74. package/dist/types/server/context-reads.d.ts +33 -7
  75. package/dist/types/server/edit-summary.d.ts +20 -0
  76. package/dist/types/server/index.d.ts +71 -10
  77. package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
  78. package/dist/types/server/lifecycle-status.d.ts +10 -0
  79. package/dist/types/server/memory-graph.d.ts +9 -0
  80. package/dist/types/server/mutation-history.d.ts +2 -0
  81. package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
  82. package/dist/types/server/operations/ops/boards.d.ts +2 -1
  83. package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
  84. package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
  85. package/dist/types/server/operations/ops/nodes.d.ts +5 -0
  86. package/dist/types/server/operations/ops/query.d.ts +23 -0
  87. package/dist/types/server/operations/registry.d.ts +1 -0
  88. package/dist/types/server/placement.d.ts +7 -0
  89. package/dist/types/server/scope-fence.d.ts +2 -1
  90. package/dist/types/server/spatial-analysis.d.ts +6 -3
  91. package/dist/types/server/tour-control.d.ts +32 -0
  92. package/dist/types/server/wiki-links.d.ts +14 -0
  93. package/dist/types/server/workbench-auth.d.ts +2 -0
  94. package/dist/types/shared/agent-context.d.ts +2 -0
  95. package/dist/types/shared/agent-presence.d.ts +15 -12
  96. package/dist/types/shared/auto-arrange.d.ts +1 -0
  97. package/dist/types/shared/boards.d.ts +4 -4
  98. package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
  99. package/dist/types/shared/knowledge-presets.d.ts +6 -0
  100. package/dist/types/shared/lifecycle-status.d.ts +24 -0
  101. package/dist/types/shared/memory-graph.d.ts +53 -0
  102. package/dist/types/shared/relations.d.ts +68 -0
  103. package/dist/types/shared/semantic-attention.d.ts +2 -0
  104. package/dist/types/shared/tour.d.ts +38 -0
  105. package/dist/types/shared/workspace-settings.d.ts +11 -0
  106. package/docs/RELEASE.md +21 -3
  107. package/docs/adr-001-bun-only-runtime.md +7 -1
  108. package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
  109. package/docs/api-reference.md +109 -0
  110. package/docs/ax-host-adapter-contract.md +1 -1
  111. package/docs/ax-state-contract.md +5 -4
  112. package/docs/cli.md +153 -15
  113. package/docs/demos/pmx-canvas-demo.mp4 +0 -0
  114. package/docs/design/AgentContext.dc.html +3 -3
  115. package/docs/design/BoardPins.dc.html +37 -22
  116. package/docs/design/Categories.dc.html +208 -0
  117. package/docs/design/CategoryEditor.dc.html +33 -0
  118. package/docs/design/Chrome.dc.html +52 -9
  119. package/docs/design/ContextChip.dc.html +224 -0
  120. package/docs/design/ContextChip600.dc.html +136 -0
  121. package/docs/design/EmptyState.dc.html +45 -0
  122. package/docs/design/Flows.dc.html +23 -1
  123. package/docs/design/HeaderMarks.dc.html +183 -0
  124. package/docs/design/Home.dc.html +19 -1
  125. package/docs/design/HomeGraph.dc.html +86 -202
  126. package/docs/design/HomeMap.dc.html +23 -1
  127. package/docs/design/HomeTeam.dc.html +32 -14
  128. package/docs/design/Lifecycle.dc.html +22 -0
  129. package/docs/design/Lifecycle600.dc.html +18 -0
  130. package/docs/design/LifecycleVariants.dc.html +157 -0
  131. package/docs/design/LinkAuthorship.dc.html +139 -0
  132. package/docs/design/LinksChanged.dc.html +203 -0
  133. package/docs/design/LinksOptions.dc.html +137 -0
  134. package/docs/design/LinksOptions600.dc.html +134 -0
  135. package/docs/design/NearPin.dc.html +9 -10
  136. package/docs/design/Pane600.dc.html +4 -3
  137. package/docs/design/Relations.dc.html +3 -2
  138. package/docs/design/Restore.dc.html +152 -0
  139. package/docs/design/SeeChange.dc.html +267 -0
  140. package/docs/design/Uses.dc.html +12 -6
  141. package/docs/design/canvas.json +146 -19
  142. package/docs/design.md +449 -16
  143. package/docs/environment.md +0 -1
  144. package/docs/evals/curation-effect.md +128 -0
  145. package/docs/http-api.md +459 -37
  146. package/docs/mcp.md +146 -18
  147. package/docs/node-types.md +58 -3
  148. package/docs/plans/design-gaps-2026-10-08.md +53 -0
  149. package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
  150. package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
  151. package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
  152. package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
  153. package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
  154. package/docs/product-context-vision-2026-09.md +17 -1
  155. package/docs/product-vision-2026-09.md +1082 -43
  156. package/docs/product-vision-review-2026-09.md +13 -1
  157. package/docs/screenshots/workbench-live-dark.png +0 -0
  158. package/docs/screenshots/workbench-live-light.png +0 -0
  159. package/docs/sdk.md +62 -2
  160. package/package.json +1 -1
  161. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  162. package/plugins/pmx-canvas/plugin.json +1 -1
  163. package/plugins/pmx-canvas/runtime.json +1 -1
  164. package/plugins/pmx-canvas/tools.json +601 -22
  165. package/skills/pmx-canvas/SKILL.md +216 -42
  166. package/skills/pmx-canvas/evals/evals.json +224 -0
  167. package/skills/pmx-canvas/references/full-reference.md +120 -21
  168. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
  169. package/skills/pmx-canvas/references/importing-context.md +132 -0
  170. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  171. package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
  172. package/skills/pmx-canvas-testing/SKILL.md +20 -0
  173. package/src/cli/agent.ts +2 -0
  174. package/src/cli/commands/ax.ts +12 -0
  175. package/src/cli/commands/boards.ts +88 -11
  176. package/src/cli/commands/edges.ts +28 -0
  177. package/src/cli/commands/groups.ts +7 -0
  178. package/src/cli/commands/nodes.ts +1 -0
  179. package/src/cli/commands/query.ts +34 -2
  180. package/src/cli/commands/record.ts +199 -48
  181. package/src/cli/index.ts +6 -1
  182. package/src/client/App.tsx +177 -70
  183. package/src/client/canvas/AttentionHistory.tsx +0 -2
  184. package/src/client/canvas/BarHint.tsx +40 -2
  185. package/src/client/canvas/BoardCopyDialog.tsx +1 -1
  186. package/src/client/canvas/BoardFolderDialog.tsx +7 -7
  187. package/src/client/canvas/BoardLinkDialog.tsx +138 -33
  188. package/src/client/canvas/BoardPinButton.tsx +33 -0
  189. package/src/client/canvas/BoardSwitcher.tsx +26 -40
  190. package/src/client/canvas/CanvasNode.tsx +360 -237
  191. package/src/client/canvas/CanvasViewport.tsx +45 -7
  192. package/src/client/canvas/CommandBar.tsx +29 -23
  193. package/src/client/canvas/CommandPalette.tsx +5 -12
  194. package/src/client/canvas/ConnectionBanner.tsx +3 -3
  195. package/src/client/canvas/ContextChip.tsx +296 -0
  196. package/src/client/canvas/ContextMenu.tsx +63 -5
  197. package/src/client/canvas/CrossBoardReferences.tsx +60 -0
  198. package/src/client/canvas/EdgeLayer.tsx +339 -76
  199. package/src/client/canvas/EmptyState.tsx +33 -18
  200. package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
  201. package/src/client/canvas/ExternalWriters.tsx +57 -42
  202. package/src/client/canvas/HomeGraphView.tsx +942 -0
  203. package/src/client/canvas/HomeMapView.tsx +641 -0
  204. package/src/client/canvas/HomeReviewView.tsx +167 -0
  205. package/src/client/canvas/HomeView.tsx +687 -160
  206. package/src/client/canvas/IntentLayer.tsx +19 -12
  207. package/src/client/canvas/KnowledgeDialog.tsx +488 -0
  208. package/src/client/canvas/LifecycleChrome.tsx +133 -0
  209. package/src/client/canvas/LifecycleFields.tsx +91 -0
  210. package/src/client/canvas/LifecyclePanel.tsx +543 -0
  211. package/src/client/canvas/MemoryInspector.tsx +104 -0
  212. package/src/client/canvas/Minimap.tsx +3 -3
  213. package/src/client/canvas/NearPinLayer.tsx +187 -0
  214. package/src/client/canvas/NodeContextMark.tsx +175 -97
  215. package/src/client/canvas/Presentation.tsx +23 -57
  216. package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
  217. package/src/client/canvas/RelationsPanel.tsx +82 -0
  218. package/src/client/canvas/SelectionBar.tsx +3 -6
  219. package/src/client/canvas/SessionPanel.tsx +90 -50
  220. package/src/client/canvas/SessionReceipt.tsx +311 -39
  221. package/src/client/canvas/ShortcutOverlay.tsx +1 -1
  222. package/src/client/canvas/SnapshotPanel.tsx +27 -36
  223. package/src/client/canvas/ToolRail.tsx +367 -192
  224. package/src/client/canvas/TopBar.tsx +354 -124
  225. package/src/client/canvas/canvas-area.ts +10 -3
  226. package/src/client/canvas/graph-geometry.ts +195 -0
  227. package/src/client/canvas/graph-model.ts +254 -0
  228. package/src/client/canvas/memory-layout.ts +106 -0
  229. package/src/client/canvas/use-chrome-height.ts +20 -0
  230. package/src/client/canvas/use-focus-trap.ts +31 -5
  231. package/src/client/canvas/use-memory-graph.ts +58 -0
  232. package/src/client/canvas/use-node-drag.ts +2 -1
  233. package/src/client/canvas/use-pan-zoom.ts +64 -7
  234. package/src/client/icons.tsx +0 -63
  235. package/src/client/mcp-app/style.css +0 -6
  236. package/src/client/nodes/AxStepControls.tsx +74 -50
  237. package/src/client/nodes/BoardNode.tsx +5 -1
  238. package/src/client/nodes/ContextNode.tsx +18 -11
  239. package/src/client/nodes/ExtAppFrame.tsx +6 -3
  240. package/src/client/nodes/FileNode.tsx +46 -42
  241. package/src/client/nodes/HtmlNode.tsx +32 -0
  242. package/src/client/nodes/ImageNode.tsx +1 -1
  243. package/src/client/nodes/InlineFormatBar.tsx +6 -9
  244. package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
  245. package/src/client/nodes/MarkdownNode.tsx +377 -109
  246. package/src/client/nodes/McpAppNode.tsx +50 -20
  247. package/src/client/nodes/MdFormatBar.tsx +21 -18
  248. package/src/client/nodes/PromptNode.tsx +1 -1
  249. package/src/client/nodes/ResponseNode.tsx +1 -1
  250. package/src/client/nodes/viewer-runtime-state.ts +54 -22
  251. package/src/client/state/attention-bridge.ts +64 -15
  252. package/src/client/state/boards-store.ts +193 -39
  253. package/src/client/state/canvas-store.ts +99 -12
  254. package/src/client/state/context-chip-store.ts +70 -0
  255. package/src/client/state/context-status-store.ts +53 -0
  256. package/src/client/state/human-store.ts +13 -7
  257. package/src/client/state/intent-bridge.ts +51 -30
  258. package/src/client/state/knowledge-store.ts +43 -0
  259. package/src/client/state/near-pin-store.ts +115 -6
  260. package/src/client/state/presence-store.ts +7 -10
  261. package/src/client/state/presentation.ts +91 -0
  262. package/src/client/state/relation-store.ts +81 -0
  263. package/src/client/state/session-store.ts +144 -18
  264. package/src/client/state/sse-bridge.ts +235 -32
  265. package/src/client/state/workspace-settings.ts +11 -0
  266. package/src/client/theme/global.css +2724 -476
  267. package/src/client/types.ts +12 -0
  268. package/src/client/utils/keyboard.ts +10 -0
  269. package/src/client/utils/time-ago.ts +14 -0
  270. package/src/json-render/renderer/index.tsx +31 -0
  271. package/src/mcp/canvas-app.ts +12 -2
  272. package/src/mcp/context-uri-template.ts +7 -4
  273. package/src/mcp/server.ts +63 -41
  274. package/src/mcp/workbench-app.ts +39 -6
  275. package/src/server/agent-presence.ts +128 -28
  276. package/src/server/ax-context.ts +27 -13
  277. package/src/server/board-export.ts +12 -1
  278. package/src/server/canvas-db.ts +331 -42
  279. package/src/server/canvas-operations.ts +195 -83
  280. package/src/server/canvas-provenance.ts +53 -22
  281. package/src/server/canvas-serialization.ts +2 -0
  282. package/src/server/canvas-state.ts +950 -122
  283. package/src/server/canvas-validation.ts +9 -2
  284. package/src/server/context-brief.ts +345 -38
  285. package/src/server/context-reads.ts +178 -30
  286. package/src/server/demo-state.json +355 -24
  287. package/src/server/edit-summary.ts +74 -0
  288. package/src/server/html-surface.ts +8 -1
  289. package/src/server/index.ts +181 -11
  290. package/src/server/knowledge-lifecycle.ts +304 -0
  291. package/src/server/lifecycle-status.ts +232 -0
  292. package/src/server/memory-graph.ts +166 -0
  293. package/src/server/mutation-history.ts +18 -6
  294. package/src/server/operations/composites.ts +32 -11
  295. package/src/server/operations/http.ts +7 -2
  296. package/src/server/operations/index.ts +4 -0
  297. package/src/server/operations/mcp.ts +26 -1
  298. package/src/server/operations/ops/ax-presence.ts +1 -1
  299. package/src/server/operations/ops/ax-read.ts +57 -6
  300. package/src/server/operations/ops/ax-timeline.ts +28 -2
  301. package/src/server/operations/ops/backup.ts +5 -3
  302. package/src/server/operations/ops/batch.ts +35 -4
  303. package/src/server/operations/ops/boards.ts +188 -47
  304. package/src/server/operations/ops/canvas-wire.ts +31 -1
  305. package/src/server/operations/ops/edges.ts +36 -2
  306. package/src/server/operations/ops/groups.ts +6 -1
  307. package/src/server/operations/ops/lifecycle.ts +262 -0
  308. package/src/server/operations/ops/memory-graph.ts +45 -0
  309. package/src/server/operations/ops/nodes.ts +107 -24
  310. package/src/server/operations/ops/query.ts +281 -68
  311. package/src/server/operations/ops/snapshots.ts +73 -1
  312. package/src/server/operations/ops/validate.ts +5 -1
  313. package/src/server/operations/ops/viewport.ts +97 -3
  314. package/src/server/operations/registry.ts +74 -25
  315. package/src/server/placement.ts +29 -0
  316. package/src/server/scope-fence.ts +78 -6
  317. package/src/server/server.ts +115 -16
  318. package/src/server/spatial-analysis.ts +48 -23
  319. package/src/server/tour-control.ts +82 -0
  320. package/src/server/wiki-links.ts +137 -0
  321. package/src/server/workbench-auth.ts +11 -0
  322. package/src/shared/agent-context.ts +3 -0
  323. package/src/shared/agent-presence.ts +15 -15
  324. package/src/shared/auto-arrange.ts +2 -0
  325. package/src/shared/boards.ts +4 -4
  326. package/src/shared/knowledge-lifecycle.ts +22 -0
  327. package/src/shared/knowledge-presets.ts +80 -0
  328. package/src/shared/lifecycle-status.ts +26 -0
  329. package/src/shared/memory-graph.ts +74 -0
  330. package/src/shared/relations.ts +98 -0
  331. package/src/shared/semantic-attention.ts +15 -1
  332. package/src/shared/tour.ts +23 -5
  333. package/src/shared/workspace-settings.ts +13 -0
  334. package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
  335. package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
  336. package/dist/types/client/canvas/kind-colors.d.ts +0 -3
  337. package/dist/types/server/agent-context.d.ts +0 -22
  338. package/src/client/canvas/ContextPinBar.tsx +0 -25
@@ -1,15 +1,8 @@
1
- import { mkdirSync, existsSync } from 'node:fs';
1
+ import { existsSync, mkdirSync } from 'node:fs';
2
2
  import { resolve, join } from 'node:path';
3
3
  import { z } from 'zod';
4
4
  import { isCanvasTheme } from '../../shared/themes.js';
5
5
  import { derivedTour, tourSchema, tourFrames, type Camera, type TourNode } from '../../shared/tour.js';
6
- import {
7
- startCanvasAutomationWebView,
8
- evaluateCanvasAutomationWebView,
9
- screenshotCanvasAutomationWebView,
10
- resizeCanvasAutomationWebView,
11
- stopCanvasAutomationWebView,
12
- } from '../../server/server.js';
13
6
  import { cmd, getBaseUrl, invokeOperation, output, parseFlags, showCommandHelp } from '../shared.js';
14
7
 
15
8
  export function parseRecordOptions(flags: Record<string, string | boolean>) {
@@ -72,6 +65,94 @@ cmd(
72
65
  output(await invokeOperation('tour.set', { tour }));
73
66
  },
74
67
  );
68
+ cmd(
69
+ 'tour go',
70
+ 'Present a stop in the workbench: an index, next or previous',
71
+ ['pmx-canvas tour go 0', 'pmx-canvas tour go next', 'pmx-canvas tour go previous --no-present'],
72
+ async (args) => {
73
+ const { positional, flags } = parseFlags(args);
74
+ if (flags.help) return showCommandHelp('tour go');
75
+ const to = positional[0];
76
+ if (to === undefined) throw new Error('Missing stop: an index, next or previous');
77
+ const target = to === 'next' || to === 'previous' ? { step: to } : { stop: Number(to) };
78
+ output(await invokeOperation('tour.go', { ...target, ...(flags['no-present'] ? { present: false } : {}) }));
79
+ },
80
+ );
81
+ cmd('tour exit', 'Leave presentation in every viewer', ['pmx-canvas tour exit'], async () => {
82
+ output(await invokeOperation('tour.exit', {}));
83
+ });
84
+
85
+ function numberList(flag: string | boolean | undefined, name: string, length: number): number[] | undefined {
86
+ if (flag === undefined) return undefined;
87
+ const values = typeof flag === 'string' ? flag.split(',').map(Number) : [];
88
+ if (values.length !== length || values.some((v) => !Number.isFinite(v))) {
89
+ throw new Error(`--${name} needs ${length} comma-separated numbers`);
90
+ }
91
+ return values;
92
+ }
93
+
94
+ cmd(
95
+ 'camera move',
96
+ 'Ease the camera to a node, world rect or viewport (not an undo step)',
97
+ [
98
+ 'pmx-canvas camera move --node <id> --duration 2 --pullback 0.6',
99
+ 'pmx-canvas camera move --rect -400,200,1600,900 --easing ease-out',
100
+ 'pmx-canvas camera move --viewport 80,100,0.5 --duration 0',
101
+ ],
102
+ async (args) => {
103
+ const { flags } = parseFlags(args);
104
+ if (flags.help) return showCommandHelp('camera move');
105
+ const rect = numberList(flags.rect, 'rect', 4);
106
+ const viewport = numberList(flags.viewport, 'viewport', 3);
107
+ const optional = (key: string) => (flags[key] === undefined ? {} : { [key]: Number(flags[key]) });
108
+ output(
109
+ await invokeOperation('camera.move', {
110
+ ...(typeof flags.node === 'string' ? { nodeId: flags.node } : {}),
111
+ ...(rect ? { rect: { x: rect[0], y: rect[1], width: rect[2], height: rect[3] } } : {}),
112
+ ...(viewport ? { viewport: { x: viewport[0], y: viewport[1], scale: viewport[2] } } : {}),
113
+ ...(typeof flags.easing === 'string' ? { easing: flags.easing } : {}),
114
+ ...optional('duration'),
115
+ ...optional('padding'),
116
+ ...optional('pullback'),
117
+ }),
118
+ );
119
+ },
120
+ );
121
+
122
+ // Bun.WebView is not in bun-types yet; record needs its CDP channel, which the
123
+ // shared automation session does not expose, so it owns its own Chrome view.
124
+ interface CaptureView extends EventTarget {
125
+ navigate(url: string): Promise<void>;
126
+ evaluate(expression: string): Promise<unknown>;
127
+ screenshot(options: { format: 'png' }): Promise<Uint8Array | ArrayBuffer | Blob>;
128
+ resize(width: number, height: number): Promise<void>;
129
+ cdp(method: string, params?: Record<string, unknown>): Promise<unknown>;
130
+ close(): void;
131
+ }
132
+ type CaptureViewConstructor = new (options: {
133
+ width: number;
134
+ height: number;
135
+ headless: true;
136
+ backend: 'chrome' | { type: 'chrome'; path: string };
137
+ dataStore: 'ephemeral';
138
+ }) => CaptureView;
139
+ interface ScreencastFrame {
140
+ data: string;
141
+ sessionId: number;
142
+ metadata: { timestamp: number };
143
+ }
144
+
145
+ // A cold Chrome launch on macOS has been measured at 15–30 s.
146
+ const CAPTURE_TIMEOUT_MS = 60_000;
147
+ function timed<T>(task: Promise<T>, action: string): Promise<T> {
148
+ let timer: ReturnType<typeof setTimeout> | undefined;
149
+ return Promise.race([
150
+ task,
151
+ new Promise<never>((_, reject) => {
152
+ timer = setTimeout(() => reject(new Error(`Timed out after 60 s while ${action}`)), CAPTURE_TIMEOUT_MS);
153
+ }),
154
+ ]).finally(() => clearTimeout(timer));
155
+ }
75
156
 
76
157
  cmd(
77
158
  'record',
@@ -89,6 +170,8 @@ cmd(
89
170
  const video = /\.mp4$/i.test(outputPath);
90
171
  const framesPath = video ? `${outputPath}.frames` : outputPath;
91
172
  if (existsSync(outputPath) || existsSync(framesPath)) throw new Error('Output already exists; choose a new path');
173
+ const WebView = (Bun as typeof Bun & { WebView?: CaptureViewConstructor }).WebView;
174
+ if (!WebView) throw new Error(`Bun.WebView is not available in Bun ${Bun.version}; Bun >=1.3.12 is required`);
92
175
  const response = await fetch(`${getBaseUrl()}/api/canvas/state`);
93
176
  if (!response.ok) throw new Error(`Cannot read board: HTTP ${response.status}`);
94
177
  const board = (await response.json()) as { nodes: TourNode[]; viewport: Camera; tour?: unknown };
@@ -109,61 +192,129 @@ cmd(
109
192
  };
110
193
  process.on('SIGINT', stop);
111
194
  process.on('SIGTERM', stop);
195
+ const frameName = (i: number) => join(framesPath, `frame-${String(i).padStart(6, '0')}.png`);
112
196
  let count = 0;
113
197
  let duplicated = 0;
198
+ const view = new WebView({
199
+ width: options.width,
200
+ height: options.height,
201
+ headless: true,
202
+ backend: options.chromePath ? { type: 'chrome', path: options.chromePath } : 'chrome',
203
+ dataStore: 'ephemeral',
204
+ });
114
205
  try {
115
- await startCanvasAutomationWebView(url.href, {
116
- backend: 'chrome',
117
- width: options.width,
118
- height: options.height,
119
- chromePath: options.chromePath,
120
- });
206
+ await timed(view.navigate(url.href), 'launching Chrome and loading the workbench');
121
207
  const deadline = Date.now() + 30_000;
122
- while (!(await evaluateCanvasAutomationWebView('!!window.pmxCapture?.ready()'))) {
208
+ while (!(await timed(view.evaluate('!!window.pmxCapture?.ready()'), 'waiting for the workbench'))) {
123
209
  if (Date.now() > deadline) throw new Error('Workbench did not become capture-ready in 30 seconds');
124
210
  await Bun.sleep(50);
125
211
  }
126
212
  // Chrome launch dimensions describe the outer window. WebView.resize
127
- // applies viewport emulation, giving screenshots the requested pixels.
128
- await resizeCanvasAutomationWebView(options.width, options.height);
129
- await evaluateCanvasAutomationWebView(
130
- 'new Promise(r => requestAnimationFrame(() => requestAnimationFrame(() => r(true))))',
213
+ // applies viewport emulation, giving captures the requested pixels.
214
+ await timed(view.resize(options.width, options.height), 'resizing the capture viewport');
215
+ await timed(
216
+ view.evaluate('new Promise(r => requestAnimationFrame(() => requestAnimationFrame(() => r(true))))'),
217
+ 'waiting for paint',
131
218
  );
132
- await evaluateCanvasAutomationWebView('document.fonts.ready.then(() => true)');
133
- const area = (await evaluateCanvasAutomationWebView('window.pmxCapture.area()')) as {
134
- width: number;
135
- height: number;
136
- };
137
- const initial = (await evaluateCanvasAutomationWebView('window.pmxCapture.camera()')) as Camera;
219
+ await timed(view.evaluate('document.fonts.ready.then(() => true)'), 'waiting for fonts');
138
220
  mkdirSync(framesPath, { recursive: true });
139
- const write = async (bytes: Uint8Array) => {
140
- await Bun.write(join(framesPath, `frame-${String(count++).padStart(6, '0')}.png`), bytes);
141
- };
142
- const started = performance.now();
143
221
  if (tour) {
222
+ const area = (await view.evaluate('window.pmxCapture.area()')) as { width: number; height: number };
223
+ const initial = (await view.evaluate('window.pmxCapture.camera()')) as Camera;
144
224
  for (const camera of tourFrames(tour, initial, board.nodes, area.width, area.height, options.fps)) {
145
225
  if (stopping) break;
146
- const applied = (await evaluateCanvasAutomationWebView(
147
- `window.pmxCapture.frame(${JSON.stringify(camera)})`,
148
- )) as Camera;
149
- if (applied.x !== camera.x || applied.y !== camera.y || applied.scale !== camera.scale) {
150
- throw new Error('Capture camera was changed before paint');
226
+ // Wait until the rendered .canvas-world matrix (screen = world * scale + offset)
227
+ // matches the requested camera, not just the viewport signal. Computed
228
+ // transforms keep six significant digits, hence the tolerances.
229
+ const applied = await timed(
230
+ view.evaluate(`window.pmxCapture.frame(${JSON.stringify(camera)}).then(async (c) => {
231
+ const world = document.querySelector('.canvas-world');
232
+ for (let i = 0; i < 30; i++) {
233
+ const m = new DOMMatrix(getComputedStyle(world).transform);
234
+ if (Math.abs(m.a / c.scale - 1) < 1e-5 && Math.abs(m.e - c.x) < 0.5 && Math.abs(m.f - c.y) < 0.5) return c;
235
+ await new Promise((r) => requestAnimationFrame(r));
236
+ }
237
+ return getComputedStyle(world).transform;
238
+ })`),
239
+ 'applying a camera frame',
240
+ );
241
+ if (JSON.stringify(applied) !== JSON.stringify(camera)) {
242
+ throw new Error(
243
+ `Frame ${count}: the canvas rendered ${JSON.stringify(applied)} instead of camera ${JSON.stringify(camera)}`,
244
+ );
151
245
  }
152
- await write(await screenshotCanvasAutomationWebView({ format: 'png' }));
246
+ await Bun.write(frameName(count++), await timed(view.screenshot({ format: 'png' }), 'capturing a frame'));
153
247
  }
154
248
  } else {
155
- const limit = options.duration ? Math.ceil(options.duration * options.fps) : Infinity;
156
- while (!stopping && count < limit) {
157
- await Bun.sleep(Math.max(0, started + (count / options.fps) * 1000 - performance.now()));
158
- const bytes = await screenshotCanvasAutomationWebView({ format: 'png' });
159
- await write(bytes);
160
- // Preserve wall-clock speed if capture is slower than requested fps.
161
- const elapsedFrames = Math.min(limit, Math.floor(((performance.now() - started) / 1000) * options.fps));
162
- while (count < elapsedFrames) {
163
- await write(bytes);
164
- duplicated++;
249
+ // Chrome pushes a frame on every paint and holds the next until the
250
+ // previous is acked; cdp() allows one call in flight, so acks chain.
251
+ // Each tick writes the newest painted frame, so frames land while recording.
252
+ let latest: Buffer | undefined;
253
+ let written: Buffer | undefined;
254
+ let captureError: unknown;
255
+ let cdpQueue: Promise<unknown> = Promise.resolve();
256
+ const send = (method: string, params?: Record<string, unknown>) => {
257
+ cdpQueue = cdpQueue
258
+ .then(() => {
259
+ if (!captureError) return view.cdp(method, params);
260
+ })
261
+ .catch((error: unknown) => {
262
+ // Terminal Ctrl+C reaches Chrome's foreground process group too.
263
+ // Only its closed-view error AFTER an intended signal stop is
264
+ // harmless. Keep earlier/unrelated failures and handle ACK
265
+ // rejections immediately, rather than leaving them unobserved.
266
+ if (
267
+ stopping &&
268
+ error instanceof Error &&
269
+ (('code' in error &&
270
+ error.code === 'ERR_INVALID_STATE' &&
271
+ error.message === 'Invalid state: WebView.cdp: view is closed') ||
272
+ // Chrome can detach while the final stop call is in flight,
273
+ // before WebView marks the view closed. Bun gives this
274
+ // rejection no code. Do not ignore it during capture.
275
+ (method === 'Page.stopScreencast' &&
276
+ !('code' in error) &&
277
+ error.message === 'page detached (crashed or closed)'))
278
+ )
279
+ return;
280
+ captureError ??= error;
281
+ });
282
+ return cdpQueue;
283
+ };
284
+ view.addEventListener('Page.screencastFrame', (event) => {
285
+ const frame = (event as MessageEvent<ScreencastFrame>).data;
286
+ latest = Buffer.from(frame.data, 'base64');
287
+ void send('Page.screencastFrameAck', { sessionId: frame.sessionId });
288
+ });
289
+ await timed(
290
+ send('Page.startScreencast', { format: 'png', maxWidth: options.width, maxHeight: options.height }),
291
+ 'starting the screencast',
292
+ );
293
+ const firstDeadline = Date.now() + 10_000;
294
+ while (!latest) {
295
+ if (captureError) throw captureError;
296
+ if (Date.now() > firstDeadline) throw new Error('Chrome sent no screencast frames');
297
+ await Bun.sleep(10);
298
+ }
299
+ const started = performance.now();
300
+ const interval = 1000 / options.fps;
301
+ let nextConnectionCheck = started;
302
+ while (!stopping && (!options.duration || count < Math.ceil(options.duration * options.fps))) {
303
+ // A static page may never paint/ACK again. Detect capture loss even
304
+ // then, rather than duplicating its last frame until a later Ctrl+C.
305
+ if (performance.now() >= nextConnectionCheck) {
306
+ await timed(send('Runtime.evaluate', { expression: '0' }), 'checking the capture connection');
307
+ nextConnectionCheck = performance.now() + 1000;
165
308
  }
309
+ if (captureError) throw captureError;
310
+ if (latest === written) duplicated++;
311
+ written = latest;
312
+ await Bun.write(frameName(count++), latest);
313
+ const wait = started + count * interval - performance.now();
314
+ if (wait > 0) await Bun.sleep(wait);
166
315
  }
316
+ await timed(send('Page.stopScreencast'), 'stopping the screencast');
317
+ if (captureError) throw captureError;
167
318
  }
168
319
  await Bun.write(
169
320
  join(framesPath, 'recording.json'),
@@ -184,12 +335,12 @@ cmd(
184
335
  } finally {
185
336
  process.off('SIGINT', stop);
186
337
  process.off('SIGTERM', stop);
187
- await stopCanvasAutomationWebView();
338
+ view.close();
188
339
  }
189
340
  let encoded = false;
190
341
  if (video && count > 0) {
191
342
  const ffmpeg = Bun.which('ffmpeg');
192
- if (!ffmpeg) console.error(`ffmpeg not found; PNG sequence retained at ${framesPath}`);
343
+ if (!ffmpeg) console.error(`ffmpeg not found; frame sequence retained at ${framesPath}`);
193
344
  else {
194
345
  const process = Bun.spawn(
195
346
  [
@@ -210,7 +361,7 @@ cmd(
210
361
  { stdout: 'ignore', stderr: 'inherit' },
211
362
  );
212
363
  encoded = (await process.exited) === 0;
213
- if (!encoded) throw new Error(`ffmpeg failed; PNG sequence retained at ${framesPath}`);
364
+ if (!encoded) throw new Error(`ffmpeg failed; frame sequence retained at ${framesPath}`);
214
365
  }
215
366
  }
216
367
  output({ ok: true, output: encoded ? outputPath : framesPath, frames: count, fps: options.fps, duplicated });
package/src/cli/index.ts CHANGED
@@ -33,6 +33,7 @@ if (args.includes('--version') || args.includes('-v')) {
33
33
  // If first arg is a known subcommand (not a --flag), route to the agent CLI.
34
34
  const AGENT_COMMANDS = new Set([
35
35
  'tour',
36
+ 'camera',
36
37
  'record',
37
38
  'node',
38
39
  'edge',
@@ -62,6 +63,7 @@ const AGENT_COMMANDS = new Set([
62
63
  'open',
63
64
  'clear',
64
65
  'code-graph',
66
+ 'memory-graph',
65
67
  'spatial',
66
68
  'watch',
67
69
  'web-artifact',
@@ -316,8 +318,10 @@ Agent CLI (works against running server):
316
318
  diagram add Add an Excalidraw diagram node
317
319
  html primitive add|schema Add or inspect HTML communication primitives
318
320
  pin <ids...> | --list | --clear Manage context pins
319
- board list|create|open|rename|category
321
+ board list|create|open|rename|folder|pin|unpin
320
322
  Manage boards and open the target before writing
323
+ board knowledge <action> [JSON] List/set workspace categories and folder properties
324
+ board lifecycle <action> <JSON> Get/set card lifecycle, review, or explicitly restore
321
325
  backup [status|schedule] Back up every board, now or on a schedule
322
326
  restore <backup-file> Replace every board with a backup
323
327
  export [--board id] [--out file] Export a board as one HTML file anyone can open
@@ -329,6 +333,7 @@ Agent CLI (works against running server):
329
333
  web-artifact build Build bundled web artifacts
330
334
  clear --yes Clear canvas
331
335
  code-graph File dependencies
336
+ memory-graph Explore connected boards and cards (summary-only)
332
337
  spatial Spatial analysis
333
338
  watch Semantic watch stream
334
339
  pump Reactive steering loop for CLI agents (Amp, Codex CLI)