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.
- package/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +601 -22
- package/CHANGELOG.md +66 -0
- package/Readme.md +131 -532
- package/dist/canvas/global.css +2759 -510
- package/dist/canvas/index.js +168 -107
- package/dist/canvas/mcp-app.html +2936 -632
- package/dist/json-render/index.css +81 -1
- package/dist/json-render/index.js +20 -20
- package/dist/types/client/canvas/BarHint.d.ts +12 -2
- package/dist/types/client/canvas/BoardLinkDialog.d.ts +3 -1
- package/dist/types/client/canvas/BoardPinButton.d.ts +10 -0
- package/dist/types/client/canvas/BoardSwitcher.d.ts +1 -1
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandBar.d.ts +3 -4
- package/dist/types/client/canvas/ConnectionBanner.d.ts +2 -1
- package/dist/types/client/canvas/ContextChip.d.ts +3 -0
- package/dist/types/client/canvas/CrossBoardReferences.d.ts +5 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +14 -3
- package/dist/types/client/canvas/EmptyState.d.ts +4 -4
- package/dist/types/client/canvas/HomeGraphView.d.ts +2 -0
- package/dist/types/client/canvas/HomeMapView.d.ts +1 -0
- package/dist/types/client/canvas/HomeReviewView.d.ts +6 -0
- package/dist/types/client/canvas/HomeView.d.ts +9 -7
- package/dist/types/client/canvas/KnowledgeDialog.d.ts +4 -0
- package/dist/types/client/canvas/LifecycleChrome.d.ts +5 -0
- package/dist/types/client/canvas/LifecycleFields.d.ts +7 -0
- package/dist/types/client/canvas/LifecyclePanel.d.ts +1 -0
- package/dist/types/client/canvas/MemoryInspector.d.ts +7 -0
- package/dist/types/client/canvas/NearPinLayer.d.ts +14 -0
- package/dist/types/client/canvas/NodeContextMark.d.ts +40 -7
- package/dist/types/client/canvas/RelationPresetsDialog.d.ts +4 -0
- package/dist/types/client/canvas/RelationsPanel.d.ts +4 -0
- package/dist/types/client/canvas/SessionPanel.d.ts +2 -0
- package/dist/types/client/canvas/canvas-area.d.ts +1 -1
- package/dist/types/client/canvas/graph-geometry.d.ts +27 -0
- package/dist/types/client/canvas/graph-model.d.ts +37 -0
- package/dist/types/client/canvas/memory-layout.d.ts +23 -0
- package/dist/types/client/canvas/use-chrome-height.d.ts +2 -0
- package/dist/types/client/canvas/use-focus-trap.d.ts +1 -0
- package/dist/types/client/canvas/use-memory-graph.d.ts +8 -0
- package/dist/types/client/canvas/use-pan-zoom.d.ts +4 -1
- package/dist/types/client/icons.d.ts +0 -10
- package/dist/types/client/nodes/AxStepControls.d.ts +1 -1
- package/dist/types/client/nodes/HtmlNode.d.ts +1 -0
- package/dist/types/client/nodes/InlineMarkdownEditor.d.ts +4 -4
- package/dist/types/client/nodes/MarkdownNode.d.ts +5 -1
- package/dist/types/client/nodes/McpAppNode.d.ts +4 -4
- package/dist/types/client/nodes/viewer-runtime-state.d.ts +1 -1
- package/dist/types/client/state/boards-store.d.ts +81 -9
- package/dist/types/client/state/canvas-store.d.ts +14 -1
- package/dist/types/client/state/context-chip-store.d.ts +22 -0
- package/dist/types/client/state/context-status-store.d.ts +35 -0
- package/dist/types/client/state/intent-bridge.d.ts +11 -8
- package/dist/types/client/state/knowledge-store.d.ts +22 -0
- package/dist/types/client/state/near-pin-store.d.ts +44 -1
- package/dist/types/client/state/presence-store.d.ts +4 -4
- package/dist/types/client/state/presentation.d.ts +28 -0
- package/dist/types/client/state/relation-store.d.ts +10 -0
- package/dist/types/client/state/session-store.d.ts +41 -2
- package/dist/types/client/state/workspace-settings.d.ts +7 -0
- package/dist/types/client/types.d.ts +13 -1
- package/dist/types/client/utils/keyboard.d.ts +2 -0
- package/dist/types/client/utils/time-ago.d.ts +4 -0
- package/dist/types/mcp/context-uri-template.d.ts +3 -1
- package/dist/types/server/agent-presence.d.ts +10 -3
- package/dist/types/server/ax-context.d.ts +1 -1
- package/dist/types/server/canvas-db.d.ts +30 -7
- package/dist/types/server/canvas-operations.d.ts +24 -2
- package/dist/types/server/canvas-provenance.d.ts +22 -2
- package/dist/types/server/canvas-serialization.d.ts +2 -0
- package/dist/types/server/canvas-state.d.ts +93 -15
- package/dist/types/server/canvas-validation.d.ts +1 -1
- package/dist/types/server/context-brief.d.ts +94 -6
- package/dist/types/server/context-reads.d.ts +33 -7
- package/dist/types/server/edit-summary.d.ts +20 -0
- package/dist/types/server/index.d.ts +71 -10
- package/dist/types/server/knowledge-lifecycle.d.ts +221 -0
- package/dist/types/server/lifecycle-status.d.ts +10 -0
- package/dist/types/server/memory-graph.d.ts +9 -0
- package/dist/types/server/mutation-history.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +1 -1
- package/dist/types/server/operations/ops/boards.d.ts +2 -1
- package/dist/types/server/operations/ops/lifecycle.d.ts +2 -0
- package/dist/types/server/operations/ops/memory-graph.d.ts +2 -0
- package/dist/types/server/operations/ops/nodes.d.ts +5 -0
- package/dist/types/server/operations/ops/query.d.ts +23 -0
- package/dist/types/server/operations/registry.d.ts +1 -0
- package/dist/types/server/placement.d.ts +7 -0
- package/dist/types/server/scope-fence.d.ts +2 -1
- package/dist/types/server/spatial-analysis.d.ts +6 -3
- package/dist/types/server/tour-control.d.ts +32 -0
- package/dist/types/server/wiki-links.d.ts +14 -0
- package/dist/types/server/workbench-auth.d.ts +2 -0
- package/dist/types/shared/agent-context.d.ts +2 -0
- package/dist/types/shared/agent-presence.d.ts +15 -12
- package/dist/types/shared/auto-arrange.d.ts +1 -0
- package/dist/types/shared/boards.d.ts +4 -4
- package/dist/types/shared/knowledge-lifecycle.d.ts +41 -0
- package/dist/types/shared/knowledge-presets.d.ts +6 -0
- package/dist/types/shared/lifecycle-status.d.ts +24 -0
- package/dist/types/shared/memory-graph.d.ts +53 -0
- package/dist/types/shared/relations.d.ts +68 -0
- package/dist/types/shared/semantic-attention.d.ts +2 -0
- package/dist/types/shared/tour.d.ts +38 -0
- package/dist/types/shared/workspace-settings.d.ts +11 -0
- package/docs/RELEASE.md +21 -3
- package/docs/adr-001-bun-only-runtime.md +7 -1
- package/docs/adr-002-shared-product-separate-runtimes.md +93 -0
- package/docs/api-reference.md +109 -0
- package/docs/ax-host-adapter-contract.md +1 -1
- package/docs/ax-state-contract.md +5 -4
- package/docs/cli.md +153 -15
- package/docs/demos/pmx-canvas-demo.mp4 +0 -0
- package/docs/design/AgentContext.dc.html +3 -3
- package/docs/design/BoardPins.dc.html +37 -22
- package/docs/design/Categories.dc.html +208 -0
- package/docs/design/CategoryEditor.dc.html +33 -0
- package/docs/design/Chrome.dc.html +55 -9
- package/docs/design/ContextChip.dc.html +224 -0
- package/docs/design/ContextChip600.dc.html +136 -0
- package/docs/design/EmptyState.dc.html +45 -0
- package/docs/design/Flows.dc.html +23 -1
- package/docs/design/HeaderMarks.dc.html +183 -0
- package/docs/design/Home.dc.html +19 -1
- package/docs/design/HomeGraph.dc.html +86 -202
- package/docs/design/HomeMap.dc.html +23 -1
- package/docs/design/HomeTeam.dc.html +32 -14
- package/docs/design/Lifecycle.dc.html +22 -0
- package/docs/design/Lifecycle600.dc.html +18 -0
- package/docs/design/LifecycleVariants.dc.html +157 -0
- package/docs/design/LinkAuthorship.dc.html +139 -0
- package/docs/design/LinksChanged.dc.html +203 -0
- package/docs/design/LinksOptions.dc.html +137 -0
- package/docs/design/LinksOptions600.dc.html +134 -0
- package/docs/design/NearPin.dc.html +9 -10
- package/docs/design/Pane600.dc.html +4 -3
- package/docs/design/Relations.dc.html +3 -2
- package/docs/design/Restore.dc.html +152 -0
- package/docs/design/SeeChange.dc.html +267 -0
- package/docs/design/Uses.dc.html +12 -6
- package/docs/design/canvas.json +146 -19
- package/docs/design.md +454 -18
- package/docs/environment.md +0 -1
- package/docs/evals/curation-effect.md +128 -0
- package/docs/http-api.md +465 -38
- package/docs/mcp.md +150 -19
- package/docs/node-types.md +58 -3
- package/docs/plans/design-gaps-2026-10-08.md +53 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +379 -0
- package/docs/plans/plan-016-context-made-visible-wave-1.md +150 -5
- package/docs/plans/plan-017-knowledge-categories-and-lifecycle.md +175 -0
- package/docs/plans/plan-018-connected-memory-wave-2.md +594 -0
- package/docs/plans/plan-019-post-0.9-roadmap.md +198 -0
- package/docs/product-context-vision-2026-09.md +17 -1
- package/docs/product-vision-2026-09.md +1082 -43
- package/docs/product-vision-review-2026-09.md +13 -1
- package/docs/screenshots/workbench-live-dark.png +0 -0
- package/docs/screenshots/workbench-live-light.png +0 -0
- package/docs/sdk.md +62 -2
- package/package.json +1 -1
- package/plugins/pmx-canvas/com.github.copilot/extensions/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/plugin.json +1 -1
- package/plugins/pmx-canvas/runtime.json +1 -1
- package/plugins/pmx-canvas/tools.json +601 -22
- package/skills/pmx-canvas/SKILL.md +234 -43
- package/skills/pmx-canvas/evals/evals.json +62 -0
- package/skills/pmx-canvas/references/ax-html-control-surface.md +6 -0
- package/skills/pmx-canvas/references/full-reference.md +120 -21
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +2 -2
- package/skills/pmx-canvas/references/importing-context.md +132 -0
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +6 -6
- package/skills/pmx-canvas/references/tours-and-recording.md +16 -1
- package/skills/pmx-canvas-testing/SKILL.md +20 -0
- package/src/cli/agent.ts +2 -0
- package/src/cli/commands/ax.ts +12 -0
- package/src/cli/commands/boards.ts +87 -11
- package/src/cli/commands/edges.ts +28 -0
- package/src/cli/commands/groups.ts +7 -0
- package/src/cli/commands/nodes.ts +4 -2
- package/src/cli/commands/query.ts +34 -2
- package/src/cli/commands/record.ts +199 -48
- package/src/cli/index.ts +7 -1
- package/src/client/App.tsx +177 -70
- package/src/client/canvas/AttentionHistory.tsx +0 -2
- package/src/client/canvas/BarHint.tsx +40 -2
- package/src/client/canvas/BoardCopyDialog.tsx +1 -1
- package/src/client/canvas/BoardFolderDialog.tsx +7 -7
- package/src/client/canvas/BoardLinkDialog.tsx +138 -33
- package/src/client/canvas/BoardPinButton.tsx +33 -0
- package/src/client/canvas/BoardSwitcher.tsx +26 -40
- package/src/client/canvas/CanvasNode.tsx +360 -237
- package/src/client/canvas/CanvasViewport.tsx +45 -7
- package/src/client/canvas/CommandBar.tsx +29 -23
- package/src/client/canvas/CommandPalette.tsx +5 -12
- package/src/client/canvas/ConnectionBanner.tsx +3 -3
- package/src/client/canvas/ContextChip.tsx +296 -0
- package/src/client/canvas/ContextMenu.tsx +63 -5
- package/src/client/canvas/CrossBoardReferences.tsx +60 -0
- package/src/client/canvas/EdgeLayer.tsx +339 -76
- package/src/client/canvas/EmptyState.tsx +33 -18
- package/src/client/canvas/ExpandedNodeOverlay.tsx +72 -40
- package/src/client/canvas/ExternalWriters.tsx +57 -42
- package/src/client/canvas/HomeGraphView.tsx +942 -0
- package/src/client/canvas/HomeMapView.tsx +641 -0
- package/src/client/canvas/HomeReviewView.tsx +167 -0
- package/src/client/canvas/HomeView.tsx +687 -160
- package/src/client/canvas/IntentLayer.tsx +19 -12
- package/src/client/canvas/KnowledgeDialog.tsx +488 -0
- package/src/client/canvas/LifecycleChrome.tsx +133 -0
- package/src/client/canvas/LifecycleFields.tsx +91 -0
- package/src/client/canvas/LifecyclePanel.tsx +533 -0
- package/src/client/canvas/MemoryInspector.tsx +104 -0
- package/src/client/canvas/Minimap.tsx +3 -3
- package/src/client/canvas/NearPinLayer.tsx +187 -0
- package/src/client/canvas/NodeContextMark.tsx +175 -97
- package/src/client/canvas/Presentation.tsx +23 -57
- package/src/client/canvas/RelationPresetsDialog.tsx +413 -0
- package/src/client/canvas/RelationsPanel.tsx +82 -0
- package/src/client/canvas/SelectionBar.tsx +3 -6
- package/src/client/canvas/SessionPanel.tsx +90 -50
- package/src/client/canvas/SessionReceipt.tsx +311 -39
- package/src/client/canvas/ShortcutOverlay.tsx +1 -1
- package/src/client/canvas/SnapshotPanel.tsx +27 -36
- package/src/client/canvas/ToolRail.tsx +367 -192
- package/src/client/canvas/TopBar.tsx +354 -124
- package/src/client/canvas/canvas-area.ts +10 -3
- package/src/client/canvas/graph-geometry.ts +195 -0
- package/src/client/canvas/graph-model.ts +254 -0
- package/src/client/canvas/memory-layout.ts +106 -0
- package/src/client/canvas/use-chrome-height.ts +20 -0
- package/src/client/canvas/use-focus-trap.ts +31 -5
- package/src/client/canvas/use-memory-graph.ts +58 -0
- package/src/client/canvas/use-node-drag.ts +2 -1
- package/src/client/canvas/use-pan-zoom.ts +64 -7
- package/src/client/icons.tsx +0 -63
- package/src/client/mcp-app/style.css +0 -6
- package/src/client/nodes/AxStepControls.tsx +74 -50
- package/src/client/nodes/BoardNode.tsx +5 -1
- package/src/client/nodes/ContextNode.tsx +18 -11
- package/src/client/nodes/ExtAppFrame.tsx +0 -1
- package/src/client/nodes/FileNode.tsx +46 -42
- package/src/client/nodes/HtmlNode.tsx +44 -2
- package/src/client/nodes/ImageNode.tsx +1 -1
- package/src/client/nodes/InlineFormatBar.tsx +6 -9
- package/src/client/nodes/InlineMarkdownEditor.tsx +56 -17
- package/src/client/nodes/MarkdownNode.tsx +377 -109
- package/src/client/nodes/McpAppNode.tsx +50 -20
- package/src/client/nodes/MdFormatBar.tsx +21 -18
- package/src/client/nodes/PromptNode.tsx +1 -1
- package/src/client/nodes/ResponseNode.tsx +1 -1
- package/src/client/nodes/viewer-runtime-state.ts +54 -22
- package/src/client/state/attention-bridge.ts +64 -15
- package/src/client/state/boards-store.ts +193 -39
- package/src/client/state/canvas-store.ts +99 -12
- package/src/client/state/context-chip-store.ts +70 -0
- package/src/client/state/context-status-store.ts +53 -0
- package/src/client/state/intent-bridge.ts +51 -30
- package/src/client/state/knowledge-store.ts +43 -0
- package/src/client/state/near-pin-store.ts +115 -6
- package/src/client/state/presence-store.ts +7 -10
- package/src/client/state/presentation.ts +91 -0
- package/src/client/state/relation-store.ts +81 -0
- package/src/client/state/session-store.ts +144 -18
- package/src/client/state/sse-bridge.ts +206 -29
- package/src/client/state/workspace-settings.ts +11 -0
- package/src/client/theme/global.css +2759 -510
- package/src/client/types.ts +12 -0
- package/src/client/utils/keyboard.ts +10 -0
- package/src/client/utils/time-ago.ts +14 -0
- package/src/json-render/renderer/index.css +6 -1
- package/src/json-render/renderer/index.tsx +31 -1
- package/src/mcp/canvas-app.ts +12 -2
- package/src/mcp/context-uri-template.ts +7 -4
- package/src/mcp/server.ts +63 -41
- package/src/mcp/workbench-app.ts +39 -6
- package/src/server/agent-presence.ts +128 -28
- package/src/server/ax-context.ts +27 -13
- package/src/server/board-export.ts +12 -1
- package/src/server/canvas-db.ts +331 -42
- package/src/server/canvas-operations.ts +196 -84
- package/src/server/canvas-provenance.ts +53 -22
- package/src/server/canvas-serialization.ts +2 -0
- package/src/server/canvas-state.ts +950 -122
- package/src/server/canvas-validation.ts +9 -2
- package/src/server/context-brief.ts +345 -38
- package/src/server/context-reads.ts +179 -30
- package/src/server/demo-state.json +355 -24
- package/src/server/edit-summary.ts +74 -0
- package/src/server/html-surface.ts +8 -1
- package/src/server/index.ts +203 -13
- package/src/server/knowledge-lifecycle.ts +304 -0
- package/src/server/lifecycle-status.ts +232 -0
- package/src/server/memory-graph.ts +166 -0
- package/src/server/mutation-history.ts +18 -6
- package/src/server/operations/composites.ts +32 -11
- package/src/server/operations/http.ts +7 -2
- package/src/server/operations/index.ts +4 -0
- package/src/server/operations/mcp.ts +32 -1
- package/src/server/operations/ops/ax-presence.ts +1 -1
- package/src/server/operations/ops/ax-read.ts +57 -6
- package/src/server/operations/ops/ax-timeline.ts +28 -2
- package/src/server/operations/ops/backup.ts +5 -3
- package/src/server/operations/ops/batch.ts +35 -4
- package/src/server/operations/ops/boards.ts +188 -47
- package/src/server/operations/ops/canvas-wire.ts +31 -1
- package/src/server/operations/ops/edges.ts +36 -2
- package/src/server/operations/ops/groups.ts +6 -1
- package/src/server/operations/ops/json-render.ts +5 -1
- package/src/server/operations/ops/lifecycle.ts +262 -0
- package/src/server/operations/ops/memory-graph.ts +45 -0
- package/src/server/operations/ops/nodes.ts +107 -24
- package/src/server/operations/ops/query.ts +281 -68
- package/src/server/operations/ops/snapshots.ts +73 -1
- package/src/server/operations/ops/validate.ts +5 -1
- package/src/server/operations/ops/viewport.ts +97 -3
- package/src/server/operations/registry.ts +77 -26
- package/src/server/placement.ts +29 -0
- package/src/server/scope-fence.ts +78 -6
- package/src/server/server.ts +115 -16
- package/src/server/spatial-analysis.ts +48 -23
- package/src/server/tour-control.ts +82 -0
- package/src/server/wiki-links.ts +137 -0
- package/src/server/workbench-auth.ts +11 -0
- package/src/shared/agent-context.ts +3 -0
- package/src/shared/agent-presence.ts +15 -15
- package/src/shared/auto-arrange.ts +2 -0
- package/src/shared/boards.ts +4 -4
- package/src/shared/knowledge-lifecycle.ts +22 -0
- package/src/shared/knowledge-presets.ts +80 -0
- package/src/shared/lifecycle-status.ts +26 -0
- package/src/shared/memory-graph.ts +74 -0
- package/src/shared/relations.ts +98 -0
- package/src/shared/semantic-attention.ts +15 -1
- package/src/shared/tour.ts +23 -5
- package/src/shared/workspace-settings.ts +13 -0
- package/dist/types/client/canvas/ContextPinBar.d.ts +0 -1
- package/dist/types/client/canvas/FocusFieldLayer.d.ts +0 -1
- package/dist/types/client/canvas/kind-colors.d.ts +0 -3
- package/dist/types/server/agent-context.d.ts +0 -22
- 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
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
128
|
-
await
|
|
129
|
-
await
|
|
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
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
|
246
|
+
await Bun.write(frameName(count++), await timed(view.screenshot({ format: 'png' }), 'capturing a frame'));
|
|
153
247
|
}
|
|
154
248
|
} else {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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;
|
|
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;
|
|
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|
|
|
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)
|