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,604 @@
|
|
|
1
|
+
// AX commands: every 'ax *' command (status, context, focus, event, steer,
|
|
2
|
+
// interaction, delivery, elicitation, mode, command, policy, timeline, work,
|
|
3
|
+
// approval, evidence, review, host).
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
cmd,
|
|
7
|
+
die,
|
|
8
|
+
getStringFlag,
|
|
9
|
+
invokeOperation,
|
|
10
|
+
optionalNumberFlag,
|
|
11
|
+
output,
|
|
12
|
+
parseFlags,
|
|
13
|
+
requireFlag,
|
|
14
|
+
resolveAxSource,
|
|
15
|
+
showCommandHelp,
|
|
16
|
+
} from '../shared.js';
|
|
17
|
+
|
|
18
|
+
// ── AX ────────────────────────────────────────────────────────
|
|
19
|
+
cmd('ax status', 'Read host-agnostic PMX AX state', ['pmx-canvas ax status'], async (args) => {
|
|
20
|
+
const { flags } = parseFlags(args);
|
|
21
|
+
if (flags.help || flags.h) return showCommandHelp('ax status');
|
|
22
|
+
|
|
23
|
+
output(await invokeOperation('ax.get', {}));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
cmd('ax context', 'Read agent-ready PMX AX context', ['pmx-canvas ax context'], async (args) => {
|
|
27
|
+
const { flags } = parseFlags(args);
|
|
28
|
+
if (flags.help || flags.h) return showCommandHelp('ax context');
|
|
29
|
+
|
|
30
|
+
output(await invokeOperation('ax.context.get', {}));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
cmd(
|
|
34
|
+
'ax focus',
|
|
35
|
+
'Set or clear PMX AX focus without moving the viewport',
|
|
36
|
+
['pmx-canvas ax focus node1 node2', 'pmx-canvas ax focus --clear'],
|
|
37
|
+
async (args) => {
|
|
38
|
+
const { positional, flags } = parseFlags(args);
|
|
39
|
+
if (flags.help || flags.h) return showCommandHelp('ax focus');
|
|
40
|
+
|
|
41
|
+
const nodeIds = flags.clear ? [] : positional;
|
|
42
|
+
if (!flags.clear && nodeIds.length === 0) {
|
|
43
|
+
die('Missing node ID', 'pmx-canvas ax focus <node-id> [more-node-ids]');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
output(await invokeOperation('ax.focus.set', { nodeIds, source: resolveAxSource(flags) }));
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
cmd(
|
|
51
|
+
'ax event add',
|
|
52
|
+
'Record a normalized AX timeline event',
|
|
53
|
+
[
|
|
54
|
+
'pmx-canvas ax event add --kind tool-start --summary "ran tests"',
|
|
55
|
+
'pmx-canvas ax event add --kind failure --summary "build broke" --detail "..." node1 node2',
|
|
56
|
+
],
|
|
57
|
+
async (args) => {
|
|
58
|
+
const { positional, flags } = parseFlags(args);
|
|
59
|
+
if (flags.help || flags.h) return showCommandHelp('ax event add');
|
|
60
|
+
|
|
61
|
+
const kind = requireFlag(flags, 'kind', 'pmx-canvas ax event add --kind <kind> --summary <text>');
|
|
62
|
+
const summary = requireFlag(flags, 'summary', 'pmx-canvas ax event add --kind <kind> --summary <text>');
|
|
63
|
+
const detail = getStringFlag(flags, 'detail');
|
|
64
|
+
|
|
65
|
+
output(
|
|
66
|
+
await invokeOperation('ax.event.record', {
|
|
67
|
+
kind,
|
|
68
|
+
summary,
|
|
69
|
+
...(detail ? { detail } : {}),
|
|
70
|
+
...(positional.length > 0 ? { nodeIds: positional } : {}),
|
|
71
|
+
source: resolveAxSource(flags),
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
cmd(
|
|
78
|
+
'ax steer',
|
|
79
|
+
'Send a steering message to the active agent session',
|
|
80
|
+
['pmx-canvas ax steer "focus on the failing test first"', 'pmx-canvas ax steer --message "stop and re-plan"'],
|
|
81
|
+
async (args) => {
|
|
82
|
+
const { positional, flags } = parseFlags(args);
|
|
83
|
+
if (flags.help || flags.h) return showCommandHelp('ax steer');
|
|
84
|
+
|
|
85
|
+
const message = getStringFlag(flags, 'message') ?? positional.join(' ').trim();
|
|
86
|
+
if (!message) {
|
|
87
|
+
die('Missing steering message', 'pmx-canvas ax steer <message>');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
output(await invokeOperation('ax.steer', { message, source: resolveAxSource(flags) }));
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
cmd(
|
|
95
|
+
'ax interaction',
|
|
96
|
+
'Submit a node-originated AX interaction (capability-gated)',
|
|
97
|
+
[
|
|
98
|
+
'pmx-canvas ax interaction --type ax.work.create --node node-1 --payload \'{"title":"Wire auth"}\'',
|
|
99
|
+
'pmx-canvas ax interaction --type ax.focus.set --node node-2',
|
|
100
|
+
],
|
|
101
|
+
async (args) => {
|
|
102
|
+
const { flags } = parseFlags(args);
|
|
103
|
+
if (flags.help || flags.h) return showCommandHelp('ax interaction');
|
|
104
|
+
|
|
105
|
+
const type = getStringFlag(flags, 'type');
|
|
106
|
+
if (!type) die('Missing --type', 'pmx-canvas ax interaction --type <ax.*> --node <id> [--payload <json>]');
|
|
107
|
+
const sourceNodeId = getStringFlag(flags, 'node');
|
|
108
|
+
if (!sourceNodeId) die('Missing --node', 'pmx-canvas ax interaction --type <ax.*> --node <id>');
|
|
109
|
+
|
|
110
|
+
let payload: unknown;
|
|
111
|
+
const payloadRaw = getStringFlag(flags, 'payload');
|
|
112
|
+
if (payloadRaw) {
|
|
113
|
+
try {
|
|
114
|
+
payload = JSON.parse(payloadRaw);
|
|
115
|
+
} catch {
|
|
116
|
+
die('Invalid --payload JSON', 'pmx-canvas ax interaction --payload \'{"title":"..."}\'');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
output(
|
|
121
|
+
await invokeOperation('ax.interaction.submit', {
|
|
122
|
+
type,
|
|
123
|
+
sourceNodeId,
|
|
124
|
+
...(payload !== undefined ? { payload } : {}),
|
|
125
|
+
source: resolveAxSource(flags),
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
cmd(
|
|
132
|
+
'ax delivery list',
|
|
133
|
+
'List pending AX steering for a consumer (loop-safe)',
|
|
134
|
+
[
|
|
135
|
+
'pmx-canvas ax delivery list',
|
|
136
|
+
'pmx-canvas ax delivery list --consumer copilot --limit 20',
|
|
137
|
+
'pmx-canvas ax delivery list --order newest # latest browser steering first (#68)',
|
|
138
|
+
],
|
|
139
|
+
async (args) => {
|
|
140
|
+
const { flags } = parseFlags(args);
|
|
141
|
+
if (flags.help || flags.h) return showCommandHelp('ax delivery list');
|
|
142
|
+
const consumer = getStringFlag(flags, 'consumer');
|
|
143
|
+
const limit = optionalNumberFlag(flags, 'limit', 'pmx-canvas ax delivery list --limit <n>');
|
|
144
|
+
const order = getStringFlag(flags, 'order');
|
|
145
|
+
if (order !== undefined && order !== 'newest' && order !== 'oldest') {
|
|
146
|
+
die('Invalid --order', 'pmx-canvas ax delivery list --order newest|oldest');
|
|
147
|
+
}
|
|
148
|
+
output(await invokeOperation('ax.delivery.pending', { consumer, limit, order }));
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
cmd(
|
|
153
|
+
'ax delivery mark',
|
|
154
|
+
'Mark an AX steering message as delivered',
|
|
155
|
+
['pmx-canvas ax delivery mark <steering-id>'],
|
|
156
|
+
async (args) => {
|
|
157
|
+
const { positional, flags } = parseFlags(args);
|
|
158
|
+
if (flags.help || flags.h) return showCommandHelp('ax delivery mark');
|
|
159
|
+
const id = getStringFlag(flags, 'id') ?? positional[0];
|
|
160
|
+
if (!id) die('Missing steering id', 'pmx-canvas ax delivery mark <steering-id>');
|
|
161
|
+
output(await invokeOperation('ax.delivery.mark', { id }));
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
cmd(
|
|
166
|
+
'ax elicitation request',
|
|
167
|
+
'Request structured human input',
|
|
168
|
+
[
|
|
169
|
+
'pmx-canvas ax elicitation request --prompt "Who owns this migration?"',
|
|
170
|
+
'pmx-canvas ax elicitation request --prompt "Pick a region" --fields region,owner',
|
|
171
|
+
],
|
|
172
|
+
async (args) => {
|
|
173
|
+
const { flags } = parseFlags(args);
|
|
174
|
+
if (flags.help || flags.h) return showCommandHelp('ax elicitation request');
|
|
175
|
+
const prompt = requireFlag(flags, 'prompt', 'pmx-canvas ax elicitation request --prompt <text>');
|
|
176
|
+
const fields = getStringFlag(flags, 'fields');
|
|
177
|
+
output(
|
|
178
|
+
await invokeOperation('ax.elicitation.request', {
|
|
179
|
+
prompt,
|
|
180
|
+
...(fields
|
|
181
|
+
? {
|
|
182
|
+
fields: fields
|
|
183
|
+
.split(',')
|
|
184
|
+
.map((f) => f.trim())
|
|
185
|
+
.filter(Boolean),
|
|
186
|
+
}
|
|
187
|
+
: {}),
|
|
188
|
+
source: resolveAxSource(flags),
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
cmd(
|
|
195
|
+
'ax elicitation respond',
|
|
196
|
+
'Answer a pending elicitation',
|
|
197
|
+
['pmx-canvas ax elicitation respond <id> --response \'{"owner":"alice"}\''],
|
|
198
|
+
async (args) => {
|
|
199
|
+
const { positional, flags } = parseFlags(args);
|
|
200
|
+
if (flags.help || flags.h) return showCommandHelp('ax elicitation respond');
|
|
201
|
+
const id = getStringFlag(flags, 'id') ?? positional[0];
|
|
202
|
+
if (!id) die('Missing elicitation id', 'pmx-canvas ax elicitation respond <id> --response <json>');
|
|
203
|
+
let response: unknown = {};
|
|
204
|
+
const raw = getStringFlag(flags, 'response');
|
|
205
|
+
if (raw) {
|
|
206
|
+
try {
|
|
207
|
+
response = JSON.parse(raw);
|
|
208
|
+
} catch {
|
|
209
|
+
die('Invalid --response JSON', '--response \'{"k":"v"}\'');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
output(
|
|
213
|
+
await invokeOperation('ax.elicitation.respond', {
|
|
214
|
+
id,
|
|
215
|
+
response,
|
|
216
|
+
source: resolveAxSource(flags),
|
|
217
|
+
}),
|
|
218
|
+
);
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
cmd('ax elicitation list', 'List elicitations', ['pmx-canvas ax elicitation list'], async (args) => {
|
|
223
|
+
const { flags } = parseFlags(args);
|
|
224
|
+
if (flags.help || flags.h) return showCommandHelp('ax elicitation list');
|
|
225
|
+
output(await invokeOperation('ax.elicitation.list', {}));
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
cmd(
|
|
229
|
+
'ax mode request',
|
|
230
|
+
'Request a workflow mode transition (plan/execute/autonomous)',
|
|
231
|
+
['pmx-canvas ax mode request --mode execute --reason "plan approved"'],
|
|
232
|
+
async (args) => {
|
|
233
|
+
const { flags } = parseFlags(args);
|
|
234
|
+
if (flags.help || flags.h) return showCommandHelp('ax mode request');
|
|
235
|
+
const mode = requireFlag(flags, 'mode', 'pmx-canvas ax mode request --mode plan|execute|autonomous');
|
|
236
|
+
const reason = getStringFlag(flags, 'reason');
|
|
237
|
+
output(
|
|
238
|
+
await invokeOperation('ax.mode.request', {
|
|
239
|
+
mode,
|
|
240
|
+
...(reason ? { reason } : {}),
|
|
241
|
+
source: resolveAxSource(flags),
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
244
|
+
},
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
cmd(
|
|
248
|
+
'ax mode resolve',
|
|
249
|
+
'Resolve a pending mode request',
|
|
250
|
+
['pmx-canvas ax mode resolve <id> --decision approved'],
|
|
251
|
+
async (args) => {
|
|
252
|
+
const { positional, flags } = parseFlags(args);
|
|
253
|
+
if (flags.help || flags.h) return showCommandHelp('ax mode resolve');
|
|
254
|
+
const id = getStringFlag(flags, 'id') ?? positional[0];
|
|
255
|
+
if (!id) die('Missing mode request id', 'pmx-canvas ax mode resolve <id> --decision approved|rejected');
|
|
256
|
+
const decision = getStringFlag(flags, 'decision');
|
|
257
|
+
if (decision !== 'approved' && decision !== 'rejected') die('Invalid --decision', '--decision approved|rejected');
|
|
258
|
+
const resolution = getStringFlag(flags, 'resolution');
|
|
259
|
+
output(
|
|
260
|
+
await invokeOperation('ax.mode.resolve', {
|
|
261
|
+
id,
|
|
262
|
+
decision,
|
|
263
|
+
...(resolution ? { resolution } : {}),
|
|
264
|
+
source: resolveAxSource(flags),
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
267
|
+
},
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
cmd('ax mode list', 'List mode requests', ['pmx-canvas ax mode list'], async (args) => {
|
|
271
|
+
const { flags } = parseFlags(args);
|
|
272
|
+
if (flags.help || flags.h) return showCommandHelp('ax mode list');
|
|
273
|
+
output(await invokeOperation('ax.mode.list', {}));
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
cmd('ax command list', 'List the PMX command registry', ['pmx-canvas ax command list'], async (args) => {
|
|
277
|
+
const { flags } = parseFlags(args);
|
|
278
|
+
if (flags.help || flags.h) return showCommandHelp('ax command list');
|
|
279
|
+
output(await invokeOperation('ax.command.list', {}));
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
cmd(
|
|
283
|
+
'ax command invoke',
|
|
284
|
+
'Invoke a registry-gated PMX command intent',
|
|
285
|
+
[
|
|
286
|
+
'pmx-canvas ax command invoke pmx.plan',
|
|
287
|
+
'pmx-canvas ax command invoke pmx.promote-context --args \'{"nodeIds":["n1"]}\'',
|
|
288
|
+
],
|
|
289
|
+
async (args) => {
|
|
290
|
+
const { positional, flags } = parseFlags(args);
|
|
291
|
+
if (flags.help || flags.h) return showCommandHelp('ax command invoke');
|
|
292
|
+
const name = getStringFlag(flags, 'name') ?? positional[0];
|
|
293
|
+
if (!name) die('Missing command name', 'pmx-canvas ax command invoke <name>');
|
|
294
|
+
let cmdArgs: unknown;
|
|
295
|
+
const raw = getStringFlag(flags, 'args');
|
|
296
|
+
if (raw) {
|
|
297
|
+
try {
|
|
298
|
+
cmdArgs = JSON.parse(raw);
|
|
299
|
+
} catch {
|
|
300
|
+
die('Invalid --args JSON', '--args \'{"k":"v"}\'');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
output(
|
|
304
|
+
await invokeOperation('ax.command.invoke', {
|
|
305
|
+
name,
|
|
306
|
+
...(cmdArgs !== undefined ? { args: cmdArgs } : {}),
|
|
307
|
+
source: resolveAxSource(flags),
|
|
308
|
+
}),
|
|
309
|
+
);
|
|
310
|
+
},
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
cmd('ax policy get', 'Show the current declarative AX policy', ['pmx-canvas ax policy get'], async (args) => {
|
|
314
|
+
const { flags } = parseFlags(args);
|
|
315
|
+
if (flags.help || flags.h) return showCommandHelp('ax policy get');
|
|
316
|
+
output(await invokeOperation('ax.policy.get', {}));
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
cmd(
|
|
320
|
+
'ax policy set',
|
|
321
|
+
'Set the declarative AX policy (stored by PMX, enforced by adapters)',
|
|
322
|
+
['pmx-canvas ax policy set --excluded-tools shell,write --mode concise'],
|
|
323
|
+
async (args) => {
|
|
324
|
+
const { flags } = parseFlags(args);
|
|
325
|
+
if (flags.help || flags.h) return showCommandHelp('ax policy set');
|
|
326
|
+
const csv = (v?: string) =>
|
|
327
|
+
v
|
|
328
|
+
? v
|
|
329
|
+
.split(',')
|
|
330
|
+
.map((s) => s.trim())
|
|
331
|
+
.filter(Boolean)
|
|
332
|
+
: undefined;
|
|
333
|
+
const allowed = csv(getStringFlag(flags, 'allowed-tools'));
|
|
334
|
+
const excluded = csv(getStringFlag(flags, 'excluded-tools'));
|
|
335
|
+
const approvalRequired = csv(getStringFlag(flags, 'approval-tools'));
|
|
336
|
+
const mode = getStringFlag(flags, 'mode');
|
|
337
|
+
const systemAppend = getStringFlag(flags, 'system-append');
|
|
338
|
+
const tools =
|
|
339
|
+
allowed || excluded || approvalRequired
|
|
340
|
+
? {
|
|
341
|
+
...(allowed ? { allowed } : {}),
|
|
342
|
+
...(excluded ? { excluded } : {}),
|
|
343
|
+
...(approvalRequired ? { approvalRequired } : {}),
|
|
344
|
+
}
|
|
345
|
+
: undefined;
|
|
346
|
+
const prompt =
|
|
347
|
+
mode || systemAppend ? { ...(mode ? { mode } : {}), ...(systemAppend ? { systemAppend } : {}) } : undefined;
|
|
348
|
+
output(
|
|
349
|
+
await invokeOperation('ax.policy.set', {
|
|
350
|
+
...(tools ? { tools } : {}),
|
|
351
|
+
...(prompt ? { prompt } : {}),
|
|
352
|
+
source: resolveAxSource(flags),
|
|
353
|
+
}),
|
|
354
|
+
);
|
|
355
|
+
},
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
cmd(
|
|
359
|
+
'ax timeline',
|
|
360
|
+
'Read the bounded AX timeline (events, evidence, steering)',
|
|
361
|
+
['pmx-canvas ax timeline', 'pmx-canvas ax timeline --limit 100'],
|
|
362
|
+
async (args) => {
|
|
363
|
+
const { flags } = parseFlags(args);
|
|
364
|
+
if (flags.help || flags.h) return showCommandHelp('ax timeline');
|
|
365
|
+
|
|
366
|
+
const limit = optionalNumberFlag(flags, 'limit', 'pmx-canvas ax timeline --limit <n>');
|
|
367
|
+
output(await invokeOperation('ax.timeline.get', limit ? { limit } : {}));
|
|
368
|
+
},
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
cmd(
|
|
372
|
+
'ax work add',
|
|
373
|
+
'Add a canvas-bound AX work item',
|
|
374
|
+
[
|
|
375
|
+
'pmx-canvas ax work add --title "Wire up auth" --status in-progress',
|
|
376
|
+
'pmx-canvas ax work add --title "Review API" node1 node2',
|
|
377
|
+
],
|
|
378
|
+
async (args) => {
|
|
379
|
+
const { positional, flags } = parseFlags(args);
|
|
380
|
+
if (flags.help || flags.h) return showCommandHelp('ax work add');
|
|
381
|
+
|
|
382
|
+
const title = requireFlag(flags, 'title', 'pmx-canvas ax work add --title <text>');
|
|
383
|
+
const status = getStringFlag(flags, 'status');
|
|
384
|
+
const detail = getStringFlag(flags, 'detail');
|
|
385
|
+
|
|
386
|
+
output(
|
|
387
|
+
await invokeOperation('ax.work.create', {
|
|
388
|
+
title,
|
|
389
|
+
...(status ? { status } : {}),
|
|
390
|
+
...(detail ? { detail } : {}),
|
|
391
|
+
...(positional.length > 0 ? { nodeIds: positional } : {}),
|
|
392
|
+
source: resolveAxSource(flags),
|
|
393
|
+
}),
|
|
394
|
+
);
|
|
395
|
+
},
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
cmd(
|
|
399
|
+
'ax work update',
|
|
400
|
+
'Update a canvas-bound AX work item by ID',
|
|
401
|
+
['pmx-canvas ax work update <id> --status done', 'pmx-canvas ax work update <id> --title "New title" --detail "..."'],
|
|
402
|
+
async (args) => {
|
|
403
|
+
const { positional, flags } = parseFlags(args);
|
|
404
|
+
if (flags.help || flags.h) return showCommandHelp('ax work update');
|
|
405
|
+
|
|
406
|
+
const id = positional[0];
|
|
407
|
+
if (!id) die('Missing work item ID', 'pmx-canvas ax work update <id> --status <status>');
|
|
408
|
+
const title = getStringFlag(flags, 'title');
|
|
409
|
+
const status = getStringFlag(flags, 'status');
|
|
410
|
+
const detail = getStringFlag(flags, 'detail');
|
|
411
|
+
|
|
412
|
+
output(
|
|
413
|
+
await invokeOperation('ax.work.update', {
|
|
414
|
+
id,
|
|
415
|
+
...(title ? { title } : {}),
|
|
416
|
+
...(status ? { status } : {}),
|
|
417
|
+
...(detail ? { detail } : {}),
|
|
418
|
+
...(positional.length > 1 ? { nodeIds: positional.slice(1) } : {}),
|
|
419
|
+
source: resolveAxSource(flags),
|
|
420
|
+
}),
|
|
421
|
+
);
|
|
422
|
+
},
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
cmd('ax work list', 'List canvas-bound AX work items', ['pmx-canvas ax work list'], async (args) => {
|
|
426
|
+
const { flags } = parseFlags(args);
|
|
427
|
+
if (flags.help || flags.h) return showCommandHelp('ax work list');
|
|
428
|
+
|
|
429
|
+
output(await invokeOperation('ax.work.list', {}));
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
cmd(
|
|
433
|
+
'ax approval request',
|
|
434
|
+
'Request a canvas-bound AX approval gate',
|
|
435
|
+
[
|
|
436
|
+
'pmx-canvas ax approval request --title "Deploy to prod"',
|
|
437
|
+
'pmx-canvas ax approval request --title "Drop table" --action db.drop node1',
|
|
438
|
+
],
|
|
439
|
+
async (args) => {
|
|
440
|
+
const { positional, flags } = parseFlags(args);
|
|
441
|
+
if (flags.help || flags.h) return showCommandHelp('ax approval request');
|
|
442
|
+
|
|
443
|
+
const title = requireFlag(flags, 'title', 'pmx-canvas ax approval request --title <text>');
|
|
444
|
+
const detail = getStringFlag(flags, 'detail');
|
|
445
|
+
const action = getStringFlag(flags, 'action');
|
|
446
|
+
|
|
447
|
+
output(
|
|
448
|
+
await invokeOperation('ax.approval.request', {
|
|
449
|
+
title,
|
|
450
|
+
...(detail ? { detail } : {}),
|
|
451
|
+
...(action ? { action } : {}),
|
|
452
|
+
...(positional.length > 0 ? { nodeIds: positional } : {}),
|
|
453
|
+
source: resolveAxSource(flags),
|
|
454
|
+
}),
|
|
455
|
+
);
|
|
456
|
+
},
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
cmd(
|
|
460
|
+
'ax approval resolve',
|
|
461
|
+
'Resolve a pending AX approval gate by ID',
|
|
462
|
+
[
|
|
463
|
+
'pmx-canvas ax approval resolve <id> --decision approved',
|
|
464
|
+
'pmx-canvas ax approval resolve <id> --decision rejected --resolution "too risky"',
|
|
465
|
+
],
|
|
466
|
+
async (args) => {
|
|
467
|
+
const { positional, flags } = parseFlags(args);
|
|
468
|
+
if (flags.help || flags.h) return showCommandHelp('ax approval resolve');
|
|
469
|
+
|
|
470
|
+
const id = positional[0];
|
|
471
|
+
if (!id) die('Missing approval gate ID', 'pmx-canvas ax approval resolve <id> --decision <approved|rejected>');
|
|
472
|
+
const decision = requireFlag(
|
|
473
|
+
flags,
|
|
474
|
+
'decision',
|
|
475
|
+
'pmx-canvas ax approval resolve <id> --decision <approved|rejected>',
|
|
476
|
+
);
|
|
477
|
+
if (decision !== 'approved' && decision !== 'rejected') {
|
|
478
|
+
die('Invalid decision', 'pmx-canvas ax approval resolve <id> --decision <approved|rejected>');
|
|
479
|
+
}
|
|
480
|
+
const resolution = getStringFlag(flags, 'resolution');
|
|
481
|
+
|
|
482
|
+
output(
|
|
483
|
+
await invokeOperation('ax.approval.resolve', {
|
|
484
|
+
id,
|
|
485
|
+
decision,
|
|
486
|
+
...(resolution ? { resolution } : {}),
|
|
487
|
+
source: resolveAxSource(flags),
|
|
488
|
+
}),
|
|
489
|
+
);
|
|
490
|
+
},
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
cmd('ax approval list', 'List canvas-bound AX approval gates', ['pmx-canvas ax approval list'], async (args) => {
|
|
494
|
+
const { flags } = parseFlags(args);
|
|
495
|
+
if (flags.help || flags.h) return showCommandHelp('ax approval list');
|
|
496
|
+
|
|
497
|
+
output(await invokeOperation('ax.approval.list', {}));
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
cmd(
|
|
501
|
+
'ax evidence add',
|
|
502
|
+
'Record an AX evidence item on the timeline',
|
|
503
|
+
[
|
|
504
|
+
'pmx-canvas ax evidence add --kind test-output --title "unit pass" --body "..."',
|
|
505
|
+
'pmx-canvas ax evidence add --kind screenshot --title "before" --ref /tmp/before.png node1',
|
|
506
|
+
],
|
|
507
|
+
async (args) => {
|
|
508
|
+
const { positional, flags } = parseFlags(args);
|
|
509
|
+
if (flags.help || flags.h) return showCommandHelp('ax evidence add');
|
|
510
|
+
|
|
511
|
+
const kind = requireFlag(flags, 'kind', 'pmx-canvas ax evidence add --kind <kind> --title <text>');
|
|
512
|
+
const title = requireFlag(flags, 'title', 'pmx-canvas ax evidence add --kind <kind> --title <text>');
|
|
513
|
+
const body = getStringFlag(flags, 'body');
|
|
514
|
+
const ref = getStringFlag(flags, 'ref');
|
|
515
|
+
|
|
516
|
+
output(
|
|
517
|
+
await invokeOperation('ax.evidence.add', {
|
|
518
|
+
kind,
|
|
519
|
+
title,
|
|
520
|
+
...(body ? { body } : {}),
|
|
521
|
+
...(ref ? { ref } : {}),
|
|
522
|
+
...(positional.length > 0 ? { nodeIds: positional } : {}),
|
|
523
|
+
source: resolveAxSource(flags),
|
|
524
|
+
}),
|
|
525
|
+
);
|
|
526
|
+
},
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
cmd(
|
|
530
|
+
'ax review add',
|
|
531
|
+
'Add a canvas-bound AX review annotation',
|
|
532
|
+
[
|
|
533
|
+
'pmx-canvas ax review add --body "needs a test" --node node1',
|
|
534
|
+
'pmx-canvas ax review add --body "off-by-one" --kind finding --severity error --file src/x.ts',
|
|
535
|
+
],
|
|
536
|
+
async (args) => {
|
|
537
|
+
const { flags } = parseFlags(args);
|
|
538
|
+
if (flags.help || flags.h) return showCommandHelp('ax review add');
|
|
539
|
+
|
|
540
|
+
const body = requireFlag(flags, 'body', 'pmx-canvas ax review add --body <text>');
|
|
541
|
+
const kind = getStringFlag(flags, 'kind');
|
|
542
|
+
const severity = getStringFlag(flags, 'severity');
|
|
543
|
+
const anchorType = getStringFlag(flags, 'anchor');
|
|
544
|
+
const nodeId = getStringFlag(flags, 'node');
|
|
545
|
+
const file = getStringFlag(flags, 'file');
|
|
546
|
+
const author = getStringFlag(flags, 'author');
|
|
547
|
+
|
|
548
|
+
output(
|
|
549
|
+
await invokeOperation('ax.review.add', {
|
|
550
|
+
body,
|
|
551
|
+
...(kind ? { kind } : {}),
|
|
552
|
+
...(severity ? { severity } : {}),
|
|
553
|
+
...(anchorType ? { anchorType } : {}),
|
|
554
|
+
...(nodeId ? { nodeId } : {}),
|
|
555
|
+
...(file ? { file } : {}),
|
|
556
|
+
...(author ? { author } : {}),
|
|
557
|
+
source: resolveAxSource(flags),
|
|
558
|
+
}),
|
|
559
|
+
);
|
|
560
|
+
},
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
cmd('ax review list', 'List canvas-bound AX review annotations', ['pmx-canvas ax review list'], async (args) => {
|
|
564
|
+
const { flags } = parseFlags(args);
|
|
565
|
+
if (flags.help || flags.h) return showCommandHelp('ax review list');
|
|
566
|
+
|
|
567
|
+
output(await invokeOperation('ax.review.list', {}));
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
cmd(
|
|
571
|
+
'ax host report',
|
|
572
|
+
'Report host/session capability to the canvas',
|
|
573
|
+
[
|
|
574
|
+
'pmx-canvas ax host report --host copilot --canvas --tools --session-messaging',
|
|
575
|
+
'pmx-canvas ax host report --host codex --canvas --files',
|
|
576
|
+
],
|
|
577
|
+
async (args) => {
|
|
578
|
+
const { flags } = parseFlags(args);
|
|
579
|
+
if (flags.help || flags.h) return showCommandHelp('ax host report');
|
|
580
|
+
|
|
581
|
+
const host = getStringFlag(flags, 'host');
|
|
582
|
+
|
|
583
|
+
output(
|
|
584
|
+
await invokeOperation('ax.host-capability.report', {
|
|
585
|
+
...(host ? { host } : {}),
|
|
586
|
+
canvas: flags.canvas === true,
|
|
587
|
+
hooks: flags.hooks === true,
|
|
588
|
+
tools: flags.tools === true,
|
|
589
|
+
sessionMessaging: flags['session-messaging'] === true,
|
|
590
|
+
permissions: flags.permissions === true,
|
|
591
|
+
files: flags.files === true,
|
|
592
|
+
uiPrompts: flags['ui-prompts'] === true,
|
|
593
|
+
source: resolveAxSource(flags),
|
|
594
|
+
}),
|
|
595
|
+
);
|
|
596
|
+
},
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
cmd('ax host status', 'Read the reported host/session capability', ['pmx-canvas ax host status'], async (args) => {
|
|
600
|
+
const { flags } = parseFlags(args);
|
|
601
|
+
if (flags.help || flags.h) return showCommandHelp('ax host status');
|
|
602
|
+
|
|
603
|
+
output(await invokeOperation('ax.host-capability.get', {}));
|
|
604
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Copilot adapter commands: copilot install-extension.
|
|
2
|
+
|
|
3
|
+
import { copyFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { cmd, die, getStringFlag, output, parseFlags, showCommandHelp } from '../shared.js';
|
|
7
|
+
|
|
8
|
+
// ── copilot install-extension ────────────────────────────────
|
|
9
|
+
cmd(
|
|
10
|
+
'copilot install-extension',
|
|
11
|
+
'Install the bundled GitHub Copilot extension adapter',
|
|
12
|
+
[
|
|
13
|
+
'pmx-canvas copilot install-extension --dry-run',
|
|
14
|
+
'pmx-canvas copilot install-extension --target .github/extensions/pmx-canvas/extension.mjs --yes',
|
|
15
|
+
],
|
|
16
|
+
async (args) => {
|
|
17
|
+
const { flags } = parseFlags(args);
|
|
18
|
+
if (flags.help || flags.h) return showCommandHelp('copilot install-extension');
|
|
19
|
+
|
|
20
|
+
const sourcePath = fileURLToPath(new URL('../../../.github/extensions/pmx-canvas/extension.mjs', import.meta.url));
|
|
21
|
+
if (!existsSync(sourcePath)) {
|
|
22
|
+
die('Bundled Copilot extension adapter not found.', `Expected at ${sourcePath}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const targetPath =
|
|
26
|
+
getStringFlag(flags, 'target') ?? join(process.cwd(), '.github', 'extensions', 'pmx-canvas', 'extension.mjs');
|
|
27
|
+
const dryRun = flags['dry-run'] === true;
|
|
28
|
+
const targetExists = existsSync(targetPath);
|
|
29
|
+
|
|
30
|
+
if (dryRun) {
|
|
31
|
+
output({ ok: true, dryRun: true, sourcePath, targetPath, targetExists, wrote: false });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (targetExists && flags.yes !== true) {
|
|
36
|
+
die('Target already exists. Re-run with --yes to overwrite.', `Target: ${targetPath}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
40
|
+
copyFileSync(sourcePath, targetPath);
|
|
41
|
+
output({ ok: true, dryRun: false, sourcePath, targetPath, wrote: true });
|
|
42
|
+
},
|
|
43
|
+
);
|