pmx-canvas 0.3.3 → 0.3.4
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/CHANGELOG.md +46 -0
- package/Readme.md +2 -1
- package/dist/canvas/index.js +83 -83
- package/dist/json-render/index.js +47 -47
- package/dist/types/cli/agent.d.ts +13 -8
- package/dist/types/cli/commands/apps.d.ts +1 -0
- package/dist/types/cli/commands/ax.d.ts +1 -0
- package/dist/types/cli/commands/copilot.d.ts +1 -0
- package/dist/types/cli/commands/edges.d.ts +1 -0
- package/dist/types/cli/commands/groups.d.ts +1 -0
- package/dist/types/cli/commands/history.d.ts +1 -0
- package/dist/types/cli/commands/nodes.d.ts +1 -0
- package/dist/types/cli/commands/pins.d.ts +1 -0
- package/dist/types/cli/commands/query.d.ts +1 -0
- package/dist/types/cli/commands/view.d.ts +1 -0
- package/dist/types/cli/commands/webview.d.ts +1 -0
- package/dist/types/cli/shared.d.ts +73 -0
- package/dist/types/client/canvas/ExpandedNodeOverlay.d.ts +1 -1
- package/dist/types/client/nodes/HtmlNode.d.ts +1 -1
- package/dist/types/client/nodes/McpAppNode.d.ts +1 -1
- package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +20 -0
- package/dist/types/server/canvas-operations.d.ts +2 -0
- package/dist/types/server/diagram-presets.d.ts +8 -0
- package/dist/types/server/operations/composites.d.ts +0 -9
- package/dist/types/shared/ax-surface-protocol.d.ts +18 -0
- package/docs/bun-webview-integration.md +5 -0
- package/docs/environment.md +48 -0
- package/docs/node-types.md +5 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +27 -9
- package/docs/screenshot.png +0 -0
- package/package.json +8 -5
- package/skills/pmx-canvas/evals/evals.json +5 -5
- package/skills/pmx-canvas/references/full-reference.md +8 -1
- package/src/cli/agent.ts +22 -3512
- package/src/cli/commands/apps.ts +165 -0
- package/src/cli/commands/ax.ts +604 -0
- package/src/cli/commands/copilot.ts +43 -0
- package/src/cli/commands/edges.ts +73 -0
- package/src/cli/commands/groups.ts +81 -0
- package/src/cli/commands/history.ts +220 -0
- package/src/cli/commands/nodes.ts +954 -0
- package/src/cli/commands/pins.ts +42 -0
- package/src/cli/commands/query.ts +223 -0
- package/src/cli/commands/view.ts +186 -0
- package/src/cli/commands/webview.ts +227 -0
- package/src/cli/shared.ts +860 -0
- package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -2
- package/src/client/nodes/ExtAppFrame.tsx +53 -49
- package/src/client/nodes/HtmlNode.tsx +8 -51
- package/src/client/nodes/McpAppNode.tsx +12 -47
- package/src/client/nodes/use-ax-surface-bridge.ts +71 -0
- package/src/json-render/renderer/index.tsx +3 -2
- package/src/mcp/server.ts +0 -47
- package/src/server/canvas-operations.ts +8 -0
- package/src/server/diagram-presets.ts +15 -1
- package/src/server/html-surface.ts +11 -6
- package/src/server/operations/composites.ts +1 -13
- package/src/server/operations/invoker.ts +10 -3
- package/src/server/operations/mcp.ts +51 -1
- package/src/server/operations/ops/app.ts +5 -0
- package/src/server/server.ts +2 -0
- package/src/shared/ax-surface-protocol.ts +22 -0
- package/dist/types/client/canvas/ContextPinHud.d.ts +0 -1
- package/src/client/canvas/ContextPinHud.tsx +0 -17
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Edge commands: edge add|list|remove.
|
|
2
|
+
|
|
3
|
+
import { cmd, die, invokeOperation, output, parseFlags, showCommandHelp } from '../shared.js';
|
|
4
|
+
|
|
5
|
+
// ── edge add ─────────────────────────────────────────────────
|
|
6
|
+
cmd(
|
|
7
|
+
'edge add',
|
|
8
|
+
'Add an edge between two nodes',
|
|
9
|
+
[
|
|
10
|
+
'pmx-canvas edge add --from <node-id> --to <node-id> --type flow',
|
|
11
|
+
'pmx-canvas edge add --from-search "DVT O3 — GitOps" --to-search "deep work trend" --type relation',
|
|
12
|
+
'pmx-canvas edge add --from n1 --to n2 --type depends-on --label "imports"',
|
|
13
|
+
'pmx-canvas edge add --from n1 --to n2 --type references --style dashed --animated',
|
|
14
|
+
],
|
|
15
|
+
async (args) => {
|
|
16
|
+
const { flags } = parseFlags(args);
|
|
17
|
+
if (flags.help || flags.h) return showCommandHelp('edge add');
|
|
18
|
+
|
|
19
|
+
const type = (flags.type as string) || 'flow';
|
|
20
|
+
const from = typeof flags.from === 'string' ? flags.from : undefined;
|
|
21
|
+
const to = typeof flags.to === 'string' ? flags.to : undefined;
|
|
22
|
+
const fromSearch = typeof flags['from-search'] === 'string' ? flags['from-search'] : undefined;
|
|
23
|
+
const toSearch = typeof flags['to-search'] === 'string' ? flags['to-search'] : undefined;
|
|
24
|
+
|
|
25
|
+
if (!from && !fromSearch) {
|
|
26
|
+
die(
|
|
27
|
+
'Missing source selector',
|
|
28
|
+
'Use --from <id> or --from-search "query". Search queries must resolve to exactly one node. Example: pmx-canvas edge add --from-search "DVT O3 — GitOps" --to-search "deep work trend" --type relation',
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (!to && !toSearch) {
|
|
32
|
+
die(
|
|
33
|
+
'Missing target selector',
|
|
34
|
+
'Use --to <id> or --to-search "query". Search queries must resolve to exactly one node. Example: pmx-canvas edge add --from-search "DVT O3 — GitOps" --to-search "deep work trend" --type relation',
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const body: Record<string, unknown> = {
|
|
39
|
+
type,
|
|
40
|
+
...(from ? { from } : {}),
|
|
41
|
+
...(to ? { to } : {}),
|
|
42
|
+
...(fromSearch ? { fromSearch } : {}),
|
|
43
|
+
...(toSearch ? { toSearch } : {}),
|
|
44
|
+
};
|
|
45
|
+
if (flags.label && flags.label !== true) body.label = flags.label;
|
|
46
|
+
if (typeof flags.style === 'string') body.style = flags.style;
|
|
47
|
+
if (flags.animated) body.animated = true;
|
|
48
|
+
|
|
49
|
+
const result = await invokeOperation('edge.add', body);
|
|
50
|
+
output(result);
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// ── edge list ────────────────────────────────────────────────
|
|
55
|
+
cmd('edge list', 'List all edges on the canvas', ['pmx-canvas edge list'], async (args) => {
|
|
56
|
+
const { flags } = parseFlags(args);
|
|
57
|
+
if (flags.help || flags.h) return showCommandHelp('edge list');
|
|
58
|
+
|
|
59
|
+
const layout = (await invokeOperation('layout.get', {})) as { edges: unknown[] };
|
|
60
|
+
output(layout.edges);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// ── edge remove ──────────────────────────────────────────────
|
|
64
|
+
cmd('edge remove', 'Remove an edge by ID', ['pmx-canvas edge remove <edge-id>'], async (args) => {
|
|
65
|
+
const { positional, flags } = parseFlags(args);
|
|
66
|
+
if (flags.help || flags.h) return showCommandHelp('edge remove');
|
|
67
|
+
|
|
68
|
+
const id = positional[0];
|
|
69
|
+
if (!id) die('Missing edge ID', 'pmx-canvas edge remove <edge-id>');
|
|
70
|
+
|
|
71
|
+
const result = await invokeOperation('edge.remove', { edge_id: id });
|
|
72
|
+
output(result);
|
|
73
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Group commands: group create|add|remove.
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
cmd,
|
|
5
|
+
die,
|
|
6
|
+
invokeOperation,
|
|
7
|
+
optionalFiniteFlag,
|
|
8
|
+
optionalPositiveFiniteFlag,
|
|
9
|
+
output,
|
|
10
|
+
parseFlags,
|
|
11
|
+
requireFlag,
|
|
12
|
+
showCommandHelp,
|
|
13
|
+
} from '../shared.js';
|
|
14
|
+
|
|
15
|
+
// ── group create ─────────────────────────────────────────────
|
|
16
|
+
cmd(
|
|
17
|
+
'group create',
|
|
18
|
+
'Create a group node',
|
|
19
|
+
[
|
|
20
|
+
'pmx-canvas group create --title "API Layer" node1 node2',
|
|
21
|
+
'pmx-canvas group create --title "Quarterly board" --x 40 --y 60 --width 1600 --height 900 --child-layout column node1 node2',
|
|
22
|
+
'pmx-canvas group create --title "Frontend" --color "#ff6b6b"',
|
|
23
|
+
],
|
|
24
|
+
async (args) => {
|
|
25
|
+
const { positional, flags } = parseFlags(args);
|
|
26
|
+
if (flags.help || flags.h) return showCommandHelp('group create');
|
|
27
|
+
|
|
28
|
+
const body: Record<string, unknown> = {};
|
|
29
|
+
if (flags.title && flags.title !== true) body.title = flags.title;
|
|
30
|
+
if (flags.color && flags.color !== true) body.color = flags.color;
|
|
31
|
+
const x = optionalFiniteFlag(flags, 'x', 'Use a finite number, e.g. --x 40');
|
|
32
|
+
const y = optionalFiniteFlag(flags, 'y', 'Use a finite number, e.g. --y 60');
|
|
33
|
+
const width = optionalPositiveFiniteFlag(flags, 'width', 'Use a positive number, e.g. --width 1600');
|
|
34
|
+
const height = optionalPositiveFiniteFlag(flags, 'height', 'Use a positive number, e.g. --height 900');
|
|
35
|
+
if (x !== undefined) body.x = x;
|
|
36
|
+
if (y !== undefined) body.y = y;
|
|
37
|
+
if (width !== undefined) body.width = width;
|
|
38
|
+
if (height !== undefined) body.height = height;
|
|
39
|
+
if (typeof flags['child-layout'] === 'string') body.childLayout = flags['child-layout'];
|
|
40
|
+
if (positional.length > 0) body.childIds = positional;
|
|
41
|
+
|
|
42
|
+
const result = await invokeOperation('group.create', body);
|
|
43
|
+
output(result);
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// ── group add ────────────────────────────────────────────────
|
|
48
|
+
cmd(
|
|
49
|
+
'group add',
|
|
50
|
+
'Add nodes to an existing group',
|
|
51
|
+
[
|
|
52
|
+
'pmx-canvas group add --group <group-id> node1 node2',
|
|
53
|
+
'pmx-canvas group add --group <group-id> --child-layout flow node1 node2',
|
|
54
|
+
],
|
|
55
|
+
async (args) => {
|
|
56
|
+
const { positional, flags } = parseFlags(args);
|
|
57
|
+
if (flags.help || flags.h) return showCommandHelp('group add');
|
|
58
|
+
|
|
59
|
+
const groupId = requireFlag(flags, 'group', 'pmx-canvas group add --group <group-id> node1 node2');
|
|
60
|
+
if (positional.length === 0) die('No node IDs provided', 'pmx-canvas group add --group <group-id> node1 node2');
|
|
61
|
+
|
|
62
|
+
const result = await invokeOperation('group.add', {
|
|
63
|
+
groupId,
|
|
64
|
+
childIds: positional,
|
|
65
|
+
...(typeof flags['child-layout'] === 'string' ? { childLayout: flags['child-layout'] } : {}),
|
|
66
|
+
});
|
|
67
|
+
output(result);
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// ── group remove ─────────────────────────────────────────────
|
|
72
|
+
cmd('group remove', 'Ungroup all children from a group', ['pmx-canvas group remove <group-id>'], async (args) => {
|
|
73
|
+
const { positional, flags } = parseFlags(args);
|
|
74
|
+
if (flags.help || flags.h) return showCommandHelp('group remove');
|
|
75
|
+
|
|
76
|
+
const id = positional[0];
|
|
77
|
+
if (!id) die('Missing group ID', 'pmx-canvas group remove <group-id>');
|
|
78
|
+
|
|
79
|
+
const result = await invokeOperation('group.remove', { groupId: id });
|
|
80
|
+
output(result);
|
|
81
|
+
});
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// History commands: undo, redo, history, snapshot save|list|gc|restore|delete,
|
|
2
|
+
// snapshot diff, and diff.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
cmd,
|
|
6
|
+
die,
|
|
7
|
+
getStringFlag,
|
|
8
|
+
invokeOperation,
|
|
9
|
+
isRecord,
|
|
10
|
+
optionalNumberFlag,
|
|
11
|
+
output,
|
|
12
|
+
parseFlags,
|
|
13
|
+
requireFlag,
|
|
14
|
+
showCommandHelp,
|
|
15
|
+
} from '../shared.js';
|
|
16
|
+
|
|
17
|
+
function summarizeHistoryResult(result: Record<string, unknown>): Record<string, unknown> {
|
|
18
|
+
const entries = Array.isArray(result.entries) ? result.entries.filter(isRecord) : [];
|
|
19
|
+
const countsByOperation: Record<string, number> = {};
|
|
20
|
+
let currentIndex = 0;
|
|
21
|
+
|
|
22
|
+
entries.forEach((entry, index) => {
|
|
23
|
+
const op = typeof entry.operationType === 'string' ? entry.operationType : 'unknown';
|
|
24
|
+
countsByOperation[op] = (countsByOperation[op] ?? 0) + 1;
|
|
25
|
+
if (entry.isCurrent === true) currentIndex = index + 1;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const recent = entries.slice(-10).map((entry, index) => ({
|
|
29
|
+
index: entries.length - Math.min(entries.length, 10) + index + 1,
|
|
30
|
+
operationType: entry.operationType,
|
|
31
|
+
description: entry.description,
|
|
32
|
+
status: entry.isCurrent === true ? 'current' : entry.isUndone === true ? 'undone' : 'applied',
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
totalMutations: entries.length,
|
|
37
|
+
currentIndex,
|
|
38
|
+
canUndo: result.canUndo === true,
|
|
39
|
+
canRedo: result.canRedo === true,
|
|
40
|
+
countsByOperation,
|
|
41
|
+
recent,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function compactHistoryResult(result: Record<string, unknown>): Record<string, unknown> {
|
|
46
|
+
const entries = Array.isArray(result.entries) ? result.entries.filter(isRecord) : [];
|
|
47
|
+
return {
|
|
48
|
+
totalMutations: entries.length,
|
|
49
|
+
canUndo: result.canUndo === true,
|
|
50
|
+
canRedo: result.canRedo === true,
|
|
51
|
+
entries: entries.slice(-20).map((entry, index) => ({
|
|
52
|
+
index: entries.length - Math.min(entries.length, 20) + index + 1,
|
|
53
|
+
operationType: entry.operationType,
|
|
54
|
+
description: entry.description,
|
|
55
|
+
status: entry.isCurrent === true ? 'current' : entry.isUndone === true ? 'undone' : 'applied',
|
|
56
|
+
})),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── undo ─────────────────────────────────────────────────────
|
|
61
|
+
cmd('undo', 'Undo the last canvas mutation', ['pmx-canvas undo'], async (args) => {
|
|
62
|
+
const { flags } = parseFlags(args);
|
|
63
|
+
if (flags.help || flags.h) return showCommandHelp('undo');
|
|
64
|
+
|
|
65
|
+
const result = await invokeOperation('canvas.undo', {});
|
|
66
|
+
output(result);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// ── redo ─────────────────────────────────────────────────────
|
|
70
|
+
cmd('redo', 'Redo the last undone mutation', ['pmx-canvas redo'], async (args) => {
|
|
71
|
+
const { flags } = parseFlags(args);
|
|
72
|
+
if (flags.help || flags.h) return showCommandHelp('redo');
|
|
73
|
+
|
|
74
|
+
const result = await invokeOperation('canvas.redo', {});
|
|
75
|
+
output(result);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// ── history ──────────────────────────────────────────────────
|
|
79
|
+
cmd(
|
|
80
|
+
'history',
|
|
81
|
+
'Show canvas mutation history',
|
|
82
|
+
['pmx-canvas history', 'pmx-canvas history --summary', 'pmx-canvas history --compact'],
|
|
83
|
+
async (args) => {
|
|
84
|
+
const { flags } = parseFlags(args);
|
|
85
|
+
if (flags.help || flags.h) return showCommandHelp('history');
|
|
86
|
+
|
|
87
|
+
const result = (await invokeOperation('history.get', {})) as Record<string, unknown>;
|
|
88
|
+
if (flags.summary) {
|
|
89
|
+
output(summarizeHistoryResult(result));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (flags.compact) {
|
|
93
|
+
output(compactHistoryResult(result));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
output(result);
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// ── snapshot save ────────────────────────────────────────────
|
|
101
|
+
cmd(
|
|
102
|
+
'snapshot save',
|
|
103
|
+
'Save a named snapshot of the current canvas',
|
|
104
|
+
['pmx-canvas snapshot save --name "before-refactor"', 'pmx-canvas snapshot save --name checkpoint-1'],
|
|
105
|
+
async (args) => {
|
|
106
|
+
const { flags } = parseFlags(args);
|
|
107
|
+
if (flags.help || flags.h) return showCommandHelp('snapshot save');
|
|
108
|
+
|
|
109
|
+
const name = requireFlag(flags, 'name', 'pmx-canvas snapshot save --name "my-snapshot"');
|
|
110
|
+
const result = await invokeOperation('snapshot.save', { name });
|
|
111
|
+
output(result);
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// ── snapshot list ────────────────────────────────────────────
|
|
116
|
+
cmd(
|
|
117
|
+
'snapshot list',
|
|
118
|
+
'List saved snapshots',
|
|
119
|
+
[
|
|
120
|
+
'pmx-canvas snapshot list',
|
|
121
|
+
'pmx-canvas snapshot list --limit 50 --query baseline',
|
|
122
|
+
'pmx-canvas snapshot list --after 2026-05-01T00:00:00Z --before 2026-05-05T00:00:00Z',
|
|
123
|
+
'pmx-canvas snapshot list --all',
|
|
124
|
+
],
|
|
125
|
+
async (args) => {
|
|
126
|
+
const { flags } = parseFlags(args);
|
|
127
|
+
if (flags.help || flags.h) return showCommandHelp('snapshot list');
|
|
128
|
+
|
|
129
|
+
const limit = optionalNumberFlag(flags, 'limit', 'Use a positive integer, e.g. --limit 50');
|
|
130
|
+
const query = getStringFlag(flags, 'query', 'q');
|
|
131
|
+
const before = getStringFlag(flags, 'before');
|
|
132
|
+
const after = getStringFlag(flags, 'after');
|
|
133
|
+
const result = await invokeOperation('snapshot.list', {
|
|
134
|
+
...(limit !== undefined ? { limit } : {}),
|
|
135
|
+
...(query ? { q: query } : {}),
|
|
136
|
+
...(before ? { before } : {}),
|
|
137
|
+
...(after ? { after } : {}),
|
|
138
|
+
...(flags.all ? { all: true } : {}),
|
|
139
|
+
});
|
|
140
|
+
output(result);
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// ── snapshot gc ──────────────────────────────────────────────
|
|
145
|
+
cmd(
|
|
146
|
+
'snapshot gc',
|
|
147
|
+
'Delete old snapshots, keeping the newest N',
|
|
148
|
+
['pmx-canvas snapshot gc --keep 20 --dry-run', 'pmx-canvas snapshot gc --keep 50 --yes'],
|
|
149
|
+
async (args) => {
|
|
150
|
+
const { flags } = parseFlags(args);
|
|
151
|
+
if (flags.help || flags.h) return showCommandHelp('snapshot gc');
|
|
152
|
+
|
|
153
|
+
const keep = optionalNumberFlag(flags, 'keep', 'Use a positive integer, e.g. --keep 20');
|
|
154
|
+
const dryRun = flags['dry-run'] === true;
|
|
155
|
+
if (!dryRun && !flags.yes) {
|
|
156
|
+
die('Destructive operation requires --yes flag', 'Preview with: pmx-canvas snapshot gc --keep 20 --dry-run');
|
|
157
|
+
}
|
|
158
|
+
const result = await invokeOperation('snapshot.gc', {
|
|
159
|
+
...(keep !== undefined ? { keep } : {}),
|
|
160
|
+
dryRun,
|
|
161
|
+
});
|
|
162
|
+
output(result);
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// ── snapshot restore ─────────────────────────────────────────
|
|
167
|
+
cmd(
|
|
168
|
+
'snapshot restore',
|
|
169
|
+
'Restore canvas from a snapshot',
|
|
170
|
+
['pmx-canvas snapshot restore <snapshot-id-or-name>'],
|
|
171
|
+
async (args) => {
|
|
172
|
+
const { positional, flags } = parseFlags(args);
|
|
173
|
+
if (flags.help || flags.h) return showCommandHelp('snapshot restore');
|
|
174
|
+
|
|
175
|
+
const id = positional[0];
|
|
176
|
+
if (!id) die('Missing snapshot ID or name', 'pmx-canvas snapshot restore <snapshot-id-or-name>');
|
|
177
|
+
|
|
178
|
+
const result = await invokeOperation('snapshot.restore', { id });
|
|
179
|
+
output(result);
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// ── snapshot delete ──────────────────────────────────────────
|
|
184
|
+
cmd('snapshot delete', 'Delete a saved snapshot', ['pmx-canvas snapshot delete <snapshot-id>'], async (args) => {
|
|
185
|
+
const { positional, flags } = parseFlags(args);
|
|
186
|
+
if (flags.help || flags.h) return showCommandHelp('snapshot delete');
|
|
187
|
+
|
|
188
|
+
const id = positional[0];
|
|
189
|
+
if (!id) die('Missing snapshot ID', 'pmx-canvas snapshot delete <snapshot-id>');
|
|
190
|
+
|
|
191
|
+
const result = await invokeOperation('snapshot.delete', { id });
|
|
192
|
+
output(result);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
async function runSnapshotDiff(args: string[]): Promise<void> {
|
|
196
|
+
const { positional, flags } = parseFlags(args);
|
|
197
|
+
const snapshot = positional[0];
|
|
198
|
+
if (!snapshot) die('Missing snapshot ID or name', 'pmx-canvas snapshot diff <snapshot-id-or-name>');
|
|
199
|
+
const result = await invokeOperation('snapshot.diff', { id: snapshot });
|
|
200
|
+
output(result);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── snapshot diff ────────────────────────────────────────────
|
|
204
|
+
cmd(
|
|
205
|
+
'snapshot diff',
|
|
206
|
+
'Compare current canvas against a saved snapshot',
|
|
207
|
+
['pmx-canvas snapshot diff <snapshot-id>', 'pmx-canvas snapshot diff "before-refactor"'],
|
|
208
|
+
async (args) => {
|
|
209
|
+
const { flags } = parseFlags(args);
|
|
210
|
+
if (flags.help || flags.h) return showCommandHelp('snapshot diff');
|
|
211
|
+
await runSnapshotDiff(args);
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// ── diff ─────────────────────────────────────────────────────
|
|
216
|
+
cmd('diff', 'Compare current canvas against a snapshot', ['pmx-canvas diff <snapshot-id>'], async (args) => {
|
|
217
|
+
const { flags } = parseFlags(args);
|
|
218
|
+
if (flags.help || flags.h) return showCommandHelp('diff');
|
|
219
|
+
await runSnapshotDiff(args);
|
|
220
|
+
});
|