@clearlysh/beehaven 0.8.36 → 0.8.38
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/dist/agent-mint-response.js +10 -0
- package/dist/build-channel.js +2 -2
- package/dist/call-brief.js +157 -0
- package/dist/claude-bridge.js +4 -1
- package/dist/clearly.js +0 -0
- package/dist/cli.js +18 -4
- package/dist/commands/agent.js +5 -3
- package/dist/commands/daemon-cmd.js +17 -1
- package/dist/commands/help.js +3 -1
- package/dist/commands/workspace.js +31 -2
- package/dist/connection-ready.js +25 -0
- package/dist/daemon-client.js +7 -2
- package/dist/daemon-process.js +26 -11
- package/dist/daemon.js +4 -3
- package/dist/ipc-client.js +4 -1
- package/dist/ipc-server.js +4 -1
- package/dist/relay.js +19 -6
- package/dist/utf8-stream.js +23 -0
- package/package.json +1 -1
- package/skills/clearly-canvas/SKILL.md +16 -8
- package/skills/clearly-canvas/authoring.md +40 -17
- package/skills/clearly-canvas/review.mjs +25 -4
- package/skills/index.json +1 -1
- package/dist/daemon-codex.js +0 -349
- package/dist/daemon-write-sandbox.js +0 -191
package/dist/relay.js
CHANGED
|
@@ -518,7 +518,16 @@ export class Relay {
|
|
|
518
518
|
expiredNoRefresh = true;
|
|
519
519
|
}
|
|
520
520
|
}
|
|
521
|
-
catch {
|
|
521
|
+
catch {
|
|
522
|
+
// A damaged cached ID token has no decodable exp, but a valid refresh session can still
|
|
523
|
+
// recover it. Do not strand that account in an unauthenticated reconnect loop. Opaque
|
|
524
|
+
// self-hosted tokens without a refresh session retain their existing dial behavior.
|
|
525
|
+
if (this.config.refreshToken) {
|
|
526
|
+
const refreshed = await this.refreshIdToken();
|
|
527
|
+
if (refreshed && this.config)
|
|
528
|
+
token = this.config.token;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
522
531
|
// If the token is already expired AND refresh failed, dialing would just
|
|
523
532
|
// get rejected — spinning the reconnect loop forever with a dead
|
|
524
533
|
// credential (the classic "daemon stuck reconnecting" state). Back off
|
|
@@ -777,10 +786,14 @@ export class Relay {
|
|
|
777
786
|
this.heartbeatInterval = null;
|
|
778
787
|
this.wsPingInterval = null;
|
|
779
788
|
this.wsWakeDetector = null;
|
|
780
|
-
// Reject pending requests
|
|
781
|
-
|
|
789
|
+
// Reject pending requests.
|
|
790
|
+
// ⚠ CAN2-175 — SAY THE OUTCOME IS UNKNOWN, as the timeout does. A dropped socket loses the REPLY,
|
|
791
|
+
// not the request: a canvas batch carries on and lands on the workspace. A bare "WebSocket closed"
|
|
792
|
+
// read as "nothing happened", the caller resent at once, and could not tell which state was real.
|
|
793
|
+
for (const [requestId, pending] of this.pendingRequests) {
|
|
782
794
|
clearTimeout(pending.timer);
|
|
783
|
-
pending.reject(new Error(
|
|
795
|
+
pending.reject(new Error(`WebSocket closed (code ${code ?? '?'}) before the reply (action=${pending.action ?? '?'}, requestId=${requestId}); outcome unknown: `
|
|
796
|
+
+ 'the request may still be running on the workspace or may already have landed. Read its state before resending.'));
|
|
784
797
|
}
|
|
785
798
|
this.pendingRequests.clear();
|
|
786
799
|
this.backoff.closed(openedAt);
|
|
@@ -1098,12 +1111,12 @@ export class Relay {
|
|
|
1098
1111
|
return reject(new Error('WebSocket not connected'));
|
|
1099
1112
|
}
|
|
1100
1113
|
const requestId = Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
1114
|
+
const action = msg.type === 'agent-invoke' ? msg.action : msg.type;
|
|
1101
1115
|
const timer = setTimeout(() => {
|
|
1102
1116
|
this.pendingRequests.delete(requestId);
|
|
1103
|
-
const action = msg.type === 'agent-invoke' ? msg.action : msg.type;
|
|
1104
1117
|
reject(new Error(`Request timeout after ${timeout}ms (action=${action}, requestId=${requestId}); outcome unknown`));
|
|
1105
1118
|
}, timeout);
|
|
1106
|
-
this.pendingRequests.set(requestId, { resolve, reject, timer });
|
|
1119
|
+
this.pendingRequests.set(requestId, { resolve, reject, timer, action });
|
|
1107
1120
|
// Send both rid (legacy) and requestId (workspace actions) for compatibility
|
|
1108
1121
|
if (!this.wsSend({ ...msg, rid: requestId, requestId })) {
|
|
1109
1122
|
clearTimeout(timer);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* utf8-stream — decode a socket's bytes ONCE PER CHARACTER, not once per chunk (CA-63).
|
|
3
|
+
*
|
|
4
|
+
* ⚠⚠ WHY THIS EXISTS (staging, 2026-10-23). A 402-step `beehaven call canvas-act @build.json` (90,714
|
|
5
|
+
* bytes, the file holding the correct C2 B7) stored ONE text node's middle dot as "��" — the
|
|
6
|
+
* same node, the same character, on every resend — while dozens of identical dots elsewhere in the batch
|
|
7
|
+
* were stored intact. Every IPC reader did `buffer += chunk.toString()`. A local socket hands the reader
|
|
8
|
+
* the stream in pieces (8 KB on macOS, 64 KB on Linux), so any multi-byte character that straddles a
|
|
9
|
+
* piece boundary was decoded as two halves, each half an invalid sequence, each a U+FFFD. Which character
|
|
10
|
+
* lands on a boundary depends only on the bytes before it, so the same payload broke the same character
|
|
11
|
+
* every time. The CLI → daemon direction corrupted what was STORED; the daemon → CLI direction corrupted
|
|
12
|
+
* what a reader was SHOWN (a long scene read could carry U+FFFD the canvas does not have).
|
|
13
|
+
*
|
|
14
|
+
* A `StringDecoder` holds an incomplete trailing sequence until its remaining bytes arrive, so the text is
|
|
15
|
+
* exactly what one decode of the whole stream would give. One decoder per stream (it carries state), and
|
|
16
|
+
* never shared between two sockets.
|
|
17
|
+
*/
|
|
18
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
19
|
+
/** A stateful UTF-8 decoder for ONE byte stream. A string chunk (a socket with an encoding set) passes through. */
|
|
20
|
+
export function utf8Stream() {
|
|
21
|
+
const decoder = new StringDecoder('utf8');
|
|
22
|
+
return (chunk) => (typeof chunk === 'string' ? chunk : decoder.write(chunk));
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -32,7 +32,7 @@ For a new composition or substantial redesign, use the workflow below.
|
|
|
32
32
|
|
|
33
33
|
Keep the requested scope. A landing concept with a logo needs its page layouts and a usable
|
|
34
34
|
logo; add application boards or a full guideline system only when the brief calls for them.
|
|
35
|
-
Read only relevant references. Save large tool responses once and print compact summaries.
|
|
35
|
+
Read only relevant references. Save large tool responses once (`beehaven call … --out file.json`) and print compact summaries.
|
|
36
36
|
|
|
37
37
|
## The studio bar — what "done" means, every time
|
|
38
38
|
|
|
@@ -46,7 +46,8 @@ A composition is done when every requested artboard meets all five conditions:
|
|
|
46
46
|
remains: text colliding or covered, contrast under WCAG (4.5:1 body, 3:1 display),
|
|
47
47
|
type below the artboard's legible floor, missing media, text overflowing its box,
|
|
48
48
|
placeholder copy. Fix it. For an intentional finding, use
|
|
49
|
-
`accept:[{check,ids:[node IDs],reason}]
|
|
49
|
+
`accept:[{check,ids:[node IDs],reason}]`; one rule may name every layer with the same
|
|
50
|
+
deliberate finding. Accepted findings stay visible and return `look`.
|
|
50
51
|
Legacy `ignore` can suppress multiple checks and is not an unconditional pass.
|
|
51
52
|
2. **Every `look` finding was looked at**: type-size and palette sprawl, near-miss
|
|
52
53
|
alignment, copy against the trim, text over imagery. Act on it or say why not.
|
|
@@ -117,8 +118,9 @@ Read `clearly-agent` before your first workspace call. Pin the intended workspac
|
|
|
117
118
|
sign in once. Do not switch a user's explicit workspace to `home`.
|
|
118
119
|
|
|
119
120
|
Your surface: the shell. The `beehaven` examples in this skill are literal commands to
|
|
120
|
-
run; they are not shapes to translate into an MCP canvas-action tool.
|
|
121
|
-
|
|
121
|
+
run; they are not shapes to translate into an MCP canvas-action tool. The Clearly connector
|
|
122
|
+
has its own canvas tools for agents without a shell (batched per kind of step, see
|
|
123
|
+
authoring.md); in a shell the CLI's mixed `canvas-act` batch takes fewer calls.
|
|
122
124
|
|
|
123
125
|
```sh
|
|
124
126
|
beehaven env
|
|
@@ -156,7 +158,7 @@ builds on the last and can be reviewed alone. Build ONE commit per canvas-act ba
|
|
|
156
158
|
`canvas.review` of the artboard and carries
|
|
157
159
|
`"ticket":{"id":"<key>","status":"done","resolution":"<what changed>"}` — it lands only if that
|
|
158
160
|
review is clean or look; `result.ticket` says whether it did, and the ticket logs the canvas
|
|
159
|
-
revision
|
|
161
|
+
revision, the layers the commit changed and a link to its frame. Auto-layout, gradients, shadows, drawing and placeholders:
|
|
160
162
|
see "Technique" in [authoring.md](authoring.md).
|
|
161
163
|
|
|
162
164
|
`composition-board-get {compositionId}` reads the plan back. `design-start` with a project, ticket or
|
|
@@ -279,7 +281,7 @@ a detail view catches collisions, wrapping, cropping and legibility.
|
|
|
279
281
|
The bundled helper saves pixels directly to disk so base64 does not fill your context:
|
|
280
282
|
|
|
281
283
|
```sh
|
|
282
|
-
node <skill-directory>/review.mjs <composition-id> <frame-id-or-unique-name>
|
|
284
|
+
node <skill-directory>/review.mjs <composition-id> <frame-id-or-unique-name> canvas-review/01
|
|
283
285
|
# Open the returned PNG using your image-viewing tool; read the JSON findings.
|
|
284
286
|
```
|
|
285
287
|
|
|
@@ -300,9 +302,15 @@ The helper prints the verdict and every finding. Work until the verdict is `clea
|
|
|
300
302
|
use a real frame ID with `--perceive` for a preview-only check and run the text measurement
|
|
301
303
|
and audit separately. A failed or missing render means visual verification is incomplete.
|
|
302
304
|
|
|
305
|
+
A deliberate finding (say why in the handoff): write the findings you are keeping to a file and
|
|
306
|
+
pass it to the same helper. They stay visible in the report and return `look`.
|
|
307
|
+
|
|
308
|
+
```json
|
|
309
|
+
[{"check":"text-over-image","ids":["<text-id>","<image-id>"],"reason":"Intentional type over this photograph; rendered contrast was inspected."}]
|
|
310
|
+
```
|
|
311
|
+
|
|
303
312
|
```sh
|
|
304
|
-
|
|
305
|
-
beehaven call canvas-act '{"compositionId":"<id>","action":"canvas.review","args":{"ids":["<frame>"],"accept":[{"check":"text-over-image","ids":["<text-id>","<image-id>"],"reason":"Intentional type over this photograph; rendered contrast was inspected."}]}}'
|
|
313
|
+
node <skill-directory>/review.mjs <composition-id> <frame> canvas-review/02 --accept accepted.json
|
|
306
314
|
```
|
|
307
315
|
|
|
308
316
|
Asset storage is a reference-aware pipeline: an SVG may have `svgRef` instead of inline markup.
|
|
@@ -38,15 +38,34 @@ add('canvas.update-nodes', { ids: ['opening-title', 'opening-deck'], patch: { la
|
|
|
38
38
|
add('canvas.auto-layout', { id: 'hero-copy', direction: 'v', gap: 24, sizingH: 'fixed', sizingV: 'hug' });
|
|
39
39
|
add('canvas.auto-layout', { id: '01-opening', direction: 'v', gap: 64, padding: grid.margin, sizingH: 'fixed', sizingV: 'hug' });
|
|
40
40
|
// Continue with the brief's central visual, supporting evidence and footer.
|
|
41
|
-
writeFileSync('
|
|
41
|
+
writeFileSync('canvas-build.json', JSON.stringify({ compositionId, batch }));
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
```sh
|
|
45
45
|
beehaven call composition-create '{"title":"Project — concept","projectId":"<project-id>"}'
|
|
46
46
|
node build.mjs <returned-id>
|
|
47
|
-
beehaven call canvas-act
|
|
47
|
+
beehaven call canvas-act @canvas-build.json --brief
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
`--brief` prints a short receipt instead of the full JSON:
|
|
51
|
+
- step counts by kind;
|
|
52
|
+
- layers added, changed and removed;
|
|
53
|
+
- every failed step with its error;
|
|
54
|
+
- rollbacks and skips;
|
|
55
|
+
- lint to fix and review verdicts.
|
|
56
|
+
|
|
57
|
+
The exit code stays the same. Use it for routine batches instead of piping the JSON through a
|
|
58
|
+
summariser of your own. Drop it when you need a step's full value.
|
|
59
|
+
|
|
60
|
+
Keep builders, payloads and results in your **working directory**:
|
|
61
|
+
- Write files with your file-writing tool, not shell heredocs or `>` redirects into `/tmp`.
|
|
62
|
+
- Run each `beehaven` command on its own rather than chained with `&&`, `;`, `| tee` or `| head`.
|
|
63
|
+
- Save a large result with `beehaven call <action> '…' --out scene.json` (add `--brief` for the
|
|
64
|
+
receipt), not `> file`: the whole JSON goes to the file and only a line reaches the console.
|
|
65
|
+
|
|
66
|
+
Agent hosts that gate shell commands (Claude Code, Codex) refuse heredocs containing JSON and
|
|
67
|
+
compound pipelines outright. One recorded run lost four tool calls that way (MCP2-20).
|
|
68
|
+
|
|
50
69
|
Check `ok`, each `results[].ok`, `count`, `skipped` and `rolledBack`. A failed headless
|
|
51
70
|
batch may roll back its scene writes. Re-read the scene before deciding what to retry;
|
|
52
71
|
do not blindly resend only skipped operations. Mixed live/headless actions may have
|
|
@@ -155,6 +174,7 @@ The same rules the in-app assistant works to (canvas-catalog returns them as `te
|
|
|
155
174
|
- COMPUTED GEOMETRY: shapes from math (radial petals, spirals, grids) are computed and drawn in ONE call: canvas-act { compositionId, script } runs JavaScript with geo.petal/circle/arc/star/polygon/spiral/smooth/rotate/radial/grid/bounds (degrees, 0 = 3 o'clock, clockwise) and a read-only scene list, then applies the { action, args } steps it returns. Never copy numbers from one tool result into another call. A new top-level artboard goes beside or below what scene already holds (or leave out both x and y), never at 0,0 over an existing one.
|
|
156
175
|
- SHEETS: several concepts or variants go on canvas.sheet { name, title, subtitle, cards or count, cols, cardW, cardH }: a live wrap grid of card frames that own their captions, so rows never overlap. Draw into a card with parentId = its id.
|
|
157
176
|
- MEASURE: answer spacing, overlap and alignment questions with canvas.measure { frame } or { ids } and quote its numbers, never from memory or a preview.
|
|
177
|
+
- REFERENCES: canvas.link {id:<exact frame/item id>} returns a stable URL and Markdown for tickets, documents, comments and inbox refs. Use the returned link instead of a screenshot or a guessed canvas URL. It preserves the parent canvas permissions; rename/move retains the layer id.
|
|
158
178
|
- PLACEHOLDERS: until real imagery exists, draw a native placeholder at the final size in a muted brand tone, never a flat accent block: a rect with the slot radius plus a simple motif (an ellipse sun, a vector.create mountain), grouped with canvas.group and named "<section> image placeholder", with any label in its own text node.
|
|
159
179
|
- SECTIONS: one frame per page section, named exactly as its ticket says, inside the page artboard. Build a section in one canvas-act batch that ends with canvas.review of the artboard and carries ticket: { id: "<its key>", status: "done", resolution: "<what changed>" }. The ticket moves only when that review is clean or look; result.ticket says whether it did. Then start the next section.
|
|
160
180
|
|
|
@@ -215,15 +235,13 @@ the backing shows through as dark wedges at the corners.
|
|
|
215
235
|
|
|
216
236
|
### Images from MCP
|
|
217
237
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
`
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
existing image's `src`, `fit` or geometry, but cannot create a node. For a local file,
|
|
226
|
-
use the CLI upload path first; an MCP-only client needs a URL it can already access.
|
|
238
|
+
A connector-only agent places an image already reachable by an absolute URL (or a
|
|
239
|
+
`data:` URI) as an `image` node in `clearly_canvas_add_nodes`:
|
|
240
|
+
`{id,type:"image",name,src,x,y,w,h,fit:"contain",parentId?}`. Use PNG or JPEG: the headless
|
|
241
|
+
review cannot draw WebP yet and does not say so (CAN2-188), so a WebP image looks blank in the
|
|
242
|
+
review PNG while the editor shows it. `clearly_canvas_update_nodes`
|
|
243
|
+
changes an existing image's `src`, `fit` or geometry. For a local file in a shell, use the
|
|
244
|
+
CLI upload path first.
|
|
227
245
|
|
|
228
246
|
Current headless rendering can resolve images, including workspace media. A blank image
|
|
229
247
|
can indicate fetch, decoding or budget failure; check its source and inspect a live view.
|
|
@@ -236,8 +254,8 @@ design. Verify the returned artifact in a browser at realistic viewport sizes.
|
|
|
236
254
|
## Review and scoped perception
|
|
237
255
|
|
|
238
256
|
```sh
|
|
239
|
-
beehaven call canvas-act '{"compositionId":"<id>","action":"canvas.review","args":{"ids":["<frame-id>"]}}'
|
|
240
|
-
beehaven call canvas-perceive '{"compositionId":"<id>","includePixels":true,"fitNodeIds":["<frame-id>"],"pixelScale":1}' >
|
|
257
|
+
beehaven call canvas-act '{"compositionId":"<id>","action":"canvas.review","args":{"ids":["<frame-id>"]}}' --brief
|
|
258
|
+
beehaven call canvas-perceive '{"compositionId":"<id>","includePixels":true,"fitNodeIds":["<frame-id>"],"pixelScale":1}' > perceive.json
|
|
241
259
|
```
|
|
242
260
|
|
|
243
261
|
Use the bundled `review.mjs` to decode the PNG and keep base64 out of the conversation.
|
|
@@ -268,7 +286,12 @@ MCP's artifact tools are split by operation:
|
|
|
268
286
|
node array — pass `expectedRev` from the read.
|
|
269
287
|
- `clearly_edit` patches existing nodes with `changes:{ids,patch}`.
|
|
270
288
|
|
|
271
|
-
|
|
272
|
-
`
|
|
273
|
-
|
|
274
|
-
|
|
289
|
+
The connector also has native canvas tools for agents without a shell, batched per kind of
|
|
290
|
+
step rather than mixed (MCP2-22): `clearly_canvas_add_nodes` builds a section per call (up to
|
|
291
|
+
300 frames, shapes, text, pen paths and groups of its own new layers),
|
|
292
|
+
`clearly_canvas_update_nodes {edits:[{ids,patch}]}` revises many layers at once,
|
|
293
|
+
`clearly_canvas_measure_text {texts:[…]}` measures several lines, and
|
|
294
|
+
`clearly_canvas_review` returns the saved artboard as an MCP image to inspect. There is no
|
|
295
|
+
`clearly_canvas_act`: a mixed batch is the CLI's `canvas-act`. In a shell, use the CLI; it
|
|
296
|
+
is fewer calls for the same scene. Do not claim the design was visually checked from scene
|
|
297
|
+
JSON alone.
|
|
@@ -123,14 +123,35 @@ export async function saveReviewPixels(report, prefix, fetchImage = fetch) {
|
|
|
123
123
|
return saveReview({ ...report, preview: { ...report.preview, dataUrl: `data:image/png;base64,${bytes.toString('base64')}` } }, prefix);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* `--accept <findings.json>`: findings you have looked at and are keeping, each
|
|
128
|
+
* `{ check, ids: [node IDs], reason }`. They stay visible in the report and return `look`.
|
|
129
|
+
* Without this, a deliberate overlap (steam rising out of a bowl) could only be accepted by
|
|
130
|
+
* hand-writing the canvas.review call — agents did exactly that (MCP2-20).
|
|
131
|
+
*/
|
|
132
|
+
export function takeAccept(argv) {
|
|
133
|
+
const i = argv.indexOf('--accept');
|
|
134
|
+
if (i < 0) return { argv, accept: undefined };
|
|
135
|
+
const file = argv[i + 1];
|
|
136
|
+
if (!file || file.startsWith('--')) throw new Error('--accept needs a JSON file: [{"check":"text-over-image","ids":["…"],"reason":"…"}]');
|
|
137
|
+
const accept = JSON.parse(readFileSync(file, 'utf8'));
|
|
138
|
+
if (!Array.isArray(accept) || !accept.every(a => a && typeof a.check === 'string' && Array.isArray(a.ids) && a.ids.length && typeof a.reason === 'string' && a.reason.trim())) {
|
|
139
|
+
throw new Error('--accept file must be an array of { check, ids: [node IDs], reason } with a non-empty reason.');
|
|
140
|
+
}
|
|
141
|
+
return { argv: argv.filter((_, j) => j !== i && j !== i + 1), accept };
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
export async function main(argv = process.argv.slice(2)) {
|
|
145
|
+
const taken = takeAccept(argv);
|
|
146
|
+
argv = taken.argv;
|
|
147
|
+
const accept = taken.accept ? { accept: taken.accept } : {};
|
|
127
148
|
if (argv.includes('--many')) {
|
|
128
149
|
const args = argv.filter(a => a !== '--many');
|
|
129
|
-
if (args.length !== 3) throw new Error('Usage: node review.mjs --many <compositionId> <artboard-ids.json> <output-directory>');
|
|
150
|
+
if (args.length !== 3) throw new Error('Usage: node review.mjs --many <compositionId> <artboard-ids.json> <output-directory> [--accept findings.json]');
|
|
130
151
|
const [compositionId, manifest, directory] = args;
|
|
131
152
|
const ids = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
132
153
|
if (!Array.isArray(ids) || !ids.length) throw new Error('The manifest must be a non-empty artboard ID/name array.');
|
|
133
|
-
const response = rpc('canvas-act', { compositionId, action: 'canvas.review-many', args: { ids, preview: 'files' } });
|
|
154
|
+
const response = rpc('canvas-act', { compositionId, action: 'canvas.review-many', args: { ids, preview: 'files', ...accept } });
|
|
134
155
|
const reports = response.results?.[0]?.value?.reviews;
|
|
135
156
|
if (!Array.isArray(reports) || reports.length !== ids.length) throw new Error('Incomplete review-many result; do not claim all artboards passed.');
|
|
136
157
|
const saved = [];
|
|
@@ -140,7 +161,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
140
161
|
}
|
|
141
162
|
const legacy = argv.includes('--perceive');
|
|
142
163
|
const args = argv.filter((a) => a !== '--perceive');
|
|
143
|
-
if (args.length !== 3) throw new Error('Usage: node review.mjs <compositionId> <frame-id-or-name> <output-prefix> [--perceive]\n--perceive: older workers, preview only; pass a frame ID.');
|
|
164
|
+
if (args.length !== 3) throw new Error('Usage: node review.mjs <compositionId> <frame-id-or-name> <output-prefix> [--accept findings.json] [--perceive]\n--perceive: older workers, preview only; pass a frame ID.');
|
|
144
165
|
const [compositionId, id, prefix] = args;
|
|
145
166
|
let report;
|
|
146
167
|
if (legacy) {
|
|
@@ -150,7 +171,7 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
150
171
|
report = { preview: response.room?.pixels, visualReviewRequired: true,
|
|
151
172
|
limitations: ['Preview-only compatibility mode. Text metrics and scoped audit were not run.'] };
|
|
152
173
|
} else {
|
|
153
|
-
const response = rpc('canvas-act', { compositionId, action: 'canvas.review', args: { ids: [id], pixelScale: 1 } });
|
|
174
|
+
const response = rpc('canvas-act', { compositionId, action: 'canvas.review', args: { ids: [id], pixelScale: 1, ...accept } });
|
|
154
175
|
report = response.results?.[0]?.value;
|
|
155
176
|
if (!report?.scope) throw new Error('canvas.review unavailable. Check canvas-catalog; use --perceive for an explicitly preview-only check on an older worker.');
|
|
156
177
|
}
|