pmx-canvas 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -0
- package/Readme.md +13 -4
- package/dist/canvas/index.js +71 -71
- package/dist/json-render/index.js +47 -47
- package/dist/types/cli/agent.d.ts +13 -8
- package/dist/types/cli/commands/apps.d.ts +1 -0
- package/dist/types/cli/commands/ax.d.ts +1 -0
- package/dist/types/cli/commands/copilot.d.ts +1 -0
- package/dist/types/cli/commands/edges.d.ts +1 -0
- package/dist/types/cli/commands/groups.d.ts +1 -0
- package/dist/types/cli/commands/history.d.ts +1 -0
- package/dist/types/cli/commands/nodes.d.ts +1 -0
- package/dist/types/cli/commands/pins.d.ts +1 -0
- package/dist/types/cli/commands/query.d.ts +1 -0
- package/dist/types/cli/commands/view.d.ts +1 -0
- package/dist/types/cli/commands/webview.d.ts +1 -0
- package/dist/types/cli/shared.d.ts +73 -0
- package/dist/types/client/canvas/ExpandedNodeOverlay.d.ts +1 -1
- package/dist/types/client/index.d.ts +1 -1
- package/dist/types/client/nodes/ExtAppFrame.d.ts +10 -0
- package/dist/types/client/nodes/HtmlNode.d.ts +1 -1
- package/dist/types/client/nodes/McpAppNode.d.ts +1 -1
- package/dist/types/client/nodes/use-ax-surface-bridge.d.ts +20 -0
- package/dist/types/client/state/node-factory.d.ts +29 -0
- package/dist/types/client/uuid-polyfill.d.ts +0 -0
- package/dist/types/server/canvas-db.d.ts +0 -1
- package/dist/types/server/canvas-operations.d.ts +2 -0
- package/dist/types/server/canvas-state.d.ts +14 -15
- package/dist/types/server/diagram-presets.d.ts +8 -0
- package/dist/types/server/operations/composites.d.ts +6 -30
- package/dist/types/server/server.d.ts +1 -0
- package/dist/types/shared/ax-surface-protocol.d.ts +18 -0
- package/docs/bun-webview-integration.md +5 -0
- package/docs/cli.md +5 -0
- package/docs/environment.md +48 -0
- package/docs/http-api.md +25 -1
- package/docs/mcp.md +21 -30
- package/docs/node-types.md +5 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +34 -15
- package/docs/screenshot.png +0 -0
- package/docs/tech-debt-assessment-2026-06.md +5 -5
- package/package.json +13 -10
- package/skills/pmx-canvas/SKILL.md +16 -6
- package/skills/pmx-canvas/evals/evals.json +7 -7
- package/skills/pmx-canvas/references/full-reference.md +25 -16
- package/src/cli/agent.ts +22 -3512
- package/src/cli/commands/apps.ts +165 -0
- package/src/cli/commands/ax.ts +604 -0
- package/src/cli/commands/copilot.ts +43 -0
- package/src/cli/commands/edges.ts +73 -0
- package/src/cli/commands/groups.ts +81 -0
- package/src/cli/commands/history.ts +220 -0
- package/src/cli/commands/nodes.ts +984 -0
- package/src/cli/commands/pins.ts +42 -0
- package/src/cli/commands/query.ts +223 -0
- package/src/cli/commands/view.ts +186 -0
- package/src/cli/commands/webview.ts +227 -0
- package/src/cli/index.ts +8 -2
- package/src/cli/shared.ts +861 -0
- package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -2
- package/src/client/index.tsx +2 -0
- package/src/client/nodes/ExtAppFrame.tsx +70 -51
- package/src/client/nodes/HtmlNode.tsx +8 -51
- package/src/client/nodes/McpAppNode.tsx +12 -47
- package/src/client/nodes/use-ax-surface-bridge.ts +71 -0
- package/src/client/state/node-factory.ts +72 -0
- package/src/client/state/sse-bridge.ts +209 -86
- package/src/client/uuid-polyfill.ts +14 -0
- package/src/json-render/renderer/index.tsx +3 -2
- package/src/mcp/server.ts +0 -47
- package/src/server/canvas-db.ts +0 -8
- package/src/server/canvas-operations.ts +8 -0
- package/src/server/canvas-schema.ts +2 -8
- package/src/server/canvas-state.ts +51 -151
- package/src/server/diagram-presets.ts +15 -1
- package/src/server/html-surface.ts +11 -6
- package/src/server/index.ts +3 -9
- package/src/server/operations/composites.ts +25 -34
- package/src/server/operations/invoker.ts +10 -3
- package/src/server/operations/mcp.ts +51 -1
- package/src/server/operations/ops/app.ts +5 -0
- package/src/server/operations/ops/snapshots.ts +6 -7
- package/src/server/operations/registry.ts +4 -1
- package/src/server/server.ts +172 -74
- package/src/shared/ax-surface-protocol.ts +22 -0
- package/dist/types/client/canvas/ContextPinHud.d.ts +0 -1
- package/src/client/canvas/ContextPinHud.tsx +0 -17
|
@@ -0,0 +1,861 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared core for the agent-native CLI.
|
|
3
|
+
*
|
|
4
|
+
* Holds the command registry (COMMANDS + cmd()), flag parsing, output/die,
|
|
5
|
+
* target resolution (getBaseUrl + global --port/--server-url handling), the
|
|
6
|
+
* operation invoker, and every helper used by two or more command domains.
|
|
7
|
+
* Command modules in src/cli/commands/ import from here only — this module
|
|
8
|
+
* must never import from agent.ts or any command module (cycle-free).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { HttpOperationInvoker, OperationError } from '../server/operations/index.js';
|
|
13
|
+
|
|
14
|
+
// ── Helpers ──────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const DEFAULT_PORT = 4313;
|
|
17
|
+
const defaultConsoleLog = console.log;
|
|
18
|
+
|
|
19
|
+
// Per-invocation target override from the global --server-url / --port flags.
|
|
20
|
+
// Before these existed, `--port 4750` on any agent command was SILENTLY ignored
|
|
21
|
+
// and the command hit the default 4313 daemon — which once pointed a test
|
|
22
|
+
// automation WebView at a live production board.
|
|
23
|
+
let cliTargetOverride: string | null = null;
|
|
24
|
+
|
|
25
|
+
function getBaseUrl(): string {
|
|
26
|
+
if (cliTargetOverride) return cliTargetOverride;
|
|
27
|
+
const envUrl = process.env.PMX_CANVAS_URL;
|
|
28
|
+
if (envUrl) return envUrl.replace(/\/$/, '');
|
|
29
|
+
const port = process.env.PMX_CANVAS_PORT || DEFAULT_PORT;
|
|
30
|
+
return `http://localhost:${port}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Extract the global `--port <n>` / `--server-url <url>` flags (any position,
|
|
35
|
+
* `=` or space-separated value) and set the invocation's target override.
|
|
36
|
+
* Returns the remaining args for command dispatch. Invalid values are a loud
|
|
37
|
+
* `die` — never a silent fallback to the default port. `--server-url` wins
|
|
38
|
+
* over `--port` when both are given.
|
|
39
|
+
*/
|
|
40
|
+
export function extractGlobalTargetFlags(args: string[]): string[] {
|
|
41
|
+
cliTargetOverride = null;
|
|
42
|
+
let portOverride: number | null = null;
|
|
43
|
+
let urlOverride: string | null = null;
|
|
44
|
+
const rest: string[] = [];
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < args.length; i++) {
|
|
47
|
+
const arg = args[i];
|
|
48
|
+
let name: 'port' | 'server-url' | null = null;
|
|
49
|
+
let value: string | undefined;
|
|
50
|
+
if (arg === '--port' || arg === '--server-url') {
|
|
51
|
+
name = arg.slice(2) as 'port' | 'server-url';
|
|
52
|
+
value = args[i + 1];
|
|
53
|
+
i += 1;
|
|
54
|
+
} else if (arg.startsWith('--port=')) {
|
|
55
|
+
name = 'port';
|
|
56
|
+
value = arg.slice('--port='.length);
|
|
57
|
+
} else if (arg.startsWith('--server-url=')) {
|
|
58
|
+
name = 'server-url';
|
|
59
|
+
value = arg.slice('--server-url='.length);
|
|
60
|
+
} else {
|
|
61
|
+
rest.push(arg);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (name === 'port') {
|
|
66
|
+
const port = Number(value);
|
|
67
|
+
if (!value || !Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
68
|
+
die(
|
|
69
|
+
`--port requires a port number, got ${value === undefined ? 'no value' : JSON.stringify(value)}.`,
|
|
70
|
+
'Example: --port 4313. Without the flag, PMX_CANVAS_PORT / PMX_CANVAS_URL pick the target.',
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
portOverride = port;
|
|
74
|
+
} else {
|
|
75
|
+
if (!value || !/^https?:\/\//.test(value)) {
|
|
76
|
+
die(
|
|
77
|
+
`--server-url requires an http(s) URL, got ${value === undefined ? 'no value' : JSON.stringify(value)}.`,
|
|
78
|
+
'Example: --server-url http://localhost:4313. Without the flag, PMX_CANVAS_URL picks the target.',
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
urlOverride = value.replace(/\/$/, '');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (urlOverride) cliTargetOverride = urlOverride;
|
|
86
|
+
else if (portOverride) cliTargetOverride = `http://localhost:${portOverride}`;
|
|
87
|
+
return rest;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function die(message: string, hint?: string): never {
|
|
91
|
+
const out: Record<string, string> = { error: message };
|
|
92
|
+
if (hint) out.hint = hint;
|
|
93
|
+
console.error(JSON.stringify(out));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function output(data: unknown): void {
|
|
98
|
+
const text = JSON.stringify(data, null, 2);
|
|
99
|
+
if (console.log !== defaultConsoleLog) {
|
|
100
|
+
console.log(text);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
process.stdout.write(`${text}\n`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Operation-registry invoker (plan-005): every server-backed command builds
|
|
107
|
+
// its HTTP request from the shared route table instead of hand-written paths.
|
|
108
|
+
// Operation failures and connection failures die with the same JSON error
|
|
109
|
+
// shape ({ error, hint? } on stderr, exit 1); routes declaring
|
|
110
|
+
// errorBodyAsResult return their failure envelope instead of throwing.
|
|
111
|
+
async function invokeOperation(name: string, input: Record<string, unknown>): Promise<unknown> {
|
|
112
|
+
const base = getBaseUrl();
|
|
113
|
+
try {
|
|
114
|
+
return await new HttpOperationInvoker(base).invoke(name, input);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (error instanceof OperationError) {
|
|
117
|
+
die(error.message);
|
|
118
|
+
}
|
|
119
|
+
die(
|
|
120
|
+
`Cannot connect to pmx-canvas at ${base}: ${error instanceof Error ? error.message : String(error)}`,
|
|
121
|
+
`Start the server first: pmx-canvas --no-open`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Flag parsing ─────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
function parseFlags(args: string[]): { positional: string[]; flags: Record<string, string | true> } {
|
|
129
|
+
const positional: string[] = [];
|
|
130
|
+
const flags: Record<string, string | true> = {};
|
|
131
|
+
// Boolean-only flags (never take a value argument)
|
|
132
|
+
const BOOL_FLAGS = new Set([
|
|
133
|
+
'help',
|
|
134
|
+
'h',
|
|
135
|
+
'ids',
|
|
136
|
+
'stdin',
|
|
137
|
+
'yes',
|
|
138
|
+
'list',
|
|
139
|
+
'clear',
|
|
140
|
+
'set',
|
|
141
|
+
'animated',
|
|
142
|
+
'dry-run',
|
|
143
|
+
'all',
|
|
144
|
+
'no-open-in-canvas',
|
|
145
|
+
'lock-arrange',
|
|
146
|
+
'unlock-arrange',
|
|
147
|
+
'json',
|
|
148
|
+
'compact',
|
|
149
|
+
'verbose',
|
|
150
|
+
'full',
|
|
151
|
+
'include-logs',
|
|
152
|
+
'no-pan',
|
|
153
|
+
'schema',
|
|
154
|
+
'example',
|
|
155
|
+
'examples',
|
|
156
|
+
'strict-size',
|
|
157
|
+
'scroll-overflow',
|
|
158
|
+
'report',
|
|
159
|
+
'canvas',
|
|
160
|
+
'hooks',
|
|
161
|
+
'tools',
|
|
162
|
+
'session-messaging',
|
|
163
|
+
'permissions',
|
|
164
|
+
'files',
|
|
165
|
+
'ui-prompts',
|
|
166
|
+
]);
|
|
167
|
+
for (let i = 0; i < args.length; i++) {
|
|
168
|
+
const arg = args[i];
|
|
169
|
+
if (arg.startsWith('--')) {
|
|
170
|
+
const eq = arg.indexOf('=');
|
|
171
|
+
if (eq !== -1) {
|
|
172
|
+
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
173
|
+
} else {
|
|
174
|
+
const key = arg.slice(2);
|
|
175
|
+
// If not a boolean flag and next arg exists and isn't a flag, consume it as value
|
|
176
|
+
if (!BOOL_FLAGS.has(key) && i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
177
|
+
flags[key] = args[++i];
|
|
178
|
+
} else {
|
|
179
|
+
flags[key] = true;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} else if (arg.startsWith('-') && arg.length === 2) {
|
|
183
|
+
const key = arg.slice(1);
|
|
184
|
+
if (!BOOL_FLAGS.has(key) && i + 1 < args.length && !args[i + 1].startsWith('-')) {
|
|
185
|
+
flags[key] = args[++i];
|
|
186
|
+
} else {
|
|
187
|
+
flags[key] = true;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
positional.push(arg);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return { positional, flags };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function requireFlag(flags: Record<string, string | true>, name: string, hint: string): string {
|
|
197
|
+
const val = flags[name];
|
|
198
|
+
if (!val || val === true) {
|
|
199
|
+
die(`Missing required flag: --${name}`, hint);
|
|
200
|
+
}
|
|
201
|
+
return val;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function getStringFlag(flags: Record<string, string | true>, ...names: string[]): string | undefined {
|
|
205
|
+
for (const name of names) {
|
|
206
|
+
const value = flags[name];
|
|
207
|
+
if (typeof value === 'string' && value.length > 0) return value;
|
|
208
|
+
}
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function optionalNumberFlag(flags: Record<string, string | true>, name: string, hint: string): number | undefined {
|
|
213
|
+
const val = flags[name];
|
|
214
|
+
if (!val || val === true) return undefined;
|
|
215
|
+
const parsed = Number(val);
|
|
216
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
217
|
+
die(`Invalid value for --${name}: ${String(val)}`, hint);
|
|
218
|
+
}
|
|
219
|
+
return Math.floor(parsed);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* AX `source` for a CLI-originated action. Defaults to `cli`, but honors an
|
|
224
|
+
* explicit `--source <label>` so an adapterless agent using the CLI as a fallback
|
|
225
|
+
* transport (e.g. `--source codex`) attributes its actions correctly — keeping
|
|
226
|
+
* loop-safety (a consumer never gets back its own steering) accurate (report #69).
|
|
227
|
+
*/
|
|
228
|
+
function resolveAxSource(flags: Record<string, string | true>): string {
|
|
229
|
+
return getStringFlag(flags, 'source') ?? 'cli';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function optionalFiniteFlag(flags: Record<string, string | true>, name: string, hint: string): number | undefined {
|
|
233
|
+
const val = flags[name];
|
|
234
|
+
if (!val || val === true) return undefined;
|
|
235
|
+
const parsed = Number(val);
|
|
236
|
+
if (!Number.isFinite(parsed)) {
|
|
237
|
+
die(`Invalid value for --${name}: ${String(val)}`, hint);
|
|
238
|
+
}
|
|
239
|
+
return parsed;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function optionalPositiveFiniteFlag(
|
|
243
|
+
flags: Record<string, string | true>,
|
|
244
|
+
name: string,
|
|
245
|
+
hint: string,
|
|
246
|
+
): number | undefined {
|
|
247
|
+
const parsed = optionalFiniteFlag(flags, name, hint);
|
|
248
|
+
if (parsed === undefined) return undefined;
|
|
249
|
+
if (parsed <= 0) {
|
|
250
|
+
die(`Invalid value for --${name}: ${String(flags[name])}`, hint);
|
|
251
|
+
}
|
|
252
|
+
return parsed;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function optionalPositiveFiniteFlagWithAliases(
|
|
256
|
+
flags: Record<string, string | true>,
|
|
257
|
+
hint: string,
|
|
258
|
+
...names: string[]
|
|
259
|
+
): number | undefined {
|
|
260
|
+
for (const name of names) {
|
|
261
|
+
const parsed = optionalPositiveFiniteFlag(flags, name, hint);
|
|
262
|
+
if (parsed !== undefined) return parsed;
|
|
263
|
+
}
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function optionalBooleanFlag(flags: Record<string, string | true>, name: string, hint: string): boolean | undefined {
|
|
268
|
+
const val = flags[name];
|
|
269
|
+
if (val === undefined) return undefined;
|
|
270
|
+
if (val === true || val === 'true') return true;
|
|
271
|
+
if (val === 'false') return false;
|
|
272
|
+
die(`Invalid value for --${name}: ${String(val)}`, hint);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function applyStrictSizeFlags(body: Record<string, unknown>, flags: Record<string, string | true>): void {
|
|
276
|
+
if (flags['strict-size'] || flags['scroll-overflow']) body.strictSize = true;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
280
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function parseStringListFlag(
|
|
284
|
+
flags: Record<string, string | true>,
|
|
285
|
+
name: string,
|
|
286
|
+
hint: string,
|
|
287
|
+
...aliases: string[]
|
|
288
|
+
): string[] | undefined {
|
|
289
|
+
const raw = getStringFlag(flags, name, ...aliases);
|
|
290
|
+
if (raw === undefined) return undefined;
|
|
291
|
+
const trimmed = raw.trim();
|
|
292
|
+
if (!trimmed) {
|
|
293
|
+
die(`Invalid value for --${name}: expected at least one string.`, hint);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (trimmed.startsWith('[')) {
|
|
297
|
+
const parsed = parseJsonValue(trimmed, `value for --${name}`, hint);
|
|
298
|
+
if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== 'string' || item.trim().length === 0)) {
|
|
299
|
+
die(`Invalid value for --${name}: expected a JSON array of non-empty strings.`, hint);
|
|
300
|
+
}
|
|
301
|
+
return parsed.map((item) => item.trim());
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const values = trimmed
|
|
305
|
+
.split(',')
|
|
306
|
+
.map((value) => value.trim())
|
|
307
|
+
.filter(Boolean);
|
|
308
|
+
if (values.length === 0) {
|
|
309
|
+
die(`Invalid value for --${name}: expected a comma-separated list of keys.`, hint);
|
|
310
|
+
}
|
|
311
|
+
return values;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function parseRecordArrayJson(raw: string, hint: string): Array<Record<string, unknown>> {
|
|
315
|
+
let parsed: unknown;
|
|
316
|
+
try {
|
|
317
|
+
parsed = JSON.parse(raw);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
die(`Invalid JSON dataset: ${error instanceof Error ? error.message : String(error)}`, hint);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!Array.isArray(parsed) || parsed.some((item) => !isRecord(item))) {
|
|
323
|
+
die('Graph data must be a JSON array of objects.', hint);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return parsed;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function parseJsonValue(raw: string, label: string, hint: string): unknown {
|
|
330
|
+
try {
|
|
331
|
+
return JSON.parse(raw);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
die(`Invalid ${label}: ${error instanceof Error ? error.message : String(error)}`, hint);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function parseJsonRecord(raw: string, label: string, hint: string): Record<string, unknown> {
|
|
338
|
+
const parsed = parseJsonValue(raw, label, hint);
|
|
339
|
+
if (!isRecord(parsed)) {
|
|
340
|
+
die(`${label} must be a JSON object.`, hint);
|
|
341
|
+
}
|
|
342
|
+
return parsed;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function readTextInput(
|
|
346
|
+
flags: Record<string, string | true>,
|
|
347
|
+
options: {
|
|
348
|
+
fileFlags?: string[];
|
|
349
|
+
valueFlags?: string[];
|
|
350
|
+
allowStdin?: boolean;
|
|
351
|
+
label: string;
|
|
352
|
+
hint: string;
|
|
353
|
+
requiredMessage: string;
|
|
354
|
+
},
|
|
355
|
+
): Promise<string> {
|
|
356
|
+
for (const name of options.fileFlags ?? []) {
|
|
357
|
+
const path = getStringFlag(flags, name);
|
|
358
|
+
if (!path) continue;
|
|
359
|
+
try {
|
|
360
|
+
return readFileSync(path, 'utf-8');
|
|
361
|
+
} catch (error) {
|
|
362
|
+
die(`Unable to read --${name}: ${error instanceof Error ? error.message : String(error)}`, options.hint);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
for (const name of options.valueFlags ?? []) {
|
|
367
|
+
const value = getStringFlag(flags, name);
|
|
368
|
+
if (value !== undefined) return value;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (options.allowStdin && flags.stdin) {
|
|
372
|
+
return await readStdin();
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
die(options.requiredMessage, options.hint);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async function readOptionalTextInput(
|
|
379
|
+
flags: Record<string, string | true>,
|
|
380
|
+
options: {
|
|
381
|
+
fileFlags?: string[];
|
|
382
|
+
valueFlags?: string[];
|
|
383
|
+
allowStdin?: boolean;
|
|
384
|
+
label: string;
|
|
385
|
+
hint: string;
|
|
386
|
+
},
|
|
387
|
+
): Promise<string | undefined> {
|
|
388
|
+
for (const name of options.fileFlags ?? []) {
|
|
389
|
+
const path = getStringFlag(flags, name);
|
|
390
|
+
if (!path) continue;
|
|
391
|
+
try {
|
|
392
|
+
return readFileSync(path, 'utf-8');
|
|
393
|
+
} catch (error) {
|
|
394
|
+
die(`Unable to read --${name}: ${error instanceof Error ? error.message : String(error)}`, options.hint);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
for (const name of options.valueFlags ?? []) {
|
|
399
|
+
const value = getStringFlag(flags, name);
|
|
400
|
+
if (value !== undefined) return value;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (options.allowStdin && flags.stdin) {
|
|
404
|
+
return await readStdin();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function applyCommonGeometryFlags(
|
|
411
|
+
body: Record<string, unknown>,
|
|
412
|
+
flags: Record<string, string | true>,
|
|
413
|
+
hints: { x: string; y: string; width: string; height: string },
|
|
414
|
+
): void {
|
|
415
|
+
const x = optionalFiniteFlag(flags, 'x', hints.x);
|
|
416
|
+
const y = optionalFiniteFlag(flags, 'y', hints.y);
|
|
417
|
+
const width = optionalPositiveFiniteFlag(flags, 'width', hints.width);
|
|
418
|
+
const height = optionalPositiveFiniteFlag(flags, 'height', hints.height);
|
|
419
|
+
if (x !== undefined) body.x = x;
|
|
420
|
+
if (y !== undefined) body.y = y;
|
|
421
|
+
if (width !== undefined) body.width = width;
|
|
422
|
+
if (height !== undefined) body.height = height;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function buildJsonRenderRequestBody(flags: Record<string, string | true>): Promise<Record<string, unknown>> {
|
|
426
|
+
const hint = 'Use: pmx-canvas node add --type json-render --spec-file ./dashboard.json --title "Ops Dashboard"';
|
|
427
|
+
const title = typeof flags.title === 'string' ? flags.title.trim() : '';
|
|
428
|
+
|
|
429
|
+
const rawSpec = await readTextInput(flags, {
|
|
430
|
+
fileFlags: ['spec-file'],
|
|
431
|
+
valueFlags: ['spec-json'],
|
|
432
|
+
allowStdin: true,
|
|
433
|
+
label: 'JSON spec',
|
|
434
|
+
hint,
|
|
435
|
+
requiredMessage: 'json-render nodes require --spec-file, --spec-json, or --stdin.',
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const spec = parseJsonValue(rawSpec, 'JSON spec', hint);
|
|
439
|
+
const body: Record<string, unknown> = { ...(title ? { title } : {}), spec };
|
|
440
|
+
applyCommonGeometryFlags(body, flags, {
|
|
441
|
+
x: 'Use a finite number, e.g. --x 500',
|
|
442
|
+
y: 'Use a finite number, e.g. --y 300',
|
|
443
|
+
width: 'Use a positive number, e.g. --width 840',
|
|
444
|
+
height: 'Use a positive number, e.g. --height 620',
|
|
445
|
+
});
|
|
446
|
+
applyStrictSizeFlags(body, flags);
|
|
447
|
+
return body;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function buildHtmlPrimitiveRequestBody(flags: Record<string, string | true>): Promise<Record<string, unknown>> {
|
|
451
|
+
const hint = 'Use: pmx-canvas html primitive add --kind choice-grid --data-file ./primitive.json --title "Options"';
|
|
452
|
+
const kind = getStringFlag(flags, 'kind', 'primitive');
|
|
453
|
+
if (!kind) die('HTML primitives require --kind.', hint);
|
|
454
|
+
const body: Record<string, unknown> = { type: 'html', primitive: kind };
|
|
455
|
+
if (typeof flags.title === 'string') body.title = flags.title;
|
|
456
|
+
const rawData = await readOptionalTextInput(flags, {
|
|
457
|
+
fileFlags: ['data-file'],
|
|
458
|
+
valueFlags: ['data-json', 'data'],
|
|
459
|
+
allowStdin: true,
|
|
460
|
+
label: 'HTML primitive data',
|
|
461
|
+
hint,
|
|
462
|
+
});
|
|
463
|
+
if (rawData !== undefined) {
|
|
464
|
+
body.data = parseJsonRecord(rawData, 'HTML primitive data', hint);
|
|
465
|
+
}
|
|
466
|
+
applyCommonGeometryFlags(body, flags, {
|
|
467
|
+
x: 'Use a finite number, e.g. --x 500',
|
|
468
|
+
y: 'Use a finite number, e.g. --y 300',
|
|
469
|
+
width: 'Use a positive number, e.g. --width 980',
|
|
470
|
+
height: 'Use a positive number, e.g. --height 720',
|
|
471
|
+
});
|
|
472
|
+
applyStrictSizeFlags(body, flags);
|
|
473
|
+
return body;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async function buildGraphRequestBody(
|
|
477
|
+
flags: Record<string, string | true>,
|
|
478
|
+
options: { requireData?: boolean; allowStdin?: boolean } = {},
|
|
479
|
+
): Promise<Record<string, unknown>> {
|
|
480
|
+
const requireData = options.requireData !== false;
|
|
481
|
+
const allowStdin = options.allowStdin !== false;
|
|
482
|
+
const hint =
|
|
483
|
+
'Use: pmx-canvas node add --type graph --graph-type bar --data-file ./metrics.json --x-key label --y-key value';
|
|
484
|
+
|
|
485
|
+
const body: Record<string, unknown> = {
|
|
486
|
+
...(requireData ? { graphType: getStringFlag(flags, 'graph-type', 'graphType') ?? 'line' } : {}),
|
|
487
|
+
};
|
|
488
|
+
const rawData = await readOptionalTextInput(flags, {
|
|
489
|
+
fileFlags: ['data-file'],
|
|
490
|
+
valueFlags: ['data-json', 'data'],
|
|
491
|
+
allowStdin,
|
|
492
|
+
label: 'graph JSON dataset',
|
|
493
|
+
hint,
|
|
494
|
+
});
|
|
495
|
+
if (rawData !== undefined) {
|
|
496
|
+
body.data = parseRecordArrayJson(rawData, hint);
|
|
497
|
+
} else if (requireData) {
|
|
498
|
+
die('Graph nodes require --data-file, --data-json, --data, or --stdin JSON data.', hint);
|
|
499
|
+
}
|
|
500
|
+
const graphType = getStringFlag(flags, 'graph-type', 'graphType');
|
|
501
|
+
if (graphType) body.graphType = graphType;
|
|
502
|
+
if (typeof flags.title === 'string') body.title = flags.title;
|
|
503
|
+
const xKey = getStringFlag(flags, 'x-key', 'xKey');
|
|
504
|
+
const yKey = getStringFlag(flags, 'y-key', 'yKey');
|
|
505
|
+
const zKey = getStringFlag(flags, 'z-key', 'zKey');
|
|
506
|
+
const nameKey = getStringFlag(flags, 'name-key', 'nameKey');
|
|
507
|
+
const valueKey = getStringFlag(flags, 'value-key', 'valueKey');
|
|
508
|
+
const axisKey = getStringFlag(flags, 'axis-key', 'axisKey');
|
|
509
|
+
if (xKey) body.xKey = xKey;
|
|
510
|
+
if (yKey) body.yKey = yKey;
|
|
511
|
+
if (zKey) body.zKey = zKey;
|
|
512
|
+
if (nameKey) body.nameKey = nameKey;
|
|
513
|
+
if (valueKey) body.valueKey = valueKey;
|
|
514
|
+
if (axisKey) body.axisKey = axisKey;
|
|
515
|
+
const metrics = parseStringListFlag(flags, 'metrics', 'Use a comma-separated list, e.g. --metrics north,south');
|
|
516
|
+
const series = parseStringListFlag(flags, 'series', 'Use a comma-separated list, e.g. --series north,south');
|
|
517
|
+
if (metrics) body.metrics = metrics;
|
|
518
|
+
if (series) body.series = series;
|
|
519
|
+
const barKey = getStringFlag(flags, 'bar-key', 'barKey');
|
|
520
|
+
const lineKey = getStringFlag(flags, 'line-key', 'lineKey');
|
|
521
|
+
if (barKey) body.barKey = barKey;
|
|
522
|
+
if (lineKey) body.lineKey = lineKey;
|
|
523
|
+
if (flags.aggregate === 'sum' || flags.aggregate === 'count' || flags.aggregate === 'avg') {
|
|
524
|
+
body.aggregate = flags.aggregate;
|
|
525
|
+
}
|
|
526
|
+
const color = getStringFlag(flags, 'color');
|
|
527
|
+
const barColor = getStringFlag(flags, 'bar-color', 'barColor');
|
|
528
|
+
const lineColor = getStringFlag(flags, 'line-color', 'lineColor');
|
|
529
|
+
if (color) body.color = color;
|
|
530
|
+
if (barColor) body.barColor = barColor;
|
|
531
|
+
if (lineColor) body.lineColor = lineColor;
|
|
532
|
+
const showLegend = optionalBooleanFlag(flags, 'show-legend', 'Use --show-legend true or --show-legend false');
|
|
533
|
+
const showLabels = optionalBooleanFlag(flags, 'show-labels', 'Use --show-labels true or --show-labels false');
|
|
534
|
+
if (showLegend !== undefined) body.showLegend = showLegend;
|
|
535
|
+
if (showLabels !== undefined) body.showLabels = showLabels;
|
|
536
|
+
|
|
537
|
+
const chartHeight = optionalPositiveFiniteFlag(
|
|
538
|
+
flags,
|
|
539
|
+
'chart-height',
|
|
540
|
+
'Use a positive number, e.g. --chart-height 300',
|
|
541
|
+
);
|
|
542
|
+
const x = optionalFiniteFlag(flags, 'x', 'Use a finite number, e.g. --x 500');
|
|
543
|
+
const y = optionalFiniteFlag(flags, 'y', 'Use a finite number, e.g. --y 300');
|
|
544
|
+
const width = optionalPositiveFiniteFlag(flags, 'width', 'Use a positive number, e.g. --width 760');
|
|
545
|
+
const nodeHeight = optionalPositiveFiniteFlagWithAliases(
|
|
546
|
+
flags,
|
|
547
|
+
'Use a positive number, e.g. --node-height 520',
|
|
548
|
+
'node-height',
|
|
549
|
+
'nodeHeight',
|
|
550
|
+
'height',
|
|
551
|
+
);
|
|
552
|
+
if (chartHeight !== undefined) body.height = chartHeight;
|
|
553
|
+
if (x !== undefined) body.x = x;
|
|
554
|
+
if (y !== undefined) body.y = y;
|
|
555
|
+
if (width !== undefined) body.width = width;
|
|
556
|
+
if (nodeHeight !== undefined) body.nodeHeight = nodeHeight;
|
|
557
|
+
applyStrictSizeFlags(body, flags);
|
|
558
|
+
return body;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
async function buildWebArtifactRequestBody(flags: Record<string, string | true>): Promise<Record<string, unknown>> {
|
|
562
|
+
const hint = 'Use: pmx-canvas web-artifact build --title "Dashboard" --app-file ./App.tsx';
|
|
563
|
+
const title = requireFlag(flags, 'title', hint);
|
|
564
|
+
const appTsx = await readTextInput(flags, {
|
|
565
|
+
fileFlags: ['app-file'],
|
|
566
|
+
valueFlags: ['app-tsx'],
|
|
567
|
+
allowStdin: true,
|
|
568
|
+
label: 'App.tsx',
|
|
569
|
+
hint,
|
|
570
|
+
requiredMessage: 'web-artifact build requires --app-file, --app-tsx, or --stdin.',
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
const body: Record<string, unknown> = { title, appTsx };
|
|
574
|
+
|
|
575
|
+
const indexCssFile = getStringFlag(flags, 'index-css-file');
|
|
576
|
+
const indexCss = getStringFlag(flags, 'index-css');
|
|
577
|
+
if (indexCssFile) {
|
|
578
|
+
body.indexCss = readFileSync(indexCssFile, 'utf-8');
|
|
579
|
+
} else if (indexCss !== undefined) {
|
|
580
|
+
body.indexCss = indexCss;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const mainFile = getStringFlag(flags, 'main-file');
|
|
584
|
+
const mainTsx = getStringFlag(flags, 'main-tsx');
|
|
585
|
+
if (mainFile) {
|
|
586
|
+
body.mainTsx = readFileSync(mainFile, 'utf-8');
|
|
587
|
+
} else if (mainTsx !== undefined) {
|
|
588
|
+
body.mainTsx = mainTsx;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const indexHtmlFile = getStringFlag(flags, 'index-html-file');
|
|
592
|
+
const indexHtml = getStringFlag(flags, 'index-html');
|
|
593
|
+
if (indexHtmlFile) {
|
|
594
|
+
body.indexHtml = readFileSync(indexHtmlFile, 'utf-8');
|
|
595
|
+
} else if (indexHtml !== undefined) {
|
|
596
|
+
body.indexHtml = indexHtml;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (typeof flags['project-path'] === 'string') body.projectPath = flags['project-path'];
|
|
600
|
+
if (typeof flags['output-path'] === 'string') body.outputPath = flags['output-path'];
|
|
601
|
+
if (typeof flags['init-script-path'] === 'string') body.initScriptPath = flags['init-script-path'];
|
|
602
|
+
if (typeof flags['bundle-script-path'] === 'string') body.bundleScriptPath = flags['bundle-script-path'];
|
|
603
|
+
const deps = parseStringListFlag(flags, 'deps', 'Use a comma-separated list, e.g. --deps recharts,zod');
|
|
604
|
+
if (deps) body.deps = deps;
|
|
605
|
+
if (flags['no-open-in-canvas']) body.openInCanvas = false;
|
|
606
|
+
if (flags.verbose || flags['include-logs']) body.includeLogs = true;
|
|
607
|
+
|
|
608
|
+
const timeoutMs = optionalPositiveFiniteFlag(flags, 'timeout-ms', 'Use a positive number, e.g. --timeout-ms 600000');
|
|
609
|
+
if (timeoutMs !== undefined) body.timeoutMs = timeoutMs;
|
|
610
|
+
|
|
611
|
+
return body;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
async function runWebArtifactBuildCommand(flags: Record<string, string | true>): Promise<void> {
|
|
615
|
+
const body = await buildWebArtifactRequestBody(flags);
|
|
616
|
+
// The build (init + dependency install + bundle) runs server-side and only
|
|
617
|
+
// returns a single HTTP response on completion, which can take minutes on a
|
|
618
|
+
// cold workspace. With no output an agent's tool wait expires before the node
|
|
619
|
+
// appears and the build looks hung. Emit a default-on heartbeat to stderr
|
|
620
|
+
// while the request is in flight — stdout (output) and the JSON response body
|
|
621
|
+
// stay untouched, so anything parsing stdout is unaffected.
|
|
622
|
+
const startedMs = Date.now();
|
|
623
|
+
process.stderr.write(
|
|
624
|
+
`[web-artifact] building "${String(body.title)}" — init + install + bundle (this can take a few minutes)...\n`,
|
|
625
|
+
);
|
|
626
|
+
const heartbeat = setInterval(() => {
|
|
627
|
+
const elapsedSeconds = Math.round((Date.now() - startedMs) / 1000);
|
|
628
|
+
process.stderr.write(`[web-artifact] still building... ${elapsedSeconds}s elapsed\n`);
|
|
629
|
+
}, 10_000);
|
|
630
|
+
if (typeof heartbeat.unref === 'function') heartbeat.unref();
|
|
631
|
+
let result: unknown;
|
|
632
|
+
try {
|
|
633
|
+
// webartifact.build declares errorBodyAsResult, so build-failure envelopes
|
|
634
|
+
// ({ ok:false, error }) are returned here and printed on stdout before the
|
|
635
|
+
// exit-1 branch below (cli-node.test.ts pins that contract).
|
|
636
|
+
result = await invokeOperation('webartifact.build', body);
|
|
637
|
+
} finally {
|
|
638
|
+
clearInterval(heartbeat);
|
|
639
|
+
}
|
|
640
|
+
output(result);
|
|
641
|
+
if (isRecord(result) && result.ok === false) {
|
|
642
|
+
process.exit(1);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// ── Commands ─────────────────────────────────────────────────
|
|
647
|
+
|
|
648
|
+
const COMMANDS: Record<string, { run: (args: string[]) => Promise<void>; help: string; examples: string[] }> = {};
|
|
649
|
+
const RESOURCE_COMMAND_ALIASES: Record<string, Record<string, string>> = {
|
|
650
|
+
node: {
|
|
651
|
+
delete: 'remove',
|
|
652
|
+
rm: 'remove',
|
|
653
|
+
},
|
|
654
|
+
edge: {
|
|
655
|
+
delete: 'remove',
|
|
656
|
+
rm: 'remove',
|
|
657
|
+
},
|
|
658
|
+
ax: {
|
|
659
|
+
// Single-subcommand AX groups: the bare verb maps to its only action so
|
|
660
|
+
// `ax event` / `ax evidence` suggest the full command instead of erroring.
|
|
661
|
+
event: 'event add',
|
|
662
|
+
evidence: 'evidence add',
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
const RESOURCE_SUBCOMMAND_HINTS: Record<string, Record<string, string>> = {
|
|
666
|
+
node: {
|
|
667
|
+
pin: 'Use the top-level pin command instead: pmx-canvas pin <node-id>',
|
|
668
|
+
},
|
|
669
|
+
ax: {
|
|
670
|
+
// Multi-subcommand AX groups: point at the available actions.
|
|
671
|
+
host: 'Pick an action: pmx-canvas ax host report | pmx-canvas ax host status',
|
|
672
|
+
work: 'Pick an action: pmx-canvas ax work add | update | list',
|
|
673
|
+
approval: 'Pick an action: pmx-canvas ax approval request | resolve | list',
|
|
674
|
+
review: 'Pick an action: pmx-canvas ax review add | list',
|
|
675
|
+
delivery: 'Pick an action: pmx-canvas ax delivery list | mark',
|
|
676
|
+
elicitation: 'Pick an action: pmx-canvas ax elicitation request | respond | list',
|
|
677
|
+
mode: 'Pick an action: pmx-canvas ax mode request | resolve | list',
|
|
678
|
+
command: 'Pick an action: pmx-canvas ax command list | invoke',
|
|
679
|
+
policy: 'Pick an action: pmx-canvas ax policy get | set',
|
|
680
|
+
},
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
function cmd(name: string, help: string, examples: string[], run: (args: string[]) => Promise<void>) {
|
|
684
|
+
COMMANDS[name] = { run, help, examples };
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// ── Help ─────────────────────────────────────────────────────
|
|
688
|
+
|
|
689
|
+
function showCommandHelp(name: string): void {
|
|
690
|
+
const cmd = COMMANDS[name];
|
|
691
|
+
if (!cmd) return;
|
|
692
|
+
console.log(`\npmx-canvas ${name} — ${cmd.help}\n`);
|
|
693
|
+
console.log('Examples:');
|
|
694
|
+
for (const ex of cmd.examples) {
|
|
695
|
+
console.log(` ${ex}`);
|
|
696
|
+
}
|
|
697
|
+
if (name === 'node add') {
|
|
698
|
+
console.log('\nSchema help:');
|
|
699
|
+
console.log(' pmx-canvas node add --help --type webpage');
|
|
700
|
+
console.log(' pmx-canvas node add --help --type html');
|
|
701
|
+
console.log(' pmx-canvas node add --help --type json-render --component Table');
|
|
702
|
+
console.log(' pmx-canvas node add --help --type graph');
|
|
703
|
+
console.log(' pmx-canvas html primitive schema --summary');
|
|
704
|
+
console.log(' pmx-canvas node add --help --type webpage --json');
|
|
705
|
+
console.log(' Use --strict-size to keep explicit width/height fixed and scroll overflowing content.');
|
|
706
|
+
console.log('\nHTML sidecar flags:');
|
|
707
|
+
console.log(' --summary <text> Explicit human/agent-readable summary');
|
|
708
|
+
console.log(' --agent-summary <text> Semantic summary for search, pinned context, and spatial context');
|
|
709
|
+
console.log(' --description <text> Optional longer semantic description');
|
|
710
|
+
console.log(' --presentation true Mark raw HTML as an explicit presentation deck');
|
|
711
|
+
console.log(' --slide-title <text> Add a presentation slide title sidecar');
|
|
712
|
+
console.log(' --embedded-node-id <id> Link represented/embedded canvas node ID');
|
|
713
|
+
}
|
|
714
|
+
if (name === 'html primitive add' || name === 'html primitive schema') {
|
|
715
|
+
console.log('\nPrimitive flags:');
|
|
716
|
+
console.log(' --kind <name> Run `pmx-canvas html primitive schema --summary` for the full catalog');
|
|
717
|
+
console.log(' --data-file <path> JSON object payload for the primitive');
|
|
718
|
+
console.log(' --data-json, --data <json> Inline JSON object payload');
|
|
719
|
+
console.log(' --stdin Read JSON object payload from stdin');
|
|
720
|
+
}
|
|
721
|
+
if (name === 'json-render') {
|
|
722
|
+
console.log('\nOptions:');
|
|
723
|
+
console.log(' --schema Show json-render catalog schema (default)');
|
|
724
|
+
console.log(' --summary Show compact component summaries');
|
|
725
|
+
console.log(' --component <name> Focus on one component');
|
|
726
|
+
console.log(' --field <name> Focus on one component prop');
|
|
727
|
+
console.log(' --example, --examples Print canonical component examples');
|
|
728
|
+
}
|
|
729
|
+
if (name === 'node add' || name === 'graph add' || name === 'validate spec') {
|
|
730
|
+
console.log('\nGraph flags:');
|
|
731
|
+
console.log(
|
|
732
|
+
' Graph fields accept kebab-case CLI flags and camelCase schema names, e.g. --graph-type/--graphType and --x-key/--xKey',
|
|
733
|
+
);
|
|
734
|
+
console.log(
|
|
735
|
+
' 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.',
|
|
736
|
+
);
|
|
737
|
+
console.log(' Pass --show-legend false to hide legends in compact node layouts.');
|
|
738
|
+
}
|
|
739
|
+
if (name === 'validate spec') {
|
|
740
|
+
console.log('\nHTML primitive flags:');
|
|
741
|
+
console.log(' --type html-primitive --kind <name> --data-file ./payload.json');
|
|
742
|
+
}
|
|
743
|
+
if (name === 'node schema') {
|
|
744
|
+
console.log('\nFilters:');
|
|
745
|
+
console.log(' --summary Show compact schema summaries');
|
|
746
|
+
console.log(' --field <name> Focus on one node field');
|
|
747
|
+
console.log(' --component <name> Focus on one json-render component');
|
|
748
|
+
}
|
|
749
|
+
if (name === 'validate spec') {
|
|
750
|
+
console.log('\nOutput control:');
|
|
751
|
+
console.log(' --summary Return only validation summary metadata');
|
|
752
|
+
console.log(' For --type html-primitive, pass --kind plus optional --data-file/--data-json.');
|
|
753
|
+
}
|
|
754
|
+
if (name === 'snapshot list') {
|
|
755
|
+
console.log('\nOptions:');
|
|
756
|
+
console.log(' --limit <number> Maximum snapshots to return (default 20)');
|
|
757
|
+
console.log(' --query <text> Case-insensitive ID/name filter');
|
|
758
|
+
console.log(' --before <timestamp> Only return snapshots created at or before this ISO timestamp');
|
|
759
|
+
console.log(' --after <timestamp> Only return snapshots created at or after this ISO timestamp');
|
|
760
|
+
console.log(' --all Return all snapshots');
|
|
761
|
+
}
|
|
762
|
+
if (name === 'node update') {
|
|
763
|
+
console.log('\nTrace fields:');
|
|
764
|
+
console.log(' --tool-name, --toolName Trace tool or operation label');
|
|
765
|
+
console.log(' --category <name> Trace category, e.g. mcp, file, subagent, other');
|
|
766
|
+
console.log(' --status <status> Trace status, e.g. running, success, failed');
|
|
767
|
+
console.log(' --duration <text> Trace duration badge text');
|
|
768
|
+
console.log(' --result-summary, --resultSummary <text>');
|
|
769
|
+
console.log(' Trace result summary');
|
|
770
|
+
console.log(' --error <text> Trace error message');
|
|
771
|
+
}
|
|
772
|
+
if (name === 'snapshot gc') {
|
|
773
|
+
console.log('\nOptions:');
|
|
774
|
+
console.log(' --keep <number> Number of newest snapshots to keep (default 20)');
|
|
775
|
+
console.log(' --dry-run Preview deletions without removing files');
|
|
776
|
+
console.log(' --yes Confirm deletion');
|
|
777
|
+
}
|
|
778
|
+
if (name === 'web-artifact build') {
|
|
779
|
+
console.log('\nDependencies:');
|
|
780
|
+
console.log(' --deps <list> Add npm dependencies before bundling, e.g. --deps recharts,zod');
|
|
781
|
+
console.log('\nOutput control:');
|
|
782
|
+
console.log(' --include-logs Include raw build stdout/stderr in the response');
|
|
783
|
+
console.log(' --verbose Alias for --include-logs');
|
|
784
|
+
console.log(' --timeout-ms <number> Optional init/install/build timeout in milliseconds');
|
|
785
|
+
}
|
|
786
|
+
if (name === 'focus') {
|
|
787
|
+
console.log('\nViewport:');
|
|
788
|
+
console.log(' --no-pan Select/raise the node without moving the viewport');
|
|
789
|
+
}
|
|
790
|
+
if (name === 'fit') {
|
|
791
|
+
console.log('\nViewport:');
|
|
792
|
+
console.log(' --width <px> Viewport width used for fit math (default 1440)');
|
|
793
|
+
console.log(' --height <px> Viewport height used for fit math (default 900)');
|
|
794
|
+
console.log(' --padding <px> World-space padding around fitted nodes (default 60)');
|
|
795
|
+
console.log(' --max-scale <scale> Maximum zoom scale (default 1)');
|
|
796
|
+
}
|
|
797
|
+
if (name === 'screenshot' || name === 'webview screenshot') {
|
|
798
|
+
console.log('\nOptions:');
|
|
799
|
+
console.log(' --output <path> Required output image path');
|
|
800
|
+
console.log(' --format <type> png, jpeg, or webp');
|
|
801
|
+
console.log(' --quality <number> Encoder quality for lossy formats');
|
|
802
|
+
console.log(' Requires an active automation session: pmx-canvas webview start');
|
|
803
|
+
}
|
|
804
|
+
if (name === 'external-app add') {
|
|
805
|
+
console.log('\nOptions:');
|
|
806
|
+
console.log(' --kind excalidraw External app kind to create');
|
|
807
|
+
console.log(' --title <title> Node title');
|
|
808
|
+
console.log(' --node-id <id> Existing Excalidraw app node to update in place');
|
|
809
|
+
console.log(' --elements <json> Optional Excalidraw elements array JSON');
|
|
810
|
+
console.log(' --elements-json <json> Optional Excalidraw elements array JSON');
|
|
811
|
+
console.log(' --elements-file <path> Optional file containing Excalidraw elements JSON');
|
|
812
|
+
console.log(' --initial-file <path> Alias for --elements-file');
|
|
813
|
+
console.log(' --timeout-ms <number> Optional downstream MCP timeout for cold starts');
|
|
814
|
+
}
|
|
815
|
+
if (name === 'diagram add') {
|
|
816
|
+
console.log('\nAlias:');
|
|
817
|
+
console.log(' Equivalent to: pmx-canvas external-app add --kind excalidraw ...');
|
|
818
|
+
}
|
|
819
|
+
console.log('');
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// ── Stdin reader ─────────────────────────────────────────────
|
|
823
|
+
|
|
824
|
+
async function readStdin(): Promise<string> {
|
|
825
|
+
const chunks: Buffer[] = [];
|
|
826
|
+
for await (const chunk of process.stdin) {
|
|
827
|
+
chunks.push(chunk);
|
|
828
|
+
}
|
|
829
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
export {
|
|
833
|
+
COMMANDS,
|
|
834
|
+
RESOURCE_COMMAND_ALIASES,
|
|
835
|
+
RESOURCE_SUBCOMMAND_HINTS,
|
|
836
|
+
applyCommonGeometryFlags,
|
|
837
|
+
applyStrictSizeFlags,
|
|
838
|
+
buildGraphRequestBody,
|
|
839
|
+
buildHtmlPrimitiveRequestBody,
|
|
840
|
+
buildJsonRenderRequestBody,
|
|
841
|
+
cmd,
|
|
842
|
+
die,
|
|
843
|
+
getBaseUrl,
|
|
844
|
+
getStringFlag,
|
|
845
|
+
invokeOperation,
|
|
846
|
+
isRecord,
|
|
847
|
+
optionalBooleanFlag,
|
|
848
|
+
optionalFiniteFlag,
|
|
849
|
+
optionalNumberFlag,
|
|
850
|
+
optionalPositiveFiniteFlag,
|
|
851
|
+
optionalPositiveFiniteFlagWithAliases,
|
|
852
|
+
output,
|
|
853
|
+
parseFlags,
|
|
854
|
+
parseJsonValue,
|
|
855
|
+
readOptionalTextInput,
|
|
856
|
+
readStdin,
|
|
857
|
+
requireFlag,
|
|
858
|
+
resolveAxSource,
|
|
859
|
+
runWebArtifactBuildCommand,
|
|
860
|
+
showCommandHelp,
|
|
861
|
+
};
|