pmx-canvas 0.1.18 → 0.1.19
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 +54 -0
- package/dist/types/server/canvas-state.d.ts +2 -0
- package/docs/RELEASE.md +153 -0
- package/docs/bun-webview-integration.md +296 -0
- package/docs/cli.md +140 -0
- package/docs/evals/e2e-cli-coverage.md +61 -0
- package/docs/http-api.md +191 -0
- package/docs/mcp.md +135 -0
- package/docs/node-types.md +244 -0
- package/docs/plans/.gitkeep +0 -0
- package/docs/plans/plan-001-semantic-watch-mvp.md +335 -0
- package/docs/plans/plan-002-human-attention-layer-design-spec.md +679 -0
- package/docs/plans/plan-003-human-attention-layer-implementation-plan.md +572 -0
- package/docs/reactive-canvas-proposal.md +578 -0
- package/docs/release-review-0.1.0.md +38 -0
- package/docs/screenshot.png +0 -0
- package/docs/screenshots/demo-workbench-dark.png +0 -0
- package/docs/screenshots/demo-workbench-light.png +0 -0
- package/docs/screenshots/welcome-dark.png +0 -0
- package/docs/screenshots/welcome-light.png +0 -0
- package/docs/sdk.md +92 -0
- package/package.json +2 -1
- package/src/cli/agent.ts +17 -0
- package/src/mcp/canvas-access.ts +2 -0
- package/src/mcp/server.ts +2 -0
- package/src/server/canvas-state.ts +18 -5
- package/src/server/server.ts +2 -0
package/docs/sdk.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# JavaScript/TypeScript SDK (Bun runtime)
|
|
2
|
+
|
|
3
|
+
The published SDK entrypoint is Bun-first. Node.js consumers should use the
|
|
4
|
+
[CLI](cli.md), [MCP server](mcp.md), or [HTTP API](http-api.md) instead.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
bun add pmx-canvas
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Quick example
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { createCanvas } from 'pmx-canvas';
|
|
14
|
+
|
|
15
|
+
const canvas = createCanvas({ port: 4313 });
|
|
16
|
+
await canvas.start({ open: true });
|
|
17
|
+
|
|
18
|
+
// Add nodes
|
|
19
|
+
const n1 = canvas.addNode({ type: 'markdown', title: 'Plan', content: '# Step 1\nDo the thing.' });
|
|
20
|
+
const n2 = canvas.addNode({ type: 'status', title: 'Build', content: 'passing' });
|
|
21
|
+
const n3 = canvas.addNode({ type: 'file', content: 'src/index.ts' });
|
|
22
|
+
|
|
23
|
+
// Connect them
|
|
24
|
+
canvas.addEdge({ from: n1, to: n2, type: 'flow' });
|
|
25
|
+
|
|
26
|
+
// Group related nodes
|
|
27
|
+
canvas.createGroup({ title: 'Build Pipeline', childIds: [n1, n2] });
|
|
28
|
+
|
|
29
|
+
// Self-contained HTML in a sandboxed iframe
|
|
30
|
+
canvas.addHtmlNode({
|
|
31
|
+
title: 'Cost projection',
|
|
32
|
+
html: '<canvas id="c"></canvas><script src="https://cdn.jsdelivr.net/npm/chart.js"></script><script>/* ... */</script>',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Hand-drawn diagram via the Excalidraw MCP-app preset
|
|
36
|
+
await canvas.addDiagram({
|
|
37
|
+
elements: [
|
|
38
|
+
{ type: 'rectangle', id: 'r1', x: 80, y: 80, width: 160, height: 60,
|
|
39
|
+
roundness: { type: 3 }, backgroundColor: '#a5d8ff', fillStyle: 'solid',
|
|
40
|
+
label: { text: 'Agent' } },
|
|
41
|
+
],
|
|
42
|
+
title: 'Quick sketch',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Batch-build a graph and group around it
|
|
46
|
+
await canvas.runBatch([
|
|
47
|
+
{
|
|
48
|
+
op: 'graph.add',
|
|
49
|
+
assign: 'graph',
|
|
50
|
+
args: {
|
|
51
|
+
title: 'Major wins',
|
|
52
|
+
graphType: 'bar',
|
|
53
|
+
data: [
|
|
54
|
+
{ label: 'Docs', value: 5 },
|
|
55
|
+
{ label: 'Tests', value: 8 },
|
|
56
|
+
],
|
|
57
|
+
xKey: 'label',
|
|
58
|
+
yKey: 'value',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
op: 'group.create',
|
|
63
|
+
args: {
|
|
64
|
+
title: 'Quarterly graphs',
|
|
65
|
+
childIds: ['$graph.id'],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
// Arrange and inspect
|
|
71
|
+
canvas.arrange('grid');
|
|
72
|
+
console.log(canvas.validate());
|
|
73
|
+
console.log(canvas.getLayout());
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## WebView automation
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const webview = await canvas.startAutomationWebView({ backend: 'chrome', width: 1280, height: 800 });
|
|
80
|
+
console.log(webview.active);
|
|
81
|
+
console.log(await canvas.evaluateAutomationWebView('document.title'));
|
|
82
|
+
await canvas.resizeAutomationWebView(1440, 900);
|
|
83
|
+
const screenshot = await canvas.screenshotAutomationWebView({ format: 'png' });
|
|
84
|
+
console.log(screenshot.byteLength);
|
|
85
|
+
await canvas.stopAutomationWebView();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## See also
|
|
89
|
+
|
|
90
|
+
- [Node types](node-types.md) — what each node type is for
|
|
91
|
+
- [HTTP API](http-api.md) — the same operations from any language
|
|
92
|
+
- [MCP reference](mcp.md) — the agent-facing surface
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "Spatial canvas workbench for coding agents — infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server/index.ts",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"src/",
|
|
20
20
|
"skills/",
|
|
21
|
+
"docs/",
|
|
21
22
|
"dist/canvas/",
|
|
22
23
|
"dist/json-render/",
|
|
23
24
|
"dist/types/",
|
package/src/cli/agent.ts
CHANGED
|
@@ -1698,6 +1698,7 @@ cmd('snapshot save', 'Save a named snapshot of the current canvas', [
|
|
|
1698
1698
|
cmd('snapshot list', 'List saved snapshots', [
|
|
1699
1699
|
'pmx-canvas snapshot list',
|
|
1700
1700
|
'pmx-canvas snapshot list --limit 50 --query baseline',
|
|
1701
|
+
'pmx-canvas snapshot list --after 2026-05-01T00:00:00Z --before 2026-05-05T00:00:00Z',
|
|
1701
1702
|
'pmx-canvas snapshot list --all',
|
|
1702
1703
|
], async (args) => {
|
|
1703
1704
|
const { flags } = parseFlags(args);
|
|
@@ -1706,8 +1707,12 @@ cmd('snapshot list', 'List saved snapshots', [
|
|
|
1706
1707
|
const params = new URLSearchParams();
|
|
1707
1708
|
const limit = optionalNumberFlag(flags, 'limit', 'Use a positive integer, e.g. --limit 50');
|
|
1708
1709
|
const query = getStringFlag(flags, 'query', 'q');
|
|
1710
|
+
const before = getStringFlag(flags, 'before');
|
|
1711
|
+
const after = getStringFlag(flags, 'after');
|
|
1709
1712
|
if (limit !== undefined) params.set('limit', String(limit));
|
|
1710
1713
|
if (query) params.set('q', query);
|
|
1714
|
+
if (before) params.set('before', before);
|
|
1715
|
+
if (after) params.set('after', after);
|
|
1711
1716
|
if (flags.all) params.set('all', 'true');
|
|
1712
1717
|
const result = await api('GET', `/api/canvas/snapshots${params.size > 0 ? `?${params.toString()}` : ''}`);
|
|
1713
1718
|
output(result);
|
|
@@ -2328,8 +2333,20 @@ function showCommandHelp(name: string): void {
|
|
|
2328
2333
|
console.log('\nOptions:');
|
|
2329
2334
|
console.log(' --limit <number> Maximum snapshots to return (default 20)');
|
|
2330
2335
|
console.log(' --query <text> Case-insensitive ID/name filter');
|
|
2336
|
+
console.log(' --before <timestamp> Only return snapshots created at or before this ISO timestamp');
|
|
2337
|
+
console.log(' --after <timestamp> Only return snapshots created at or after this ISO timestamp');
|
|
2331
2338
|
console.log(' --all Return all snapshots');
|
|
2332
2339
|
}
|
|
2340
|
+
if (name === 'node update') {
|
|
2341
|
+
console.log('\nTrace fields:');
|
|
2342
|
+
console.log(' --tool-name, --toolName Trace tool or operation label');
|
|
2343
|
+
console.log(' --category <name> Trace category, e.g. mcp, file, subagent, other');
|
|
2344
|
+
console.log(' --status <status> Trace status, e.g. running, success, failed');
|
|
2345
|
+
console.log(' --duration <text> Trace duration badge text');
|
|
2346
|
+
console.log(' --result-summary, --resultSummary <text>');
|
|
2347
|
+
console.log(' Trace result summary');
|
|
2348
|
+
console.log(' --error <text> Trace error message');
|
|
2349
|
+
}
|
|
2333
2350
|
if (name === 'snapshot gc') {
|
|
2334
2351
|
console.log('\nOptions:');
|
|
2335
2352
|
console.log(' --keep <number> Number of newest snapshots to keep (default 20)');
|
package/src/mcp/canvas-access.ts
CHANGED
|
@@ -559,6 +559,8 @@ class RemoteCanvasAccess implements CanvasAccess {
|
|
|
559
559
|
const params = new URLSearchParams();
|
|
560
560
|
if (typeof options?.limit === 'number') params.set('limit', String(options.limit));
|
|
561
561
|
if (options?.query) params.set('q', options.query);
|
|
562
|
+
if (options?.before) params.set('before', options.before);
|
|
563
|
+
if (options?.after) params.set('after', options.after);
|
|
562
564
|
if (options?.all) params.set('all', 'true');
|
|
563
565
|
const query = params.size > 0 ? `?${params.toString()}` : '';
|
|
564
566
|
return await this.requestJson<SnapshotList>('GET', `/api/canvas/snapshots${query}`);
|
package/src/mcp/server.ts
CHANGED
|
@@ -1759,6 +1759,8 @@ export async function startMcpServer(): Promise<void> {
|
|
|
1759
1759
|
{
|
|
1760
1760
|
limit: z.number().optional().describe('Maximum snapshots to return (default: 20)'),
|
|
1761
1761
|
query: z.string().optional().describe('Optional case-insensitive ID/name filter'),
|
|
1762
|
+
before: z.string().optional().describe('Only return snapshots created at or before this ISO timestamp'),
|
|
1763
|
+
after: z.string().optional().describe('Only return snapshots created at or after this ISO timestamp'),
|
|
1762
1764
|
all: z.boolean().optional().describe('Return all snapshots instead of the default limit'),
|
|
1763
1765
|
},
|
|
1764
1766
|
async (input) => {
|
|
@@ -32,6 +32,12 @@ function normalizePositiveInteger(value: number | undefined): number | undefined
|
|
|
32
32
|
return Math.floor(value);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function normalizeSnapshotTimestamp(value: string | undefined): string | undefined {
|
|
36
|
+
if (!value) return undefined;
|
|
37
|
+
const parsed = Date.parse(value);
|
|
38
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
export const PMX_CANVAS_DIR = '.pmx-canvas';
|
|
36
42
|
const STATE_FILENAME = 'state.json';
|
|
37
43
|
const SNAPSHOTS_SUBDIR = 'snapshots';
|
|
@@ -89,6 +95,8 @@ export interface CanvasSnapshot {
|
|
|
89
95
|
export interface CanvasSnapshotListOptions {
|
|
90
96
|
limit?: number;
|
|
91
97
|
query?: string;
|
|
98
|
+
before?: string;
|
|
99
|
+
after?: string;
|
|
92
100
|
all?: boolean;
|
|
93
101
|
}
|
|
94
102
|
|
|
@@ -856,11 +864,16 @@ class CanvasStateManager {
|
|
|
856
864
|
}
|
|
857
865
|
}
|
|
858
866
|
const query = options.query?.trim().toLowerCase();
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
)
|
|
863
|
-
|
|
867
|
+
const before = normalizeSnapshotTimestamp(options.before);
|
|
868
|
+
const after = normalizeSnapshotTimestamp(options.after);
|
|
869
|
+
const filtered = snapshots.filter((snapshot) => {
|
|
870
|
+
if (query && !snapshot.id.toLowerCase().includes(query) && !snapshot.name.toLowerCase().includes(query)) {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
if (before && snapshot.createdAt > before) return false;
|
|
874
|
+
if (after && snapshot.createdAt < after) return false;
|
|
875
|
+
return true;
|
|
876
|
+
});
|
|
864
877
|
const sorted = filtered.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
865
878
|
const limit = options.all ? undefined : (normalizePositiveInteger(options.limit) ?? 20);
|
|
866
879
|
return limit === undefined ? sorted : sorted.slice(0, limit);
|
package/src/server/server.ts
CHANGED
|
@@ -4145,6 +4145,8 @@ export function startCanvasServer(options: CanvasServerOptions = {}): string | n
|
|
|
4145
4145
|
return responseJson(listCanvasSnapshots({
|
|
4146
4146
|
limit: parsePositiveIntegerParam(url.searchParams.get('limit')),
|
|
4147
4147
|
query: url.searchParams.get('q') ?? url.searchParams.get('query') ?? undefined,
|
|
4148
|
+
before: url.searchParams.get('before') ?? undefined,
|
|
4149
|
+
after: url.searchParams.get('after') ?? undefined,
|
|
4148
4150
|
all: url.searchParams.get('all') === 'true',
|
|
4149
4151
|
}));
|
|
4150
4152
|
}
|