pmx-canvas 0.8.0 → 0.9.0

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 (341) 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 +66 -0
  4. package/Readme.md +131 -532
  5. package/dist/canvas/global.css +2759 -510
  6. package/dist/canvas/index.js +168 -107
  7. package/dist/canvas/mcp-app.html +2936 -632
  8. package/dist/json-render/index.css +81 -1
  9. package/dist/json-render/index.js +20 -20
  10. package/dist/types/client/canvas/BarHint.d.ts +12 -2
  11. package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
  12. package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
  13. package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
  14. package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
  15. package/dist/types/client/canvas/CommandBar.d.ts +3 -4
  16. package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
  17. package/dist/types/client/canvas/ContextChip.d.ts +3 -0
  18. package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
  19. package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
  20. package/dist/types/client/canvas/EmptyState.d.ts +4 -4
  21. package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
  22. package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
  23. package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
  24. package/dist/types/client/canvas/HomeView.d.ts +9 -7
  25. package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
  26. package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
  27. package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
  28. package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
  29. package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
  30. package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
  31. package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
  32. package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
  33. package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
  34. package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
  35. package/dist/types/client/canvas/canvas-area.d.ts +1 -1
  36. package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
  37. package/dist/types/client/canvas/graph-model.d.ts +37 -0
  38. package/dist/types/client/canvas/memory-layout.d.ts +23 -0
  39. package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
  40. package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
  41. package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
  42. package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
  43. package/dist/types/client/icons.d.ts +0 -10
  44. package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
  45. package/dist/types/client/nodes/HtmlNode.d.ts +1 -0
  46. package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
  47. package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
  48. package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
  49. package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
  50. package/dist/types/client/state/boards-store.d.ts +81 -9
  51. package/dist/types/client/state/canvas-store.d.ts +14 -1
  52. package/dist/types/client/state/context-chip-store.d.ts +22 -0
  53. package/dist/types/client/state/context-status-store.d.ts +35 -0
  54. package/dist/types/client/state/intent-bridge.d.ts +11 -8
  55. package/dist/types/client/state/knowledge-store.d.ts +22 -0
  56. package/dist/types/client/state/near-pin-store.d.ts +44 -1
  57. package/dist/types/client/state/presence-store.d.ts +4 -4
  58. package/dist/types/client/state/presentation.d.ts +28 -0
  59. package/dist/types/client/state/relation-store.d.ts +10 -0
  60. package/dist/types/client/state/session-store.d.ts +41 -2
  61. package/dist/types/client/state/workspace-settings.d.ts +7 -0
  62. package/dist/types/client/types.d.ts +13 -1
  63. package/dist/types/client/utils/keyboard.d.ts +2 -0
  64. package/dist/types/client/utils/time-ago.d.ts +4 -0
  65. package/dist/types/mcp/context-uri-template.d.ts +3 -1
  66. package/dist/types/server/agent-presence.d.ts +10 -3
  67. package/dist/types/server/ax-context.d.ts +1 -1
  68. package/dist/types/server/canvas-db.d.ts +30 -7
  69. package/dist/types/server/canvas-operations.d.ts +24 -2
  70. package/dist/types/server/canvas-provenance.d.ts +22 -2
  71. package/dist/types/server/canvas-serialization.d.ts +2 -0
  72. package/dist/types/server/canvas-state.d.ts +93 -15
  73. package/dist/types/server/canvas-validation.d.ts +1 -1
  74. package/dist/types/server/context-brief.d.ts +94 -6
  75. package/dist/types/server/context-reads.d.ts +33 -7
  76. package/dist/types/server/edit-summary.d.ts +20 -0
  77. package/dist/types/server/index.d.ts +71 -10
  78. package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
  79. package/dist/types/server/lifecycle-status.d.ts +10 -0
  80. package/dist/types/server/memory-graph.d.ts +9 -0
  81. package/dist/types/server/mutation-history.d.ts +2 -0
  82. package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
  83. package/dist/types/server/operations/ops/boards.d.ts +2 -1
  84. package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
  85. package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
  86. package/dist/types/server/operations/ops/nodes.d.ts +5 -0
  87. package/dist/types/server/operations/ops/query.d.ts +23 -0
  88. package/dist/types/server/operations/registry.d.ts +1 -0
  89. package/dist/types/server/placement.d.ts +7 -0
  90. package/dist/types/server/scope-fence.d.ts +2 -1
  91. package/dist/types/server/spatial-analysis.d.ts +6 -3
  92. package/dist/types/server/tour-control.d.ts +32 -0
  93. package/dist/types/server/wiki-links.d.ts +14 -0
  94. package/dist/types/server/workbench-auth.d.ts +2 -0
  95. package/dist/types/shared/agent-context.d.ts +2 -0
  96. package/dist/types/shared/agent-presence.d.ts +15 -12
  97. package/dist/types/shared/auto-arrange.d.ts +1 -0
  98. package/dist/types/shared/boards.d.ts +4 -4
  99. package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
  100. package/dist/types/shared/knowledge-presets.d.ts +6 -0
  101. package/dist/types/shared/lifecycle-status.d.ts +24 -0
  102. package/dist/types/shared/memory-graph.d.ts +53 -0
  103. package/dist/types/shared/relations.d.ts +68 -0
  104. package/dist/types/shared/semantic-attention.d.ts +2 -0
  105. package/dist/types/shared/tour.d.ts +38 -0
  106. package/dist/types/shared/workspace-settings.d.ts +11 -0
  107. package/docs/RELEASE.md +21 -3
  108. package/docs/adr-001-bun-only-runtime.md +7 -1
  109. package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
  110. package/docs/api-reference.md +109 -0
  111. package/docs/ax-host-adapter-contract.md +1 -1
  112. package/docs/ax-state-contract.md +5 -4
  113. package/docs/cli.md +153 -15
  114. package/docs/demos/pmx-canvas-demo.mp4 +0 -0
  115. package/docs/design/AgentContext.dc.html +3 -3
  116. package/docs/design/BoardPins.dc.html +37 -22
  117. package/docs/design/Categories.dc.html +208 -0
  118. package/docs/design/CategoryEditor.dc.html +33 -0
  119. package/docs/design/Chrome.dc.html +55 -9
  120. package/docs/design/ContextChip.dc.html +224 -0
  121. package/docs/design/ContextChip600.dc.html +136 -0
  122. package/docs/design/EmptyState.dc.html +45 -0
  123. package/docs/design/Flows.dc.html +23 -1
  124. package/docs/design/HeaderMarks.dc.html +183 -0
  125. package/docs/design/Home.dc.html +19 -1
  126. package/docs/design/HomeGraph.dc.html +86 -202
  127. package/docs/design/HomeMap.dc.html +23 -1
  128. package/docs/design/HomeTeam.dc.html +32 -14
  129. package/docs/design/Lifecycle.dc.html +22 -0
  130. package/docs/design/Lifecycle600.dc.html +18 -0
  131. package/docs/design/LifecycleVariants.dc.html +157 -0
  132. package/docs/design/LinkAuthorship.dc.html +139 -0
  133. package/docs/design/LinksChanged.dc.html +203 -0
  134. package/docs/design/LinksOptions.dc.html +137 -0
  135. package/docs/design/LinksOptions600.dc.html +134 -0
  136. package/docs/design/NearPin.dc.html +9 -10
  137. package/docs/design/Pane600.dc.html +4 -3
  138. package/docs/design/Relations.dc.html +3 -2
  139. package/docs/design/Restore.dc.html +152 -0
  140. package/docs/design/SeeChange.dc.html +267 -0
  141. package/docs/design/Uses.dc.html +12 -6
  142. package/docs/design/canvas.json +146 -19
  143. package/docs/design.md +454 -18
  144. package/docs/environment.md +0 -1
  145. package/docs/evals/curation-effect.md +128 -0
  146. package/docs/http-api.md +465 -38
  147. package/docs/mcp.md +150 -19
  148. package/docs/node-types.md +58 -3
  149. package/docs/plans/design-gaps-2026-10-08.md +53 -0
  150. package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
  151. package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
  152. package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
  153. package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
  154. package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
  155. package/docs/product-context-vision-2026-09.md +17 -1
  156. package/docs/product-vision-2026-09.md +1082 -43
  157. package/docs/product-vision-review-2026-09.md +13 -1
  158. package/docs/screenshots/workbench-live-dark.png +0 -0
  159. package/docs/screenshots/workbench-live-light.png +0 -0
  160. package/docs/sdk.md +62 -2
  161. package/package.json +1 -1
  162. package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
  163. package/plugins/pmx-canvas/plugin.json +1 -1
  164. package/plugins/pmx-canvas/runtime.json +1 -1
  165. package/plugins/pmx-canvas/tools.json +601 -22
  166. package/skills/pmx-canvas/SKILL.md +234 -43
  167. package/skills/pmx-canvas/evals/evals.json +62 -0
  168. package/skills/pmx-canvas/references/ax-html-control-surface.md +6 -0
  169. package/skills/pmx-canvas/references/full-reference.md +120 -21
  170. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
  171. package/skills/pmx-canvas/references/importing-context.md +132 -0
  172. package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
  173. package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
  174. package/skills/pmx-canvas-testing/SKILL.md +20 -0
  175. package/src/cli/agent.ts +2 -0
  176. package/src/cli/commands/ax.ts +12 -0
  177. package/src/cli/commands/boards.ts +87 -11
  178. package/src/cli/commands/edges.ts +28 -0
  179. package/src/cli/commands/groups.ts +7 -0
  180. package/src/cli/commands/nodes.ts +4 -2
  181. package/src/cli/commands/query.ts +34 -2
  182. package/src/cli/commands/record.ts +199 -48
  183. package/src/cli/index.ts +7 -1
  184. package/src/client/App.tsx +177 -70
  185. package/src/client/canvas/AttentionHistory.tsx +0 -2
  186. package/src/client/canvas/BarHint.tsx +40 -2
  187. package/src/client/canvas/BoardCopyDialog.tsx +1 -1
  188. package/src/client/canvas/BoardFolderDialog.tsx +7 -7
  189. package/src/client/canvas/BoardLinkDialog.tsx +138 -33
  190. package/src/client/canvas/BoardPinButton.tsx +33 -0
  191. package/src/client/canvas/BoardSwitcher.tsx +26 -40
  192. package/src/client/canvas/CanvasNode.tsx +360 -237
  193. package/src/client/canvas/CanvasViewport.tsx +45 -7
  194. package/src/client/canvas/CommandBar.tsx +29 -23
  195. package/src/client/canvas/CommandPalette.tsx +5 -12
  196. package/src/client/canvas/ConnectionBanner.tsx +3 -3
  197. package/src/client/canvas/ContextChip.tsx +296 -0
  198. package/src/client/canvas/ContextMenu.tsx +63 -5
  199. package/src/client/canvas/CrossBoardReferences.tsx +60 -0
  200. package/src/client/canvas/EdgeLayer.tsx +339 -76
  201. package/src/client/canvas/EmptyState.tsx +33 -18
  202. package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
  203. package/src/client/canvas/ExternalWriters.tsx +57 -42
  204. package/src/client/canvas/HomeGraphView.tsx +942 -0
  205. package/src/client/canvas/HomeMapView.tsx +641 -0
  206. package/src/client/canvas/HomeReviewView.tsx +167 -0
  207. package/src/client/canvas/HomeView.tsx +687 -160
  208. package/src/client/canvas/IntentLayer.tsx +19 -12
  209. package/src/client/canvas/KnowledgeDialog.tsx +488 -0
  210. package/src/client/canvas/LifecycleChrome.tsx +133 -0
  211. package/src/client/canvas/LifecycleFields.tsx +91 -0
  212. package/src/client/canvas/LifecyclePanel.tsx +533 -0
  213. package/src/client/canvas/MemoryInspector.tsx +104 -0
  214. package/src/client/canvas/Minimap.tsx +3 -3
  215. package/src/client/canvas/NearPinLayer.tsx +187 -0
  216. package/src/client/canvas/NodeContextMark.tsx +175 -97
  217. package/src/client/canvas/Presentation.tsx +23 -57
  218. package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
  219. package/src/client/canvas/RelationsPanel.tsx +82 -0
  220. package/src/client/canvas/SelectionBar.tsx +3 -6
  221. package/src/client/canvas/SessionPanel.tsx +90 -50
  222. package/src/client/canvas/SessionReceipt.tsx +311 -39
  223. package/src/client/canvas/ShortcutOverlay.tsx +1 -1
  224. package/src/client/canvas/SnapshotPanel.tsx +27 -36
  225. package/src/client/canvas/ToolRail.tsx +367 -192
  226. package/src/client/canvas/TopBar.tsx +354 -124
  227. package/src/client/canvas/canvas-area.ts +10 -3
  228. package/src/client/canvas/graph-geometry.ts +195 -0
  229. package/src/client/canvas/graph-model.ts +254 -0
  230. package/src/client/canvas/memory-layout.ts +106 -0
  231. package/src/client/canvas/use-chrome-height.ts +20 -0
  232. package/src/client/canvas/use-focus-trap.ts +31 -5
  233. package/src/client/canvas/use-memory-graph.ts +58 -0
  234. package/src/client/canvas/use-node-drag.ts +2 -1
  235. package/src/client/canvas/use-pan-zoom.ts +64 -7
  236. package/src/client/icons.tsx +0 -63
  237. package/src/client/mcp-app/style.css +0 -6
  238. package/src/client/nodes/AxStepControls.tsx +74 -50
  239. package/src/client/nodes/BoardNode.tsx +5 -1
  240. package/src/client/nodes/ContextNode.tsx +18 -11
  241. package/src/client/nodes/ExtAppFrame.tsx +0 -1
  242. package/src/client/nodes/FileNode.tsx +46 -42
  243. package/src/client/nodes/HtmlNode.tsx +44 -2
  244. package/src/client/nodes/ImageNode.tsx +1 -1
  245. package/src/client/nodes/InlineFormatBar.tsx +6 -9
  246. package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
  247. package/src/client/nodes/MarkdownNode.tsx +377 -109
  248. package/src/client/nodes/McpAppNode.tsx +50 -20
  249. package/src/client/nodes/MdFormatBar.tsx +21 -18
  250. package/src/client/nodes/PromptNode.tsx +1 -1
  251. package/src/client/nodes/ResponseNode.tsx +1 -1
  252. package/src/client/nodes/viewer-runtime-state.ts +54 -22
  253. package/src/client/state/attention-bridge.ts +64 -15
  254. package/src/client/state/boards-store.ts +193 -39
  255. package/src/client/state/canvas-store.ts +99 -12
  256. package/src/client/state/context-chip-store.ts +70 -0
  257. package/src/client/state/context-status-store.ts +53 -0
  258. package/src/client/state/intent-bridge.ts +51 -30
  259. package/src/client/state/knowledge-store.ts +43 -0
  260. package/src/client/state/near-pin-store.ts +115 -6
  261. package/src/client/state/presence-store.ts +7 -10
  262. package/src/client/state/presentation.ts +91 -0
  263. package/src/client/state/relation-store.ts +81 -0
  264. package/src/client/state/session-store.ts +144 -18
  265. package/src/client/state/sse-bridge.ts +206 -29
  266. package/src/client/state/workspace-settings.ts +11 -0
  267. package/src/client/theme/global.css +2759 -510
  268. package/src/client/types.ts +12 -0
  269. package/src/client/utils/keyboard.ts +10 -0
  270. package/src/client/utils/time-ago.ts +14 -0
  271. package/src/json-render/renderer/index.css +6 -1
  272. package/src/json-render/renderer/index.tsx +31 -1
  273. package/src/mcp/canvas-app.ts +12 -2
  274. package/src/mcp/context-uri-template.ts +7 -4
  275. package/src/mcp/server.ts +63 -41
  276. package/src/mcp/workbench-app.ts +39 -6
  277. package/src/server/agent-presence.ts +128 -28
  278. package/src/server/ax-context.ts +27 -13
  279. package/src/server/board-export.ts +12 -1
  280. package/src/server/canvas-db.ts +331 -42
  281. package/src/server/canvas-operations.ts +196 -84
  282. package/src/server/canvas-provenance.ts +53 -22
  283. package/src/server/canvas-serialization.ts +2 -0
  284. package/src/server/canvas-state.ts +950 -122
  285. package/src/server/canvas-validation.ts +9 -2
  286. package/src/server/context-brief.ts +345 -38
  287. package/src/server/context-reads.ts +179 -30
  288. package/src/server/demo-state.json +355 -24
  289. package/src/server/edit-summary.ts +74 -0
  290. package/src/server/html-surface.ts +8 -1
  291. package/src/server/index.ts +203 -13
  292. package/src/server/knowledge-lifecycle.ts +304 -0
  293. package/src/server/lifecycle-status.ts +232 -0
  294. package/src/server/memory-graph.ts +166 -0
  295. package/src/server/mutation-history.ts +18 -6
  296. package/src/server/operations/composites.ts +32 -11
  297. package/src/server/operations/http.ts +7 -2
  298. package/src/server/operations/index.ts +4 -0
  299. package/src/server/operations/mcp.ts +32 -1
  300. package/src/server/operations/ops/ax-presence.ts +1 -1
  301. package/src/server/operations/ops/ax-read.ts +57 -6
  302. package/src/server/operations/ops/ax-timeline.ts +28 -2
  303. package/src/server/operations/ops/backup.ts +5 -3
  304. package/src/server/operations/ops/batch.ts +35 -4
  305. package/src/server/operations/ops/boards.ts +188 -47
  306. package/src/server/operations/ops/canvas-wire.ts +31 -1
  307. package/src/server/operations/ops/edges.ts +36 -2
  308. package/src/server/operations/ops/groups.ts +6 -1
  309. package/src/server/operations/ops/json-render.ts +5 -1
  310. package/src/server/operations/ops/lifecycle.ts +262 -0
  311. package/src/server/operations/ops/memory-graph.ts +45 -0
  312. package/src/server/operations/ops/nodes.ts +107 -24
  313. package/src/server/operations/ops/query.ts +281 -68
  314. package/src/server/operations/ops/snapshots.ts +73 -1
  315. package/src/server/operations/ops/validate.ts +5 -1
  316. package/src/server/operations/ops/viewport.ts +97 -3
  317. package/src/server/operations/registry.ts +77 -26
  318. package/src/server/placement.ts +29 -0
  319. package/src/server/scope-fence.ts +78 -6
  320. package/src/server/server.ts +115 -16
  321. package/src/server/spatial-analysis.ts +48 -23
  322. package/src/server/tour-control.ts +82 -0
  323. package/src/server/wiki-links.ts +137 -0
  324. package/src/server/workbench-auth.ts +11 -0
  325. package/src/shared/agent-context.ts +3 -0
  326. package/src/shared/agent-presence.ts +15 -15
  327. package/src/shared/auto-arrange.ts +2 -0
  328. package/src/shared/boards.ts +4 -4
  329. package/src/shared/knowledge-lifecycle.ts +22 -0
  330. package/src/shared/knowledge-presets.ts +80 -0
  331. package/src/shared/lifecycle-status.ts +26 -0
  332. package/src/shared/memory-graph.ts +74 -0
  333. package/src/shared/relations.ts +98 -0
  334. package/src/shared/semantic-attention.ts +15 -1
  335. package/src/shared/tour.ts +23 -5
  336. package/src/shared/workspace-settings.ts +13 -0
  337. package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
  338. package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
  339. package/dist/types/client/canvas/kind-colors.d.ts +0 -3
  340. package/dist/types/server/agent-context.d.ts +0 -22
  341. package/src/client/canvas/ContextPinBar.tsx +0 -25
@@ -39,18 +39,46 @@ cmd(
39
39
  type,
40
40
  ...(from ? { from } : {}),
41
41
  ...(to ? { to } : {}),
42
+ ...(typeof flags['to-board'] === 'string' ? { toBoardId: flags['to-board'] } : {}),
42
43
  ...(fromSearch ? { fromSearch } : {}),
43
44
  ...(toSearch ? { toSearch } : {}),
44
45
  };
45
46
  if (flags.label && flags.label !== true) body.label = flags.label;
47
+ if (typeof flags.kind === 'string') body.kind = flags.kind;
48
+ if (typeof flags.reason === 'string') body.reason = flags.reason;
46
49
  if (typeof flags.style === 'string') body.style = flags.style;
47
50
  if (flags.animated) body.animated = true;
51
+ for (const [flag, field] of [
52
+ ['destination-board', 'destinationBoardId'],
53
+ ['request-id', 'requestId'],
54
+ ['expected-board', 'expectedBoardId'],
55
+ ] as const) {
56
+ if (typeof flags[flag] === 'string') body[field] = flags[flag];
57
+ }
48
58
 
49
59
  const result = await invokeOperation('edge.add', body);
50
60
  output(result);
51
61
  },
52
62
  );
53
63
 
64
+ cmd(
65
+ 'edge update',
66
+ 'Edit a relation kind, reason, label or appearance',
67
+ ['pmx-canvas edge update <id> --kind supports --reason "Evidence from interviews"'],
68
+ async (args) => {
69
+ const { positional, flags } = parseFlags(args);
70
+ if (flags.help || flags.h) return showCommandHelp('edge update');
71
+ if (!positional[0]) die('Missing edge ID', 'pmx-canvas edge update <id> --kind supports');
72
+ const body: Record<string, unknown> = { id: positional[0] };
73
+ for (const key of ['kind', 'reason', 'label', 'type', 'style']) {
74
+ if (typeof flags[key] === 'string') body[key] = flags[key];
75
+ }
76
+ if (flags['clear-kind']) body.kind = null;
77
+ if (flags['clear-reason']) body.reason = null;
78
+ output(await invokeOperation('edge.update', body));
79
+ },
80
+ );
81
+
54
82
  // ── edge list ────────────────────────────────────────────────
55
83
  cmd('edge list', 'List all edges on the canvas', ['pmx-canvas edge list'], async (args) => {
56
84
  const { flags } = parseFlags(args);
@@ -38,6 +38,13 @@ cmd(
38
38
  if (height !== undefined) body.height = height;
39
39
  if (typeof flags['child-layout'] === 'string') body.childLayout = flags['child-layout'];
40
40
  if (positional.length > 0) body.childIds = positional;
41
+ for (const [flag, field] of [
42
+ ['destination-board', 'destinationBoardId'],
43
+ ['request-id', 'requestId'],
44
+ ['expected-board', 'expectedBoardId'],
45
+ ] as const) {
46
+ if (typeof flags[flag] === 'string') body[field] = flags[flag];
47
+ }
41
48
 
42
49
  const result = await invokeOperation('group.create', body);
43
50
  output(result);
@@ -133,6 +133,7 @@ function summarizeNodeResult(node: Record<string, unknown>): Record<string, unkn
133
133
  ...(node.path !== undefined ? { path: node.path } : {}),
134
134
  ...(node.url !== undefined ? { url: node.url } : {}),
135
135
  ...(node.provenance !== undefined ? { provenance: node.provenance } : {}),
136
+ ...(node.qualification !== undefined ? { qualification: node.qualification } : {}),
136
137
  ...(typeof data.mode === 'string' ? { mode: data.mode } : {}),
137
138
  ...(typeof data.viewerType === 'string' ? { viewerType: data.viewerType } : {}),
138
139
  ...(typeof data.serverName === 'string' ? { serverName: data.serverName } : {}),
@@ -796,11 +797,12 @@ cmd(
796
797
  const id = positional[0];
797
798
  if (!id) die('Missing node ID', 'pmx-canvas node get <node-id>');
798
799
 
800
+ const requestedFields = collectRequestedFields(args, flags);
799
801
  const result = (await invokeOperation('node.get', {
800
802
  id,
801
803
  board: typeof flags.board === 'string' ? flags.board : undefined,
804
+ contextDelivery: !flags.summary && !flags.compact && requestedFields.length === 0,
802
805
  })) as Record<string, unknown>;
803
- const requestedFields = collectRequestedFields(args, flags);
804
806
  if (requestedFields.length > 0) {
805
807
  const picked = Object.fromEntries(requestedFields.map((field) => [field, resolveNodeFieldValue(result, field)]));
806
808
  const missing = requestedFields.filter((field) => picked[field] === undefined);
@@ -893,7 +895,7 @@ cmd(
893
895
  frameHeight !== undefined ||
894
896
  arrangeLocked !== undefined
895
897
  ) {
896
- const existing = (await invokeOperation('node.get', { id })) as {
898
+ const existing = (await invokeOperation('node.get', { id, contextDelivery: false })) as {
897
899
  position: { x: number; y: number };
898
900
  size: { width: number; height: number };
899
901
  data: Record<string, unknown>;
@@ -28,7 +28,7 @@ import {
28
28
  cmd(
29
29
  'search',
30
30
  'Search nodes by title or content',
31
- ['pmx-canvas search "design doc"', 'pmx-canvas search --query "TODO"'],
31
+ ['pmx-canvas search "design doc"', 'pmx-canvas search --query "TODO" --folder "Research" --board-id <id>'],
32
32
  async (args) => {
33
33
  const { positional, flags } = parseFlags(args);
34
34
  if (flags.help || flags.h) return showCommandHelp('search');
@@ -36,11 +36,43 @@ cmd(
36
36
  const query = positional[0] || (typeof flags.query === 'string' ? flags.query : '');
37
37
  if (!query) die('Missing search query', 'pmx-canvas search "query"');
38
38
 
39
- const result = await invokeOperation('search', { q: query, ...(flags.library ? { scope: 'library' } : {}) });
39
+ if (flags.scope !== undefined && flags.scope !== 'active' && flags.scope !== 'library')
40
+ die('--scope must be active or library.');
41
+ const result = await invokeOperation('search', {
42
+ q: query,
43
+ ...(flags.scope ? { scope: flags.scope } : flags.library ? { scope: 'library' } : {}),
44
+ ...(typeof flags['board-id'] === 'string' ? { boardId: flags['board-id'] } : {}),
45
+ ...(typeof flags.folder === 'string' ? { folder: flags.folder } : {}),
46
+ ...(typeof flags.limit === 'string' ? { limit: Number(flags.limit) } : {}),
47
+ });
40
48
  output(result);
41
49
  },
42
50
  );
43
51
 
52
+ cmd(
53
+ 'memory-graph',
54
+ 'Explore the board/card relation graph (summary-only)',
55
+ [
56
+ 'pmx-canvas memory-graph',
57
+ 'pmx-canvas memory-graph --focus pins --depth 2',
58
+ 'pmx-canvas memory-graph --projection boards',
59
+ ],
60
+ async (args) => {
61
+ const { flags } = parseFlags(args);
62
+ if (flags.help || flags.h) return showCommandHelp('memory-graph');
63
+ if (flags.depth !== undefined && (typeof flags.depth !== 'string' || !flags.depth.trim())) {
64
+ die('--depth requires an integer from 0 to 4.');
65
+ }
66
+ output(
67
+ await invokeOperation('memory-graph.get', {
68
+ ...(typeof flags.focus === 'string' ? { focus: flags.focus } : {}),
69
+ ...(flags.depth !== undefined ? { depth: Number(flags.depth) } : {}),
70
+ ...(flags.projection !== undefined ? { projection: flags.projection } : {}),
71
+ }),
72
+ );
73
+ },
74
+ );
75
+
44
76
  cmd(
45
77
  'context',
46
78
  'Read the budgeted cross-board context brief',
@@ -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,7 +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|rename|category Manage boards (the human opens them in the workbench)
321
+ board list|create|open|rename|folder|pin|unpin
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
320
325
  backup [status|schedule] Back up every board, now or on a schedule
321
326
  restore <backup-file> Replace every board with a backup
322
327
  export [--board id] [--out file] Export a board as one HTML file anyone can open
@@ -328,6 +333,7 @@ Agent CLI (works against running server):
328
333
  web-artifact build Build bundled web artifacts
329
334
  clear --yes Clear canvas
330
335
  code-graph File dependencies
336
+ memory-graph Explore connected boards and cards (summary-only)
331
337
  spatial Spatial analysis
332
338
  watch Semantic watch stream
333
339
  pump Reactive steering loop for CLI agents (Amp, Codex CLI)