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.
- package/.github/extensions/pmx-canvas/runtime.json +1 -1
- package/.github/extensions/pmx-canvas/tools.json +601 -22
- package/CHANGELOG.md +59 -0
- package/Readme.md +131 -532
- package/dist/canvas/global.css +2724 -476
- package/dist/canvas/index.js +168 -107
- package/dist/canvas/mcp-app.html +2899 -596
- 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/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/human-store.d.ts +2 -1
- 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 +52 -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 +449 -16
- package/docs/environment.md +0 -1
- package/docs/evals/curation-effect.md +128 -0
- package/docs/http-api.md +459 -37
- package/docs/mcp.md +146 -18
- 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 +216 -42
- package/skills/pmx-canvas/evals/evals.json +224 -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 +88 -11
- package/src/cli/commands/edges.ts +28 -0
- package/src/cli/commands/groups.ts +7 -0
- package/src/cli/commands/nodes.ts +1 -0
- package/src/cli/commands/query.ts +34 -2
- package/src/cli/commands/record.ts +199 -48
- package/src/cli/index.ts +6 -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 +543 -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 +6 -3
- package/src/client/nodes/FileNode.tsx +46 -42
- package/src/client/nodes/HtmlNode.tsx +32 -0
- 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/human-store.ts +13 -7
- 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 +235 -32
- package/src/client/state/workspace-settings.ts +11 -0
- package/src/client/theme/global.css +2724 -476
- 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.tsx +31 -0
- 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 +195 -83
- 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 +178 -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 +181 -11
- 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 +26 -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/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 +74 -25
- 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
|
@@ -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,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|
|
|
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)
|