pmx-canvas 0.1.5 → 0.1.6
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/Readme.md +325 -68
- package/dist/types/server/canvas-schema.d.ts +2 -0
- package/dist/types/server/index.d.ts +1 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +179 -12
- package/src/cli/agent.ts +35 -2
- package/src/cli/index.ts +3 -1
- package/src/json-render/server.ts +24 -0
- package/src/mcp/server.ts +15 -5
- package/src/server/canvas-schema.ts +53 -1
- package/src/server/server.ts +6 -0
|
@@ -22,14 +22,41 @@ relatedness, clusters imply grouping, reading order (top-left to bottom-right) i
|
|
|
22
22
|
|
|
23
23
|
## When to Use
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
The canvas is **agnostic about what you do with it**. Reach for it whenever a
|
|
26
|
+
flat conversation, a list, or a single document hides the relationships
|
|
27
|
+
between pieces of information. The total reach of any session is the union
|
|
28
|
+
of pmx-canvas's own node types and whatever your harness already has access
|
|
29
|
+
to — MCP servers, MCP apps, shell commands and CLIs, files in the working
|
|
30
|
+
directory, web fetch, anything else in your toolbelt. The canvas does not
|
|
31
|
+
care where the data came from; it cares about getting it on the surface as
|
|
32
|
+
the right node type.
|
|
33
|
+
|
|
34
|
+
The README has a non-exhaustive list of example use cases (idea generation,
|
|
35
|
+
validation, research, analysis, mind mapping, investigation boards,
|
|
36
|
+
architecture diagrams, status dashboards, comparison views, plus whatever
|
|
37
|
+
your toolbelt unlocks). This skill stays focused on the operational
|
|
38
|
+
mechanics. If a flat list or text wall is not enough to hold the
|
|
39
|
+
relationships you're working with, the canvas is the right tool — the rest
|
|
40
|
+
of this document is how to drive it.
|
|
41
|
+
|
|
42
|
+
### When the connected tool is unfamiliar
|
|
43
|
+
|
|
44
|
+
When the human asks you to use a data source or tool you have not used
|
|
45
|
+
before — an MCP server, an MCP app, a CLI, a script, an arbitrary file
|
|
46
|
+
tree:
|
|
47
|
+
|
|
48
|
+
1. List the tools / commands available; sample one or two outputs to see
|
|
49
|
+
the actual shape.
|
|
50
|
+
2. Decide which canvas node type best matches each output:
|
|
51
|
+
- Long-form / narrative results → `markdown`
|
|
52
|
+
- Structured records, tables, dashboards → `json-render` or `graph`
|
|
53
|
+
- Interactive tool surfaces with their own UI → `mcp-app` (open with
|
|
54
|
+
`canvas_open_mcp_app`)
|
|
55
|
+
- Local source files → `file` (live-watched)
|
|
56
|
+
- URLs that need cached fetches → `webpage`
|
|
57
|
+
- Stream of state events → `status` or `ledger`
|
|
58
|
+
3. Propose the mapping to the human before bulk-creating nodes; let them
|
|
59
|
+
confirm or adjust before you commit a layout.
|
|
33
60
|
|
|
34
61
|
## Starting the Canvas
|
|
35
62
|
|
|
@@ -46,11 +73,31 @@ pmx-canvas --no-open # Start without opening browser (for agents)
|
|
|
46
73
|
pmx-canvas --port=8080 # Custom port
|
|
47
74
|
pmx-canvas --demo # Start with sample content
|
|
48
75
|
pmx-canvas --theme=light # Light theme
|
|
76
|
+
pmx-canvas --version # Print installed version and exit
|
|
49
77
|
```
|
|
50
78
|
|
|
79
|
+
`--theme` accepts `dark` (default), `light`, or `high-contrast`. Same value can be set via
|
|
80
|
+
the `PMX_CANVAS_THEME` environment variable, or toggled live in the browser toolbar.
|
|
81
|
+
|
|
51
82
|
Start the canvas once per session, then reuse it. Use `--no-open` when running as an agent — the
|
|
52
83
|
human can open the browser URL themselves.
|
|
53
84
|
|
|
85
|
+
### Daemon mode (long-running background server)
|
|
86
|
+
|
|
87
|
+
When you need the canvas to outlive the current shell or agent session — e.g. so a follow-up
|
|
88
|
+
agent run can attach to the same state — start it as a daemon:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pmx-canvas serve --daemon --no-open --wait-ms=20000 # Detach, wait for /health
|
|
92
|
+
pmx-canvas serve status # Print daemon health + pid state
|
|
93
|
+
pmx-canvas serve stop # Stop the daemon for this port
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`serve --daemon` writes a pid file (`.pmx-canvas/daemon-<port>.pid`) and a log file
|
|
97
|
+
(`.pmx-canvas/daemon-<port>.log`); the wait flag blocks until `/health` returns OK so a script
|
|
98
|
+
can rely on the server being responsive when the command returns. `serve stop` reads the pid
|
|
99
|
+
file, sends SIGTERM, and cleans up on exit.
|
|
100
|
+
|
|
54
101
|
## Browser Workflows
|
|
55
102
|
|
|
56
103
|
The browser is not just a passive view. Human interactions on the canvas persist back to the
|
|
@@ -86,6 +133,7 @@ pmx-canvas node add --type markdown --title "Plan"
|
|
|
86
133
|
pmx-canvas node add --type webpage --url https://example.com/docs
|
|
87
134
|
pmx-canvas node add --type graph --graph-type bar --data '[{"x":"a","y":1}]' --x-key x --y-key y
|
|
88
135
|
pmx-canvas node add --type graph --graphType bar --data '[{"x":"a","y":1}]' --xKey x --yKey y
|
|
136
|
+
pmx-canvas graph add --graph-type bar --data '[{"x":"a","y":1}]' --x-key x --y-key y
|
|
89
137
|
pmx-canvas external-app add --kind excalidraw --title "Diagram"
|
|
90
138
|
pmx-canvas node add --help --type webpage --json
|
|
91
139
|
pmx-canvas node schema --type json-render --component Table --summary
|
|
@@ -109,7 +157,11 @@ pmx-canvas spatial
|
|
|
109
157
|
|
|
110
158
|
- `node add|list|get|update|remove` — manage nodes
|
|
111
159
|
- `node schema` — inspect running-server create schemas and canonical examples, with `--summary`, `--field`, and `--component` filters
|
|
160
|
+
- `graph add` — convenience alias for graph nodes; `node add --type graph` remains the canonical form
|
|
112
161
|
- Graph CLI fields accept both kebab-case flags and camelCase schema names, e.g. `--graph-type`/`--graphType`, `--x-key`/`--xKey`, and `--bar-color`/`--barColor`.
|
|
162
|
+
- Graph CLI height flags are split: use `--node-height`/`--nodeHeight` for the
|
|
163
|
+
canvas frame and `--chart-height` for the chart content. CLI `--height`
|
|
164
|
+
remains a frame-height compatibility alias.
|
|
113
165
|
- `edge add|list|remove` — manage edges
|
|
114
166
|
- Search-based edge selectors must be specific enough to resolve exactly one node. Queries like
|
|
115
167
|
`"DVT O3"` can be ambiguous; prefer the full visible title such as `"DVT O3 — GitOps"`.
|
|
@@ -139,6 +191,8 @@ Current caveat:
|
|
|
139
191
|
- Generic `pmx-canvas node add --type mcp-app` is intentionally not supported because app nodes
|
|
140
192
|
need app/session metadata. Use `pmx-canvas web-artifact build` for bundled React artifacts or
|
|
141
193
|
`pmx-canvas external-app add --kind excalidraw` for the Excalidraw preset.
|
|
194
|
+
- For local `image` nodes on macOS, iCloud/OneDrive cloud-only placeholder files are rejected with
|
|
195
|
+
a download-first hint. Download the image locally before adding it to the canvas.
|
|
142
196
|
|
|
143
197
|
The CLI targets `http://localhost:4313` by default. Override with `PMX_CANVAS_URL` or
|
|
144
198
|
`PMX_CANVAS_PORT` when the canvas is running elsewhere.
|
|
@@ -160,8 +214,8 @@ The CLI targets `http://localhost:4313` by default. Override with `PMX_CANVAS_UR
|
|
|
160
214
|
| `json-render` | Native structured UI panel | Dashboards, forms, tables, interactive layouts from json-render specs |
|
|
161
215
|
| `graph` | Native chart panel | Line, bar, pie, area, scatter, radar, stacked-bar, and composed charts rendered inside the canvas |
|
|
162
216
|
| `group` | Spatial container/frame | Visually group related nodes together |
|
|
163
|
-
| `prompt` | Prompt thread root | Canvas-native prompt entry points for agent conversations |
|
|
164
|
-
| `response` | Prompt reply / streamed answer | Agent responses linked to prompt threads |
|
|
217
|
+
| `prompt` | Prompt thread root | Canvas-native prompt entry points for agent conversations. **Internal type — surfaces in `canvas://layout` for thread rendering but is not created via the public `canvas_add_node` API. Don't try to add one directly.** |
|
|
218
|
+
| `response` | Prompt reply / streamed answer | Agent responses linked to prompt threads. **Same internal-only restriction as `prompt`.** |
|
|
165
219
|
|
|
166
220
|
### Edge Types
|
|
167
221
|
|
|
@@ -196,8 +250,22 @@ Use color consistently to convey meaning:
|
|
|
196
250
|
|
|
197
251
|
### Node Operations
|
|
198
252
|
|
|
253
|
+
MCP node-type routing:
|
|
254
|
+
|
|
255
|
+
| Node category | MCP creation tool |
|
|
256
|
+
|---------------|-------------------|
|
|
257
|
+
| Basic nodes (`markdown`, `status`, `context`, `ledger`, `trace`, `file`, `image`, `webpage`) | `canvas_add_node` |
|
|
258
|
+
| `json-render` | `canvas_add_json_render_node` |
|
|
259
|
+
| `graph` | `canvas_add_graph_node` |
|
|
260
|
+
| `web-artifact` | `canvas_build_web_artifact` |
|
|
261
|
+
| `external-app` / tool-backed `mcp-app` | `canvas_open_mcp_app` |
|
|
262
|
+
| `group` | `canvas_create_group` |
|
|
263
|
+
|
|
264
|
+
If a node type is rejected by `canvas_add_node`, call `canvas_describe_schema` and read
|
|
265
|
+
`mcp.nodeTypeRouting`; do not keep retrying the generic tool.
|
|
266
|
+
|
|
199
267
|
**`canvas_add_node`** — Add a node to the canvas
|
|
200
|
-
- `type` (required): node type
|
|
268
|
+
- `type` (required): basic node type only; structured/app/group nodes use the routing table above
|
|
201
269
|
- `title`: short, scannable title
|
|
202
270
|
- `content`: for most types, this is markdown text. For `file` type, pass the **file path**
|
|
203
271
|
(e.g., `"src/auth/login.ts"`) — the server auto-loads the file content and watches for changes.
|
|
@@ -220,10 +288,30 @@ Use color consistently to convey meaning:
|
|
|
220
288
|
**`canvas_get_node`** — Get a single node's full data
|
|
221
289
|
- `id` (required): node to retrieve
|
|
222
290
|
|
|
291
|
+
**`canvas_refresh_webpage_node`** — Re-fetch the URL stored on a `webpage` node
|
|
292
|
+
- `id` (required): webpage node to refresh
|
|
293
|
+
- Optional `url`: replace the stored URL before refreshing (use when the human moved the page)
|
|
294
|
+
- Returns the refreshed node with updated `pageTitle` and cached extracted text
|
|
295
|
+
- Use this when a saved canvas is reopened and the agent needs fresh page content without
|
|
296
|
+
losing the node's identity, position, or pins. Example flow:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// Add the page once
|
|
300
|
+
canvas_add_node({ type: 'webpage', url: 'https://example.com/docs' })
|
|
301
|
+
// → returns { id: 'node-abc' }
|
|
302
|
+
|
|
303
|
+
// …later, after the human reopens the canvas…
|
|
304
|
+
canvas_refresh_webpage_node({ id: 'node-abc' })
|
|
305
|
+
// → re-fetches the URL, updates pageTitle + extracted text, keeps the node ID and position
|
|
306
|
+
```
|
|
307
|
+
|
|
223
308
|
**`canvas_add_json_render_node`** — Add a native json-render node
|
|
224
309
|
- Required: `title`, `spec`
|
|
225
310
|
- The `spec` must be a complete json-render object with `root`, `elements`, and optional `state`
|
|
226
311
|
- Use this when you want a structured UI panel rendered directly inside PMX Canvas
|
|
312
|
+
- For shadcn `Badge`, prefer `props.text` with variants `default`, `secondary`, `destructive`, or
|
|
313
|
+
`outline`. Legacy `props.label` and status variants (`success`, `info`, `warning`, `error`,
|
|
314
|
+
`danger`) are normalized for saved-spec compatibility.
|
|
227
315
|
|
|
228
316
|
**`canvas_add_graph_node`** — Add a native graph/chart node
|
|
229
317
|
- Required: `graphType`, `data`
|
|
@@ -235,10 +323,29 @@ Use color consistently to convey meaning:
|
|
|
235
323
|
- Use `axisKey` plus `metrics` for radar graphs
|
|
236
324
|
- Use `series` for stacked-bar graphs
|
|
237
325
|
- Use `barKey`/`lineKey` plus optional `barColor`/`lineColor` for composed graphs
|
|
326
|
+
- Use `nodeHeight` for the canvas frame height and `height` for chart content height
|
|
238
327
|
- Uses the native json-render chart catalog under the hood
|
|
239
328
|
|
|
329
|
+
**`canvas_build_web_artifact`** — Build and optionally open a bundled web artifact
|
|
330
|
+
- Required: `title`, `appTsx` (source string contents, not a file path)
|
|
331
|
+
- CLI `--app-file` reads a file before calling the same build path; MCP callers must pass the source contents
|
|
332
|
+
- Cold builds commonly take 45-60 seconds; use a long client timeout such as 300000 ms or more
|
|
333
|
+
- Returns both `id` and `nodeId` for the created artifact node when `openInCanvas` is true
|
|
334
|
+
|
|
335
|
+
**`canvas_open_mcp_app`** — Open a tool-backed external MCP app node
|
|
336
|
+
- Required: `toolName`, `transport`
|
|
337
|
+
- `transport` is either `{ type: "stdio", command, args?, cwd?, env? }` or `{ type: "http", url, headers? }`
|
|
338
|
+
- This is lower-level than `pmx-canvas external-app add --kind excalidraw`; use `canvas_add_diagram` for the built-in Excalidraw preset
|
|
339
|
+
|
|
340
|
+
**`canvas_pin_nodes`** — Set, add, or remove pinned context nodes
|
|
341
|
+
- Use `{ nodeIds: [...] }`; the field is `nodeIds`, not `ids`
|
|
342
|
+
|
|
343
|
+
**`canvas_diff`** — Compare current canvas state with a saved snapshot
|
|
344
|
+
- Requires `{ snapshot: "<snapshot-id-or-name>" }`; there is no implicit previous-snapshot default
|
|
345
|
+
|
|
240
346
|
**`canvas_describe_schema`** — Inspect the running server's create schemas and canonical examples
|
|
241
347
|
- Use this before generating structured payloads when you need the authoritative current shape
|
|
348
|
+
- Read `mcp.nodeTypeRouting` to choose the right MCP creation tool for each node category
|
|
242
349
|
|
|
243
350
|
**`canvas_validate_spec`** — Validate a json-render spec or graph payload without creating a node
|
|
244
351
|
- Returns the normalized json-render spec the server would accept
|
|
@@ -251,6 +358,7 @@ Use color consistently to convey meaning:
|
|
|
251
358
|
`xKey`, `yKey`, `zKey`, `nameKey`, `valueKey`, `axisKey`, `metrics`, `series`, `barKey`,
|
|
252
359
|
`lineKey`, `aggregate`, `color`, `barColor`, `lineColor`, `height`, `x`, `y`, `width`, and
|
|
253
360
|
`nodeHeight`.
|
|
361
|
+
- In batch/MCP/HTTP payloads, `height` is chart content height and `nodeHeight` is the canvas frame height.
|
|
254
362
|
|
|
255
363
|
### Edge Operations
|
|
256
364
|
|
|
@@ -369,6 +477,58 @@ Current product caveats for grouped comparison boards:
|
|
|
369
477
|
- **Always call `canvas_snapshot` first** to save a backup before clearing
|
|
370
478
|
- This is irreversible without a prior snapshot
|
|
371
479
|
|
|
480
|
+
### Browser Automation (WebView)
|
|
481
|
+
|
|
482
|
+
The canvas exposes a headless browser session over MCP for self-inspection and
|
|
483
|
+
automated screenshotting. Use this when you want to (a) verify what the live
|
|
484
|
+
canvas actually looks like after a sequence of mutations, (b) capture an image
|
|
485
|
+
of a freshly-built artifact for the human to review, or (c) drive arbitrary
|
|
486
|
+
JavaScript inside the workbench page.
|
|
487
|
+
|
|
488
|
+
The WebView automation runs on Bun's WebKit-based WebView (macOS) or a headless
|
|
489
|
+
Chromium fallback (Linux). It does **not** open a visible window; it's an
|
|
490
|
+
additional headless renderer attached to the same canvas server, so all five
|
|
491
|
+
tools below operate on the live canvas state.
|
|
492
|
+
|
|
493
|
+
**`canvas_webview_status`** — Inspect the current automation session
|
|
494
|
+
- Returns `{ supported, active, backend, viewportWidth, viewportHeight, url, lastError }`
|
|
495
|
+
- Call before `start` to check whether a session is already alive
|
|
496
|
+
|
|
497
|
+
**`canvas_webview_start`** — Start (or replace) the automation session
|
|
498
|
+
- Optional: `backend` (`webkit` macOS-only, or `chrome`), `width`, `height`
|
|
499
|
+
- The session opens `/workbench` at the canvas URL, waits for the SPA to
|
|
500
|
+
hydrate, and reports back via `canvas_webview_status`
|
|
501
|
+
|
|
502
|
+
**`canvas_webview_stop`** — Tear down the automation session
|
|
503
|
+
|
|
504
|
+
**`canvas_evaluate`** — Run JavaScript inside the workbench page and return the result
|
|
505
|
+
- Required: `expression` (a JS expression or a function body)
|
|
506
|
+
- Useful for asserting DOM state after a sequence of canvas mutations
|
|
507
|
+
- Example: read the count of rendered `.canvas-node` elements:
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
canvas_evaluate({ expression: 'document.querySelectorAll(".canvas-node").length' })
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**`canvas_resize`** — Change the WebView viewport
|
|
514
|
+
- Required: `width`, `height`
|
|
515
|
+
- Use before `canvas_screenshot` when the human needs a specific aspect ratio
|
|
516
|
+
|
|
517
|
+
**`canvas_screenshot`** — Capture a PNG of the current workbench
|
|
518
|
+
- Optional: `format` (`png` default), `fullPage` (boolean)
|
|
519
|
+
- Returns both an MCP image payload (renderable inline by capable agents) and
|
|
520
|
+
a path under `.pmx-canvas/screenshots/` so the human can view the file
|
|
521
|
+
- Pair with `canvas_resize` to control the framing
|
|
522
|
+
|
|
523
|
+
Typical flow when you want to show a result:
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
canvas_webview_start({ width: 1440, height: 900 });
|
|
527
|
+
// …mutations…
|
|
528
|
+
canvas_screenshot({ fullPage: true });
|
|
529
|
+
canvas_webview_stop();
|
|
530
|
+
```
|
|
531
|
+
|
|
372
532
|
### Diagrams (Excalidraw MCP app preset)
|
|
373
533
|
|
|
374
534
|
**`canvas_add_diagram`** — Draw a hand-drawn diagram on the canvas via the hosted
|
|
@@ -432,7 +592,8 @@ what the human has set up and what they're focusing on.
|
|
|
432
592
|
| `canvas://summary` | Compact overview: node counts by type, pinned titles |
|
|
433
593
|
| `canvas://spatial-context` | Proximity clusters, reading order, pinned neighborhoods |
|
|
434
594
|
| `canvas://history` | Human-readable mutation timeline |
|
|
435
|
-
| `canvas://code-graph` | Auto-detected file import dependencies |
|
|
595
|
+
| `canvas://code-graph` | Auto-detected file import dependencies (JS/TS, Python, Go, Rust) |
|
|
596
|
+
| `canvas://skills` | Index of bundled agent skills shipped with the install. Each skill is also addressable as `canvas://skills/<name>` (e.g. `canvas://skills/web-artifacts-builder`) and returns the full SKILL.md. Read this resource first to discover companion workflows the canvas is built to support. |
|
|
436
597
|
|
|
437
598
|
### Reading Spatial Intent
|
|
438
599
|
|
|
@@ -493,6 +654,12 @@ All POST/PATCH endpoints accept `Content-Type: application/json`. Default base U
|
|
|
493
654
|
|
|
494
655
|
## Workflow Patterns
|
|
495
656
|
|
|
657
|
+
These are **operational recipes** — how to sequence canvas calls for a few
|
|
658
|
+
recurring shapes of work. They are not the project's use cases (those live
|
|
659
|
+
in the README and are intentionally non-exhaustive). The patterns here exist
|
|
660
|
+
to make the agent's tool-call sequencing concrete: which MCP tool fires
|
|
661
|
+
when, what to pin, when to read `canvas://pinned-context`, when to snapshot.
|
|
662
|
+
|
|
496
663
|
### Responding to Pinned Context
|
|
497
664
|
|
|
498
665
|
When the human pins nodes, they're telling you what matters. This is the most important
|
package/src/cli/agent.ts
CHANGED
|
@@ -229,6 +229,18 @@ function optionalPositiveFiniteFlag(flags: Record<string, string | true>, name:
|
|
|
229
229
|
return parsed;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
function optionalPositiveFiniteFlagWithAliases(
|
|
233
|
+
flags: Record<string, string | true>,
|
|
234
|
+
hint: string,
|
|
235
|
+
...names: string[]
|
|
236
|
+
): number | undefined {
|
|
237
|
+
for (const name of names) {
|
|
238
|
+
const parsed = optionalPositiveFiniteFlag(flags, name, hint);
|
|
239
|
+
if (parsed !== undefined) return parsed;
|
|
240
|
+
}
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
|
|
232
244
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
233
245
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
234
246
|
}
|
|
@@ -590,7 +602,13 @@ async function buildGraphRequestBody(
|
|
|
590
602
|
const x = optionalFiniteFlag(flags, 'x', 'Use a finite number, e.g. --x 500');
|
|
591
603
|
const y = optionalFiniteFlag(flags, 'y', 'Use a finite number, e.g. --y 300');
|
|
592
604
|
const width = optionalPositiveFiniteFlag(flags, 'width', 'Use a positive number, e.g. --width 760');
|
|
593
|
-
const nodeHeight =
|
|
605
|
+
const nodeHeight = optionalPositiveFiniteFlagWithAliases(
|
|
606
|
+
flags,
|
|
607
|
+
'Use a positive number, e.g. --node-height 520',
|
|
608
|
+
'node-height',
|
|
609
|
+
'nodeHeight',
|
|
610
|
+
'height',
|
|
611
|
+
);
|
|
594
612
|
if (chartHeight !== undefined) body.height = chartHeight;
|
|
595
613
|
if (x !== undefined) body.x = x;
|
|
596
614
|
if (y !== undefined) body.y = y;
|
|
@@ -992,6 +1010,18 @@ cmd('node add', 'Add a node to the canvas', [
|
|
|
992
1010
|
output(result);
|
|
993
1011
|
});
|
|
994
1012
|
|
|
1013
|
+
cmd('graph add', 'Add a graph node to the canvas', [
|
|
1014
|
+
'pmx-canvas graph add --graph-type bar --data-file ./metrics.json --x-key label --y-key value',
|
|
1015
|
+
'pmx-canvas graph add --graphType composed --data \'[{"day":"Mon","visits":10,"conversion":0.4}]\' --xKey day --barKey visits --lineKey conversion',
|
|
1016
|
+
'pmx-canvas node add --type graph --graph-type bar --data-file ./metrics.json --x-key label --y-key value',
|
|
1017
|
+
], async (args) => {
|
|
1018
|
+
const { flags } = parseFlags(args);
|
|
1019
|
+
if (flags.help || flags.h) return showCommandHelp('graph add');
|
|
1020
|
+
|
|
1021
|
+
const result = await api('POST', '/api/canvas/graph', await buildGraphRequestBody(flags));
|
|
1022
|
+
output(result);
|
|
1023
|
+
});
|
|
1024
|
+
|
|
995
1025
|
cmd('node schema', 'Describe server-supported node create schemas and canonical examples', [
|
|
996
1026
|
'pmx-canvas node schema',
|
|
997
1027
|
'pmx-canvas node schema --type webpage',
|
|
@@ -2056,9 +2086,10 @@ function showCommandHelp(name: string): void {
|
|
|
2056
2086
|
console.log(' pmx-canvas node add --help --type graph');
|
|
2057
2087
|
console.log(' pmx-canvas node add --help --type webpage --json');
|
|
2058
2088
|
}
|
|
2059
|
-
if (name === 'node add' || name === 'validate spec') {
|
|
2089
|
+
if (name === 'node add' || name === 'graph add' || name === 'validate spec') {
|
|
2060
2090
|
console.log('\nGraph flags:');
|
|
2061
2091
|
console.log(' Graph fields accept kebab-case CLI flags and camelCase schema names, e.g. --graph-type/--graphType and --x-key/--xKey');
|
|
2092
|
+
console.log(' 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.');
|
|
2062
2093
|
}
|
|
2063
2094
|
if (name === 'node schema') {
|
|
2064
2095
|
console.log('\nFilters:');
|
|
@@ -2112,6 +2143,7 @@ Node commands:
|
|
|
2112
2143
|
pmx-canvas node get <id> Get a node by ID
|
|
2113
2144
|
pmx-canvas node update <id> [opts] Update a node
|
|
2114
2145
|
pmx-canvas node remove <id> Remove a node
|
|
2146
|
+
pmx-canvas graph add [options] Add a graph node
|
|
2115
2147
|
|
|
2116
2148
|
Edge commands:
|
|
2117
2149
|
pmx-canvas edge add [options] Add an edge between nodes
|
|
@@ -2179,6 +2211,7 @@ Examples:
|
|
|
2179
2211
|
pmx-canvas node add --type json-render --title "Dashboard" --spec-file ./dashboard.json
|
|
2180
2212
|
pmx-canvas node add --type web-artifact --title "Dashboard" --app-file ./App.tsx
|
|
2181
2213
|
pmx-canvas node add --type graph --graph-type bar --data-file ./metrics.json --x-key label --y-key value
|
|
2214
|
+
pmx-canvas graph add --graph-type bar --data-file ./metrics.json --x-key label --y-key value
|
|
2182
2215
|
pmx-canvas node add --help --type webpage
|
|
2183
2216
|
pmx-canvas node schema --type json-render
|
|
2184
2217
|
pmx-canvas node schema --type json-render --component Table --summary
|
package/src/cli/index.ts
CHANGED
|
@@ -31,7 +31,7 @@ if (args.includes('--version') || args.includes('-v')) {
|
|
|
31
31
|
const AGENT_COMMANDS = new Set([
|
|
32
32
|
'node', 'edge', 'search', 'layout', 'status', 'arrange', 'focus',
|
|
33
33
|
'pin', 'undo', 'redo', 'history', 'snapshot', 'diff', 'group', 'webview', 'open',
|
|
34
|
-
'clear', 'code-graph', 'spatial', 'watch', 'web-artifact', 'external-app', 'batch', 'validate', 'serve',
|
|
34
|
+
'clear', 'code-graph', 'spatial', 'watch', 'web-artifact', 'external-app', 'graph', 'batch', 'validate', 'serve',
|
|
35
35
|
]);
|
|
36
36
|
|
|
37
37
|
const firstArg = args[0] ?? '';
|
|
@@ -486,6 +486,7 @@ Server options:
|
|
|
486
486
|
|
|
487
487
|
Agent CLI (works against running server):
|
|
488
488
|
node add|list|get|update|remove Manage nodes
|
|
489
|
+
graph add Add graph nodes (alias for node add --type graph)
|
|
489
490
|
edge add|list|remove Manage edges
|
|
490
491
|
webview status|start|evaluate|resize|screenshot|stop
|
|
491
492
|
Manage Bun.WebView automation session
|
|
@@ -540,6 +541,7 @@ Examples:
|
|
|
540
541
|
pmx-canvas node add --type webpage --url "https://example.com" Add a webpage node
|
|
541
542
|
pmx-canvas node add --type json-render --title "Dashboard" --spec-file ./dashboard.json
|
|
542
543
|
pmx-canvas node add --type web-artifact --title "Dashboard" --app-file ./App.tsx
|
|
544
|
+
pmx-canvas graph add --graph-type bar --data-file ./metrics.json --x-key label --y-key value
|
|
543
545
|
pmx-canvas node list List all nodes
|
|
544
546
|
pmx-canvas node schema --type json-render Show running-server schema info
|
|
545
547
|
pmx-canvas web-artifact build --title "Dashboard" --app-file ./App.tsx
|
|
@@ -262,6 +262,14 @@ function normalizeButtonVariant(value: unknown): unknown {
|
|
|
262
262
|
return value;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
function normalizeBadgeVariant(value: unknown): unknown {
|
|
266
|
+
if (value === 'success') return 'default';
|
|
267
|
+
if (value === 'info') return 'secondary';
|
|
268
|
+
if (value === 'warning') return 'outline';
|
|
269
|
+
if (value === 'error' || value === 'danger') return 'destructive';
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
|
|
265
273
|
function deriveElementName(elementKey: string): string {
|
|
266
274
|
const normalized = elementKey.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
267
275
|
return normalized || 'field';
|
|
@@ -325,6 +333,22 @@ function normalizeElementProps(
|
|
|
325
333
|
props.text = props.content;
|
|
326
334
|
}
|
|
327
335
|
|
|
336
|
+
if (type === 'Badge') {
|
|
337
|
+
if (!hasString(props.text) && hasString(props.label)) {
|
|
338
|
+
props.text = props.label;
|
|
339
|
+
}
|
|
340
|
+
// Drop the legacy alias once it's been migrated so the persisted spec
|
|
341
|
+
// contains exactly one of `text` or `label` rather than both. Keeps
|
|
342
|
+
// saved canvases tidy and prevents future stricter validators from
|
|
343
|
+
// flagging the old key as unknown.
|
|
344
|
+
if ('label' in props) {
|
|
345
|
+
delete props.label;
|
|
346
|
+
}
|
|
347
|
+
if ('variant' in props) {
|
|
348
|
+
props.variant = normalizeBadgeVariant(props.variant);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
328
352
|
if (type === 'Select' || type === 'Radio') {
|
|
329
353
|
if (!Array.isArray(props.options) && Array.isArray(props.items)) {
|
|
330
354
|
props.options = normalizeLabelArray(props.items);
|
package/src/mcp/server.ts
CHANGED
|
@@ -47,6 +47,13 @@ const jsonRenderSpecSchema = z.object({
|
|
|
47
47
|
state: z.record(z.string(), z.unknown()).optional(),
|
|
48
48
|
}).passthrough();
|
|
49
49
|
|
|
50
|
+
function structuredSchemaDescription(): string {
|
|
51
|
+
const routing = describeCanvasSchema().mcp.nodeTypeRouting;
|
|
52
|
+
return Object.entries(routing)
|
|
53
|
+
.map(([type, tool]) => `${type}: ${tool}`)
|
|
54
|
+
.join(', ');
|
|
55
|
+
}
|
|
56
|
+
|
|
50
57
|
function workspaceRoot(): string {
|
|
51
58
|
return resolve(process.cwd());
|
|
52
59
|
}
|
|
@@ -127,7 +134,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
127
134
|
// ── canvas_add_node ────────────────────────────────────────────
|
|
128
135
|
server.tool(
|
|
129
136
|
'canvas_add_node',
|
|
130
|
-
'Add a node to the canvas. Returns the created node with normalized title/content and rendered geometry.
|
|
137
|
+
'Add a basic node to the canvas. Returns the created node with normalized title/content and rendered geometry. Supported here: markdown, status, context, ledger, trace, file, image, webpage, mcp-app, group. Dedicated node tools: json-render -> canvas_add_json_render_node, graph -> canvas_add_graph_node, web-artifact -> canvas_build_web_artifact, external apps -> canvas_open_mcp_app, groups -> canvas_create_group. Call canvas_describe_schema for the full nodeTypeRouting table.',
|
|
131
138
|
{
|
|
132
139
|
type: z.enum(['markdown', 'status', 'context', 'ledger', 'trace', 'file', 'image', 'webpage', 'mcp-app', 'group'])
|
|
133
140
|
.describe('Node type (prefer canvas_create_group for groups)'),
|
|
@@ -171,7 +178,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
171
178
|
|
|
172
179
|
server.tool(
|
|
173
180
|
'canvas_open_mcp_app',
|
|
174
|
-
'Connect to an external MCP server that declares a ui:// app resource, call the specified tool, and open the resulting MCP App inside a canvas mcp-app node.',
|
|
181
|
+
'Connect to an external MCP server that declares a ui:// app resource, call the specified tool, and open the resulting MCP App inside a canvas mcp-app node. This is a full external-MCP transport call, not the CLI kind shortcut; use canvas_add_diagram for the built-in Excalidraw preset.',
|
|
175
182
|
{
|
|
176
183
|
toolName: z.string().describe('Tool name on the external MCP server'),
|
|
177
184
|
serverName: z.string().optional().describe('Optional display name for the external MCP server'),
|
|
@@ -261,7 +268,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
261
268
|
|
|
262
269
|
server.tool(
|
|
263
270
|
'canvas_describe_schema',
|
|
264
|
-
'Describe the current server-supported canvas create schemas, json-render component catalog, canonical examples, and related MCP entry points.',
|
|
271
|
+
'Describe the current server-supported canvas create schemas, json-render component catalog, canonical examples, and related MCP entry points. Includes mcp.nodeTypeRouting, the authoritative map from node type to MCP creation tool.',
|
|
265
272
|
{},
|
|
266
273
|
async () => ({
|
|
267
274
|
content: [{ type: 'text', text: JSON.stringify(describeCanvasSchema(), null, 2) }],
|
|
@@ -356,7 +363,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
356
363
|
// ── canvas_build_web_artifact ───────────────────────────────
|
|
357
364
|
server.tool(
|
|
358
365
|
'canvas_build_web_artifact',
|
|
359
|
-
'Build a bundled single-file HTML web artifact from React/Tailwind source files using the bundled web-artifacts-builder skill scripts. Optionally opens the generated artifact as an embedded node on the canvas. Read canvas://skills/web-artifacts-builder for the full workflow, stack, and anti-slop design guidelines before calling.',
|
|
366
|
+
'Build a bundled single-file HTML web artifact from React/Tailwind source files using the bundled web-artifacts-builder skill scripts. MCP callers pass source content in appTsx (the CLI app-file flag reads a file before calling this path). Builds commonly take 45-60s on cold workspaces; use a long client timeout. Optionally opens the generated artifact as an embedded node on the canvas. Read canvas://skills/web-artifacts-builder for the full workflow, stack, and anti-slop design guidelines before calling.',
|
|
360
367
|
{
|
|
361
368
|
title: z.string().describe('Artifact title used for default project and output paths'),
|
|
362
369
|
appTsx: z.string().describe('Contents for src/App.tsx'),
|
|
@@ -407,6 +414,9 @@ export async function startMcpServer(): Promise<void> {
|
|
|
407
414
|
bytes: result.fileSize,
|
|
408
415
|
projectPath: result.projectPath,
|
|
409
416
|
openedInCanvas: result.openedInCanvas,
|
|
417
|
+
// `id` only present when a canvas node was actually created.
|
|
418
|
+
// See the matching block in src/server/server.ts handleCanvasBuildWebArtifact.
|
|
419
|
+
...(typeof result.nodeId === 'string' ? { id: result.nodeId } : {}),
|
|
410
420
|
nodeId: result.nodeId,
|
|
411
421
|
url: result.url,
|
|
412
422
|
metadata: result.metadata,
|
|
@@ -1003,7 +1013,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
1003
1013
|
'canvas://schema',
|
|
1004
1014
|
{
|
|
1005
1015
|
description:
|
|
1006
|
-
|
|
1016
|
+
`Machine-readable create schemas, canonical examples, json-render catalog details, and MCP node-type routing from the running PMX Canvas server version. Routing: ${structuredSchemaDescription()}.`,
|
|
1007
1017
|
mimeType: 'application/json',
|
|
1008
1018
|
},
|
|
1009
1019
|
async () => ({
|