pmx-canvas 0.3.0 → 0.3.2
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 +73 -0
- package/dist/canvas/index.js +65 -65
- package/dist/types/cli/agent.d.ts +9 -1
- package/dist/types/cli/daemon.d.ts +74 -0
- package/dist/types/cli/watch.d.ts +2 -2
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
- package/dist/types/client/canvas/Minimap.d.ts +1 -1
- package/dist/types/client/nodes/ExtAppFrame.d.ts +9 -1
- package/dist/types/client/nodes/FileNode.d.ts +1 -1
- package/dist/types/client/nodes/ImageNode.d.ts +1 -1
- package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
- package/dist/types/client/nodes/PromptNode.d.ts +1 -1
- package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
- package/dist/types/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- package/dist/types/server/operations/index.d.ts +1 -1
- package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
- package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
- package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
- package/dist/types/server/server.d.ts +8 -0
- package/docs/cli.md +10 -1
- package/docs/http-api.md +28 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-07.md +2 -2
- package/package.json +5 -2
- package/skills/pmx-canvas/SKILL.md +15 -10
- package/skills/pmx-canvas/references/full-reference.md +17 -3
- package/src/cli/agent.ts +1951 -1571
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +80 -323
- package/src/cli/watch.ts +2 -10
- package/src/client/App.tsx +48 -46
- package/src/client/canvas/AttentionHistory.tsx +11 -1
- package/src/client/canvas/CanvasNode.tsx +41 -29
- package/src/client/canvas/CanvasViewport.tsx +101 -66
- package/src/client/canvas/CommandPalette.tsx +61 -27
- package/src/client/canvas/ContextMenu.tsx +13 -20
- package/src/client/canvas/ContextPinBar.tsx +1 -5
- package/src/client/canvas/ContextPinHud.tsx +1 -6
- package/src/client/canvas/DockedNode.tsx +4 -4
- package/src/client/canvas/EdgeLayer.tsx +37 -36
- package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
- package/src/client/canvas/FocusFieldLayer.tsx +20 -22
- package/src/client/canvas/IntentLayer.tsx +31 -14
- package/src/client/canvas/Minimap.tsx +11 -16
- package/src/client/canvas/SelectionBar.tsx +4 -11
- package/src/client/canvas/ShortcutOverlay.tsx +3 -1
- package/src/client/canvas/SnapshotPanel.tsx +77 -95
- package/src/client/canvas/auto-fit.ts +15 -14
- package/src/client/canvas/snap-guides.ts +12 -12
- package/src/client/canvas/use-node-resize.ts +1 -5
- package/src/client/canvas/use-pan-zoom.ts +25 -26
- package/src/client/ext-app/bridge.ts +3 -12
- package/src/client/icons.tsx +63 -20
- package/src/client/nodes/ContextNode.tsx +14 -25
- package/src/client/nodes/ExtAppFrame.tsx +194 -80
- package/src/client/nodes/FileNode.tsx +74 -62
- package/src/client/nodes/GroupNode.tsx +4 -6
- package/src/client/nodes/HtmlNode.tsx +76 -46
- package/src/client/nodes/ImageNode.tsx +18 -27
- package/src/client/nodes/InlineFormatBar.tsx +4 -21
- package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
- package/src/client/nodes/LedgerNode.tsx +10 -4
- package/src/client/nodes/MarkdownNode.tsx +3 -10
- package/src/client/nodes/McpAppNode.tsx +26 -22
- package/src/client/nodes/MdFormatBar.tsx +10 -7
- package/src/client/nodes/PromptNode.tsx +23 -51
- package/src/client/nodes/ResponseNode.tsx +3 -13
- package/src/client/nodes/StatusNode.tsx +5 -9
- package/src/client/nodes/StatusSummary.tsx +2 -8
- package/src/client/nodes/WebpageNode.tsx +20 -14
- package/src/client/nodes/iframe-document-url.ts +25 -16
- package/src/client/nodes/image-warnings.ts +1 -7
- package/src/client/nodes/md-format.ts +20 -5
- package/src/client/state/attention-bridge.ts +4 -9
- package/src/client/state/attention-store.ts +1 -7
- package/src/client/state/canvas-store.ts +52 -36
- package/src/client/state/intent-bridge.ts +176 -112
- package/src/client/state/intent-store.ts +4 -1
- package/src/client/state/sse-bridge.ts +53 -70
- package/src/json-render/catalog.ts +12 -16
- package/src/json-render/charts/components.tsx +16 -20
- package/src/json-render/charts/extra-components.tsx +8 -16
- package/src/json-render/charts/extra-definitions.ts +1 -2
- package/src/json-render/charts/tufte-components.tsx +37 -20
- package/src/json-render/charts/tufte-definitions.ts +8 -2
- package/src/json-render/renderer/index.tsx +42 -22
- package/src/json-render/schema.ts +6 -3
- package/src/json-render/server.ts +33 -39
- package/src/mcp/canvas-access.ts +35 -21
- package/src/mcp/server.ts +132 -70
- package/src/server/agent-context.ts +63 -36
- package/src/server/ax-context.ts +7 -5
- package/src/server/ax-interaction.ts +176 -43
- package/src/server/ax-state-manager.ts +182 -39
- package/src/server/ax-state.ts +142 -47
- package/src/server/canvas-db.ts +213 -95
- package/src/server/canvas-operations.ts +180 -123
- package/src/server/canvas-provenance.ts +1 -4
- package/src/server/canvas-schema.ts +454 -73
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +150 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +19 -6
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +3 -12
- package/src/server/html-node-summary.ts +19 -10
- package/src/server/html-primitives.ts +326 -97
- package/src/server/html-surface.ts +6 -9
- package/src/server/image-source.ts +6 -4
- package/src/server/index.ts +320 -217
- package/src/server/intent-registry.ts +2 -5
- package/src/server/mcp-app-candidate.ts +5 -10
- package/src/server/mcp-app-host.ts +14 -38
- package/src/server/mcp-app-runtime.ts +12 -20
- package/src/server/mutation-history.ts +15 -5
- package/src/server/operations/composites.ts +1 -3
- package/src/server/operations/http.ts +2 -3
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +22 -30
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +98 -73
- package/src/server/operations/ops/ax-await.ts +17 -10
- package/src/server/operations/ops/ax-read.ts +347 -0
- package/src/server/operations/ops/ax-shared.ts +2 -7
- package/src/server/operations/ops/ax-state.ts +32 -14
- package/src/server/operations/ops/ax-timeline.ts +32 -19
- package/src/server/operations/ops/ax-work.ts +54 -37
- package/src/server/operations/ops/batch.ts +41 -15
- package/src/server/operations/ops/canvas-wire.ts +91 -0
- package/src/server/operations/ops/edges.ts +37 -25
- package/src/server/operations/ops/ext-app.ts +346 -0
- package/src/server/operations/ops/groups.ts +49 -20
- package/src/server/operations/ops/intent.ts +18 -12
- package/src/server/operations/ops/json-render.ts +239 -98
- package/src/server/operations/ops/nodes.ts +298 -109
- package/src/server/operations/ops/query.ts +46 -28
- package/src/server/operations/ops/snapshots.ts +35 -26
- package/src/server/operations/ops/validate.ts +2 -1
- package/src/server/operations/ops/viewport.ts +60 -16
- package/src/server/operations/ops/webview.ts +44 -18
- package/src/server/operations/registry.ts +2 -3
- package/src/server/operations/types.ts +7 -5
- package/src/server/operations/webview-runner.ts +1 -3
- package/src/server/placement.ts +8 -18
- package/src/server/server.ts +122 -1028
- package/src/server/spatial-analysis.ts +39 -25
- package/src/server/trace-manager.ts +3 -8
- package/src/server/web-artifacts.ts +23 -27
- package/src/server/webpage-node.ts +5 -13
- package/src/shared/auto-arrange.ts +12 -5
- package/src/shared/content-height-reporter.ts +8 -6
- package/src/shared/ext-app-tool-result.ts +2 -6
- package/src/shared/placement.ts +1 -4
- package/src/shared/semantic-attention.ts +39 -37
- package/src/shared/surface.ts +8 -4
package/src/cli/agent.ts
CHANGED
|
@@ -72,9 +72,7 @@ interface CanvasSchemaResponse {
|
|
|
72
72
|
components: JsonRenderComponentSchema[];
|
|
73
73
|
};
|
|
74
74
|
graph: {
|
|
75
|
-
graphTypes: Array<
|
|
76
|
-
'line' | 'bar' | 'pie' | 'area' | 'scatter' | 'radar' | 'stacked-bar' | 'composed'
|
|
77
|
-
>;
|
|
75
|
+
graphTypes: Array<'line' | 'bar' | 'pie' | 'area' | 'scatter' | 'radar' | 'stacked-bar' | 'composed'>;
|
|
78
76
|
};
|
|
79
77
|
htmlPrimitives?: Array<{
|
|
80
78
|
kind: string;
|
|
@@ -91,13 +89,77 @@ interface CanvasSchemaResponse {
|
|
|
91
89
|
};
|
|
92
90
|
}
|
|
93
91
|
|
|
92
|
+
// Per-invocation target override from the global --server-url / --port flags.
|
|
93
|
+
// Before these existed, `--port 4750` on any agent command was SILENTLY ignored
|
|
94
|
+
// and the command hit the default 4313 daemon — which once pointed a test
|
|
95
|
+
// automation WebView at a live production board.
|
|
96
|
+
let cliTargetOverride: string | null = null;
|
|
97
|
+
|
|
94
98
|
function getBaseUrl(): string {
|
|
99
|
+
if (cliTargetOverride) return cliTargetOverride;
|
|
95
100
|
const envUrl = process.env.PMX_CANVAS_URL;
|
|
96
101
|
if (envUrl) return envUrl.replace(/\/$/, '');
|
|
97
102
|
const port = process.env.PMX_CANVAS_PORT || DEFAULT_PORT;
|
|
98
103
|
return `http://localhost:${port}`;
|
|
99
104
|
}
|
|
100
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Extract the global `--port <n>` / `--server-url <url>` flags (any position,
|
|
108
|
+
* `=` or space-separated value) and set the invocation's target override.
|
|
109
|
+
* Returns the remaining args for command dispatch. Invalid values are a loud
|
|
110
|
+
* `die` — never a silent fallback to the default port. `--server-url` wins
|
|
111
|
+
* over `--port` when both are given.
|
|
112
|
+
*/
|
|
113
|
+
export function extractGlobalTargetFlags(args: string[]): string[] {
|
|
114
|
+
cliTargetOverride = null;
|
|
115
|
+
let portOverride: number | null = null;
|
|
116
|
+
let urlOverride: string | null = null;
|
|
117
|
+
const rest: string[] = [];
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < args.length; i++) {
|
|
120
|
+
const arg = args[i];
|
|
121
|
+
let name: 'port' | 'server-url' | null = null;
|
|
122
|
+
let value: string | undefined;
|
|
123
|
+
if (arg === '--port' || arg === '--server-url') {
|
|
124
|
+
name = arg.slice(2) as 'port' | 'server-url';
|
|
125
|
+
value = args[i + 1];
|
|
126
|
+
i += 1;
|
|
127
|
+
} else if (arg.startsWith('--port=')) {
|
|
128
|
+
name = 'port';
|
|
129
|
+
value = arg.slice('--port='.length);
|
|
130
|
+
} else if (arg.startsWith('--server-url=')) {
|
|
131
|
+
name = 'server-url';
|
|
132
|
+
value = arg.slice('--server-url='.length);
|
|
133
|
+
} else {
|
|
134
|
+
rest.push(arg);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (name === 'port') {
|
|
139
|
+
const port = Number(value);
|
|
140
|
+
if (!value || !Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
141
|
+
die(
|
|
142
|
+
`--port requires a port number, got ${value === undefined ? 'no value' : JSON.stringify(value)}.`,
|
|
143
|
+
'Example: --port 4313. Without the flag, PMX_CANVAS_PORT / PMX_CANVAS_URL pick the target.',
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
portOverride = port;
|
|
147
|
+
} else {
|
|
148
|
+
if (!value || !/^https?:\/\//.test(value)) {
|
|
149
|
+
die(
|
|
150
|
+
`--server-url requires an http(s) URL, got ${value === undefined ? 'no value' : JSON.stringify(value)}.`,
|
|
151
|
+
'Example: --server-url http://localhost:4313. Without the flag, PMX_CANVAS_URL picks the target.',
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
urlOverride = value.replace(/\/$/, '');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (urlOverride) cliTargetOverride = urlOverride;
|
|
159
|
+
else if (portOverride) cliTargetOverride = `http://localhost:${portOverride}`;
|
|
160
|
+
return rest;
|
|
161
|
+
}
|
|
162
|
+
|
|
101
163
|
function die(message: string, hint?: string): never {
|
|
102
164
|
const out: Record<string, string> = { error: message };
|
|
103
165
|
if (hint) out.hint = hint;
|
|
@@ -151,10 +213,7 @@ async function api(
|
|
|
151
213
|
if (!res.ok) {
|
|
152
214
|
if (options?.allowErrorJson) return json;
|
|
153
215
|
const err = json as Record<string, unknown>;
|
|
154
|
-
die(
|
|
155
|
-
err.error ? String(err.error) : `HTTP ${res.status}`,
|
|
156
|
-
typeof err.hint === 'string' ? err.hint : undefined,
|
|
157
|
-
);
|
|
216
|
+
die(err.error ? String(err.error) : `HTTP ${res.status}`, typeof err.hint === 'string' ? err.hint : undefined);
|
|
158
217
|
}
|
|
159
218
|
return json;
|
|
160
219
|
}
|
|
@@ -186,10 +245,38 @@ function parseFlags(args: string[]): { positional: string[]; flags: Record<strin
|
|
|
186
245
|
const flags: Record<string, string | true> = {};
|
|
187
246
|
// Boolean-only flags (never take a value argument)
|
|
188
247
|
const BOOL_FLAGS = new Set([
|
|
189
|
-
'help',
|
|
190
|
-
'
|
|
191
|
-
'
|
|
192
|
-
'
|
|
248
|
+
'help',
|
|
249
|
+
'h',
|
|
250
|
+
'ids',
|
|
251
|
+
'stdin',
|
|
252
|
+
'yes',
|
|
253
|
+
'list',
|
|
254
|
+
'clear',
|
|
255
|
+
'set',
|
|
256
|
+
'animated',
|
|
257
|
+
'dry-run',
|
|
258
|
+
'all',
|
|
259
|
+
'no-open-in-canvas',
|
|
260
|
+
'lock-arrange',
|
|
261
|
+
'unlock-arrange',
|
|
262
|
+
'json',
|
|
263
|
+
'compact',
|
|
264
|
+
'verbose',
|
|
265
|
+
'include-logs',
|
|
266
|
+
'no-pan',
|
|
267
|
+
'schema',
|
|
268
|
+
'example',
|
|
269
|
+
'examples',
|
|
270
|
+
'strict-size',
|
|
271
|
+
'scroll-overflow',
|
|
272
|
+
'report',
|
|
273
|
+
'canvas',
|
|
274
|
+
'hooks',
|
|
275
|
+
'tools',
|
|
276
|
+
'session-messaging',
|
|
277
|
+
'permissions',
|
|
278
|
+
'files',
|
|
279
|
+
'ui-prompts',
|
|
193
280
|
]);
|
|
194
281
|
for (let i = 0; i < args.length; i++) {
|
|
195
282
|
const arg = args[i];
|
|
@@ -228,10 +315,7 @@ function requireFlag(flags: Record<string, string | true>, name: string, hint: s
|
|
|
228
315
|
return val;
|
|
229
316
|
}
|
|
230
317
|
|
|
231
|
-
function getStringFlag(
|
|
232
|
-
flags: Record<string, string | true>,
|
|
233
|
-
...names: string[]
|
|
234
|
-
): string | undefined {
|
|
318
|
+
function getStringFlag(flags: Record<string, string | true>, ...names: string[]): string | undefined {
|
|
235
319
|
for (const name of names) {
|
|
236
320
|
const value = flags[name];
|
|
237
321
|
if (typeof value === 'string' && value.length > 0) return value;
|
|
@@ -269,7 +353,11 @@ function optionalFiniteFlag(flags: Record<string, string | true>, name: string,
|
|
|
269
353
|
return parsed;
|
|
270
354
|
}
|
|
271
355
|
|
|
272
|
-
function optionalPositiveFiniteFlag(
|
|
356
|
+
function optionalPositiveFiniteFlag(
|
|
357
|
+
flags: Record<string, string | true>,
|
|
358
|
+
name: string,
|
|
359
|
+
hint: string,
|
|
360
|
+
): number | undefined {
|
|
273
361
|
const parsed = optionalFiniteFlag(flags, name, hint);
|
|
274
362
|
if (parsed === undefined) return undefined;
|
|
275
363
|
if (parsed <= 0) {
|
|
@@ -411,14 +499,14 @@ function collectFlagValues(args: string[], name: string): string[] {
|
|
|
411
499
|
return values;
|
|
412
500
|
}
|
|
413
501
|
|
|
414
|
-
function collectRequestedFields(
|
|
415
|
-
args: string[],
|
|
416
|
-
flags: Record<string, string | true>,
|
|
417
|
-
): string[] {
|
|
502
|
+
function collectRequestedFields(args: string[], flags: Record<string, string | true>): string[] {
|
|
418
503
|
const requested = [
|
|
419
504
|
...collectFlagValues(args, 'field'),
|
|
420
|
-
...(
|
|
421
|
-
? flags.fields
|
|
505
|
+
...(typeof flags.fields === 'string'
|
|
506
|
+
? flags.fields
|
|
507
|
+
.split(',')
|
|
508
|
+
.map((value) => value.trim())
|
|
509
|
+
.filter(Boolean)
|
|
422
510
|
: []),
|
|
423
511
|
];
|
|
424
512
|
return Array.from(new Set(requested));
|
|
@@ -453,9 +541,7 @@ function listAvailableNodeFields(node: Record<string, unknown>): string[] {
|
|
|
453
541
|
}
|
|
454
542
|
|
|
455
543
|
function summarizeHistoryResult(result: Record<string, unknown>): Record<string, unknown> {
|
|
456
|
-
const entries = Array.isArray(result.entries)
|
|
457
|
-
? result.entries.filter(isRecord)
|
|
458
|
-
: [];
|
|
544
|
+
const entries = Array.isArray(result.entries) ? result.entries.filter(isRecord) : [];
|
|
459
545
|
const countsByOperation: Record<string, number> = {};
|
|
460
546
|
let currentIndex = 0;
|
|
461
547
|
|
|
@@ -483,9 +569,7 @@ function summarizeHistoryResult(result: Record<string, unknown>): Record<string,
|
|
|
483
569
|
}
|
|
484
570
|
|
|
485
571
|
function compactHistoryResult(result: Record<string, unknown>): Record<string, unknown> {
|
|
486
|
-
const entries = Array.isArray(result.entries)
|
|
487
|
-
? result.entries.filter(isRecord)
|
|
488
|
-
: [];
|
|
572
|
+
const entries = Array.isArray(result.entries) ? result.entries.filter(isRecord) : [];
|
|
489
573
|
return {
|
|
490
574
|
totalMutations: entries.length,
|
|
491
575
|
canUndo: result.canUndo === true,
|
|
@@ -504,10 +588,7 @@ function parseRecordArrayJson(raw: string, hint: string): Array<Record<string, u
|
|
|
504
588
|
try {
|
|
505
589
|
parsed = JSON.parse(raw);
|
|
506
590
|
} catch (error) {
|
|
507
|
-
die(
|
|
508
|
-
`Invalid JSON dataset: ${error instanceof Error ? error.message : String(error)}`,
|
|
509
|
-
hint,
|
|
510
|
-
);
|
|
591
|
+
die(`Invalid JSON dataset: ${error instanceof Error ? error.message : String(error)}`, hint);
|
|
511
592
|
}
|
|
512
593
|
|
|
513
594
|
if (!Array.isArray(parsed) || parsed.some((item) => !isRecord(item))) {
|
|
@@ -521,10 +602,7 @@ function parseJsonValue(raw: string, label: string, hint: string): unknown {
|
|
|
521
602
|
try {
|
|
522
603
|
return JSON.parse(raw);
|
|
523
604
|
} catch (error) {
|
|
524
|
-
die(
|
|
525
|
-
`Invalid ${label}: ${error instanceof Error ? error.message : String(error)}`,
|
|
526
|
-
hint,
|
|
527
|
-
);
|
|
605
|
+
die(`Invalid ${label}: ${error instanceof Error ? error.message : String(error)}`, hint);
|
|
528
606
|
}
|
|
529
607
|
}
|
|
530
608
|
|
|
@@ -553,10 +631,7 @@ async function readTextInput(
|
|
|
553
631
|
try {
|
|
554
632
|
return readFileSync(path, 'utf-8');
|
|
555
633
|
} catch (error) {
|
|
556
|
-
die(
|
|
557
|
-
`Unable to read --${name}: ${error instanceof Error ? error.message : String(error)}`,
|
|
558
|
-
options.hint,
|
|
559
|
-
);
|
|
634
|
+
die(`Unable to read --${name}: ${error instanceof Error ? error.message : String(error)}`, options.hint);
|
|
560
635
|
}
|
|
561
636
|
}
|
|
562
637
|
|
|
@@ -588,10 +663,7 @@ async function readOptionalTextInput(
|
|
|
588
663
|
try {
|
|
589
664
|
return readFileSync(path, 'utf-8');
|
|
590
665
|
} catch (error) {
|
|
591
|
-
die(
|
|
592
|
-
`Unable to read --${name}: ${error instanceof Error ? error.message : String(error)}`,
|
|
593
|
-
options.hint,
|
|
594
|
-
);
|
|
666
|
+
die(`Unable to read --${name}: ${error instanceof Error ? error.message : String(error)}`, options.hint);
|
|
595
667
|
}
|
|
596
668
|
}
|
|
597
669
|
|
|
@@ -634,7 +706,11 @@ async function applyStructuredNodeUpdateFlags(
|
|
|
634
706
|
hint: 'Use: pmx-canvas node update <node-id> --spec-file ./new-spec.json',
|
|
635
707
|
});
|
|
636
708
|
if (specRaw !== undefined) {
|
|
637
|
-
body.spec = parseJsonValue(
|
|
709
|
+
body.spec = parseJsonValue(
|
|
710
|
+
specRaw,
|
|
711
|
+
'JSON spec',
|
|
712
|
+
'Use: pmx-canvas node update <node-id> --spec-file ./new-spec.json',
|
|
713
|
+
);
|
|
638
714
|
}
|
|
639
715
|
|
|
640
716
|
const graphPatch = await buildGraphRequestBody(flags, { requireData: false, allowStdin: false });
|
|
@@ -643,11 +719,8 @@ async function applyStructuredNodeUpdateFlags(
|
|
|
643
719
|
}
|
|
644
720
|
}
|
|
645
721
|
|
|
646
|
-
async function buildJsonRenderRequestBody(
|
|
647
|
-
|
|
648
|
-
): Promise<Record<string, unknown>> {
|
|
649
|
-
const hint =
|
|
650
|
-
'Use: pmx-canvas node add --type json-render --spec-file ./dashboard.json --title "Ops Dashboard"';
|
|
722
|
+
async function buildJsonRenderRequestBody(flags: Record<string, string | true>): Promise<Record<string, unknown>> {
|
|
723
|
+
const hint = 'Use: pmx-canvas node add --type json-render --spec-file ./dashboard.json --title "Ops Dashboard"';
|
|
651
724
|
const title = typeof flags.title === 'string' ? flags.title.trim() : '';
|
|
652
725
|
|
|
653
726
|
const rawSpec = await readTextInput(flags, {
|
|
@@ -671,9 +744,7 @@ async function buildJsonRenderRequestBody(
|
|
|
671
744
|
return body;
|
|
672
745
|
}
|
|
673
746
|
|
|
674
|
-
async function buildHtmlPrimitiveRequestBody(
|
|
675
|
-
flags: Record<string, string | true>,
|
|
676
|
-
): Promise<Record<string, unknown>> {
|
|
747
|
+
async function buildHtmlPrimitiveRequestBody(flags: Record<string, string | true>): Promise<Record<string, unknown>> {
|
|
677
748
|
const hint = 'Use: pmx-canvas html primitive add --kind choice-grid --data-file ./primitive.json --title "Options"';
|
|
678
749
|
const kind = getStringFlag(flags, 'kind', 'primitive');
|
|
679
750
|
if (!kind) die('HTML primitives require --kind.', hint);
|
|
@@ -760,7 +831,11 @@ async function buildGraphRequestBody(
|
|
|
760
831
|
if (showLegend !== undefined) body.showLegend = showLegend;
|
|
761
832
|
if (showLabels !== undefined) body.showLabels = showLabels;
|
|
762
833
|
|
|
763
|
-
const chartHeight = optionalPositiveFiniteFlag(
|
|
834
|
+
const chartHeight = optionalPositiveFiniteFlag(
|
|
835
|
+
flags,
|
|
836
|
+
'chart-height',
|
|
837
|
+
'Use a positive number, e.g. --chart-height 300',
|
|
838
|
+
);
|
|
764
839
|
const x = optionalFiniteFlag(flags, 'x', 'Use a finite number, e.g. --x 500');
|
|
765
840
|
const y = optionalFiniteFlag(flags, 'y', 'Use a finite number, e.g. --y 300');
|
|
766
841
|
const width = optionalPositiveFiniteFlag(flags, 'width', 'Use a positive number, e.g. --width 760');
|
|
@@ -780,9 +855,7 @@ async function buildGraphRequestBody(
|
|
|
780
855
|
return body;
|
|
781
856
|
}
|
|
782
857
|
|
|
783
|
-
async function buildWebArtifactRequestBody(
|
|
784
|
-
flags: Record<string, string | true>,
|
|
785
|
-
): Promise<Record<string, unknown>> {
|
|
858
|
+
async function buildWebArtifactRequestBody(flags: Record<string, string | true>): Promise<Record<string, unknown>> {
|
|
786
859
|
const hint = 'Use: pmx-canvas web-artifact build --title "Dashboard" --app-file ./App.tsx';
|
|
787
860
|
const title = requireFlag(flags, 'title', hint);
|
|
788
861
|
const appTsx = await readTextInput(flags, {
|
|
@@ -904,9 +977,7 @@ function printNodeSchemaHelp(schema: CanvasSchemaType): void {
|
|
|
904
977
|
console.log(`Endpoint: ${schema.endpoint}`);
|
|
905
978
|
console.log('Flags:');
|
|
906
979
|
for (const field of schema.fields) {
|
|
907
|
-
const aliases = field.aliases?.length
|
|
908
|
-
? ` (aliases: ${field.aliases.map((alias) => `--${alias}`).join(', ')})`
|
|
909
|
-
: '';
|
|
980
|
+
const aliases = field.aliases?.length ? ` (aliases: ${field.aliases.map((alias) => `--${alias}`).join(', ')})` : '';
|
|
910
981
|
console.log(
|
|
911
982
|
` --${field.name}${field.required ? ' [required]' : ''} <${field.type}> ${field.description}${aliases}`,
|
|
912
983
|
);
|
|
@@ -936,13 +1007,19 @@ async function showNodeAddTypeHelp(flags: Record<string, string | true>): Promis
|
|
|
936
1007
|
if (componentName) {
|
|
937
1008
|
const component = schema.jsonRender.components.find((entry) => entry.type === componentName);
|
|
938
1009
|
if (!component) {
|
|
939
|
-
die(
|
|
1010
|
+
die(
|
|
1011
|
+
`Unknown json-render component: ${componentName}`,
|
|
1012
|
+
'Run: pmx-canvas node schema --type json-render --summary',
|
|
1013
|
+
);
|
|
940
1014
|
}
|
|
941
1015
|
const requestedField = getStringFlag(flags, 'field');
|
|
942
1016
|
if (requestedField) {
|
|
943
1017
|
const prop = component.props.find((entry) => entry.name === requestedField);
|
|
944
1018
|
if (!prop) {
|
|
945
|
-
die(
|
|
1019
|
+
die(
|
|
1020
|
+
`Unknown json-render prop: ${requestedField}`,
|
|
1021
|
+
`Run: pmx-canvas node schema --type json-render --component ${componentName}`,
|
|
1022
|
+
);
|
|
946
1023
|
}
|
|
947
1024
|
payload = {
|
|
948
1025
|
command: 'node add',
|
|
@@ -1061,7 +1138,10 @@ function filterJsonRenderSchemaView(
|
|
|
1061
1138
|
if (requestedField) {
|
|
1062
1139
|
const prop = component.props.find((entry) => entry.name === requestedField);
|
|
1063
1140
|
if (!prop) {
|
|
1064
|
-
die(
|
|
1141
|
+
die(
|
|
1142
|
+
`Unknown json-render prop: ${requestedField}`,
|
|
1143
|
+
`Run: pmx-canvas node schema --type json-render --component ${componentName}`,
|
|
1144
|
+
);
|
|
1065
1145
|
}
|
|
1066
1146
|
return {
|
|
1067
1147
|
component: componentName,
|
|
@@ -1072,7 +1152,9 @@ function filterJsonRenderSchemaView(
|
|
|
1072
1152
|
return flags.summary ? summarizeJsonRenderComponent(component) : component;
|
|
1073
1153
|
}
|
|
1074
1154
|
|
|
1075
|
-
function summarizeHtmlPrimitive(
|
|
1155
|
+
function summarizeHtmlPrimitive(
|
|
1156
|
+
primitive: NonNullable<CanvasSchemaResponse['htmlPrimitives']>[number],
|
|
1157
|
+
): Record<string, unknown> {
|
|
1076
1158
|
return {
|
|
1077
1159
|
kind: primitive.kind,
|
|
1078
1160
|
title: primitive.title,
|
|
@@ -1138,18 +1220,11 @@ const RESOURCE_SUBCOMMAND_HINTS: Record<string, Record<string, string>> = {
|
|
|
1138
1220
|
},
|
|
1139
1221
|
};
|
|
1140
1222
|
|
|
1141
|
-
function cmd(
|
|
1142
|
-
name: string,
|
|
1143
|
-
help: string,
|
|
1144
|
-
examples: string[],
|
|
1145
|
-
run: (args: string[]) => Promise<void>,
|
|
1146
|
-
) {
|
|
1223
|
+
function cmd(name: string, help: string, examples: string[], run: (args: string[]) => Promise<void>) {
|
|
1147
1224
|
COMMANDS[name] = { run, help, examples };
|
|
1148
1225
|
}
|
|
1149
1226
|
|
|
1150
|
-
cmd('open', 'Open the current workbench in the browser', [
|
|
1151
|
-
'pmx-canvas open',
|
|
1152
|
-
], async (args) => {
|
|
1227
|
+
cmd('open', 'Open the current workbench in the browser', ['pmx-canvas open'], async (args) => {
|
|
1153
1228
|
const { flags } = parseFlags(args);
|
|
1154
1229
|
if (flags.help || flags.h) return showCommandHelp('open');
|
|
1155
1230
|
|
|
@@ -1174,486 +1249,547 @@ cmd('open', 'Open the current workbench in the browser', [
|
|
|
1174
1249
|
});
|
|
1175
1250
|
|
|
1176
1251
|
// ── node add ─────────────────────────────────────────────────
|
|
1177
|
-
cmd(
|
|
1178
|
-
'
|
|
1179
|
-
'
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
return;
|
|
1205
|
-
}
|
|
1252
|
+
cmd(
|
|
1253
|
+
'node add',
|
|
1254
|
+
'Add a node to the canvas',
|
|
1255
|
+
[
|
|
1256
|
+
'pmx-canvas node add --type markdown --title "Design Doc" --content "# Overview"',
|
|
1257
|
+
'pmx-canvas node add --type status --title "Build" --content "passing"',
|
|
1258
|
+
'pmx-canvas node add --type file --content "src/index.ts"',
|
|
1259
|
+
'pmx-canvas node add --type webpage --url "https://example.com/docs"',
|
|
1260
|
+
'pmx-canvas node add --type html --title "Widget" --content "<main>Hello</main>"',
|
|
1261
|
+
'pmx-canvas node add --type html --title "Showcase" --content ./report.html (a .html path is read from disk; otherwise --content is raw HTML)',
|
|
1262
|
+
'pmx-canvas node add --type html --primitive choice-grid --data-file ./options.json --title "Options"',
|
|
1263
|
+
'pmx-canvas node add --type markdown --title "Note" --x 100 --y 200',
|
|
1264
|
+
'pmx-canvas node add --type json-render --title "Ops Dashboard" --spec-file ./dashboard.json',
|
|
1265
|
+
'pmx-canvas node add --type graph --graph-type bar --data-file ./metrics.json --x-key label --y-key value',
|
|
1266
|
+
'pmx-canvas node add --type web-artifact --title "Dashboard" --app-file ./App.tsx',
|
|
1267
|
+
],
|
|
1268
|
+
async (args) => {
|
|
1269
|
+
const { flags } = parseFlags(args);
|
|
1270
|
+
if (flags.help || flags.h) return showNodeAddTypeHelp(flags);
|
|
1271
|
+
|
|
1272
|
+
const type = (flags.type as string) || 'markdown';
|
|
1273
|
+
|
|
1274
|
+
if (type === 'json-render') {
|
|
1275
|
+
const result = await invokeOperation('jsonrender.add', await buildJsonRenderRequestBody(flags));
|
|
1276
|
+
output(result);
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1206
1279
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1280
|
+
if (type === 'graph') {
|
|
1281
|
+
const result = await invokeOperation('graph.add', await buildGraphRequestBody(flags));
|
|
1282
|
+
output(result);
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1211
1285
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
}
|
|
1286
|
+
if (type === 'web-artifact') {
|
|
1287
|
+
await runWebArtifactBuildCommand(flags);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1217
1290
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1291
|
+
if (type === 'html-primitive') {
|
|
1292
|
+
const result = await invokeOperation('node.add', await buildHtmlPrimitiveRequestBody(flags));
|
|
1293
|
+
output(result);
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1223
1296
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
}
|
|
1297
|
+
if (type === 'html' && getStringFlag(flags, 'primitive', 'kind')) {
|
|
1298
|
+
const result = await invokeOperation('node.add', await buildHtmlPrimitiveRequestBody(flags));
|
|
1299
|
+
output(result);
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1230
1302
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
body.url = webpageUrl;
|
|
1237
|
-
} else if (type === 'image' && imagePath && !flags.content) {
|
|
1238
|
-
body.content = imagePath;
|
|
1239
|
-
} else if (type === 'html') {
|
|
1240
|
-
const html = getStringFlag(flags, 'html') ?? getStringFlag(flags, 'content');
|
|
1241
|
-
if (html !== undefined) body.html = html;
|
|
1242
|
-
const summary = getStringFlag(flags, 'summary');
|
|
1243
|
-
const agentSummary = getStringFlag(flags, 'agent-summary', 'agentSummary');
|
|
1244
|
-
const description = getStringFlag(flags, 'description');
|
|
1245
|
-
if (summary !== undefined) body.summary = summary;
|
|
1246
|
-
if (agentSummary !== undefined) body.agentSummary = agentSummary;
|
|
1247
|
-
if (description !== undefined) body.description = description;
|
|
1248
|
-
if (optionalBooleanFlag(flags, 'presentation', 'Use --presentation true or --presentation false') === true) body.presentation = true;
|
|
1249
|
-
if (typeof flags['slide-title'] === 'string') body.slideTitles = [flags['slide-title']];
|
|
1250
|
-
if (typeof flags['embedded-node-id'] === 'string') body.embeddedNodeIds = [flags['embedded-node-id']];
|
|
1251
|
-
} else if (flags.content) {
|
|
1252
|
-
body.content = flags.content;
|
|
1253
|
-
}
|
|
1254
|
-
applyCommonGeometryFlags(body, flags, {
|
|
1255
|
-
x: 'Use a finite number, e.g. --x 500',
|
|
1256
|
-
y: 'Use a finite number, e.g. --y 300',
|
|
1257
|
-
width: 'Use a positive number, e.g. --width 500',
|
|
1258
|
-
height: 'Use a positive number, e.g. --height 280',
|
|
1259
|
-
});
|
|
1260
|
-
applyStrictSizeFlags(body, flags);
|
|
1261
|
-
if (type === 'trace') {
|
|
1262
|
-
for (const field of TRACE_NODE_FIELDS) {
|
|
1263
|
-
const value = getStringFlag(flags, field, field.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`));
|
|
1264
|
-
if (value !== undefined) body[field] = value;
|
|
1303
|
+
if (type === 'mcp-app') {
|
|
1304
|
+
die(
|
|
1305
|
+
'mcp-app nodes require tool-backed app metadata and cannot be created with generic node add.',
|
|
1306
|
+
'Use: pmx-canvas web-artifact build --title "Dashboard" --app-file ./App.tsx, or pmx-canvas external-app add --kind excalidraw --title "Diagram"',
|
|
1307
|
+
);
|
|
1265
1308
|
}
|
|
1266
|
-
}
|
|
1267
1309
|
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1310
|
+
const body: Record<string, unknown> = { type };
|
|
1311
|
+
if (flags.title) body.title = flags.title;
|
|
1312
|
+
const webpageUrl = getStringFlag(flags, 'url');
|
|
1313
|
+
const imagePath = getStringFlag(flags, 'path');
|
|
1314
|
+
if (type === 'webpage' && webpageUrl) {
|
|
1315
|
+
body.url = webpageUrl;
|
|
1316
|
+
} else if (type === 'image' && imagePath && !flags.content) {
|
|
1317
|
+
body.content = imagePath;
|
|
1272
1318
|
} else if (type === 'html') {
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1319
|
+
const html = getStringFlag(flags, 'html') ?? getStringFlag(flags, 'content');
|
|
1320
|
+
if (html !== undefined) body.html = html;
|
|
1321
|
+
const summary = getStringFlag(flags, 'summary');
|
|
1322
|
+
const agentSummary = getStringFlag(flags, 'agent-summary', 'agentSummary');
|
|
1323
|
+
const description = getStringFlag(flags, 'description');
|
|
1324
|
+
if (summary !== undefined) body.summary = summary;
|
|
1325
|
+
if (agentSummary !== undefined) body.agentSummary = agentSummary;
|
|
1326
|
+
if (description !== undefined) body.description = description;
|
|
1327
|
+
if (optionalBooleanFlag(flags, 'presentation', 'Use --presentation true or --presentation false') === true)
|
|
1328
|
+
body.presentation = true;
|
|
1329
|
+
if (typeof flags['slide-title'] === 'string') body.slideTitles = [flags['slide-title']];
|
|
1330
|
+
if (typeof flags['embedded-node-id'] === 'string') body.embeddedNodeIds = [flags['embedded-node-id']];
|
|
1331
|
+
} else if (flags.content) {
|
|
1332
|
+
body.content = flags.content;
|
|
1333
|
+
}
|
|
1334
|
+
applyCommonGeometryFlags(body, flags, {
|
|
1335
|
+
x: 'Use a finite number, e.g. --x 500',
|
|
1336
|
+
y: 'Use a finite number, e.g. --y 300',
|
|
1337
|
+
width: 'Use a positive number, e.g. --width 500',
|
|
1338
|
+
height: 'Use a positive number, e.g. --height 280',
|
|
1339
|
+
});
|
|
1340
|
+
applyStrictSizeFlags(body, flags);
|
|
1341
|
+
if (type === 'trace') {
|
|
1342
|
+
for (const field of TRACE_NODE_FIELDS) {
|
|
1343
|
+
const value = getStringFlag(
|
|
1344
|
+
flags,
|
|
1345
|
+
field,
|
|
1346
|
+
field.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`),
|
|
1347
|
+
);
|
|
1348
|
+
if (value !== undefined) body[field] = value;
|
|
1349
|
+
}
|
|
1276
1350
|
}
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
const result = await invokeOperation('node.add', body);
|
|
1280
|
-
output(result);
|
|
1281
|
-
});
|
|
1282
|
-
|
|
1283
|
-
cmd('json-render', 'Show json-render schema and canonical examples', [
|
|
1284
|
-
'pmx-canvas json-render --schema --summary',
|
|
1285
|
-
'pmx-canvas json-render --examples',
|
|
1286
|
-
'pmx-canvas json-render --example --component Table',
|
|
1287
|
-
'pmx-canvas json-render --schema --component Badge --field variant',
|
|
1288
|
-
], async (args) => {
|
|
1289
|
-
const { flags } = parseFlags(args);
|
|
1290
|
-
if (flags.help || flags.h) return showCommandHelp('json-render');
|
|
1291
1351
|
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1352
|
+
// Support --stdin for piping content
|
|
1353
|
+
if (flags.stdin) {
|
|
1354
|
+
if (type === 'webpage') {
|
|
1355
|
+
body.url = await readStdin();
|
|
1356
|
+
} else if (type === 'html') {
|
|
1357
|
+
body.html = await readStdin();
|
|
1358
|
+
} else {
|
|
1359
|
+
body.content = await readStdin();
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1295
1362
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1363
|
+
const result = await invokeOperation('node.add', body);
|
|
1364
|
+
output(result);
|
|
1365
|
+
},
|
|
1366
|
+
);
|
|
1367
|
+
|
|
1368
|
+
cmd(
|
|
1369
|
+
'json-render',
|
|
1370
|
+
'Show json-render schema and canonical examples',
|
|
1371
|
+
[
|
|
1372
|
+
'pmx-canvas json-render --schema --summary',
|
|
1373
|
+
'pmx-canvas json-render --examples',
|
|
1374
|
+
'pmx-canvas json-render --example --component Table',
|
|
1375
|
+
'pmx-canvas json-render --schema --component Badge --field variant',
|
|
1376
|
+
],
|
|
1377
|
+
async (args) => {
|
|
1378
|
+
const { flags } = parseFlags(args);
|
|
1379
|
+
if (flags.help || flags.h) return showCommandHelp('json-render');
|
|
1380
|
+
|
|
1381
|
+
const schema = await loadCanvasSchema();
|
|
1382
|
+
const componentName = getStringFlag(flags, 'component');
|
|
1383
|
+
const fieldName = getStringFlag(flags, 'field');
|
|
1384
|
+
|
|
1385
|
+
if (flags.example || flags.examples) {
|
|
1386
|
+
if (fieldName)
|
|
1387
|
+
die(
|
|
1388
|
+
'--field is only supported with --schema.',
|
|
1389
|
+
'Use: pmx-canvas json-render --schema --component Table --field rows',
|
|
1390
|
+
);
|
|
1391
|
+
if (componentName) {
|
|
1392
|
+
const component = schema.jsonRender.components.find((entry) => entry.type === componentName);
|
|
1393
|
+
if (!component)
|
|
1394
|
+
die(`Unknown json-render component: ${componentName}`, 'Run: pmx-canvas json-render --schema --summary');
|
|
1395
|
+
output({ component: component.type, example: component.example });
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
output({
|
|
1399
|
+
rootShape: schema.jsonRender.rootShape,
|
|
1400
|
+
examples: Object.fromEntries(schema.jsonRender.components.map((entry) => [entry.type, entry.example])),
|
|
1401
|
+
});
|
|
1302
1402
|
return;
|
|
1303
1403
|
}
|
|
1304
|
-
output({
|
|
1305
|
-
rootShape: schema.jsonRender.rootShape,
|
|
1306
|
-
examples: Object.fromEntries(schema.jsonRender.components.map((entry) => [entry.type, entry.example])),
|
|
1307
|
-
});
|
|
1308
|
-
return;
|
|
1309
|
-
}
|
|
1310
1404
|
|
|
1311
|
-
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
'
|
|
1317
|
-
'
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1405
|
+
output(filterJsonRenderSchemaView(schema.jsonRender, flags));
|
|
1406
|
+
},
|
|
1407
|
+
);
|
|
1408
|
+
|
|
1409
|
+
cmd(
|
|
1410
|
+
'html primitive add',
|
|
1411
|
+
'Create a reusable sandboxed HTML communication primitive',
|
|
1412
|
+
[
|
|
1413
|
+
'pmx-canvas html primitive add --kind choice-grid --data-file ./options.json --title "Options"',
|
|
1414
|
+
'pmx-canvas html primitive add --kind plan-timeline --data-json \'{"milestones":[{"title":"Ship","detail":"Implement and verify","status":"next"}]}\'',
|
|
1415
|
+
'pmx-canvas html primitive add --kind triage-board --data-file ./tickets.json --strict-size',
|
|
1416
|
+
],
|
|
1417
|
+
async (args) => {
|
|
1418
|
+
const { flags } = parseFlags(args);
|
|
1419
|
+
if (flags.help || flags.h) return showCommandHelp('html primitive add');
|
|
1420
|
+
const result = await api('POST', '/api/canvas/node', await buildHtmlPrimitiveRequestBody(flags));
|
|
1421
|
+
output(result);
|
|
1422
|
+
},
|
|
1423
|
+
);
|
|
1424
|
+
|
|
1425
|
+
cmd(
|
|
1426
|
+
'html primitive schema',
|
|
1427
|
+
'Describe reusable HTML communication primitives',
|
|
1428
|
+
[
|
|
1429
|
+
'pmx-canvas html primitive schema --summary',
|
|
1430
|
+
'pmx-canvas html primitive schema --kind choice-grid',
|
|
1431
|
+
'pmx-canvas html primitive schema --kind triage-board --summary',
|
|
1432
|
+
],
|
|
1433
|
+
async (args) => {
|
|
1434
|
+
const { flags } = parseFlags(args);
|
|
1435
|
+
if (flags.help || flags.h) return showCommandHelp('html primitive schema');
|
|
1436
|
+
const schema = await loadCanvasSchema();
|
|
1437
|
+
output(filterHtmlPrimitiveSchemaView(schema, flags));
|
|
1438
|
+
},
|
|
1439
|
+
);
|
|
1440
|
+
|
|
1441
|
+
cmd(
|
|
1442
|
+
'graph add',
|
|
1443
|
+
'Add a graph node to the canvas',
|
|
1444
|
+
[
|
|
1445
|
+
'pmx-canvas graph add --graph-type bar --data-file ./metrics.json --x-key label --y-key value',
|
|
1446
|
+
'pmx-canvas graph add --graphType composed --data \'[{"day":"Mon","visits":10,"conversion":0.4}]\' --xKey day --barKey visits --lineKey conversion',
|
|
1447
|
+
'pmx-canvas node add --type graph --graph-type bar --data-file ./metrics.json --x-key label --y-key value',
|
|
1448
|
+
],
|
|
1449
|
+
async (args) => {
|
|
1450
|
+
const { flags } = parseFlags(args);
|
|
1451
|
+
if (flags.help || flags.h) return showCommandHelp('graph add');
|
|
1324
1452
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1453
|
+
const result = await invokeOperation('graph.add', await buildGraphRequestBody(flags));
|
|
1454
|
+
output(result);
|
|
1455
|
+
},
|
|
1456
|
+
);
|
|
1457
|
+
|
|
1458
|
+
cmd(
|
|
1459
|
+
'node schema',
|
|
1460
|
+
'Describe server-supported node create schemas and canonical examples',
|
|
1461
|
+
[
|
|
1462
|
+
'pmx-canvas node schema',
|
|
1463
|
+
'pmx-canvas node schema --type webpage',
|
|
1464
|
+
'pmx-canvas node schema --type json-render',
|
|
1465
|
+
'pmx-canvas json-render --schema --summary',
|
|
1466
|
+
'pmx-canvas node schema --type json-render --component Table',
|
|
1467
|
+
'pmx-canvas node schema --type webpage --field url',
|
|
1468
|
+
'pmx-canvas node schema --summary',
|
|
1469
|
+
],
|
|
1470
|
+
async (args) => {
|
|
1471
|
+
const { flags } = parseFlags(args);
|
|
1472
|
+
if (flags.help || flags.h) return showCommandHelp('node schema');
|
|
1473
|
+
|
|
1474
|
+
const result = await loadCanvasSchema();
|
|
1475
|
+
if (getStringFlag(flags, 'component') && flags.type !== 'json-render') {
|
|
1476
|
+
die('--component is only supported with --type json-render.');
|
|
1477
|
+
}
|
|
1335
1478
|
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1479
|
+
if (typeof flags.type !== 'string') {
|
|
1480
|
+
if (flags.summary) {
|
|
1481
|
+
output({
|
|
1482
|
+
source: result.source,
|
|
1483
|
+
version: result.version,
|
|
1484
|
+
nodeTypes: result.nodeTypes.map((entry) => summarizeNodeSchema(entry)),
|
|
1485
|
+
jsonRender: {
|
|
1486
|
+
componentCount: result.jsonRender.components.length,
|
|
1487
|
+
rootShape: result.jsonRender.rootShape,
|
|
1488
|
+
},
|
|
1489
|
+
graph: result.graph,
|
|
1490
|
+
htmlPrimitives: result.htmlPrimitives?.map((entry) => summarizeHtmlPrimitive(entry)) ?? [],
|
|
1491
|
+
mcp: result.mcp,
|
|
1492
|
+
});
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
output(result);
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1343
1498
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1499
|
+
const requested = flags.type;
|
|
1500
|
+
if (requested === 'json-render') {
|
|
1501
|
+
output(filterJsonRenderSchemaView(result.jsonRender, flags));
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
if (requested === 'graph') {
|
|
1505
|
+
const graphSchema = result.nodeTypes.find((entry) => entry.type === 'graph');
|
|
1506
|
+
if (graphSchema) {
|
|
1507
|
+
output(filterNodeSchemaView(graphSchema, flags));
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
output(flags.summary ? result.graph : { ...result.graph });
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
const nodeType = result.nodeTypes.find((entry) => entry.type === requested);
|
|
1514
|
+
if (nodeType) {
|
|
1515
|
+
output(filterNodeSchemaView(nodeType, flags));
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
die(`Unknown schema type: ${requested}`, 'Run: pmx-canvas node schema');
|
|
1519
|
+
},
|
|
1520
|
+
);
|
|
1347
1521
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
'
|
|
1351
|
-
'
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1522
|
+
// ── node list ────────────────────────────────────────────────
|
|
1523
|
+
cmd(
|
|
1524
|
+
'node list',
|
|
1525
|
+
'List all nodes on the canvas',
|
|
1526
|
+
[
|
|
1527
|
+
'pmx-canvas node list',
|
|
1528
|
+
'pmx-canvas node list --type markdown',
|
|
1529
|
+
'pmx-canvas node list --type mcp-app',
|
|
1530
|
+
'pmx-canvas node list --ids',
|
|
1531
|
+
],
|
|
1532
|
+
async (args) => {
|
|
1533
|
+
const { flags } = parseFlags(args);
|
|
1534
|
+
if (flags.help || flags.h) return showCommandHelp('node list');
|
|
1535
|
+
|
|
1536
|
+
const layout = (await invokeOperation('layout.get', {})) as { nodes: Array<Record<string, unknown>> };
|
|
1537
|
+
let nodes = layout.nodes;
|
|
1538
|
+
|
|
1539
|
+
if (flags.type && flags.type !== true) {
|
|
1540
|
+
nodes = nodes.filter((n) => n.type === flags.type || n.kind === flags.type);
|
|
1541
|
+
}
|
|
1359
1542
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1543
|
+
if (flags.ids) {
|
|
1544
|
+
output(nodes.map((n) => n.id));
|
|
1545
|
+
} else {
|
|
1546
|
+
const shouldSummarize = flags.summary === true || flags.compact === true || flags.type === 'mcp-app';
|
|
1547
|
+
output(shouldSummarize ? nodes.map((node) => summarizeNodeResult(node)) : nodes);
|
|
1548
|
+
}
|
|
1549
|
+
},
|
|
1550
|
+
);
|
|
1364
1551
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1552
|
+
// ── node get ─────────────────────────────────────────────────
|
|
1553
|
+
cmd(
|
|
1554
|
+
'node get',
|
|
1555
|
+
'Get a node by ID',
|
|
1556
|
+
[
|
|
1557
|
+
'pmx-canvas node get <node-id>',
|
|
1558
|
+
'pmx-canvas node get node-abc123',
|
|
1559
|
+
'pmx-canvas node get node-abc123 --summary',
|
|
1560
|
+
'pmx-canvas node get node-abc123 --field title --field graphConfig',
|
|
1561
|
+
],
|
|
1562
|
+
async (args) => {
|
|
1563
|
+
const { positional, flags } = parseFlags(args);
|
|
1564
|
+
if (flags.help || flags.h) return showCommandHelp('node get');
|
|
1565
|
+
|
|
1566
|
+
const id = positional[0];
|
|
1567
|
+
if (!id) die('Missing node ID', 'pmx-canvas node get <node-id>');
|
|
1568
|
+
|
|
1569
|
+
const result = (await invokeOperation('node.get', { id })) as Record<string, unknown>;
|
|
1570
|
+
const requestedFields = collectRequestedFields(args, flags);
|
|
1571
|
+
if (requestedFields.length > 0) {
|
|
1572
|
+
const picked = Object.fromEntries(requestedFields.map((field) => [field, resolveNodeFieldValue(result, field)]));
|
|
1573
|
+
const missing = requestedFields.filter((field) => picked[field] === undefined);
|
|
1574
|
+
if (missing.length > 0) {
|
|
1575
|
+
die(
|
|
1576
|
+
`Unknown node field${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}`,
|
|
1577
|
+
`Available fields: ${listAvailableNodeFields(result).join(', ')}`,
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1367
1580
|
output({
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
nodeTypes: result.nodeTypes.map((entry) => summarizeNodeSchema(entry)),
|
|
1371
|
-
jsonRender: {
|
|
1372
|
-
componentCount: result.jsonRender.components.length,
|
|
1373
|
-
rootShape: result.jsonRender.rootShape,
|
|
1374
|
-
},
|
|
1375
|
-
graph: result.graph,
|
|
1376
|
-
htmlPrimitives: result.htmlPrimitives?.map((entry) => summarizeHtmlPrimitive(entry)) ?? [],
|
|
1377
|
-
mcp: result.mcp,
|
|
1581
|
+
id: result.id ?? id,
|
|
1582
|
+
fields: picked,
|
|
1378
1583
|
});
|
|
1379
1584
|
return;
|
|
1380
1585
|
}
|
|
1381
|
-
output(result);
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1384
1586
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
output(filterJsonRenderSchemaView(result.jsonRender, flags));
|
|
1388
|
-
return;
|
|
1389
|
-
}
|
|
1390
|
-
if (requested === 'graph') {
|
|
1391
|
-
const graphSchema = result.nodeTypes.find((entry) => entry.type === 'graph');
|
|
1392
|
-
if (graphSchema) {
|
|
1393
|
-
output(filterNodeSchemaView(graphSchema, flags));
|
|
1587
|
+
if (flags.summary || flags.compact) {
|
|
1588
|
+
output(summarizeNodeResult(result));
|
|
1394
1589
|
return;
|
|
1395
1590
|
}
|
|
1396
|
-
output(
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
const nodeType = result.nodeTypes.find((entry) => entry.type === requested);
|
|
1400
|
-
if (nodeType) {
|
|
1401
|
-
output(filterNodeSchemaView(nodeType, flags));
|
|
1402
|
-
return;
|
|
1403
|
-
}
|
|
1404
|
-
die(`Unknown schema type: ${requested}`, 'Run: pmx-canvas node schema');
|
|
1405
|
-
});
|
|
1406
|
-
|
|
1407
|
-
// ── node list ────────────────────────────────────────────────
|
|
1408
|
-
cmd('node list', 'List all nodes on the canvas', [
|
|
1409
|
-
'pmx-canvas node list',
|
|
1410
|
-
'pmx-canvas node list --type markdown',
|
|
1411
|
-
'pmx-canvas node list --type mcp-app',
|
|
1412
|
-
'pmx-canvas node list --ids',
|
|
1413
|
-
], async (args) => {
|
|
1414
|
-
const { flags } = parseFlags(args);
|
|
1415
|
-
if (flags.help || flags.h) return showCommandHelp('node list');
|
|
1416
|
-
|
|
1417
|
-
const layout = (await invokeOperation('layout.get', {})) as { nodes: Array<Record<string, unknown>> };
|
|
1418
|
-
let nodes = layout.nodes;
|
|
1419
|
-
|
|
1420
|
-
if (flags.type && flags.type !== true) {
|
|
1421
|
-
nodes = nodes.filter((n) => n.type === flags.type || n.kind === flags.type);
|
|
1422
|
-
}
|
|
1591
|
+
output(result);
|
|
1592
|
+
},
|
|
1593
|
+
);
|
|
1423
1594
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1595
|
+
// ── node update ──────────────────────────────────────────────
|
|
1596
|
+
cmd(
|
|
1597
|
+
'node update',
|
|
1598
|
+
'Update a node by ID',
|
|
1599
|
+
[
|
|
1600
|
+
'pmx-canvas node update <node-id> --title "New Title"',
|
|
1601
|
+
'pmx-canvas node update <node-id> --content "Updated content"',
|
|
1602
|
+
'pmx-canvas node update <node-id> --title "Moved" --x 500 --y 300',
|
|
1603
|
+
'pmx-canvas node update <node-id> --width 840 --height 620',
|
|
1604
|
+
'pmx-canvas node update <node-id> --spec-file ./dashboard.json',
|
|
1605
|
+
'pmx-canvas node update <graph-id> --data-file ./metrics.json --chart-height 420',
|
|
1606
|
+
'pmx-canvas node update <node-id> --pinned true',
|
|
1607
|
+
'pmx-canvas node update <node-id> --dock-position right',
|
|
1608
|
+
'pmx-canvas node update <node-id> --dock-position none # undock back to the canvas',
|
|
1609
|
+
'pmx-canvas node update <node-id> --lock-arrange',
|
|
1610
|
+
],
|
|
1611
|
+
async (args) => {
|
|
1612
|
+
const { positional, flags } = parseFlags(args);
|
|
1613
|
+
if (flags.help || flags.h) return showCommandHelp('node update');
|
|
1614
|
+
|
|
1615
|
+
const id = positional[0];
|
|
1616
|
+
if (!id) die('Missing node ID', 'pmx-canvas node update <node-id> --title "New Title"');
|
|
1617
|
+
|
|
1618
|
+
const body: Record<string, unknown> = {};
|
|
1619
|
+
await applyStructuredNodeUpdateFlags(body, flags);
|
|
1620
|
+
if (flags.title && flags.title !== true) body.title = flags.title;
|
|
1621
|
+
if (flags.content && flags.content !== true) body.content = flags.content;
|
|
1622
|
+
if (flags.stdin) body.content = await readStdin();
|
|
1623
|
+
|
|
1624
|
+
const x = optionalFiniteFlag(flags, 'x', 'Use a finite number, e.g. --x 500');
|
|
1625
|
+
const y = optionalFiniteFlag(flags, 'y', 'Use a finite number, e.g. --y 300');
|
|
1626
|
+
const width = optionalPositiveFiniteFlag(flags, 'width', 'Use a positive number, e.g. --width 840');
|
|
1627
|
+
const height = optionalPositiveFiniteFlag(flags, 'height', 'Use a positive number, e.g. --height 620');
|
|
1628
|
+
const nodeHeight = optionalPositiveFiniteFlagWithAliases(
|
|
1629
|
+
flags,
|
|
1630
|
+
'Use a positive number, e.g. --node-height 620',
|
|
1631
|
+
'node-height',
|
|
1632
|
+
'nodeHeight',
|
|
1633
|
+
);
|
|
1634
|
+
if (height !== undefined && nodeHeight !== undefined) {
|
|
1635
|
+
die('Use either --height/--node-height, not both.');
|
|
1636
|
+
}
|
|
1637
|
+
const frameHeight = height ?? nodeHeight;
|
|
1638
|
+
const pinned = optionalBooleanFlag(flags, 'pinned', 'Use --pinned true or --pinned false');
|
|
1639
|
+
if (flags['lock-arrange'] && flags['unlock-arrange']) {
|
|
1640
|
+
die('Use either --lock-arrange or --unlock-arrange, not both.');
|
|
1641
|
+
}
|
|
1642
|
+
const arrangeLocked = flags['lock-arrange'] ? true : flags['unlock-arrange'] ? false : undefined;
|
|
1434
1643
|
|
|
1435
|
-
|
|
1436
|
-
cmd('node get', 'Get a node by ID', [
|
|
1437
|
-
'pmx-canvas node get <node-id>',
|
|
1438
|
-
'pmx-canvas node get node-abc123',
|
|
1439
|
-
'pmx-canvas node get node-abc123 --summary',
|
|
1440
|
-
'pmx-canvas node get node-abc123 --field title --field graphConfig',
|
|
1441
|
-
], async (args) => {
|
|
1442
|
-
const { positional, flags } = parseFlags(args);
|
|
1443
|
-
if (flags.help || flags.h) return showCommandHelp('node get');
|
|
1644
|
+
applyStrictSizeFlags(body, flags);
|
|
1444
1645
|
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
if (requestedFields.length > 0) {
|
|
1451
|
-
const picked = Object.fromEntries(requestedFields.map((field) => [field, resolveNodeFieldValue(result, field)]));
|
|
1452
|
-
const missing = requestedFields.filter((field) => picked[field] === undefined);
|
|
1453
|
-
if (missing.length > 0) {
|
|
1454
|
-
die(
|
|
1455
|
-
`Unknown node field${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}`,
|
|
1456
|
-
`Available fields: ${listAvailableNodeFields(result).join(', ')}`,
|
|
1646
|
+
for (const field of TRACE_NODE_FIELDS) {
|
|
1647
|
+
const value = getStringFlag(
|
|
1648
|
+
flags,
|
|
1649
|
+
field,
|
|
1650
|
+
field.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`),
|
|
1457
1651
|
);
|
|
1652
|
+
if (value !== undefined) body[field] = value;
|
|
1458
1653
|
}
|
|
1459
|
-
output({
|
|
1460
|
-
id: result.id ?? id,
|
|
1461
|
-
fields: picked,
|
|
1462
|
-
});
|
|
1463
|
-
return;
|
|
1464
|
-
}
|
|
1465
1654
|
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1655
|
+
if (
|
|
1656
|
+
x !== undefined ||
|
|
1657
|
+
y !== undefined ||
|
|
1658
|
+
width !== undefined ||
|
|
1659
|
+
frameHeight !== undefined ||
|
|
1660
|
+
arrangeLocked !== undefined
|
|
1661
|
+
) {
|
|
1662
|
+
const existing = (await invokeOperation('node.get', { id })) as {
|
|
1663
|
+
position: { x: number; y: number };
|
|
1664
|
+
size: { width: number; height: number };
|
|
1665
|
+
data: Record<string, unknown>;
|
|
1666
|
+
};
|
|
1472
1667
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
'pmx-canvas node update <node-id> --spec-file ./dashboard.json',
|
|
1480
|
-
'pmx-canvas node update <graph-id> --data-file ./metrics.json --chart-height 420',
|
|
1481
|
-
'pmx-canvas node update <node-id> --pinned true',
|
|
1482
|
-
'pmx-canvas node update <node-id> --dock-position right',
|
|
1483
|
-
'pmx-canvas node update <node-id> --dock-position none # undock back to the canvas',
|
|
1484
|
-
'pmx-canvas node update <node-id> --lock-arrange',
|
|
1485
|
-
], async (args) => {
|
|
1486
|
-
const { positional, flags } = parseFlags(args);
|
|
1487
|
-
if (flags.help || flags.h) return showCommandHelp('node update');
|
|
1668
|
+
if (x !== undefined || y !== undefined) {
|
|
1669
|
+
body.position = {
|
|
1670
|
+
x: x ?? existing.position.x,
|
|
1671
|
+
y: y ?? existing.position.y,
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1488
1674
|
|
|
1489
|
-
|
|
1490
|
-
|
|
1675
|
+
if (width !== undefined || frameHeight !== undefined) {
|
|
1676
|
+
body.size = {
|
|
1677
|
+
width: width ?? existing.size.width,
|
|
1678
|
+
height: frameHeight ?? existing.size.height,
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1491
1681
|
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
if (flags.stdin) body.content = await readStdin();
|
|
1682
|
+
if (arrangeLocked !== undefined) {
|
|
1683
|
+
body.arrangeLocked = arrangeLocked;
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1497
1686
|
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
'
|
|
1506
|
-
'
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
die('Use either --lock-arrange or --unlock-arrange, not both.');
|
|
1515
|
-
}
|
|
1516
|
-
const arrangeLocked = flags['lock-arrange']
|
|
1517
|
-
? true
|
|
1518
|
-
: flags['unlock-arrange']
|
|
1519
|
-
? false
|
|
1520
|
-
: undefined;
|
|
1687
|
+
if (pinned !== undefined) body.pinned = pinned;
|
|
1688
|
+
|
|
1689
|
+
// --dock-position left|right|none : dock a node into the top HUD or undock it.
|
|
1690
|
+
// `none`/`null`/empty map to JS null (undock). Assigned with a !== undefined
|
|
1691
|
+
// guard so the null survives JSON.stringify to the server (which accepts a
|
|
1692
|
+
// top-level dockPosition: null). HTTP PATCH already supports this; this is the
|
|
1693
|
+
// CLI path the report (#40) found missing.
|
|
1694
|
+
const dockRaw = getStringFlag(flags, 'dock-position', 'dockPosition');
|
|
1695
|
+
let dockPosition: 'left' | 'right' | null | undefined;
|
|
1696
|
+
if (dockRaw !== undefined) {
|
|
1697
|
+
const v = dockRaw.trim().toLowerCase();
|
|
1698
|
+
if (v === 'left' || v === 'right') dockPosition = v;
|
|
1699
|
+
else if (v === 'none' || v === 'null' || v === '') dockPosition = null;
|
|
1700
|
+
else die(`Invalid --dock-position "${dockRaw}".`, 'Use left, right, or none (to undock).');
|
|
1701
|
+
}
|
|
1702
|
+
if (dockPosition !== undefined) body.dockPosition = dockPosition;
|
|
1521
1703
|
|
|
1522
|
-
|
|
1704
|
+
if (Object.keys(body).length === 0) {
|
|
1705
|
+
die(
|
|
1706
|
+
'No updates specified',
|
|
1707
|
+
'Use --title, --content, --x, --y, --width, --height, --strict-size, --pinned, --dock-position, trace fields, --lock-arrange, --unlock-arrange, or --stdin',
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1523
1710
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1711
|
+
const result = await invokeOperation('node.update', { id, ...body });
|
|
1712
|
+
output(result);
|
|
1713
|
+
},
|
|
1714
|
+
);
|
|
1528
1715
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1716
|
+
// ── node remove ──────────────────────────────────────────────
|
|
1717
|
+
cmd(
|
|
1718
|
+
'node remove',
|
|
1719
|
+
'Remove a node from the canvas',
|
|
1720
|
+
['pmx-canvas node remove <node-id>', 'pmx-canvas node remove node-abc123'],
|
|
1721
|
+
async (args) => {
|
|
1722
|
+
const { positional, flags } = parseFlags(args);
|
|
1723
|
+
if (flags.help || flags.h) return showCommandHelp('node remove');
|
|
1724
|
+
|
|
1725
|
+
const id = positional[0];
|
|
1726
|
+
if (!id) die('Missing node ID', 'pmx-canvas node remove <node-id>');
|
|
1727
|
+
|
|
1728
|
+
const result = await invokeOperation('node.remove', { id });
|
|
1729
|
+
output(result);
|
|
1730
|
+
},
|
|
1731
|
+
);
|
|
1535
1732
|
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1733
|
+
// ── edge add ─────────────────────────────────────────────────
|
|
1734
|
+
cmd(
|
|
1735
|
+
'edge add',
|
|
1736
|
+
'Add an edge between two nodes',
|
|
1737
|
+
[
|
|
1738
|
+
'pmx-canvas edge add --from <node-id> --to <node-id> --type flow',
|
|
1739
|
+
'pmx-canvas edge add --from-search "DVT O3 — GitOps" --to-search "deep work trend" --type relation',
|
|
1740
|
+
'pmx-canvas edge add --from n1 --to n2 --type depends-on --label "imports"',
|
|
1741
|
+
'pmx-canvas edge add --from n1 --to n2 --type references --style dashed --animated',
|
|
1742
|
+
],
|
|
1743
|
+
async (args) => {
|
|
1744
|
+
const { flags } = parseFlags(args);
|
|
1745
|
+
if (flags.help || flags.h) return showCommandHelp('edge add');
|
|
1746
|
+
|
|
1747
|
+
const type = (flags.type as string) || 'flow';
|
|
1748
|
+
const from = typeof flags.from === 'string' ? flags.from : undefined;
|
|
1749
|
+
const to = typeof flags.to === 'string' ? flags.to : undefined;
|
|
1750
|
+
const fromSearch = typeof flags['from-search'] === 'string' ? flags['from-search'] : undefined;
|
|
1751
|
+
const toSearch = typeof flags['to-search'] === 'string' ? flags['to-search'] : undefined;
|
|
1752
|
+
|
|
1753
|
+
if (!from && !fromSearch) {
|
|
1754
|
+
die(
|
|
1755
|
+
'Missing source selector',
|
|
1756
|
+
'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',
|
|
1757
|
+
);
|
|
1541
1758
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
};
|
|
1759
|
+
if (!to && !toSearch) {
|
|
1760
|
+
die(
|
|
1761
|
+
'Missing target selector',
|
|
1762
|
+
'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',
|
|
1763
|
+
);
|
|
1548
1764
|
}
|
|
1549
1765
|
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1766
|
+
const body: Record<string, unknown> = {
|
|
1767
|
+
type,
|
|
1768
|
+
...(from ? { from } : {}),
|
|
1769
|
+
...(to ? { to } : {}),
|
|
1770
|
+
...(fromSearch ? { fromSearch } : {}),
|
|
1771
|
+
...(toSearch ? { toSearch } : {}),
|
|
1772
|
+
};
|
|
1773
|
+
if (flags.label && flags.label !== true) body.label = flags.label;
|
|
1774
|
+
if (typeof flags.style === 'string') body.style = flags.style;
|
|
1775
|
+
if (flags.animated) body.animated = true;
|
|
1554
1776
|
|
|
1555
|
-
|
|
1777
|
+
const result = await invokeOperation('edge.add', body);
|
|
1778
|
+
output(result);
|
|
1779
|
+
},
|
|
1780
|
+
);
|
|
1556
1781
|
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
// CLI path the report (#40) found missing.
|
|
1562
|
-
const dockRaw = getStringFlag(flags, 'dock-position', 'dockPosition');
|
|
1563
|
-
let dockPosition: 'left' | 'right' | null | undefined;
|
|
1564
|
-
if (dockRaw !== undefined) {
|
|
1565
|
-
const v = dockRaw.trim().toLowerCase();
|
|
1566
|
-
if (v === 'left' || v === 'right') dockPosition = v;
|
|
1567
|
-
else if (v === 'none' || v === 'null' || v === '') dockPosition = null;
|
|
1568
|
-
else die(`Invalid --dock-position "${dockRaw}".`, 'Use left, right, or none (to undock).');
|
|
1569
|
-
}
|
|
1570
|
-
if (dockPosition !== undefined) body.dockPosition = dockPosition;
|
|
1782
|
+
// ── edge list ────────────────────────────────────────────────
|
|
1783
|
+
cmd('edge list', 'List all edges on the canvas', ['pmx-canvas edge list'], async (args) => {
|
|
1784
|
+
const { flags } = parseFlags(args);
|
|
1785
|
+
if (flags.help || flags.h) return showCommandHelp('edge list');
|
|
1571
1786
|
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
'Use --title, --content, --x, --y, --width, --height, --strict-size, --pinned, --dock-position, trace fields, --lock-arrange, --unlock-arrange, or --stdin',
|
|
1576
|
-
);
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
const result = await invokeOperation('node.update', { id, ...body });
|
|
1580
|
-
output(result);
|
|
1581
|
-
});
|
|
1582
|
-
|
|
1583
|
-
// ── node remove ──────────────────────────────────────────────
|
|
1584
|
-
cmd('node remove', 'Remove a node from the canvas', [
|
|
1585
|
-
'pmx-canvas node remove <node-id>',
|
|
1586
|
-
'pmx-canvas node remove node-abc123',
|
|
1587
|
-
], async (args) => {
|
|
1588
|
-
const { positional, flags } = parseFlags(args);
|
|
1589
|
-
if (flags.help || flags.h) return showCommandHelp('node remove');
|
|
1590
|
-
|
|
1591
|
-
const id = positional[0];
|
|
1592
|
-
if (!id) die('Missing node ID', 'pmx-canvas node remove <node-id>');
|
|
1593
|
-
|
|
1594
|
-
const result = await invokeOperation('node.remove', { id });
|
|
1595
|
-
output(result);
|
|
1596
|
-
});
|
|
1597
|
-
|
|
1598
|
-
// ── edge add ─────────────────────────────────────────────────
|
|
1599
|
-
cmd('edge add', 'Add an edge between two nodes', [
|
|
1600
|
-
'pmx-canvas edge add --from <node-id> --to <node-id> --type flow',
|
|
1601
|
-
'pmx-canvas edge add --from-search "DVT O3 — GitOps" --to-search "deep work trend" --type relation',
|
|
1602
|
-
'pmx-canvas edge add --from n1 --to n2 --type depends-on --label "imports"',
|
|
1603
|
-
'pmx-canvas edge add --from n1 --to n2 --type references --style dashed --animated',
|
|
1604
|
-
], async (args) => {
|
|
1605
|
-
const { flags } = parseFlags(args);
|
|
1606
|
-
if (flags.help || flags.h) return showCommandHelp('edge add');
|
|
1607
|
-
|
|
1608
|
-
const type = (flags.type as string) || 'flow';
|
|
1609
|
-
const from = typeof flags.from === 'string' ? flags.from : undefined;
|
|
1610
|
-
const to = typeof flags.to === 'string' ? flags.to : undefined;
|
|
1611
|
-
const fromSearch = typeof flags['from-search'] === 'string' ? flags['from-search'] : undefined;
|
|
1612
|
-
const toSearch = typeof flags['to-search'] === 'string' ? flags['to-search'] : undefined;
|
|
1613
|
-
|
|
1614
|
-
if (!from && !fromSearch) {
|
|
1615
|
-
die(
|
|
1616
|
-
'Missing source selector',
|
|
1617
|
-
'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',
|
|
1618
|
-
);
|
|
1619
|
-
}
|
|
1620
|
-
if (!to && !toSearch) {
|
|
1621
|
-
die(
|
|
1622
|
-
'Missing target selector',
|
|
1623
|
-
'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',
|
|
1624
|
-
);
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
const body: Record<string, unknown> = {
|
|
1628
|
-
type,
|
|
1629
|
-
...(from ? { from } : {}),
|
|
1630
|
-
...(to ? { to } : {}),
|
|
1631
|
-
...(fromSearch ? { fromSearch } : {}),
|
|
1632
|
-
...(toSearch ? { toSearch } : {}),
|
|
1633
|
-
};
|
|
1634
|
-
if (flags.label && flags.label !== true) body.label = flags.label;
|
|
1635
|
-
if (typeof flags.style === 'string') body.style = flags.style;
|
|
1636
|
-
if (flags.animated) body.animated = true;
|
|
1637
|
-
|
|
1638
|
-
const result = await invokeOperation('edge.add', body);
|
|
1639
|
-
output(result);
|
|
1640
|
-
});
|
|
1641
|
-
|
|
1642
|
-
// ── edge list ────────────────────────────────────────────────
|
|
1643
|
-
cmd('edge list', 'List all edges on the canvas', [
|
|
1644
|
-
'pmx-canvas edge list',
|
|
1645
|
-
], async (args) => {
|
|
1646
|
-
const { flags } = parseFlags(args);
|
|
1647
|
-
if (flags.help || flags.h) return showCommandHelp('edge list');
|
|
1648
|
-
|
|
1649
|
-
const layout = (await api('GET', '/api/canvas/state')) as { edges: unknown[] };
|
|
1650
|
-
output(layout.edges);
|
|
1651
|
-
});
|
|
1787
|
+
const layout = (await api('GET', '/api/canvas/state')) as { edges: unknown[] };
|
|
1788
|
+
output(layout.edges);
|
|
1789
|
+
});
|
|
1652
1790
|
|
|
1653
1791
|
// ── edge remove ──────────────────────────────────────────────
|
|
1654
|
-
cmd('edge remove', 'Remove an edge by ID', [
|
|
1655
|
-
'pmx-canvas edge remove <edge-id>',
|
|
1656
|
-
], async (args) => {
|
|
1792
|
+
cmd('edge remove', 'Remove an edge by ID', ['pmx-canvas edge remove <edge-id>'], async (args) => {
|
|
1657
1793
|
const { positional, flags } = parseFlags(args);
|
|
1658
1794
|
if (flags.help || flags.h) return showCommandHelp('edge remove');
|
|
1659
1795
|
|
|
@@ -1665,40 +1801,42 @@ cmd('edge remove', 'Remove an edge by ID', [
|
|
|
1665
1801
|
});
|
|
1666
1802
|
|
|
1667
1803
|
// ── search ───────────────────────────────────────────────────
|
|
1668
|
-
cmd(
|
|
1669
|
-
'
|
|
1670
|
-
'
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1804
|
+
cmd(
|
|
1805
|
+
'search',
|
|
1806
|
+
'Search nodes by title or content',
|
|
1807
|
+
['pmx-canvas search "design doc"', 'pmx-canvas search --query "TODO"'],
|
|
1808
|
+
async (args) => {
|
|
1809
|
+
const { positional, flags } = parseFlags(args);
|
|
1810
|
+
if (flags.help || flags.h) return showCommandHelp('search');
|
|
1811
|
+
|
|
1812
|
+
const query = positional[0] || (typeof flags.query === 'string' ? flags.query : '');
|
|
1813
|
+
if (!query) die('Missing search query', 'pmx-canvas search "query"');
|
|
1814
|
+
|
|
1815
|
+
const result = await invokeOperation('search', { q: query });
|
|
1816
|
+
output(result);
|
|
1817
|
+
},
|
|
1818
|
+
);
|
|
1681
1819
|
|
|
1682
1820
|
// ── layout ───────────────────────────────────────────────────
|
|
1683
|
-
cmd(
|
|
1684
|
-
'
|
|
1685
|
-
'
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1821
|
+
cmd(
|
|
1822
|
+
'layout',
|
|
1823
|
+
'Get the full canvas layout (nodes, edges, viewport)',
|
|
1824
|
+
['pmx-canvas layout', 'pmx-canvas layout --summary'],
|
|
1825
|
+
async (args) => {
|
|
1826
|
+
const { flags } = parseFlags(args);
|
|
1827
|
+
if (flags.help || flags.h) return showCommandHelp('layout');
|
|
1828
|
+
|
|
1829
|
+
if (flags.summary || flags.compact) {
|
|
1830
|
+
output(await api('GET', '/api/canvas/summary'));
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
const result = await api('GET', '/api/canvas/state');
|
|
1834
|
+
output(result);
|
|
1835
|
+
},
|
|
1836
|
+
);
|
|
1697
1837
|
|
|
1698
1838
|
// ── status ───────────────────────────────────────────────────
|
|
1699
|
-
cmd('status', 'Quick canvas summary', [
|
|
1700
|
-
'pmx-canvas status',
|
|
1701
|
-
], async (args) => {
|
|
1839
|
+
cmd('status', 'Quick canvas summary', ['pmx-canvas status'], async (args) => {
|
|
1702
1840
|
const { flags } = parseFlags(args);
|
|
1703
1841
|
if (flags.help || flags.h) return showCommandHelp('status');
|
|
1704
1842
|
|
|
@@ -1712,11 +1850,12 @@ cmd('status', 'Quick canvas summary', [
|
|
|
1712
1850
|
const typeCounts: Record<string, number> = {};
|
|
1713
1851
|
for (const n of layout.nodes) {
|
|
1714
1852
|
const data = isRecord(n.data) ? n.data : {};
|
|
1715
|
-
const t =
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1853
|
+
const t =
|
|
1854
|
+
typeof n.kind === 'string'
|
|
1855
|
+
? n.kind
|
|
1856
|
+
: n.type === 'mcp-app' && data.hostMode === 'hosted' && typeof data.path === 'string'
|
|
1857
|
+
? 'web-artifact'
|
|
1858
|
+
: (n.type as string);
|
|
1720
1859
|
typeCounts[t] = (typeCounts[t] || 0) + 1;
|
|
1721
1860
|
}
|
|
1722
1861
|
|
|
@@ -1730,25 +1869,24 @@ cmd('status', 'Quick canvas summary', [
|
|
|
1730
1869
|
});
|
|
1731
1870
|
|
|
1732
1871
|
// ── arrange ──────────────────────────────────────────────────
|
|
1733
|
-
cmd(
|
|
1734
|
-
'
|
|
1735
|
-
'
|
|
1736
|
-
'pmx-canvas arrange --layout flow',
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
}
|
|
1872
|
+
cmd(
|
|
1873
|
+
'arrange',
|
|
1874
|
+
'Auto-arrange nodes on the canvas',
|
|
1875
|
+
['pmx-canvas arrange', 'pmx-canvas arrange --layout column', 'pmx-canvas arrange --layout flow'],
|
|
1876
|
+
async (args) => {
|
|
1877
|
+
const { flags } = parseFlags(args);
|
|
1878
|
+
if (flags.help || flags.h) return showCommandHelp('arrange');
|
|
1879
|
+
|
|
1880
|
+
const body: Record<string, unknown> = {};
|
|
1881
|
+
if (flags.layout && flags.layout !== true) body.layout = flags.layout;
|
|
1882
|
+
|
|
1883
|
+
const result = await api('POST', '/api/canvas/arrange', body);
|
|
1884
|
+
output(result);
|
|
1885
|
+
},
|
|
1886
|
+
);
|
|
1747
1887
|
|
|
1748
1888
|
// ── focus ────────────────────────────────────────────────────
|
|
1749
|
-
cmd('focus', 'Pan viewport to center on a node', [
|
|
1750
|
-
'pmx-canvas focus <node-id>',
|
|
1751
|
-
], async (args) => {
|
|
1889
|
+
cmd('focus', 'Pan viewport to center on a node', ['pmx-canvas focus <node-id>'], async (args) => {
|
|
1752
1890
|
const { positional, flags } = parseFlags(args);
|
|
1753
1891
|
if (flags.help || flags.h) return showCommandHelp('focus');
|
|
1754
1892
|
|
|
@@ -1759,269 +1897,347 @@ cmd('focus', 'Pan viewport to center on a node', [
|
|
|
1759
1897
|
output(result);
|
|
1760
1898
|
});
|
|
1761
1899
|
|
|
1762
|
-
cmd(
|
|
1763
|
-
'
|
|
1764
|
-
'
|
|
1765
|
-
'pmx-canvas fit node-a node-b',
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
}
|
|
1900
|
+
cmd(
|
|
1901
|
+
'fit',
|
|
1902
|
+
'Fit the viewport to all nodes or a selected subset',
|
|
1903
|
+
['pmx-canvas fit', 'pmx-canvas fit --width 1440 --height 900 --padding 80', 'pmx-canvas fit node-a node-b'],
|
|
1904
|
+
async (args) => {
|
|
1905
|
+
const { positional, flags } = parseFlags(args);
|
|
1906
|
+
if (flags.help || flags.h) return showCommandHelp('fit');
|
|
1907
|
+
|
|
1908
|
+
const body: Record<string, unknown> = {};
|
|
1909
|
+
const width = optionalPositiveFiniteFlag(flags, 'width', 'Use a positive number, e.g. --width 1440');
|
|
1910
|
+
const height = optionalPositiveFiniteFlag(flags, 'height', 'Use a positive number, e.g. --height 900');
|
|
1911
|
+
const padding = optionalPositiveFiniteFlag(flags, 'padding', 'Use a positive number, e.g. --padding 80');
|
|
1912
|
+
const maxScale = optionalPositiveFiniteFlag(flags, 'max-scale', 'Use a positive number, e.g. --max-scale 1');
|
|
1913
|
+
if (width !== undefined) body.width = width;
|
|
1914
|
+
if (height !== undefined) body.height = height;
|
|
1915
|
+
if (padding !== undefined) body.padding = padding;
|
|
1916
|
+
if (maxScale !== undefined) body.maxScale = maxScale;
|
|
1917
|
+
if (positional.length > 0) body.nodeIds = positional;
|
|
1918
|
+
|
|
1919
|
+
const result = await api('POST', '/api/canvas/fit', body);
|
|
1920
|
+
output(result);
|
|
1921
|
+
},
|
|
1922
|
+
);
|
|
1784
1923
|
|
|
1785
1924
|
// ── external-app add ─────────────────────────────────────────
|
|
1786
|
-
cmd(
|
|
1787
|
-
'
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
title: typeof flags.title === 'string' ? flags.title : 'Excalidraw Diagram',
|
|
1799
|
-
elements: DEFAULT_EXCALIDRAW_ELEMENTS,
|
|
1800
|
-
};
|
|
1801
|
-
const nodeId = getStringFlag(flags, 'node-id', 'nodeId', 'id');
|
|
1802
|
-
if (nodeId) body.nodeId = nodeId;
|
|
1803
|
-
const elementsJson = getStringFlag(flags, 'elements-json', 'elements');
|
|
1804
|
-
if (elementsJson !== undefined) body.elements = parseJsonValue(elementsJson, 'Excalidraw elements', 'Use --elements-json \'[{"type":"rectangle","id":"r1","x":0,"y":0,"width":120,"height":80}]\'');
|
|
1805
|
-
const elementsFile = getStringFlag(flags, 'elements-file', 'initial-file');
|
|
1806
|
-
if (elementsFile) body.elements = parseJsonValue(readFileSync(elementsFile, 'utf-8'), 'Excalidraw elements file', 'Use --elements-file ./scene.excalidraw');
|
|
1807
|
-
applyCommonGeometryFlags(body, flags, {
|
|
1808
|
-
x: 'Use a finite number, e.g. --x 500',
|
|
1809
|
-
y: 'Use a finite number, e.g. --y 300',
|
|
1810
|
-
width: 'Use a positive number, e.g. --width 960',
|
|
1811
|
-
height: 'Use a positive number, e.g. --height 720',
|
|
1812
|
-
});
|
|
1813
|
-
const timeoutMs = optionalPositiveFiniteFlag(flags, 'timeout-ms', 'Use a positive number, e.g. --timeout-ms 120000');
|
|
1814
|
-
if (timeoutMs !== undefined) body.timeoutMs = timeoutMs;
|
|
1925
|
+
cmd(
|
|
1926
|
+
'external-app add',
|
|
1927
|
+
'Create a hosted external app node',
|
|
1928
|
+
['pmx-canvas external-app add --kind excalidraw --title "Diagram"'],
|
|
1929
|
+
async (args) => {
|
|
1930
|
+
const { flags } = parseFlags(args);
|
|
1931
|
+
if (flags.help || flags.h) return showCommandHelp('external-app add');
|
|
1932
|
+
|
|
1933
|
+
const kind = typeof flags.kind === 'string' ? flags.kind.trim() : '';
|
|
1934
|
+
if (kind !== 'excalidraw') {
|
|
1935
|
+
die('Unsupported external app kind.', 'Use: pmx-canvas external-app add --kind excalidraw --title "Diagram"');
|
|
1936
|
+
}
|
|
1815
1937
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1938
|
+
const body: Record<string, unknown> = {
|
|
1939
|
+
title: typeof flags.title === 'string' ? flags.title : 'Excalidraw Diagram',
|
|
1940
|
+
elements: DEFAULT_EXCALIDRAW_ELEMENTS,
|
|
1941
|
+
};
|
|
1942
|
+
const nodeId = getStringFlag(flags, 'node-id', 'nodeId', 'id');
|
|
1943
|
+
if (nodeId) body.nodeId = nodeId;
|
|
1944
|
+
const elementsJson = getStringFlag(flags, 'elements-json', 'elements');
|
|
1945
|
+
if (elementsJson !== undefined)
|
|
1946
|
+
body.elements = parseJsonValue(
|
|
1947
|
+
elementsJson,
|
|
1948
|
+
'Excalidraw elements',
|
|
1949
|
+
'Use --elements-json \'[{"type":"rectangle","id":"r1","x":0,"y":0,"width":120,"height":80}]\'',
|
|
1950
|
+
);
|
|
1951
|
+
const elementsFile = getStringFlag(flags, 'elements-file', 'initial-file');
|
|
1952
|
+
if (elementsFile)
|
|
1953
|
+
body.elements = parseJsonValue(
|
|
1954
|
+
readFileSync(elementsFile, 'utf-8'),
|
|
1955
|
+
'Excalidraw elements file',
|
|
1956
|
+
'Use --elements-file ./scene.excalidraw',
|
|
1957
|
+
);
|
|
1958
|
+
applyCommonGeometryFlags(body, flags, {
|
|
1959
|
+
x: 'Use a finite number, e.g. --x 500',
|
|
1960
|
+
y: 'Use a finite number, e.g. --y 300',
|
|
1961
|
+
width: 'Use a positive number, e.g. --width 960',
|
|
1962
|
+
height: 'Use a positive number, e.g. --height 720',
|
|
1963
|
+
});
|
|
1964
|
+
const timeoutMs = optionalPositiveFiniteFlag(
|
|
1965
|
+
flags,
|
|
1966
|
+
'timeout-ms',
|
|
1967
|
+
'Use a positive number, e.g. --timeout-ms 120000',
|
|
1968
|
+
);
|
|
1969
|
+
if (timeoutMs !== undefined) body.timeoutMs = timeoutMs;
|
|
1821
1970
|
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1971
|
+
const result = await api('POST', '/api/canvas/diagram', body);
|
|
1972
|
+
output(
|
|
1973
|
+
result && typeof result === 'object' && !Array.isArray(result) && 'nodeId' in result && !('id' in result)
|
|
1974
|
+
? { id: (result as { nodeId?: unknown }).nodeId, ...result }
|
|
1975
|
+
: result,
|
|
1976
|
+
);
|
|
1977
|
+
},
|
|
1978
|
+
);
|
|
1979
|
+
|
|
1980
|
+
cmd(
|
|
1981
|
+
'diagram add',
|
|
1982
|
+
'Create an Excalidraw diagram node',
|
|
1983
|
+
[
|
|
1984
|
+
'pmx-canvas diagram add --title "Architecture"',
|
|
1985
|
+
'pmx-canvas diagram add --title "Architecture" --elements \'[{"type":"rectangle","id":"r1","x":0,"y":0,"width":120,"height":80}]\'',
|
|
1986
|
+
],
|
|
1987
|
+
async (args) => {
|
|
1988
|
+
const { flags } = parseFlags(args);
|
|
1989
|
+
if (flags.help || flags.h) return showCommandHelp('diagram add');
|
|
1990
|
+
const externalAppAdd = COMMANDS['external-app add'];
|
|
1991
|
+
await externalAppAdd.run([...args, '--kind', 'excalidraw']);
|
|
1992
|
+
},
|
|
1993
|
+
);
|
|
1831
1994
|
|
|
1832
1995
|
// ── pin ──────────────────────────────────────────────────────
|
|
1833
|
-
cmd(
|
|
1834
|
-
'
|
|
1835
|
-
'
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1996
|
+
cmd(
|
|
1997
|
+
'pin',
|
|
1998
|
+
'Manage context pins',
|
|
1999
|
+
[
|
|
2000
|
+
'pmx-canvas pin node1 node2 node3',
|
|
2001
|
+
'pmx-canvas pin --set node1 node2 node3',
|
|
2002
|
+
'pmx-canvas pin --list',
|
|
2003
|
+
'pmx-canvas pin --clear',
|
|
2004
|
+
],
|
|
2005
|
+
async (args) => {
|
|
2006
|
+
const { positional, flags } = parseFlags(args);
|
|
2007
|
+
if (flags.help || flags.h) return showCommandHelp('pin');
|
|
2008
|
+
|
|
2009
|
+
if (flags.list) {
|
|
2010
|
+
const result = await api('GET', '/api/canvas/pinned-context');
|
|
2011
|
+
output(result);
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
1841
2014
|
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
2015
|
+
if (flags.clear) {
|
|
2016
|
+
const result = await invokeOperation('pin.set', { nodeIds: [] });
|
|
2017
|
+
output(result);
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
1847
2020
|
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
2021
|
+
// --set: positional args are node IDs
|
|
2022
|
+
if (positional.length > 0 || flags.set) {
|
|
2023
|
+
const result = await invokeOperation('pin.set', { nodeIds: positional });
|
|
2024
|
+
output(result);
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
1853
2027
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
const result = await invokeOperation('pin.set', { nodeIds: positional });
|
|
2028
|
+
// Default: list
|
|
2029
|
+
const result = await api('GET', '/api/canvas/pinned-context');
|
|
1857
2030
|
output(result);
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
// Default: list
|
|
1862
|
-
const result = await api('GET', '/api/canvas/pinned-context');
|
|
1863
|
-
output(result);
|
|
1864
|
-
});
|
|
2031
|
+
},
|
|
2032
|
+
);
|
|
1865
2033
|
|
|
1866
2034
|
// ── AX ────────────────────────────────────────────────────────
|
|
1867
|
-
cmd('ax status', 'Read host-agnostic PMX AX state', [
|
|
1868
|
-
'pmx-canvas ax status',
|
|
1869
|
-
], async (args) => {
|
|
2035
|
+
cmd('ax status', 'Read host-agnostic PMX AX state', ['pmx-canvas ax status'], async (args) => {
|
|
1870
2036
|
const { flags } = parseFlags(args);
|
|
1871
2037
|
if (flags.help || flags.h) return showCommandHelp('ax status');
|
|
1872
2038
|
|
|
1873
2039
|
output(await api('GET', '/api/canvas/ax'));
|
|
1874
2040
|
});
|
|
1875
2041
|
|
|
1876
|
-
cmd('ax context', 'Read agent-ready PMX AX context', [
|
|
1877
|
-
'pmx-canvas ax context',
|
|
1878
|
-
], async (args) => {
|
|
2042
|
+
cmd('ax context', 'Read agent-ready PMX AX context', ['pmx-canvas ax context'], async (args) => {
|
|
1879
2043
|
const { flags } = parseFlags(args);
|
|
1880
2044
|
if (flags.help || flags.h) return showCommandHelp('ax context');
|
|
1881
2045
|
|
|
1882
2046
|
output(await api('GET', '/api/canvas/ax/context'));
|
|
1883
2047
|
});
|
|
1884
2048
|
|
|
1885
|
-
cmd(
|
|
1886
|
-
'
|
|
1887
|
-
'
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
const nodeIds = flags.clear ? [] : positional;
|
|
1893
|
-
if (!flags.clear && nodeIds.length === 0) {
|
|
1894
|
-
die('Missing node ID', 'pmx-canvas ax focus <node-id> [more-node-ids]');
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
output(await api('POST', '/api/canvas/ax/focus', { nodeIds, source: resolveAxSource(flags) }));
|
|
1898
|
-
});
|
|
1899
|
-
|
|
1900
|
-
cmd('ax event add', 'Record a normalized AX timeline event', [
|
|
1901
|
-
'pmx-canvas ax event add --kind tool-start --summary "ran tests"',
|
|
1902
|
-
'pmx-canvas ax event add --kind failure --summary "build broke" --detail "..." node1 node2',
|
|
1903
|
-
], async (args) => {
|
|
1904
|
-
const { positional, flags } = parseFlags(args);
|
|
1905
|
-
if (flags.help || flags.h) return showCommandHelp('ax event add');
|
|
1906
|
-
|
|
1907
|
-
const kind = requireFlag(flags, 'kind', 'pmx-canvas ax event add --kind <kind> --summary <text>');
|
|
1908
|
-
const summary = requireFlag(flags, 'summary', 'pmx-canvas ax event add --kind <kind> --summary <text>');
|
|
1909
|
-
const detail = getStringFlag(flags, 'detail');
|
|
1910
|
-
|
|
1911
|
-
output(await api('POST', '/api/canvas/ax/event', {
|
|
1912
|
-
kind,
|
|
1913
|
-
summary,
|
|
1914
|
-
...(detail ? { detail } : {}),
|
|
1915
|
-
...(positional.length > 0 ? { nodeIds: positional } : {}),
|
|
1916
|
-
source: resolveAxSource(flags),
|
|
1917
|
-
}));
|
|
1918
|
-
});
|
|
1919
|
-
|
|
1920
|
-
cmd('ax steer', 'Send a steering message to the active agent session', [
|
|
1921
|
-
'pmx-canvas ax steer "focus on the failing test first"',
|
|
1922
|
-
'pmx-canvas ax steer --message "stop and re-plan"',
|
|
1923
|
-
], async (args) => {
|
|
1924
|
-
const { positional, flags } = parseFlags(args);
|
|
1925
|
-
if (flags.help || flags.h) return showCommandHelp('ax steer');
|
|
2049
|
+
cmd(
|
|
2050
|
+
'ax focus',
|
|
2051
|
+
'Set or clear PMX AX focus without moving the viewport',
|
|
2052
|
+
['pmx-canvas ax focus node1 node2', 'pmx-canvas ax focus --clear'],
|
|
2053
|
+
async (args) => {
|
|
2054
|
+
const { positional, flags } = parseFlags(args);
|
|
2055
|
+
if (flags.help || flags.h) return showCommandHelp('ax focus');
|
|
1926
2056
|
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
output(await api('POST', '/api/canvas/ax/steer', { message, source: resolveAxSource(flags) }));
|
|
1933
|
-
});
|
|
1934
|
-
|
|
1935
|
-
cmd('ax interaction', 'Submit a node-originated AX interaction (capability-gated)', [
|
|
1936
|
-
'pmx-canvas ax interaction --type ax.work.create --node node-1 --payload \'{"title":"Wire auth"}\'',
|
|
1937
|
-
'pmx-canvas ax interaction --type ax.focus.set --node node-2',
|
|
1938
|
-
], async (args) => {
|
|
1939
|
-
const { flags } = parseFlags(args);
|
|
1940
|
-
if (flags.help || flags.h) return showCommandHelp('ax interaction');
|
|
1941
|
-
|
|
1942
|
-
const type = getStringFlag(flags, 'type');
|
|
1943
|
-
if (!type) die('Missing --type', 'pmx-canvas ax interaction --type <ax.*> --node <id> [--payload <json>]');
|
|
1944
|
-
const sourceNodeId = getStringFlag(flags, 'node');
|
|
1945
|
-
if (!sourceNodeId) die('Missing --node', 'pmx-canvas ax interaction --type <ax.*> --node <id>');
|
|
1946
|
-
|
|
1947
|
-
let payload: unknown;
|
|
1948
|
-
const payloadRaw = getStringFlag(flags, 'payload');
|
|
1949
|
-
if (payloadRaw) {
|
|
1950
|
-
try {
|
|
1951
|
-
payload = JSON.parse(payloadRaw);
|
|
1952
|
-
} catch {
|
|
1953
|
-
die('Invalid --payload JSON', 'pmx-canvas ax interaction --payload \'{"title":"..."}\'');
|
|
2057
|
+
const nodeIds = flags.clear ? [] : positional;
|
|
2058
|
+
if (!flags.clear && nodeIds.length === 0) {
|
|
2059
|
+
die('Missing node ID', 'pmx-canvas ax focus <node-id> [more-node-ids]');
|
|
1954
2060
|
}
|
|
1955
|
-
}
|
|
1956
2061
|
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
2062
|
+
output(await api('POST', '/api/canvas/ax/focus', { nodeIds, source: resolveAxSource(flags) }));
|
|
2063
|
+
},
|
|
2064
|
+
);
|
|
2065
|
+
|
|
2066
|
+
cmd(
|
|
2067
|
+
'ax event add',
|
|
2068
|
+
'Record a normalized AX timeline event',
|
|
2069
|
+
[
|
|
2070
|
+
'pmx-canvas ax event add --kind tool-start --summary "ran tests"',
|
|
2071
|
+
'pmx-canvas ax event add --kind failure --summary "build broke" --detail "..." node1 node2',
|
|
2072
|
+
],
|
|
2073
|
+
async (args) => {
|
|
2074
|
+
const { positional, flags } = parseFlags(args);
|
|
2075
|
+
if (flags.help || flags.h) return showCommandHelp('ax event add');
|
|
2076
|
+
|
|
2077
|
+
const kind = requireFlag(flags, 'kind', 'pmx-canvas ax event add --kind <kind> --summary <text>');
|
|
2078
|
+
const summary = requireFlag(flags, 'summary', 'pmx-canvas ax event add --kind <kind> --summary <text>');
|
|
2079
|
+
const detail = getStringFlag(flags, 'detail');
|
|
2080
|
+
|
|
2081
|
+
output(
|
|
2082
|
+
await api('POST', '/api/canvas/ax/event', {
|
|
2083
|
+
kind,
|
|
2084
|
+
summary,
|
|
2085
|
+
...(detail ? { detail } : {}),
|
|
2086
|
+
...(positional.length > 0 ? { nodeIds: positional } : {}),
|
|
2087
|
+
source: resolveAxSource(flags),
|
|
2088
|
+
}),
|
|
2089
|
+
);
|
|
2090
|
+
},
|
|
2091
|
+
);
|
|
2092
|
+
|
|
2093
|
+
cmd(
|
|
2094
|
+
'ax steer',
|
|
2095
|
+
'Send a steering message to the active agent session',
|
|
2096
|
+
['pmx-canvas ax steer "focus on the failing test first"', 'pmx-canvas ax steer --message "stop and re-plan"'],
|
|
2097
|
+
async (args) => {
|
|
2098
|
+
const { positional, flags } = parseFlags(args);
|
|
2099
|
+
if (flags.help || flags.h) return showCommandHelp('ax steer');
|
|
2100
|
+
|
|
2101
|
+
const message = getStringFlag(flags, 'message') ?? positional.join(' ').trim();
|
|
2102
|
+
if (!message) {
|
|
2103
|
+
die('Missing steering message', 'pmx-canvas ax steer <message>');
|
|
2104
|
+
}
|
|
1995
2105
|
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2106
|
+
output(await api('POST', '/api/canvas/ax/steer', { message, source: resolveAxSource(flags) }));
|
|
2107
|
+
},
|
|
2108
|
+
);
|
|
2109
|
+
|
|
2110
|
+
cmd(
|
|
2111
|
+
'ax interaction',
|
|
2112
|
+
'Submit a node-originated AX interaction (capability-gated)',
|
|
2113
|
+
[
|
|
2114
|
+
'pmx-canvas ax interaction --type ax.work.create --node node-1 --payload \'{"title":"Wire auth"}\'',
|
|
2115
|
+
'pmx-canvas ax interaction --type ax.focus.set --node node-2',
|
|
2116
|
+
],
|
|
2117
|
+
async (args) => {
|
|
2118
|
+
const { flags } = parseFlags(args);
|
|
2119
|
+
if (flags.help || flags.h) return showCommandHelp('ax interaction');
|
|
2120
|
+
|
|
2121
|
+
const type = getStringFlag(flags, 'type');
|
|
2122
|
+
if (!type) die('Missing --type', 'pmx-canvas ax interaction --type <ax.*> --node <id> [--payload <json>]');
|
|
2123
|
+
const sourceNodeId = getStringFlag(flags, 'node');
|
|
2124
|
+
if (!sourceNodeId) die('Missing --node', 'pmx-canvas ax interaction --type <ax.*> --node <id>');
|
|
2125
|
+
|
|
2126
|
+
let payload: unknown;
|
|
2127
|
+
const payloadRaw = getStringFlag(flags, 'payload');
|
|
2128
|
+
if (payloadRaw) {
|
|
2129
|
+
try {
|
|
2130
|
+
payload = JSON.parse(payloadRaw);
|
|
2131
|
+
} catch {
|
|
2132
|
+
die('Invalid --payload JSON', 'pmx-canvas ax interaction --payload \'{"title":"..."}\'');
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2010
2135
|
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2136
|
+
output(
|
|
2137
|
+
await api('POST', '/api/canvas/ax/interaction', {
|
|
2138
|
+
type,
|
|
2139
|
+
sourceNodeId,
|
|
2140
|
+
...(payload !== undefined ? { payload } : {}),
|
|
2141
|
+
source: resolveAxSource(flags),
|
|
2142
|
+
}),
|
|
2143
|
+
);
|
|
2144
|
+
},
|
|
2145
|
+
);
|
|
2146
|
+
|
|
2147
|
+
cmd(
|
|
2148
|
+
'ax delivery list',
|
|
2149
|
+
'List pending AX steering for a consumer (loop-safe)',
|
|
2150
|
+
[
|
|
2151
|
+
'pmx-canvas ax delivery list',
|
|
2152
|
+
'pmx-canvas ax delivery list --consumer copilot --limit 20',
|
|
2153
|
+
'pmx-canvas ax delivery list --order newest # latest browser steering first (#68)',
|
|
2154
|
+
],
|
|
2155
|
+
async (args) => {
|
|
2156
|
+
const { flags } = parseFlags(args);
|
|
2157
|
+
if (flags.help || flags.h) return showCommandHelp('ax delivery list');
|
|
2158
|
+
const consumer = getStringFlag(flags, 'consumer');
|
|
2159
|
+
const limit = optionalNumberFlag(flags, 'limit', 'pmx-canvas ax delivery list --limit <n>');
|
|
2160
|
+
const order = getStringFlag(flags, 'order');
|
|
2161
|
+
if (order !== undefined && order !== 'newest' && order !== 'oldest') {
|
|
2162
|
+
die('Invalid --order', 'pmx-canvas ax delivery list --order newest|oldest');
|
|
2163
|
+
}
|
|
2164
|
+
const params = new URLSearchParams();
|
|
2165
|
+
if (consumer) params.set('consumer', consumer);
|
|
2166
|
+
if (limit) params.set('limit', String(limit));
|
|
2167
|
+
if (order) params.set('order', order);
|
|
2168
|
+
const qs = params.toString();
|
|
2169
|
+
output(await api('GET', `/api/canvas/ax/delivery/pending${qs ? `?${qs}` : ''}`));
|
|
2170
|
+
},
|
|
2171
|
+
);
|
|
2172
|
+
|
|
2173
|
+
cmd(
|
|
2174
|
+
'ax delivery mark',
|
|
2175
|
+
'Mark an AX steering message as delivered',
|
|
2176
|
+
['pmx-canvas ax delivery mark <steering-id>'],
|
|
2177
|
+
async (args) => {
|
|
2178
|
+
const { positional, flags } = parseFlags(args);
|
|
2179
|
+
if (flags.help || flags.h) return showCommandHelp('ax delivery mark');
|
|
2180
|
+
const id = getStringFlag(flags, 'id') ?? positional[0];
|
|
2181
|
+
if (!id) die('Missing steering id', 'pmx-canvas ax delivery mark <steering-id>');
|
|
2182
|
+
output(await api('POST', `/api/canvas/ax/delivery/${encodeURIComponent(id)}/mark`, {}));
|
|
2183
|
+
},
|
|
2184
|
+
);
|
|
2185
|
+
|
|
2186
|
+
cmd(
|
|
2187
|
+
'ax elicitation request',
|
|
2188
|
+
'Request structured human input',
|
|
2189
|
+
[
|
|
2190
|
+
'pmx-canvas ax elicitation request --prompt "Who owns this migration?"',
|
|
2191
|
+
'pmx-canvas ax elicitation request --prompt "Pick a region" --fields region,owner',
|
|
2192
|
+
],
|
|
2193
|
+
async (args) => {
|
|
2194
|
+
const { flags } = parseFlags(args);
|
|
2195
|
+
if (flags.help || flags.h) return showCommandHelp('ax elicitation request');
|
|
2196
|
+
const prompt = requireFlag(flags, 'prompt', 'pmx-canvas ax elicitation request --prompt <text>');
|
|
2197
|
+
const fields = getStringFlag(flags, 'fields');
|
|
2198
|
+
output(
|
|
2199
|
+
await api('POST', '/api/canvas/ax/elicitation', {
|
|
2200
|
+
prompt,
|
|
2201
|
+
...(fields
|
|
2202
|
+
? {
|
|
2203
|
+
fields: fields
|
|
2204
|
+
.split(',')
|
|
2205
|
+
.map((f) => f.trim())
|
|
2206
|
+
.filter(Boolean),
|
|
2207
|
+
}
|
|
2208
|
+
: {}),
|
|
2209
|
+
source: resolveAxSource(flags),
|
|
2210
|
+
}),
|
|
2211
|
+
);
|
|
2212
|
+
},
|
|
2213
|
+
);
|
|
2214
|
+
|
|
2215
|
+
cmd(
|
|
2216
|
+
'ax elicitation respond',
|
|
2217
|
+
'Answer a pending elicitation',
|
|
2218
|
+
['pmx-canvas ax elicitation respond <id> --response \'{"owner":"alice"}\''],
|
|
2219
|
+
async (args) => {
|
|
2220
|
+
const { positional, flags } = parseFlags(args);
|
|
2221
|
+
if (flags.help || flags.h) return showCommandHelp('ax elicitation respond');
|
|
2222
|
+
const id = getStringFlag(flags, 'id') ?? positional[0];
|
|
2223
|
+
if (!id) die('Missing elicitation id', 'pmx-canvas ax elicitation respond <id> --response <json>');
|
|
2224
|
+
let response: unknown = {};
|
|
2225
|
+
const raw = getStringFlag(flags, 'response');
|
|
2226
|
+
if (raw) {
|
|
2227
|
+
try {
|
|
2228
|
+
response = JSON.parse(raw);
|
|
2229
|
+
} catch {
|
|
2230
|
+
die('Invalid --response JSON', '--response \'{"k":"v"}\'');
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
output(
|
|
2234
|
+
await api('POST', `/api/canvas/ax/elicitation/${encodeURIComponent(id)}/respond`, {
|
|
2235
|
+
response,
|
|
2236
|
+
source: resolveAxSource(flags),
|
|
2237
|
+
}),
|
|
2238
|
+
);
|
|
2239
|
+
},
|
|
2240
|
+
);
|
|
2025
2241
|
|
|
2026
2242
|
cmd('ax elicitation list', 'List elicitations', ['pmx-canvas ax elicitation list'], async (args) => {
|
|
2027
2243
|
const { flags } = parseFlags(args);
|
|
@@ -2029,32 +2245,42 @@ cmd('ax elicitation list', 'List elicitations', ['pmx-canvas ax elicitation list
|
|
|
2029
2245
|
output(await api('GET', '/api/canvas/ax/elicitation'));
|
|
2030
2246
|
});
|
|
2031
2247
|
|
|
2032
|
-
cmd(
|
|
2033
|
-
'
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2248
|
+
cmd(
|
|
2249
|
+
'ax mode request',
|
|
2250
|
+
'Request a workflow mode transition (plan/execute/autonomous)',
|
|
2251
|
+
['pmx-canvas ax mode request --mode execute --reason "plan approved"'],
|
|
2252
|
+
async (args) => {
|
|
2253
|
+
const { flags } = parseFlags(args);
|
|
2254
|
+
if (flags.help || flags.h) return showCommandHelp('ax mode request');
|
|
2255
|
+
const mode = requireFlag(flags, 'mode', 'pmx-canvas ax mode request --mode plan|execute|autonomous');
|
|
2256
|
+
const reason = getStringFlag(flags, 'reason');
|
|
2257
|
+
output(
|
|
2258
|
+
await api('POST', '/api/canvas/ax/mode', { mode, ...(reason ? { reason } : {}), source: resolveAxSource(flags) }),
|
|
2259
|
+
);
|
|
2260
|
+
},
|
|
2261
|
+
);
|
|
2262
|
+
|
|
2263
|
+
cmd(
|
|
2264
|
+
'ax mode resolve',
|
|
2265
|
+
'Resolve a pending mode request',
|
|
2266
|
+
['pmx-canvas ax mode resolve <id> --decision approved'],
|
|
2267
|
+
async (args) => {
|
|
2268
|
+
const { positional, flags } = parseFlags(args);
|
|
2269
|
+
if (flags.help || flags.h) return showCommandHelp('ax mode resolve');
|
|
2270
|
+
const id = getStringFlag(flags, 'id') ?? positional[0];
|
|
2271
|
+
if (!id) die('Missing mode request id', 'pmx-canvas ax mode resolve <id> --decision approved|rejected');
|
|
2272
|
+
const decision = getStringFlag(flags, 'decision');
|
|
2273
|
+
if (decision !== 'approved' && decision !== 'rejected') die('Invalid --decision', '--decision approved|rejected');
|
|
2274
|
+
const resolution = getStringFlag(flags, 'resolution');
|
|
2275
|
+
output(
|
|
2276
|
+
await api('POST', `/api/canvas/ax/mode/${encodeURIComponent(id)}/resolve`, {
|
|
2277
|
+
decision,
|
|
2278
|
+
...(resolution ? { resolution } : {}),
|
|
2279
|
+
source: resolveAxSource(flags),
|
|
2280
|
+
}),
|
|
2281
|
+
);
|
|
2282
|
+
},
|
|
2283
|
+
);
|
|
2058
2284
|
|
|
2059
2285
|
cmd('ax mode list', 'List mode requests', ['pmx-canvas ax mode list'], async (args) => {
|
|
2060
2286
|
const { flags } = parseFlags(args);
|
|
@@ -2068,21 +2294,36 @@ cmd('ax command list', 'List the PMX command registry', ['pmx-canvas ax command
|
|
|
2068
2294
|
output(await api('GET', '/api/canvas/ax/command'));
|
|
2069
2295
|
});
|
|
2070
2296
|
|
|
2071
|
-
cmd(
|
|
2072
|
-
'
|
|
2073
|
-
'
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2297
|
+
cmd(
|
|
2298
|
+
'ax command invoke',
|
|
2299
|
+
'Invoke a registry-gated PMX command intent',
|
|
2300
|
+
[
|
|
2301
|
+
'pmx-canvas ax command invoke pmx.plan',
|
|
2302
|
+
'pmx-canvas ax command invoke pmx.promote-context --args \'{"nodeIds":["n1"]}\'',
|
|
2303
|
+
],
|
|
2304
|
+
async (args) => {
|
|
2305
|
+
const { positional, flags } = parseFlags(args);
|
|
2306
|
+
if (flags.help || flags.h) return showCommandHelp('ax command invoke');
|
|
2307
|
+
const name = getStringFlag(flags, 'name') ?? positional[0];
|
|
2308
|
+
if (!name) die('Missing command name', 'pmx-canvas ax command invoke <name>');
|
|
2309
|
+
let cmdArgs: unknown;
|
|
2310
|
+
const raw = getStringFlag(flags, 'args');
|
|
2311
|
+
if (raw) {
|
|
2312
|
+
try {
|
|
2313
|
+
cmdArgs = JSON.parse(raw);
|
|
2314
|
+
} catch {
|
|
2315
|
+
die('Invalid --args JSON', '--args \'{"k":"v"}\'');
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
output(
|
|
2319
|
+
await api('POST', '/api/canvas/ax/command', {
|
|
2320
|
+
name,
|
|
2321
|
+
...(cmdArgs !== undefined ? { args: cmdArgs } : {}),
|
|
2322
|
+
source: resolveAxSource(flags),
|
|
2323
|
+
}),
|
|
2324
|
+
);
|
|
2325
|
+
},
|
|
2326
|
+
);
|
|
2086
2327
|
|
|
2087
2328
|
cmd('ax policy get', 'Show the current declarative AX policy', ['pmx-canvas ax policy get'], async (args) => {
|
|
2088
2329
|
const { flags } = parseFlags(args);
|
|
@@ -2090,222 +2331,285 @@ cmd('ax policy get', 'Show the current declarative AX policy', ['pmx-canvas ax p
|
|
|
2090
2331
|
output(await api('GET', '/api/canvas/ax/policy'));
|
|
2091
2332
|
});
|
|
2092
2333
|
|
|
2093
|
-
cmd(
|
|
2094
|
-
'
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
'
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
})
|
|
2334
|
+
cmd(
|
|
2335
|
+
'ax policy set',
|
|
2336
|
+
'Set the declarative AX policy (stored by PMX, enforced by adapters)',
|
|
2337
|
+
['pmx-canvas ax policy set --excluded-tools shell,write --mode concise'],
|
|
2338
|
+
async (args) => {
|
|
2339
|
+
const { flags } = parseFlags(args);
|
|
2340
|
+
if (flags.help || flags.h) return showCommandHelp('ax policy set');
|
|
2341
|
+
const csv = (v?: string) =>
|
|
2342
|
+
v
|
|
2343
|
+
? v
|
|
2344
|
+
.split(',')
|
|
2345
|
+
.map((s) => s.trim())
|
|
2346
|
+
.filter(Boolean)
|
|
2347
|
+
: undefined;
|
|
2348
|
+
const allowed = csv(getStringFlag(flags, 'allowed-tools'));
|
|
2349
|
+
const excluded = csv(getStringFlag(flags, 'excluded-tools'));
|
|
2350
|
+
const approvalRequired = csv(getStringFlag(flags, 'approval-tools'));
|
|
2351
|
+
const mode = getStringFlag(flags, 'mode');
|
|
2352
|
+
const systemAppend = getStringFlag(flags, 'system-append');
|
|
2353
|
+
const tools =
|
|
2354
|
+
allowed || excluded || approvalRequired
|
|
2355
|
+
? {
|
|
2356
|
+
...(allowed ? { allowed } : {}),
|
|
2357
|
+
...(excluded ? { excluded } : {}),
|
|
2358
|
+
...(approvalRequired ? { approvalRequired } : {}),
|
|
2359
|
+
}
|
|
2360
|
+
: undefined;
|
|
2361
|
+
const prompt =
|
|
2362
|
+
mode || systemAppend ? { ...(mode ? { mode } : {}), ...(systemAppend ? { systemAppend } : {}) } : undefined;
|
|
2363
|
+
output(
|
|
2364
|
+
await api('POST', '/api/canvas/ax/policy', {
|
|
2365
|
+
...(tools ? { tools } : {}),
|
|
2366
|
+
...(prompt ? { prompt } : {}),
|
|
2367
|
+
source: resolveAxSource(flags),
|
|
2368
|
+
}),
|
|
2369
|
+
);
|
|
2370
|
+
},
|
|
2371
|
+
);
|
|
2372
|
+
|
|
2373
|
+
cmd(
|
|
2374
|
+
'ax timeline',
|
|
2375
|
+
'Read the bounded AX timeline (events, evidence, steering)',
|
|
2376
|
+
['pmx-canvas ax timeline', 'pmx-canvas ax timeline --limit 100'],
|
|
2377
|
+
async (args) => {
|
|
2378
|
+
const { flags } = parseFlags(args);
|
|
2379
|
+
if (flags.help || flags.h) return showCommandHelp('ax timeline');
|
|
2380
|
+
|
|
2381
|
+
const limit = optionalNumberFlag(flags, 'limit', 'pmx-canvas ax timeline --limit <n>');
|
|
2382
|
+
output(await api('GET', `/api/canvas/ax/timeline${limit ? `?limit=${limit}` : ''}`));
|
|
2383
|
+
},
|
|
2384
|
+
);
|
|
2385
|
+
|
|
2386
|
+
cmd(
|
|
2387
|
+
'ax work add',
|
|
2388
|
+
'Add a canvas-bound AX work item',
|
|
2389
|
+
[
|
|
2390
|
+
'pmx-canvas ax work add --title "Wire up auth" --status in-progress',
|
|
2391
|
+
'pmx-canvas ax work add --title "Review API" node1 node2',
|
|
2392
|
+
],
|
|
2393
|
+
async (args) => {
|
|
2394
|
+
const { positional, flags } = parseFlags(args);
|
|
2395
|
+
if (flags.help || flags.h) return showCommandHelp('ax work add');
|
|
2396
|
+
|
|
2397
|
+
const title = requireFlag(flags, 'title', 'pmx-canvas ax work add --title <text>');
|
|
2398
|
+
const status = getStringFlag(flags, 'status');
|
|
2399
|
+
const detail = getStringFlag(flags, 'detail');
|
|
2400
|
+
|
|
2401
|
+
output(
|
|
2402
|
+
await api('POST', '/api/canvas/ax/work', {
|
|
2403
|
+
title,
|
|
2404
|
+
...(status ? { status } : {}),
|
|
2405
|
+
...(detail ? { detail } : {}),
|
|
2406
|
+
...(positional.length > 0 ? { nodeIds: positional } : {}),
|
|
2407
|
+
source: resolveAxSource(flags),
|
|
2408
|
+
}),
|
|
2409
|
+
);
|
|
2410
|
+
},
|
|
2411
|
+
);
|
|
2412
|
+
|
|
2413
|
+
cmd(
|
|
2414
|
+
'ax work update',
|
|
2415
|
+
'Update a canvas-bound AX work item by ID',
|
|
2416
|
+
['pmx-canvas ax work update <id> --status done', 'pmx-canvas ax work update <id> --title "New title" --detail "..."'],
|
|
2417
|
+
async (args) => {
|
|
2418
|
+
const { positional, flags } = parseFlags(args);
|
|
2419
|
+
if (flags.help || flags.h) return showCommandHelp('ax work update');
|
|
2420
|
+
|
|
2421
|
+
const id = positional[0];
|
|
2422
|
+
if (!id) die('Missing work item ID', 'pmx-canvas ax work update <id> --status <status>');
|
|
2423
|
+
const title = getStringFlag(flags, 'title');
|
|
2424
|
+
const status = getStringFlag(flags, 'status');
|
|
2425
|
+
const detail = getStringFlag(flags, 'detail');
|
|
2426
|
+
|
|
2427
|
+
output(
|
|
2428
|
+
await api('PATCH', `/api/canvas/ax/work/${encodeURIComponent(id)}`, {
|
|
2429
|
+
...(title ? { title } : {}),
|
|
2430
|
+
...(status ? { status } : {}),
|
|
2431
|
+
...(detail ? { detail } : {}),
|
|
2432
|
+
...(positional.length > 1 ? { nodeIds: positional.slice(1) } : {}),
|
|
2433
|
+
source: resolveAxSource(flags),
|
|
2434
|
+
}),
|
|
2435
|
+
);
|
|
2436
|
+
},
|
|
2437
|
+
);
|
|
2165
2438
|
|
|
2166
|
-
cmd('ax work list', 'List canvas-bound AX work items', [
|
|
2167
|
-
'pmx-canvas ax work list',
|
|
2168
|
-
], async (args) => {
|
|
2439
|
+
cmd('ax work list', 'List canvas-bound AX work items', ['pmx-canvas ax work list'], async (args) => {
|
|
2169
2440
|
const { flags } = parseFlags(args);
|
|
2170
2441
|
if (flags.help || flags.h) return showCommandHelp('ax work list');
|
|
2171
2442
|
|
|
2172
2443
|
output(await api('GET', '/api/canvas/ax/work'));
|
|
2173
2444
|
});
|
|
2174
2445
|
|
|
2175
|
-
cmd(
|
|
2176
|
-
'
|
|
2177
|
-
'
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
})
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2446
|
+
cmd(
|
|
2447
|
+
'ax approval request',
|
|
2448
|
+
'Request a canvas-bound AX approval gate',
|
|
2449
|
+
[
|
|
2450
|
+
'pmx-canvas ax approval request --title "Deploy to prod"',
|
|
2451
|
+
'pmx-canvas ax approval request --title "Drop table" --action db.drop node1',
|
|
2452
|
+
],
|
|
2453
|
+
async (args) => {
|
|
2454
|
+
const { positional, flags } = parseFlags(args);
|
|
2455
|
+
if (flags.help || flags.h) return showCommandHelp('ax approval request');
|
|
2456
|
+
|
|
2457
|
+
const title = requireFlag(flags, 'title', 'pmx-canvas ax approval request --title <text>');
|
|
2458
|
+
const detail = getStringFlag(flags, 'detail');
|
|
2459
|
+
const action = getStringFlag(flags, 'action');
|
|
2460
|
+
|
|
2461
|
+
output(
|
|
2462
|
+
await api('POST', '/api/canvas/ax/approval', {
|
|
2463
|
+
title,
|
|
2464
|
+
...(detail ? { detail } : {}),
|
|
2465
|
+
...(action ? { action } : {}),
|
|
2466
|
+
...(positional.length > 0 ? { nodeIds: positional } : {}),
|
|
2467
|
+
source: resolveAxSource(flags),
|
|
2468
|
+
}),
|
|
2469
|
+
);
|
|
2470
|
+
},
|
|
2471
|
+
);
|
|
2472
|
+
|
|
2473
|
+
cmd(
|
|
2474
|
+
'ax approval resolve',
|
|
2475
|
+
'Resolve a pending AX approval gate by ID',
|
|
2476
|
+
[
|
|
2477
|
+
'pmx-canvas ax approval resolve <id> --decision approved',
|
|
2478
|
+
'pmx-canvas ax approval resolve <id> --decision rejected --resolution "too risky"',
|
|
2479
|
+
],
|
|
2480
|
+
async (args) => {
|
|
2481
|
+
const { positional, flags } = parseFlags(args);
|
|
2482
|
+
if (flags.help || flags.h) return showCommandHelp('ax approval resolve');
|
|
2483
|
+
|
|
2484
|
+
const id = positional[0];
|
|
2485
|
+
if (!id) die('Missing approval gate ID', 'pmx-canvas ax approval resolve <id> --decision <approved|rejected>');
|
|
2486
|
+
const decision = requireFlag(
|
|
2487
|
+
flags,
|
|
2488
|
+
'decision',
|
|
2489
|
+
'pmx-canvas ax approval resolve <id> --decision <approved|rejected>',
|
|
2490
|
+
);
|
|
2491
|
+
if (decision !== 'approved' && decision !== 'rejected') {
|
|
2492
|
+
die('Invalid decision', 'pmx-canvas ax approval resolve <id> --decision <approved|rejected>');
|
|
2493
|
+
}
|
|
2494
|
+
const resolution = getStringFlag(flags, 'resolution');
|
|
2495
|
+
|
|
2496
|
+
output(
|
|
2497
|
+
await api('POST', `/api/canvas/ax/approval/${encodeURIComponent(id)}/resolve`, {
|
|
2498
|
+
decision,
|
|
2499
|
+
...(resolution ? { resolution } : {}),
|
|
2500
|
+
source: resolveAxSource(flags),
|
|
2501
|
+
}),
|
|
2502
|
+
);
|
|
2503
|
+
},
|
|
2504
|
+
);
|
|
2216
2505
|
|
|
2217
|
-
cmd('ax approval list', 'List canvas-bound AX approval gates', [
|
|
2218
|
-
'pmx-canvas ax approval list',
|
|
2219
|
-
], async (args) => {
|
|
2506
|
+
cmd('ax approval list', 'List canvas-bound AX approval gates', ['pmx-canvas ax approval list'], async (args) => {
|
|
2220
2507
|
const { flags } = parseFlags(args);
|
|
2221
2508
|
if (flags.help || flags.h) return showCommandHelp('ax approval list');
|
|
2222
2509
|
|
|
2223
2510
|
output(await api('GET', '/api/canvas/ax/approval'));
|
|
2224
2511
|
});
|
|
2225
2512
|
|
|
2226
|
-
cmd(
|
|
2227
|
-
'
|
|
2228
|
-
'
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
})
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2513
|
+
cmd(
|
|
2514
|
+
'ax evidence add',
|
|
2515
|
+
'Record an AX evidence item on the timeline',
|
|
2516
|
+
[
|
|
2517
|
+
'pmx-canvas ax evidence add --kind test-output --title "unit pass" --body "..."',
|
|
2518
|
+
'pmx-canvas ax evidence add --kind screenshot --title "before" --ref /tmp/before.png node1',
|
|
2519
|
+
],
|
|
2520
|
+
async (args) => {
|
|
2521
|
+
const { positional, flags } = parseFlags(args);
|
|
2522
|
+
if (flags.help || flags.h) return showCommandHelp('ax evidence add');
|
|
2523
|
+
|
|
2524
|
+
const kind = requireFlag(flags, 'kind', 'pmx-canvas ax evidence add --kind <kind> --title <text>');
|
|
2525
|
+
const title = requireFlag(flags, 'title', 'pmx-canvas ax evidence add --kind <kind> --title <text>');
|
|
2526
|
+
const body = getStringFlag(flags, 'body');
|
|
2527
|
+
const ref = getStringFlag(flags, 'ref');
|
|
2528
|
+
|
|
2529
|
+
output(
|
|
2530
|
+
await api('POST', '/api/canvas/ax/evidence', {
|
|
2531
|
+
kind,
|
|
2532
|
+
title,
|
|
2533
|
+
...(body ? { body } : {}),
|
|
2534
|
+
...(ref ? { ref } : {}),
|
|
2535
|
+
...(positional.length > 0 ? { nodeIds: positional } : {}),
|
|
2536
|
+
source: resolveAxSource(flags),
|
|
2537
|
+
}),
|
|
2538
|
+
);
|
|
2539
|
+
},
|
|
2540
|
+
);
|
|
2541
|
+
|
|
2542
|
+
cmd(
|
|
2543
|
+
'ax review add',
|
|
2544
|
+
'Add a canvas-bound AX review annotation',
|
|
2545
|
+
[
|
|
2546
|
+
'pmx-canvas ax review add --body "needs a test" --node node1',
|
|
2547
|
+
'pmx-canvas ax review add --body "off-by-one" --kind finding --severity error --file src/x.ts',
|
|
2548
|
+
],
|
|
2549
|
+
async (args) => {
|
|
2550
|
+
const { flags } = parseFlags(args);
|
|
2551
|
+
if (flags.help || flags.h) return showCommandHelp('ax review add');
|
|
2552
|
+
|
|
2553
|
+
const body = requireFlag(flags, 'body', 'pmx-canvas ax review add --body <text>');
|
|
2554
|
+
const kind = getStringFlag(flags, 'kind');
|
|
2555
|
+
const severity = getStringFlag(flags, 'severity');
|
|
2556
|
+
const anchorType = getStringFlag(flags, 'anchor');
|
|
2557
|
+
const nodeId = getStringFlag(flags, 'node');
|
|
2558
|
+
const file = getStringFlag(flags, 'file');
|
|
2559
|
+
const author = getStringFlag(flags, 'author');
|
|
2560
|
+
|
|
2561
|
+
output(
|
|
2562
|
+
await api('POST', '/api/canvas/ax/review', {
|
|
2563
|
+
body,
|
|
2564
|
+
...(kind ? { kind } : {}),
|
|
2565
|
+
...(severity ? { severity } : {}),
|
|
2566
|
+
...(anchorType ? { anchorType } : {}),
|
|
2567
|
+
...(nodeId ? { nodeId } : {}),
|
|
2568
|
+
...(file ? { file } : {}),
|
|
2569
|
+
...(author ? { author } : {}),
|
|
2570
|
+
source: resolveAxSource(flags),
|
|
2571
|
+
}),
|
|
2572
|
+
);
|
|
2573
|
+
},
|
|
2574
|
+
);
|
|
2274
2575
|
|
|
2275
|
-
cmd('ax review list', 'List canvas-bound AX review annotations', [
|
|
2276
|
-
'pmx-canvas ax review list',
|
|
2277
|
-
], async (args) => {
|
|
2576
|
+
cmd('ax review list', 'List canvas-bound AX review annotations', ['pmx-canvas ax review list'], async (args) => {
|
|
2278
2577
|
const { flags } = parseFlags(args);
|
|
2279
2578
|
if (flags.help || flags.h) return showCommandHelp('ax review list');
|
|
2280
2579
|
|
|
2281
2580
|
output(await api('GET', '/api/canvas/ax/review'));
|
|
2282
2581
|
});
|
|
2283
2582
|
|
|
2284
|
-
cmd(
|
|
2285
|
-
'
|
|
2286
|
-
'
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2583
|
+
cmd(
|
|
2584
|
+
'ax host report',
|
|
2585
|
+
'Report host/session capability to the canvas',
|
|
2586
|
+
[
|
|
2587
|
+
'pmx-canvas ax host report --host copilot --canvas --tools --session-messaging',
|
|
2588
|
+
'pmx-canvas ax host report --host codex --canvas --files',
|
|
2589
|
+
],
|
|
2590
|
+
async (args) => {
|
|
2591
|
+
const { flags } = parseFlags(args);
|
|
2592
|
+
if (flags.help || flags.h) return showCommandHelp('ax host report');
|
|
2593
|
+
|
|
2594
|
+
const host = getStringFlag(flags, 'host');
|
|
2595
|
+
|
|
2596
|
+
output(
|
|
2597
|
+
await api('PUT', '/api/canvas/ax/host-capability', {
|
|
2598
|
+
...(host ? { host } : {}),
|
|
2599
|
+
canvas: flags.canvas === true,
|
|
2600
|
+
hooks: flags.hooks === true,
|
|
2601
|
+
tools: flags.tools === true,
|
|
2602
|
+
sessionMessaging: flags['session-messaging'] === true,
|
|
2603
|
+
permissions: flags.permissions === true,
|
|
2604
|
+
files: flags.files === true,
|
|
2605
|
+
uiPrompts: flags['ui-prompts'] === true,
|
|
2606
|
+
source: resolveAxSource(flags),
|
|
2607
|
+
}),
|
|
2608
|
+
);
|
|
2609
|
+
},
|
|
2610
|
+
);
|
|
2305
2611
|
|
|
2306
|
-
cmd('ax host status', 'Read the reported host/session capability', [
|
|
2307
|
-
'pmx-canvas ax host status',
|
|
2308
|
-
], async (args) => {
|
|
2612
|
+
cmd('ax host status', 'Read the reported host/session capability', ['pmx-canvas ax host status'], async (args) => {
|
|
2309
2613
|
const { flags } = parseFlags(args);
|
|
2310
2614
|
if (flags.help || flags.h) return showCommandHelp('ax host status');
|
|
2311
2615
|
|
|
@@ -2313,155 +2617,167 @@ cmd('ax host status', 'Read the reported host/session capability', [
|
|
|
2313
2617
|
});
|
|
2314
2618
|
|
|
2315
2619
|
// ── copilot install-extension ────────────────────────────────
|
|
2316
|
-
cmd(
|
|
2317
|
-
'
|
|
2318
|
-
'
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2620
|
+
cmd(
|
|
2621
|
+
'copilot install-extension',
|
|
2622
|
+
'Install the bundled GitHub Copilot extension adapter',
|
|
2623
|
+
[
|
|
2624
|
+
'pmx-canvas copilot install-extension --dry-run',
|
|
2625
|
+
'pmx-canvas copilot install-extension --target .github/extensions/pmx-canvas/extension.mjs --yes',
|
|
2626
|
+
],
|
|
2627
|
+
async (args) => {
|
|
2628
|
+
const { flags } = parseFlags(args);
|
|
2629
|
+
if (flags.help || flags.h) return showCommandHelp('copilot install-extension');
|
|
2630
|
+
|
|
2631
|
+
const sourcePath = fileURLToPath(new URL('../../.github/extensions/pmx-canvas/extension.mjs', import.meta.url));
|
|
2632
|
+
if (!existsSync(sourcePath)) {
|
|
2633
|
+
die('Bundled Copilot extension adapter not found.', `Expected at ${sourcePath}`);
|
|
2634
|
+
}
|
|
2327
2635
|
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2636
|
+
const targetPath =
|
|
2637
|
+
getStringFlag(flags, 'target') ?? join(process.cwd(), '.github', 'extensions', 'pmx-canvas', 'extension.mjs');
|
|
2638
|
+
const dryRun = flags['dry-run'] === true;
|
|
2639
|
+
const targetExists = existsSync(targetPath);
|
|
2332
2640
|
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2641
|
+
if (dryRun) {
|
|
2642
|
+
output({ ok: true, dryRun: true, sourcePath, targetPath, targetExists, wrote: false });
|
|
2643
|
+
return;
|
|
2644
|
+
}
|
|
2337
2645
|
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2646
|
+
if (targetExists && flags.yes !== true) {
|
|
2647
|
+
die('Target already exists. Re-run with --yes to overwrite.', `Target: ${targetPath}`);
|
|
2648
|
+
}
|
|
2341
2649
|
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
}
|
|
2650
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
2651
|
+
copyFileSync(sourcePath, targetPath);
|
|
2652
|
+
output({ ok: true, dryRun: false, sourcePath, targetPath, wrote: true });
|
|
2653
|
+
},
|
|
2654
|
+
);
|
|
2346
2655
|
|
|
2347
2656
|
// ── undo ─────────────────────────────────────────────────────
|
|
2348
|
-
cmd('undo', 'Undo the last canvas mutation', [
|
|
2349
|
-
'pmx-canvas undo',
|
|
2350
|
-
], async (args) => {
|
|
2351
|
-
const { flags } = parseFlags(args);
|
|
2352
|
-
if (flags.help || flags.h) return showCommandHelp('undo');
|
|
2353
|
-
|
|
2354
|
-
const result = await invokeOperation('canvas.undo', {});
|
|
2355
|
-
output(result);
|
|
2356
|
-
});
|
|
2357
|
-
|
|
2358
|
-
// ── redo ─────────────────────────────────────────────────────
|
|
2359
|
-
cmd('redo', 'Redo the last undone mutation', [
|
|
2360
|
-
'pmx-canvas redo',
|
|
2361
|
-
], async (args) => {
|
|
2362
|
-
const { flags } = parseFlags(args);
|
|
2363
|
-
if (flags.help || flags.h) return showCommandHelp('redo');
|
|
2364
|
-
|
|
2365
|
-
const result = await invokeOperation('canvas.redo', {});
|
|
2366
|
-
output(result);
|
|
2367
|
-
});
|
|
2368
|
-
|
|
2369
|
-
// ── history ──────────────────────────────────────────────────
|
|
2370
|
-
cmd('history', 'Show canvas mutation history', [
|
|
2371
|
-
'pmx-canvas history',
|
|
2372
|
-
'pmx-canvas history --summary',
|
|
2373
|
-
'pmx-canvas history --compact',
|
|
2374
|
-
], async (args) => {
|
|
2375
|
-
const { flags } = parseFlags(args);
|
|
2376
|
-
if (flags.help || flags.h) return showCommandHelp('history');
|
|
2377
|
-
|
|
2378
|
-
const result = await invokeOperation('history.get', {}) as Record<string, unknown>;
|
|
2379
|
-
if (flags.summary) {
|
|
2380
|
-
output(summarizeHistoryResult(result));
|
|
2381
|
-
return;
|
|
2382
|
-
}
|
|
2383
|
-
if (flags.compact) {
|
|
2384
|
-
output(compactHistoryResult(result));
|
|
2385
|
-
return;
|
|
2386
|
-
}
|
|
2387
|
-
output(result);
|
|
2388
|
-
});
|
|
2389
|
-
|
|
2390
|
-
// ── snapshot save ────────────────────────────────────────────
|
|
2391
|
-
cmd('snapshot save', 'Save a named snapshot of the current canvas', [
|
|
2392
|
-
'pmx-canvas snapshot save --name "before-refactor"',
|
|
2393
|
-
'pmx-canvas snapshot save --name checkpoint-1',
|
|
2394
|
-
], async (args) => {
|
|
2395
|
-
const { flags } = parseFlags(args);
|
|
2396
|
-
if (flags.help || flags.h) return showCommandHelp('snapshot save');
|
|
2397
|
-
|
|
2398
|
-
const name = requireFlag(flags, 'name', 'pmx-canvas snapshot save --name "my-snapshot"');
|
|
2399
|
-
const result = await invokeOperation('snapshot.save', { name });
|
|
2400
|
-
output(result);
|
|
2401
|
-
});
|
|
2402
|
-
|
|
2403
|
-
// ── snapshot list ────────────────────────────────────────────
|
|
2404
|
-
cmd('snapshot list', 'List saved snapshots', [
|
|
2405
|
-
'pmx-canvas snapshot list',
|
|
2406
|
-
'pmx-canvas snapshot list --limit 50 --query baseline',
|
|
2407
|
-
'pmx-canvas snapshot list --after 2026-05-01T00:00:00Z --before 2026-05-05T00:00:00Z',
|
|
2408
|
-
'pmx-canvas snapshot list --all',
|
|
2409
|
-
], async (args) => {
|
|
2657
|
+
cmd('undo', 'Undo the last canvas mutation', ['pmx-canvas undo'], async (args) => {
|
|
2410
2658
|
const { flags } = parseFlags(args);
|
|
2411
|
-
if (flags.help || flags.h) return showCommandHelp('
|
|
2412
|
-
|
|
2413
|
-
const
|
|
2414
|
-
const query = getStringFlag(flags, 'query', 'q');
|
|
2415
|
-
const before = getStringFlag(flags, 'before');
|
|
2416
|
-
const after = getStringFlag(flags, 'after');
|
|
2417
|
-
const result = await invokeOperation('snapshot.list', {
|
|
2418
|
-
...(limit !== undefined ? { limit } : {}),
|
|
2419
|
-
...(query ? { q: query } : {}),
|
|
2420
|
-
...(before ? { before } : {}),
|
|
2421
|
-
...(after ? { after } : {}),
|
|
2422
|
-
...(flags.all ? { all: true } : {}),
|
|
2423
|
-
});
|
|
2659
|
+
if (flags.help || flags.h) return showCommandHelp('undo');
|
|
2660
|
+
|
|
2661
|
+
const result = await invokeOperation('canvas.undo', {});
|
|
2424
2662
|
output(result);
|
|
2425
2663
|
});
|
|
2426
2664
|
|
|
2427
|
-
// ──
|
|
2428
|
-
cmd('
|
|
2429
|
-
'pmx-canvas snapshot gc --keep 20 --dry-run',
|
|
2430
|
-
'pmx-canvas snapshot gc --keep 50 --yes',
|
|
2431
|
-
], async (args) => {
|
|
2665
|
+
// ── redo ─────────────────────────────────────────────────────
|
|
2666
|
+
cmd('redo', 'Redo the last undone mutation', ['pmx-canvas redo'], async (args) => {
|
|
2432
2667
|
const { flags } = parseFlags(args);
|
|
2433
|
-
if (flags.help || flags.h) return showCommandHelp('
|
|
2668
|
+
if (flags.help || flags.h) return showCommandHelp('redo');
|
|
2434
2669
|
|
|
2435
|
-
const
|
|
2436
|
-
const dryRun = flags['dry-run'] === true;
|
|
2437
|
-
if (!dryRun && !flags.yes) {
|
|
2438
|
-
die('Destructive operation requires --yes flag', 'Preview with: pmx-canvas snapshot gc --keep 20 --dry-run');
|
|
2439
|
-
}
|
|
2440
|
-
const result = await invokeOperation('snapshot.gc', {
|
|
2441
|
-
...(keep !== undefined ? { keep } : {}),
|
|
2442
|
-
dryRun,
|
|
2443
|
-
});
|
|
2670
|
+
const result = await invokeOperation('canvas.redo', {});
|
|
2444
2671
|
output(result);
|
|
2445
2672
|
});
|
|
2446
2673
|
|
|
2447
|
-
// ──
|
|
2448
|
-
cmd(
|
|
2449
|
-
'
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2674
|
+
// ── history ──────────────────────────────────────────────────
|
|
2675
|
+
cmd(
|
|
2676
|
+
'history',
|
|
2677
|
+
'Show canvas mutation history',
|
|
2678
|
+
['pmx-canvas history', 'pmx-canvas history --summary', 'pmx-canvas history --compact'],
|
|
2679
|
+
async (args) => {
|
|
2680
|
+
const { flags } = parseFlags(args);
|
|
2681
|
+
if (flags.help || flags.h) return showCommandHelp('history');
|
|
2682
|
+
|
|
2683
|
+
const result = (await invokeOperation('history.get', {})) as Record<string, unknown>;
|
|
2684
|
+
if (flags.summary) {
|
|
2685
|
+
output(summarizeHistoryResult(result));
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
if (flags.compact) {
|
|
2689
|
+
output(compactHistoryResult(result));
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
output(result);
|
|
2693
|
+
},
|
|
2694
|
+
);
|
|
2453
2695
|
|
|
2454
|
-
|
|
2455
|
-
|
|
2696
|
+
// ── snapshot save ────────────────────────────────────────────
|
|
2697
|
+
cmd(
|
|
2698
|
+
'snapshot save',
|
|
2699
|
+
'Save a named snapshot of the current canvas',
|
|
2700
|
+
['pmx-canvas snapshot save --name "before-refactor"', 'pmx-canvas snapshot save --name checkpoint-1'],
|
|
2701
|
+
async (args) => {
|
|
2702
|
+
const { flags } = parseFlags(args);
|
|
2703
|
+
if (flags.help || flags.h) return showCommandHelp('snapshot save');
|
|
2704
|
+
|
|
2705
|
+
const name = requireFlag(flags, 'name', 'pmx-canvas snapshot save --name "my-snapshot"');
|
|
2706
|
+
const result = await invokeOperation('snapshot.save', { name });
|
|
2707
|
+
output(result);
|
|
2708
|
+
},
|
|
2709
|
+
);
|
|
2456
2710
|
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2711
|
+
// ── snapshot list ────────────────────────────────────────────
|
|
2712
|
+
cmd(
|
|
2713
|
+
'snapshot list',
|
|
2714
|
+
'List saved snapshots',
|
|
2715
|
+
[
|
|
2716
|
+
'pmx-canvas snapshot list',
|
|
2717
|
+
'pmx-canvas snapshot list --limit 50 --query baseline',
|
|
2718
|
+
'pmx-canvas snapshot list --after 2026-05-01T00:00:00Z --before 2026-05-05T00:00:00Z',
|
|
2719
|
+
'pmx-canvas snapshot list --all',
|
|
2720
|
+
],
|
|
2721
|
+
async (args) => {
|
|
2722
|
+
const { flags } = parseFlags(args);
|
|
2723
|
+
if (flags.help || flags.h) return showCommandHelp('snapshot list');
|
|
2724
|
+
|
|
2725
|
+
const limit = optionalNumberFlag(flags, 'limit', 'Use a positive integer, e.g. --limit 50');
|
|
2726
|
+
const query = getStringFlag(flags, 'query', 'q');
|
|
2727
|
+
const before = getStringFlag(flags, 'before');
|
|
2728
|
+
const after = getStringFlag(flags, 'after');
|
|
2729
|
+
const result = await invokeOperation('snapshot.list', {
|
|
2730
|
+
...(limit !== undefined ? { limit } : {}),
|
|
2731
|
+
...(query ? { q: query } : {}),
|
|
2732
|
+
...(before ? { before } : {}),
|
|
2733
|
+
...(after ? { after } : {}),
|
|
2734
|
+
...(flags.all ? { all: true } : {}),
|
|
2735
|
+
});
|
|
2736
|
+
output(result);
|
|
2737
|
+
},
|
|
2738
|
+
);
|
|
2739
|
+
|
|
2740
|
+
// ── snapshot gc ──────────────────────────────────────────────
|
|
2741
|
+
cmd(
|
|
2742
|
+
'snapshot gc',
|
|
2743
|
+
'Delete old snapshots, keeping the newest N',
|
|
2744
|
+
['pmx-canvas snapshot gc --keep 20 --dry-run', 'pmx-canvas snapshot gc --keep 50 --yes'],
|
|
2745
|
+
async (args) => {
|
|
2746
|
+
const { flags } = parseFlags(args);
|
|
2747
|
+
if (flags.help || flags.h) return showCommandHelp('snapshot gc');
|
|
2748
|
+
|
|
2749
|
+
const keep = optionalNumberFlag(flags, 'keep', 'Use a positive integer, e.g. --keep 20');
|
|
2750
|
+
const dryRun = flags['dry-run'] === true;
|
|
2751
|
+
if (!dryRun && !flags.yes) {
|
|
2752
|
+
die('Destructive operation requires --yes flag', 'Preview with: pmx-canvas snapshot gc --keep 20 --dry-run');
|
|
2753
|
+
}
|
|
2754
|
+
const result = await invokeOperation('snapshot.gc', {
|
|
2755
|
+
...(keep !== undefined ? { keep } : {}),
|
|
2756
|
+
dryRun,
|
|
2757
|
+
});
|
|
2758
|
+
output(result);
|
|
2759
|
+
},
|
|
2760
|
+
);
|
|
2761
|
+
|
|
2762
|
+
// ── snapshot restore ─────────────────────────────────────────
|
|
2763
|
+
cmd(
|
|
2764
|
+
'snapshot restore',
|
|
2765
|
+
'Restore canvas from a snapshot',
|
|
2766
|
+
['pmx-canvas snapshot restore <snapshot-id-or-name>'],
|
|
2767
|
+
async (args) => {
|
|
2768
|
+
const { positional, flags } = parseFlags(args);
|
|
2769
|
+
if (flags.help || flags.h) return showCommandHelp('snapshot restore');
|
|
2770
|
+
|
|
2771
|
+
const id = positional[0];
|
|
2772
|
+
if (!id) die('Missing snapshot ID or name', 'pmx-canvas snapshot restore <snapshot-id-or-name>');
|
|
2773
|
+
|
|
2774
|
+
const result = await invokeOperation('snapshot.restore', { id });
|
|
2775
|
+
output(result);
|
|
2776
|
+
},
|
|
2777
|
+
);
|
|
2460
2778
|
|
|
2461
2779
|
// ── snapshot delete ──────────────────────────────────────────
|
|
2462
|
-
cmd('snapshot delete', 'Delete a saved snapshot', [
|
|
2463
|
-
'pmx-canvas snapshot delete <snapshot-id>',
|
|
2464
|
-
], async (args) => {
|
|
2780
|
+
cmd('snapshot delete', 'Delete a saved snapshot', ['pmx-canvas snapshot delete <snapshot-id>'], async (args) => {
|
|
2465
2781
|
const { positional, flags } = parseFlags(args);
|
|
2466
2782
|
if (flags.help || flags.h) return showCommandHelp('snapshot delete');
|
|
2467
2783
|
|
|
@@ -2481,171 +2797,196 @@ async function runSnapshotDiff(args: string[]): Promise<void> {
|
|
|
2481
2797
|
}
|
|
2482
2798
|
|
|
2483
2799
|
// ── snapshot diff ────────────────────────────────────────────
|
|
2484
|
-
cmd(
|
|
2485
|
-
'
|
|
2486
|
-
'
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2800
|
+
cmd(
|
|
2801
|
+
'snapshot diff',
|
|
2802
|
+
'Compare current canvas against a saved snapshot',
|
|
2803
|
+
['pmx-canvas snapshot diff <snapshot-id>', 'pmx-canvas snapshot diff "before-refactor"'],
|
|
2804
|
+
async (args) => {
|
|
2805
|
+
const { flags } = parseFlags(args);
|
|
2806
|
+
if (flags.help || flags.h) return showCommandHelp('snapshot diff');
|
|
2807
|
+
await runSnapshotDiff(args);
|
|
2808
|
+
},
|
|
2809
|
+
);
|
|
2492
2810
|
|
|
2493
2811
|
// ── diff ─────────────────────────────────────────────────────
|
|
2494
|
-
cmd('diff', 'Compare current canvas against a snapshot', [
|
|
2495
|
-
'pmx-canvas diff <snapshot-id>',
|
|
2496
|
-
], async (args) => {
|
|
2812
|
+
cmd('diff', 'Compare current canvas against a snapshot', ['pmx-canvas diff <snapshot-id>'], async (args) => {
|
|
2497
2813
|
const { flags } = parseFlags(args);
|
|
2498
2814
|
if (flags.help || flags.h) return showCommandHelp('diff');
|
|
2499
2815
|
await runSnapshotDiff(args);
|
|
2500
2816
|
});
|
|
2501
2817
|
|
|
2502
2818
|
// ── group create ─────────────────────────────────────────────
|
|
2503
|
-
cmd(
|
|
2504
|
-
'
|
|
2505
|
-
'
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2819
|
+
cmd(
|
|
2820
|
+
'group create',
|
|
2821
|
+
'Create a group node',
|
|
2822
|
+
[
|
|
2823
|
+
'pmx-canvas group create --title "API Layer" node1 node2',
|
|
2824
|
+
'pmx-canvas group create --title "Quarterly board" --x 40 --y 60 --width 1600 --height 900 --child-layout column node1 node2',
|
|
2825
|
+
'pmx-canvas group create --title "Frontend" --color "#ff6b6b"',
|
|
2826
|
+
],
|
|
2827
|
+
async (args) => {
|
|
2828
|
+
const { positional, flags } = parseFlags(args);
|
|
2829
|
+
if (flags.help || flags.h) return showCommandHelp('group create');
|
|
2830
|
+
|
|
2831
|
+
const body: Record<string, unknown> = {};
|
|
2832
|
+
if (flags.title && flags.title !== true) body.title = flags.title;
|
|
2833
|
+
if (flags.color && flags.color !== true) body.color = flags.color;
|
|
2834
|
+
const x = optionalFiniteFlag(flags, 'x', 'Use a finite number, e.g. --x 40');
|
|
2835
|
+
const y = optionalFiniteFlag(flags, 'y', 'Use a finite number, e.g. --y 60');
|
|
2836
|
+
const width = optionalPositiveFiniteFlag(flags, 'width', 'Use a positive number, e.g. --width 1600');
|
|
2837
|
+
const height = optionalPositiveFiniteFlag(flags, 'height', 'Use a positive number, e.g. --height 900');
|
|
2838
|
+
if (x !== undefined) body.x = x;
|
|
2839
|
+
if (y !== undefined) body.y = y;
|
|
2840
|
+
if (width !== undefined) body.width = width;
|
|
2841
|
+
if (height !== undefined) body.height = height;
|
|
2842
|
+
if (typeof flags['child-layout'] === 'string') body.childLayout = flags['child-layout'];
|
|
2843
|
+
if (positional.length > 0) body.childIds = positional;
|
|
2844
|
+
|
|
2845
|
+
const result = await api('POST', '/api/canvas/group', body);
|
|
2846
|
+
output(result);
|
|
2847
|
+
},
|
|
2848
|
+
);
|
|
2528
2849
|
|
|
2529
2850
|
// ── group add ────────────────────────────────────────────────
|
|
2530
|
-
cmd(
|
|
2531
|
-
'
|
|
2532
|
-
'
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
groupId,
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2851
|
+
cmd(
|
|
2852
|
+
'group add',
|
|
2853
|
+
'Add nodes to an existing group',
|
|
2854
|
+
[
|
|
2855
|
+
'pmx-canvas group add --group <group-id> node1 node2',
|
|
2856
|
+
'pmx-canvas group add --group <group-id> --child-layout flow node1 node2',
|
|
2857
|
+
],
|
|
2858
|
+
async (args) => {
|
|
2859
|
+
const { positional, flags } = parseFlags(args);
|
|
2860
|
+
if (flags.help || flags.h) return showCommandHelp('group add');
|
|
2861
|
+
|
|
2862
|
+
const groupId = requireFlag(flags, 'group', 'pmx-canvas group add --group <group-id> node1 node2');
|
|
2863
|
+
if (positional.length === 0) die('No node IDs provided', 'pmx-canvas group add --group <group-id> node1 node2');
|
|
2864
|
+
|
|
2865
|
+
const result = await api('POST', '/api/canvas/group/add', {
|
|
2866
|
+
groupId,
|
|
2867
|
+
childIds: positional,
|
|
2868
|
+
...(typeof flags['child-layout'] === 'string' ? { childLayout: flags['child-layout'] } : {}),
|
|
2869
|
+
});
|
|
2870
|
+
output(result);
|
|
2871
|
+
},
|
|
2872
|
+
);
|
|
2547
2873
|
|
|
2548
2874
|
// ── batch ────────────────────────────────────────────────────
|
|
2549
|
-
cmd(
|
|
2550
|
-
'
|
|
2551
|
-
'
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2875
|
+
cmd(
|
|
2876
|
+
'batch',
|
|
2877
|
+
'Run a batch of canvas operations from JSON',
|
|
2878
|
+
[
|
|
2879
|
+
'pmx-canvas batch --file ./canvas-ops.json',
|
|
2880
|
+
'pmx-canvas batch --json \'[{"op":"node.add","assign":"a","args":{"type":"markdown","title":"A"}}]\'',
|
|
2881
|
+
'pmx-canvas batch --json \'[{"op":"graph.add","assign":"g","args":{"graphType":"bar","data":[{"label":"Docs","value":5}],"xKey":"label","yKey":"value"}}]\'',
|
|
2882
|
+
'cat ops.json | pmx-canvas batch --stdin',
|
|
2883
|
+
],
|
|
2884
|
+
async (args) => {
|
|
2885
|
+
const { flags } = parseFlags(args);
|
|
2886
|
+
if (flags.help || flags.h) return showCommandHelp('batch');
|
|
2887
|
+
|
|
2888
|
+
let raw = '';
|
|
2889
|
+
if (typeof flags.file === 'string') {
|
|
2890
|
+
try {
|
|
2891
|
+
raw = readFileSync(flags.file, 'utf-8');
|
|
2892
|
+
} catch (error) {
|
|
2893
|
+
die(
|
|
2894
|
+
`Unable to read --file: ${error instanceof Error ? error.message : String(error)}`,
|
|
2895
|
+
'Use: pmx-canvas batch --file ./canvas-ops.json',
|
|
2896
|
+
);
|
|
2897
|
+
}
|
|
2898
|
+
} else if (typeof flags.json === 'string') {
|
|
2899
|
+
raw = flags.json;
|
|
2900
|
+
} else if (flags.stdin) {
|
|
2901
|
+
raw = await readStdin();
|
|
2902
|
+
} else {
|
|
2903
|
+
die('Batch operations require --file, --json, or --stdin.', 'Use: pmx-canvas batch --file ./canvas-ops.json');
|
|
2904
|
+
}
|
|
2557
2905
|
|
|
2558
|
-
|
|
2559
|
-
if (typeof flags.file === 'string') {
|
|
2906
|
+
let parsed: unknown;
|
|
2560
2907
|
try {
|
|
2561
|
-
|
|
2908
|
+
parsed = JSON.parse(raw);
|
|
2562
2909
|
} catch (error) {
|
|
2563
2910
|
die(
|
|
2564
|
-
`
|
|
2565
|
-
'Use
|
|
2911
|
+
`Invalid batch JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
2912
|
+
'Use a JSON array of operations or an object with an "operations" array.',
|
|
2566
2913
|
);
|
|
2567
2914
|
}
|
|
2568
|
-
} else if (typeof flags.json === 'string') {
|
|
2569
|
-
raw = flags.json;
|
|
2570
|
-
} else if (flags.stdin) {
|
|
2571
|
-
raw = await readStdin();
|
|
2572
|
-
} else {
|
|
2573
|
-
die(
|
|
2574
|
-
'Batch operations require --file, --json, or --stdin.',
|
|
2575
|
-
'Use: pmx-canvas batch --file ./canvas-ops.json',
|
|
2576
|
-
);
|
|
2577
|
-
}
|
|
2578
2915
|
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
die(
|
|
2584
|
-
`Invalid batch JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
2585
|
-
'Use a JSON array of operations or an object with an "operations" array.',
|
|
2916
|
+
const result = await api(
|
|
2917
|
+
'POST',
|
|
2918
|
+
'/api/canvas/batch',
|
|
2919
|
+
Array.isArray(parsed) ? { operations: parsed } : (parsed as Record<string, unknown>),
|
|
2586
2920
|
);
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
output(result);
|
|
2591
|
-
});
|
|
2921
|
+
output(result);
|
|
2922
|
+
},
|
|
2923
|
+
);
|
|
2592
2924
|
|
|
2593
2925
|
// ── validate ─────────────────────────────────────────────────
|
|
2594
|
-
cmd(
|
|
2595
|
-
'
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
'
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2926
|
+
cmd(
|
|
2927
|
+
'validate',
|
|
2928
|
+
'Validate the current layout for collisions and missing edge endpoints',
|
|
2929
|
+
['pmx-canvas validate'],
|
|
2930
|
+
async (args) => {
|
|
2931
|
+
const { flags } = parseFlags(args);
|
|
2932
|
+
if (flags.help || flags.h) return showCommandHelp('validate');
|
|
2933
|
+
|
|
2934
|
+
const result = await api('GET', '/api/canvas/validate');
|
|
2935
|
+
output(result);
|
|
2936
|
+
},
|
|
2937
|
+
);
|
|
2938
|
+
|
|
2939
|
+
cmd(
|
|
2940
|
+
'validate spec',
|
|
2941
|
+
'Validate a json-render spec or graph payload without creating a node',
|
|
2942
|
+
[
|
|
2943
|
+
'pmx-canvas validate spec --type json-render --spec-file ./dashboard.json',
|
|
2944
|
+
'pmx-canvas validate spec --type graph --graph-type bar --data-file ./metrics.json --x-key label --y-key value',
|
|
2945
|
+
'pmx-canvas validate spec --type html-primitive --kind choice-grid --data-file ./options.json',
|
|
2946
|
+
'pmx-canvas validate spec --type json-render --spec-file ./dashboard.json --summary',
|
|
2947
|
+
],
|
|
2948
|
+
async (args) => {
|
|
2949
|
+
const { flags } = parseFlags(args);
|
|
2950
|
+
if (flags.help || flags.h) return showCommandHelp('validate spec');
|
|
2951
|
+
|
|
2952
|
+
const type = getStringFlag(flags, 'type');
|
|
2953
|
+
if (type !== 'json-render' && type !== 'graph' && type !== 'html-primitive') {
|
|
2954
|
+
die('validate spec requires --type json-render, --type graph, or --type html-primitive.');
|
|
2955
|
+
}
|
|
2617
2956
|
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2957
|
+
let body: Record<string, unknown>;
|
|
2958
|
+
if (type === 'json-render') {
|
|
2959
|
+
body = {
|
|
2960
|
+
type,
|
|
2961
|
+
spec: (await buildJsonRenderRequestBody({ ...flags, title: String(flags.title ?? 'Validation') })).spec,
|
|
2962
|
+
};
|
|
2963
|
+
} else if (type === 'html-primitive') {
|
|
2964
|
+
const primitiveBody = await buildHtmlPrimitiveRequestBody(flags);
|
|
2965
|
+
body = {
|
|
2966
|
+
type,
|
|
2967
|
+
kind: primitiveBody.primitive,
|
|
2968
|
+
...(typeof primitiveBody.title === 'string' ? { title: primitiveBody.title } : {}),
|
|
2969
|
+
...(isRecord(primitiveBody.data) ? { data: primitiveBody.data } : {}),
|
|
2970
|
+
};
|
|
2971
|
+
} else {
|
|
2972
|
+
body = { type, ...(await buildGraphRequestBody(flags)) };
|
|
2973
|
+
}
|
|
2632
2974
|
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
}
|
|
2975
|
+
const result = (await api('POST', '/api/canvas/schema/validate', body)) as Record<string, unknown>;
|
|
2976
|
+
if (flags.summary) {
|
|
2977
|
+
output({
|
|
2978
|
+
ok: result.ok,
|
|
2979
|
+
type: result.type,
|
|
2980
|
+
summary: result.summary,
|
|
2981
|
+
});
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
output(result);
|
|
2985
|
+
},
|
|
2986
|
+
);
|
|
2644
2987
|
|
|
2645
2988
|
// ── group remove ─────────────────────────────────────────────
|
|
2646
|
-
cmd('group remove', 'Ungroup all children from a group', [
|
|
2647
|
-
'pmx-canvas group remove <group-id>',
|
|
2648
|
-
], async (args) => {
|
|
2989
|
+
cmd('group remove', 'Ungroup all children from a group', ['pmx-canvas group remove <group-id>'], async (args) => {
|
|
2649
2990
|
const { positional, flags } = parseFlags(args);
|
|
2650
2991
|
if (flags.help || flags.h) return showCommandHelp('group remove');
|
|
2651
2992
|
|
|
@@ -2657,46 +2998,51 @@ cmd('group remove', 'Ungroup all children from a group', [
|
|
|
2657
2998
|
});
|
|
2658
2999
|
|
|
2659
3000
|
// ── web-artifact build ───────────────────────────────────────
|
|
2660
|
-
cmd(
|
|
2661
|
-
'
|
|
2662
|
-
'
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
3001
|
+
cmd(
|
|
3002
|
+
'web-artifact build',
|
|
3003
|
+
'Build a bundled HTML web artifact and optionally open it on the canvas',
|
|
3004
|
+
[
|
|
3005
|
+
'pmx-canvas web-artifact build --title "Dashboard" --app-file ./App.tsx',
|
|
3006
|
+
'pmx-canvas web-artifact build --title "Dashboard" --app-file ./App.tsx --index-css-file ./index.css',
|
|
3007
|
+
'pmx-canvas web-artifact build --title "Dashboard" --app-file ./App.tsx --include-logs',
|
|
3008
|
+
],
|
|
3009
|
+
async (args) => {
|
|
3010
|
+
const { flags } = parseFlags(args);
|
|
3011
|
+
if (flags.help || flags.h) return showCommandHelp('web-artifact build');
|
|
3012
|
+
await runWebArtifactBuildCommand(flags);
|
|
3013
|
+
},
|
|
3014
|
+
);
|
|
2669
3015
|
|
|
2670
3016
|
// ── clear ────────────────────────────────────────────────────
|
|
2671
|
-
cmd(
|
|
2672
|
-
'
|
|
2673
|
-
'
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
3017
|
+
cmd(
|
|
3018
|
+
'clear',
|
|
3019
|
+
'Remove all nodes and edges from the canvas',
|
|
3020
|
+
['pmx-canvas clear --yes', 'pmx-canvas clear --dry-run'],
|
|
3021
|
+
async (args) => {
|
|
3022
|
+
const { flags } = parseFlags(args);
|
|
3023
|
+
if (flags.help || flags.h) return showCommandHelp('clear');
|
|
3024
|
+
|
|
3025
|
+
if (flags['dry-run']) {
|
|
3026
|
+
const layout = (await api('GET', '/api/canvas/state')) as { nodes: unknown[]; edges: unknown[] };
|
|
3027
|
+
output({
|
|
3028
|
+
dry_run: true,
|
|
3029
|
+
would_remove: { nodes: layout.nodes.length, edges: layout.edges.length },
|
|
3030
|
+
message: 'No changes made. Pass --yes to confirm.',
|
|
3031
|
+
});
|
|
3032
|
+
return;
|
|
3033
|
+
}
|
|
2687
3034
|
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
3035
|
+
if (!flags.yes) {
|
|
3036
|
+
die('Destructive operation requires --yes flag', 'pmx-canvas clear --yes (or preview with --dry-run)');
|
|
3037
|
+
}
|
|
2691
3038
|
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
}
|
|
3039
|
+
const result = await api('POST', '/api/canvas/clear');
|
|
3040
|
+
output(result);
|
|
3041
|
+
},
|
|
3042
|
+
);
|
|
2695
3043
|
|
|
2696
3044
|
// ── webview status ────────────────────────────────────────────
|
|
2697
|
-
cmd('webview status', 'Show Bun.WebView automation status', [
|
|
2698
|
-
'pmx-canvas webview status',
|
|
2699
|
-
], async (args) => {
|
|
3045
|
+
cmd('webview status', 'Show Bun.WebView automation status', ['pmx-canvas webview status'], async (args) => {
|
|
2700
3046
|
const { flags } = parseFlags(args);
|
|
2701
3047
|
if (flags.help || flags.h) return showCommandHelp('webview status');
|
|
2702
3048
|
|
|
@@ -2705,51 +3051,54 @@ cmd('webview status', 'Show Bun.WebView automation status', [
|
|
|
2705
3051
|
});
|
|
2706
3052
|
|
|
2707
3053
|
// ── webview start ─────────────────────────────────────────────
|
|
2708
|
-
cmd(
|
|
2709
|
-
'
|
|
2710
|
-
'
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
3054
|
+
cmd(
|
|
3055
|
+
'webview start',
|
|
3056
|
+
'Start or replace the Bun.WebView automation session',
|
|
3057
|
+
[
|
|
3058
|
+
'pmx-canvas webview start',
|
|
3059
|
+
'pmx-canvas webview start --backend chrome --width 1440 --height 900',
|
|
3060
|
+
'pmx-canvas webview start --chrome-path /Applications/Google\\ Chrome.app/.../Google\\ Chrome',
|
|
3061
|
+
],
|
|
3062
|
+
async (args) => {
|
|
3063
|
+
const { flags } = parseFlags(args);
|
|
3064
|
+
if (flags.help || flags.h) return showCommandHelp('webview start');
|
|
3065
|
+
|
|
3066
|
+
const backend = flags.backend;
|
|
3067
|
+
if (backend && backend !== true && backend !== 'chrome' && backend !== 'webkit') {
|
|
3068
|
+
die('Invalid value for --backend', 'Use: --backend chrome or --backend webkit');
|
|
3069
|
+
}
|
|
2720
3070
|
|
|
2721
|
-
|
|
2722
|
-
|
|
3071
|
+
const body: Record<string, unknown> = {};
|
|
3072
|
+
if (backend && backend !== true) body.backend = backend;
|
|
2723
3073
|
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
3074
|
+
const width = optionalNumberFlag(flags, 'width', 'Use a positive integer width, e.g. --width 1440');
|
|
3075
|
+
const height = optionalNumberFlag(flags, 'height', 'Use a positive integer height, e.g. --height 900');
|
|
3076
|
+
if (width !== undefined) body.width = width;
|
|
3077
|
+
if (height !== undefined) body.height = height;
|
|
2728
3078
|
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
3079
|
+
if (flags['chrome-path'] && flags['chrome-path'] !== true) {
|
|
3080
|
+
body.chromePath = flags['chrome-path'];
|
|
3081
|
+
}
|
|
2732
3082
|
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
3083
|
+
if (flags['data-dir'] && flags['data-dir'] !== true) {
|
|
3084
|
+
body.dataStoreDir = flags['data-dir'];
|
|
3085
|
+
}
|
|
2736
3086
|
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
3087
|
+
if (flags['chrome-argv'] && flags['chrome-argv'] !== true) {
|
|
3088
|
+
const chromeArgv = String(flags['chrome-argv'])
|
|
3089
|
+
.split(',')
|
|
3090
|
+
.map((value) => value.trim())
|
|
3091
|
+
.filter((value) => value.length > 0);
|
|
3092
|
+
if (chromeArgv.length > 0) body.chromeArgv = chromeArgv;
|
|
3093
|
+
}
|
|
2744
3094
|
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
}
|
|
3095
|
+
const result = await api('POST', '/api/workbench/webview/start', body, { allowErrorJson: true });
|
|
3096
|
+
output(result);
|
|
3097
|
+
},
|
|
3098
|
+
);
|
|
2748
3099
|
|
|
2749
3100
|
// ── webview stop ──────────────────────────────────────────────
|
|
2750
|
-
cmd('webview stop', 'Stop the active Bun.WebView automation session', [
|
|
2751
|
-
'pmx-canvas webview stop',
|
|
2752
|
-
], async (args) => {
|
|
3101
|
+
cmd('webview stop', 'Stop the active Bun.WebView automation session', ['pmx-canvas webview stop'], async (args) => {
|
|
2753
3102
|
const { flags } = parseFlags(args);
|
|
2754
3103
|
if (flags.help || flags.h) return showCommandHelp('webview stop');
|
|
2755
3104
|
|
|
@@ -2758,137 +3107,148 @@ cmd('webview stop', 'Stop the active Bun.WebView automation session', [
|
|
|
2758
3107
|
});
|
|
2759
3108
|
|
|
2760
3109
|
// ── webview evaluate ──────────────────────────────────────────
|
|
2761
|
-
cmd(
|
|
2762
|
-
'
|
|
2763
|
-
'
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
if (typeof flags.file === 'string') {
|
|
2776
|
-
let script = '';
|
|
2777
|
-
try {
|
|
2778
|
-
script = readFileSync(flags.file, 'utf-8');
|
|
2779
|
-
} catch (error) {
|
|
3110
|
+
cmd(
|
|
3111
|
+
'webview evaluate',
|
|
3112
|
+
'Evaluate JavaScript in the active Bun.WebView automation session',
|
|
3113
|
+
[
|
|
3114
|
+
'pmx-canvas webview evaluate --expression "document.title"',
|
|
3115
|
+
'pmx-canvas webview evaluate --script "const title = document.title; return title.toUpperCase()"',
|
|
3116
|
+
'pmx-canvas webview evaluate --file ./probe.js',
|
|
3117
|
+
],
|
|
3118
|
+
async (args) => {
|
|
3119
|
+
const { flags } = parseFlags(args);
|
|
3120
|
+
if (flags.help || flags.h) return showCommandHelp('webview evaluate');
|
|
3121
|
+
|
|
3122
|
+
const sourceCount = [flags.expression, flags.script, flags.file].filter(Boolean).length;
|
|
3123
|
+
if (sourceCount > 1) {
|
|
2780
3124
|
die(
|
|
2781
|
-
|
|
2782
|
-
'pmx-canvas webview evaluate --
|
|
3125
|
+
'Use only one of --expression, --script, or --file.',
|
|
3126
|
+
'pmx-canvas webview evaluate --expression "document.title"',
|
|
2783
3127
|
);
|
|
2784
3128
|
}
|
|
2785
|
-
expression = wrapCanvasAutomationScript(script);
|
|
2786
|
-
} else if (typeof flags.script === 'string') {
|
|
2787
|
-
expression = wrapCanvasAutomationScript(flags.script);
|
|
2788
|
-
} else {
|
|
2789
|
-
expression = requireFlag(
|
|
2790
|
-
flags,
|
|
2791
|
-
'expression',
|
|
2792
|
-
'pmx-canvas webview evaluate --expression "document.title"',
|
|
2793
|
-
);
|
|
2794
|
-
}
|
|
2795
3129
|
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
3130
|
+
let expression = '';
|
|
3131
|
+
if (typeof flags.file === 'string') {
|
|
3132
|
+
let script = '';
|
|
3133
|
+
try {
|
|
3134
|
+
script = readFileSync(flags.file, 'utf-8');
|
|
3135
|
+
} catch (error) {
|
|
3136
|
+
die(
|
|
3137
|
+
`Unable to read --file: ${error instanceof Error ? error.message : String(error)}`,
|
|
3138
|
+
'pmx-canvas webview evaluate --file ./probe.js',
|
|
3139
|
+
);
|
|
3140
|
+
}
|
|
3141
|
+
expression = wrapCanvasAutomationScript(script);
|
|
3142
|
+
} else if (typeof flags.script === 'string') {
|
|
3143
|
+
expression = wrapCanvasAutomationScript(flags.script);
|
|
3144
|
+
} else {
|
|
3145
|
+
expression = requireFlag(flags, 'expression', 'pmx-canvas webview evaluate --expression "document.title"');
|
|
3146
|
+
}
|
|
2799
3147
|
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
const { flags } = parseFlags(args);
|
|
2805
|
-
if (flags.help || flags.h) return showCommandHelp('webview resize');
|
|
3148
|
+
const result = await api('POST', '/api/workbench/webview/evaluate', { expression });
|
|
3149
|
+
output(result);
|
|
3150
|
+
},
|
|
3151
|
+
);
|
|
2806
3152
|
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
3153
|
+
// ── webview resize ────────────────────────────────────────────
|
|
3154
|
+
cmd(
|
|
3155
|
+
'webview resize',
|
|
3156
|
+
'Resize the active Bun.WebView automation session viewport',
|
|
3157
|
+
['pmx-canvas webview resize --width 1280 --height 800'],
|
|
3158
|
+
async (args) => {
|
|
3159
|
+
const { flags } = parseFlags(args);
|
|
3160
|
+
if (flags.help || flags.h) return showCommandHelp('webview resize');
|
|
3161
|
+
|
|
3162
|
+
const width = optionalNumberFlag(flags, 'width', 'Use: pmx-canvas webview resize --width 1280 --height 800');
|
|
3163
|
+
const height = optionalNumberFlag(flags, 'height', 'Use: pmx-canvas webview resize --width 1280 --height 800');
|
|
3164
|
+
if (width === undefined || height === undefined) {
|
|
3165
|
+
die('Missing required flags: --width, --height', 'Use: pmx-canvas webview resize --width 1280 --height 800');
|
|
3166
|
+
}
|
|
2812
3167
|
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
}
|
|
3168
|
+
const result = await api('POST', '/api/workbench/webview/resize', { width, height });
|
|
3169
|
+
output(result);
|
|
3170
|
+
},
|
|
3171
|
+
);
|
|
2816
3172
|
|
|
2817
3173
|
// ── webview screenshot ────────────────────────────────────────
|
|
2818
|
-
cmd(
|
|
2819
|
-
'
|
|
2820
|
-
'
|
|
2821
|
-
|
|
2822
|
-
const { flags } = parseFlags(args);
|
|
2823
|
-
if (flags.help || flags.h) return showCommandHelp('webview screenshot');
|
|
2824
|
-
|
|
2825
|
-
const outputPath = requireFlag(
|
|
2826
|
-
flags,
|
|
2827
|
-
'output',
|
|
3174
|
+
cmd(
|
|
3175
|
+
'webview screenshot',
|
|
3176
|
+
'Capture a screenshot from the active Bun.WebView automation session',
|
|
3177
|
+
[
|
|
2828
3178
|
'pmx-canvas webview screenshot --output ./canvas.png',
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
3179
|
+
'pmx-canvas webview screenshot --output ./canvas.webp --format webp --quality 80',
|
|
3180
|
+
],
|
|
3181
|
+
async (args) => {
|
|
3182
|
+
const { flags } = parseFlags(args);
|
|
3183
|
+
if (flags.help || flags.h) return showCommandHelp('webview screenshot');
|
|
3184
|
+
|
|
3185
|
+
const outputPath = requireFlag(flags, 'output', 'pmx-canvas webview screenshot --output ./canvas.png');
|
|
3186
|
+
|
|
3187
|
+
const body: Record<string, unknown> = {};
|
|
3188
|
+
if (flags.format && flags.format !== true) {
|
|
3189
|
+
const format = String(flags.format);
|
|
3190
|
+
if (format !== 'png' && format !== 'jpeg' && format !== 'webp') {
|
|
3191
|
+
die('Invalid value for --format', 'Use: --format png, jpeg, or webp');
|
|
3192
|
+
}
|
|
3193
|
+
body.format = format;
|
|
2836
3194
|
}
|
|
2837
|
-
body.format = format;
|
|
2838
|
-
}
|
|
2839
3195
|
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
3196
|
+
if (flags.quality && flags.quality !== true) {
|
|
3197
|
+
const quality = Number(flags.quality);
|
|
3198
|
+
if (!Number.isFinite(quality)) {
|
|
3199
|
+
die(`Invalid value for --quality: ${String(flags.quality)}`, 'Use a numeric quality, e.g. --quality 80');
|
|
3200
|
+
}
|
|
3201
|
+
body.quality = quality;
|
|
2844
3202
|
}
|
|
2845
|
-
body.quality = quality;
|
|
2846
|
-
}
|
|
2847
3203
|
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
3204
|
+
const base = getBaseUrl();
|
|
3205
|
+
const response = await fetch(`${base}/api/workbench/webview/screenshot`, {
|
|
3206
|
+
method: 'POST',
|
|
3207
|
+
headers: { 'Content-Type': 'application/json' },
|
|
3208
|
+
body: JSON.stringify(body),
|
|
3209
|
+
});
|
|
2854
3210
|
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
3211
|
+
if (!response.ok) {
|
|
3212
|
+
const text = await response.text();
|
|
3213
|
+
try {
|
|
3214
|
+
const json = JSON.parse(text) as Record<string, unknown>;
|
|
3215
|
+
die(
|
|
3216
|
+
json.error ? String(json.error) : `HTTP ${response.status}`,
|
|
3217
|
+
typeof json.hint === 'string' ? json.hint : undefined,
|
|
3218
|
+
);
|
|
3219
|
+
} catch {
|
|
3220
|
+
die(`HTTP ${response.status}: ${text}`);
|
|
3221
|
+
}
|
|
2865
3222
|
}
|
|
2866
|
-
}
|
|
2867
|
-
|
|
2868
|
-
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
2869
|
-
writeFileSync(outputPath, bytes);
|
|
2870
|
-
output({
|
|
2871
|
-
ok: true,
|
|
2872
|
-
output: outputPath,
|
|
2873
|
-
bytes: bytes.byteLength,
|
|
2874
|
-
mimeType: response.headers.get('Content-Type') ?? 'application/octet-stream',
|
|
2875
|
-
});
|
|
2876
|
-
});
|
|
2877
3223
|
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
}
|
|
3224
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
3225
|
+
writeFileSync(outputPath, bytes);
|
|
3226
|
+
output({
|
|
3227
|
+
ok: true,
|
|
3228
|
+
output: outputPath,
|
|
3229
|
+
bytes: bytes.byteLength,
|
|
3230
|
+
mimeType: response.headers.get('Content-Type') ?? 'application/octet-stream',
|
|
3231
|
+
});
|
|
3232
|
+
},
|
|
3233
|
+
);
|
|
3234
|
+
|
|
3235
|
+
cmd(
|
|
3236
|
+
'screenshot',
|
|
3237
|
+
'Capture a screenshot from the active Bun.WebView automation session',
|
|
3238
|
+
[
|
|
3239
|
+
'pmx-canvas screenshot --output ./canvas.png',
|
|
3240
|
+
'pmx-canvas screenshot --output ./canvas.webp --format webp --quality 80',
|
|
3241
|
+
],
|
|
3242
|
+
async (args) => {
|
|
3243
|
+
if (args.includes('--help') || args.includes('-h')) return showCommandHelp('screenshot');
|
|
3244
|
+
const screenshotCommand = COMMANDS['webview screenshot'];
|
|
3245
|
+
if (!screenshotCommand) die('Internal error: webview screenshot command is unavailable.');
|
|
3246
|
+
await screenshotCommand.run(args);
|
|
3247
|
+
},
|
|
3248
|
+
);
|
|
2887
3249
|
|
|
2888
3250
|
// ── code-graph ───────────────────────────────────────────────
|
|
2889
|
-
cmd('code-graph', 'Show auto-detected file dependency graph', [
|
|
2890
|
-
'pmx-canvas code-graph',
|
|
2891
|
-
], async (args) => {
|
|
3251
|
+
cmd('code-graph', 'Show auto-detected file dependency graph', ['pmx-canvas code-graph'], async (args) => {
|
|
2892
3252
|
const { flags } = parseFlags(args);
|
|
2893
3253
|
if (flags.help || flags.h) return showCommandHelp('code-graph');
|
|
2894
3254
|
|
|
@@ -2897,9 +3257,7 @@ cmd('code-graph', 'Show auto-detected file dependency graph', [
|
|
|
2897
3257
|
});
|
|
2898
3258
|
|
|
2899
3259
|
// ── spatial ──────────────────────────────────────────────────
|
|
2900
|
-
cmd('spatial', 'Spatial analysis: clusters, reading order, neighborhoods', [
|
|
2901
|
-
'pmx-canvas spatial',
|
|
2902
|
-
], async (args) => {
|
|
3260
|
+
cmd('spatial', 'Spatial analysis: clusters, reading order, neighborhoods', ['pmx-canvas spatial'], async (args) => {
|
|
2903
3261
|
const { flags } = parseFlags(args);
|
|
2904
3262
|
if (flags.help || flags.h) return showCommandHelp('spatial');
|
|
2905
3263
|
|
|
@@ -2908,102 +3266,119 @@ cmd('spatial', 'Spatial analysis: clusters, reading order, neighborhoods', [
|
|
|
2908
3266
|
});
|
|
2909
3267
|
|
|
2910
3268
|
// ── watch ────────────────────────────────────────────────────
|
|
2911
|
-
cmd(
|
|
2912
|
-
'
|
|
2913
|
-
'
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
3269
|
+
cmd(
|
|
3270
|
+
'watch',
|
|
3271
|
+
'Watch low-token semantic canvas changes over the existing SSE stream',
|
|
3272
|
+
[
|
|
3273
|
+
'pmx-canvas watch',
|
|
3274
|
+
'pmx-canvas watch --json',
|
|
3275
|
+
'pmx-canvas watch --events context-pin,move-end',
|
|
3276
|
+
'pmx-canvas watch --json --events connect --max-events 1',
|
|
3277
|
+
],
|
|
3278
|
+
async (args) => {
|
|
3279
|
+
const { flags } = parseFlags(args);
|
|
3280
|
+
if (flags.help || flags.h) return showCommandHelp('watch');
|
|
3281
|
+
|
|
3282
|
+
if (flags.json && flags.compact) {
|
|
3283
|
+
die('Use either --json or --compact, not both.');
|
|
3284
|
+
}
|
|
2923
3285
|
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
die(
|
|
2938
|
-
`Invalid value for --events: ${filtersRaw}`,
|
|
2939
|
-
'Use a comma-separated subset of: context-pin,move-end,group,connect,remove',
|
|
3286
|
+
const filtersRaw = typeof flags.events === 'string' ? flags.events : undefined;
|
|
3287
|
+
const requestedFilters = filtersRaw
|
|
3288
|
+
? Array.from(
|
|
3289
|
+
new Set(
|
|
3290
|
+
filtersRaw
|
|
3291
|
+
.split(',')
|
|
3292
|
+
.map((value) => value.trim())
|
|
3293
|
+
.filter((value) => value.length > 0),
|
|
3294
|
+
),
|
|
3295
|
+
)
|
|
3296
|
+
: [];
|
|
3297
|
+
const invalidFilter = requestedFilters.find(
|
|
3298
|
+
(value) => !ALL_SEMANTIC_WATCH_EVENT_TYPES.includes(value as (typeof ALL_SEMANTIC_WATCH_EVENT_TYPES)[number]),
|
|
2940
3299
|
);
|
|
2941
|
-
|
|
3300
|
+
if (invalidFilter) {
|
|
3301
|
+
die(
|
|
3302
|
+
`Invalid value in --events: ${invalidFilter}`,
|
|
3303
|
+
'Use a comma-separated subset of: context-pin,move-end,group,connect,remove',
|
|
3304
|
+
);
|
|
3305
|
+
}
|
|
3306
|
+
const filters = parseSemanticEventFilter(filtersRaw);
|
|
3307
|
+
if (filtersRaw && filters.size === 0) {
|
|
3308
|
+
die(
|
|
3309
|
+
`Invalid value for --events: ${filtersRaw}`,
|
|
3310
|
+
'Use a comma-separated subset of: context-pin,move-end,group,connect,remove',
|
|
3311
|
+
);
|
|
3312
|
+
}
|
|
2942
3313
|
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
3314
|
+
const maxEvents = optionalNumberFlag(flags, 'max-events', 'Use a positive integer, e.g. --max-events 1');
|
|
3315
|
+
const jsonMode = Boolean(flags.json);
|
|
3316
|
+
const reducer = new SemanticWatchReducer();
|
|
3317
|
+
const pinned = (await api('GET', '/api/canvas/pinned-context')) as { nodeIds?: string[] };
|
|
3318
|
+
reducer.setInitialPins(Array.isArray(pinned.nodeIds) ? pinned.nodeIds : []);
|
|
2948
3319
|
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
3320
|
+
const base = getBaseUrl();
|
|
3321
|
+
const controller = new AbortController();
|
|
3322
|
+
let response: Response;
|
|
3323
|
+
try {
|
|
3324
|
+
response = await fetch(`${base}/api/workbench/events`, {
|
|
3325
|
+
method: 'GET',
|
|
3326
|
+
headers: { Accept: 'text/event-stream' },
|
|
3327
|
+
signal: controller.signal,
|
|
3328
|
+
});
|
|
3329
|
+
} catch (error) {
|
|
3330
|
+
die(
|
|
3331
|
+
`Cannot connect to pmx-canvas event stream at ${base}: ${error instanceof Error ? error.message : String(error)}`,
|
|
3332
|
+
'Start the server first: pmx-canvas --no-open',
|
|
3333
|
+
);
|
|
3334
|
+
}
|
|
2964
3335
|
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
3336
|
+
if (!response.ok) {
|
|
3337
|
+
const text = await response.text();
|
|
3338
|
+
die(`Failed to open event stream: HTTP ${response.status}`, text);
|
|
3339
|
+
}
|
|
3340
|
+
if (!response.body) {
|
|
3341
|
+
die('Workbench event stream did not return a readable body.');
|
|
3342
|
+
}
|
|
2972
3343
|
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
return;
|
|
3344
|
+
let emitted = 0;
|
|
3345
|
+
try {
|
|
3346
|
+
for await (const message of parseSseStream(response.body)) {
|
|
3347
|
+
const semanticEvents = reducer.handleMessage(message).filter((event) => filters.has(event.type));
|
|
3348
|
+
|
|
3349
|
+
for (const event of semanticEvents) {
|
|
3350
|
+
console.log(jsonMode ? JSON.stringify(event) : formatCompactWatchEvent(event));
|
|
3351
|
+
emitted++;
|
|
3352
|
+
if (maxEvents !== undefined && emitted >= maxEvents) {
|
|
3353
|
+
controller.abort();
|
|
3354
|
+
return;
|
|
3355
|
+
}
|
|
2986
3356
|
}
|
|
2987
3357
|
}
|
|
3358
|
+
} catch (error) {
|
|
3359
|
+
if (controller.signal.aborted) return;
|
|
3360
|
+
die(
|
|
3361
|
+
`Watch stream failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3362
|
+
'Ensure the server is still running and reachable.',
|
|
3363
|
+
);
|
|
2988
3364
|
}
|
|
2989
|
-
}
|
|
2990
|
-
|
|
2991
|
-
die(
|
|
2992
|
-
`Watch stream failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
2993
|
-
'Ensure the server is still running and reachable.',
|
|
2994
|
-
);
|
|
2995
|
-
}
|
|
2996
|
-
});
|
|
3365
|
+
},
|
|
3366
|
+
);
|
|
2997
3367
|
|
|
2998
3368
|
// ── serve (delegates back to original CLI) ───────────────────
|
|
2999
|
-
cmd(
|
|
3000
|
-
'
|
|
3001
|
-
'
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3369
|
+
cmd(
|
|
3370
|
+
'serve',
|
|
3371
|
+
'Start the canvas server',
|
|
3372
|
+
[
|
|
3373
|
+
'pmx-canvas serve',
|
|
3374
|
+
'pmx-canvas serve --port=8080 --no-open',
|
|
3375
|
+
'pmx-canvas serve --demo --theme=light',
|
|
3376
|
+
'pmx-canvas --no-open --webview-automation',
|
|
3377
|
+
],
|
|
3378
|
+
async (_args) => {
|
|
3379
|
+
console.log('Use: pmx-canvas [--port=PORT] [--demo] [--no-open] [--theme=THEME] [--webview-automation]');
|
|
3380
|
+
},
|
|
3381
|
+
);
|
|
3007
3382
|
|
|
3008
3383
|
// ── Help ─────────────────────────────────────────────────────
|
|
3009
3384
|
|
|
@@ -3049,8 +3424,12 @@ function showCommandHelp(name: string): void {
|
|
|
3049
3424
|
}
|
|
3050
3425
|
if (name === 'node add' || name === 'graph add' || name === 'validate spec') {
|
|
3051
3426
|
console.log('\nGraph flags:');
|
|
3052
|
-
console.log(
|
|
3053
|
-
|
|
3427
|
+
console.log(
|
|
3428
|
+
' Graph fields accept kebab-case CLI flags and camelCase schema names, e.g. --graph-type/--graphType and --x-key/--xKey',
|
|
3429
|
+
);
|
|
3430
|
+
console.log(
|
|
3431
|
+
' Use --node-height/--nodeHeight for canvas frame height; use --chart-height for chart content height. --height is kept as a frame-height alias for compatibility.',
|
|
3432
|
+
);
|
|
3054
3433
|
console.log(' Pass --show-legend false to hide legends in compact node layouts.');
|
|
3055
3434
|
}
|
|
3056
3435
|
if (name === 'validate spec') {
|
|
@@ -3220,6 +3599,8 @@ Analysis:
|
|
|
3220
3599
|
|
|
3221
3600
|
Global flags:
|
|
3222
3601
|
--help, -h Show help for any command
|
|
3602
|
+
--port <n> Target daemon port for this invocation (overrides PMX_CANVAS_PORT)
|
|
3603
|
+
--server-url <url> Target server URL for this invocation (overrides PMX_CANVAS_URL; wins over --port)
|
|
3223
3604
|
|
|
3224
3605
|
Environment:
|
|
3225
3606
|
PMX_CANVAS_URL Server URL (default: http://localhost:4313)
|
|
@@ -3277,7 +3658,8 @@ async function readStdin(): Promise<string> {
|
|
|
3277
3658
|
|
|
3278
3659
|
// ── Router ───────────────────────────────────────────────────
|
|
3279
3660
|
|
|
3280
|
-
export async function runAgentCli(
|
|
3661
|
+
export async function runAgentCli(rawArgs: string[]): Promise<void> {
|
|
3662
|
+
const args = extractGlobalTargetFlags(rawArgs);
|
|
3281
3663
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
3282
3664
|
showTopLevelHelp();
|
|
3283
3665
|
return;
|
|
@@ -3326,9 +3708,7 @@ export async function runAgentCli(args: string[]): Promise<void> {
|
|
|
3326
3708
|
`Available subcommands: ${available}`,
|
|
3327
3709
|
].filter((hint): hint is string => typeof hint === 'string');
|
|
3328
3710
|
die(
|
|
3329
|
-
subcommand
|
|
3330
|
-
? `Unknown ${oneWord} subcommand: "${subcommand}".`
|
|
3331
|
-
: `Missing ${oneWord} subcommand.`,
|
|
3711
|
+
subcommand ? `Unknown ${oneWord} subcommand: "${subcommand}".` : `Missing ${oneWord} subcommand.`,
|
|
3332
3712
|
hints.join(' '),
|
|
3333
3713
|
);
|
|
3334
3714
|
}
|