@just-every/design 0.1.31 → 0.1.33
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/README.md +10 -45
- package/dist/cli.js +113 -653
- package/dist/cli.js.map +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +36 -53
- package/dist/install.js.map +1 -1
- package/dist/instructions.d.ts +2 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +69 -0
- package/dist/instructions.js.map +1 -0
- package/dist/progress.d.ts +9 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +88 -0
- package/dist/progress.js.map +1 -0
- package/dist/response-guidance.d.ts +3 -0
- package/dist/response-guidance.d.ts.map +1 -1
- package/dist/response-guidance.js +428 -92
- package/dist/response-guidance.js.map +1 -1
- package/dist/screenshot.d.ts +17 -0
- package/dist/screenshot.d.ts.map +1 -0
- package/dist/screenshot.js +222 -0
- package/dist/screenshot.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +130 -465
- package/dist/server.js.map +1 -1
- package/dist/tool-logic.d.ts +1 -0
- package/dist/tool-logic.d.ts.map +1 -1
- package/dist/tool-logic.js +19 -96
- package/dist/tool-logic.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,21 +5,22 @@
|
|
|
5
5
|
import { createInterface } from 'node:readline/promises';
|
|
6
6
|
import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
7
7
|
import { existsSync, statSync } from 'node:fs';
|
|
8
|
-
import {
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
12
12
|
import { runApprovalLinkLoginFlow } from './auth.js';
|
|
13
13
|
import { refreshLoginSession } from './auth.js';
|
|
14
14
|
import { pickAccountSlug } from './account-picker.js';
|
|
15
|
-
import { parseArtifactsDownloadPositional, parseArtifactsListPositional } from './artifacts-cli.js';
|
|
16
15
|
import { clearConfig, readConfig, resolveConfigPath, writeConfig } from './config.js';
|
|
17
16
|
import { DesignAppClient } from './design-client.js';
|
|
18
17
|
import { parseJsonOrThrow } from './json.js';
|
|
19
18
|
import { checkIntegrations, detectClients, detectDefaultClients, ensureLocalLauncher, resolveLocalInstallLayout, runInstall, runRemove, } from './install.js';
|
|
20
19
|
import { startMcpServer } from './server.js';
|
|
21
20
|
import { buildCreateRunRequest, NOT_AUTHENTICATED_HELP, watchRun } from './tool-logic.js';
|
|
22
|
-
import { wrapToolResponse } from './response-guidance.js';
|
|
21
|
+
import { formatSyncCompletionMarkdown, formatSyncStartMarkdown, formatToolResponseMarkdown, wrapToolResponse, } from './response-guidance.js';
|
|
22
|
+
import { createSyncProgressTracker, getProgressPct } from './progress.js';
|
|
23
|
+
import { runScreenshotCommand, which } from './screenshot.js';
|
|
23
24
|
function parseArgs(argv) {
|
|
24
25
|
const flags = {};
|
|
25
26
|
const command = [];
|
|
@@ -60,25 +61,9 @@ function printHelp() {
|
|
|
60
61
|
console.error(' (default) Start the MCP server (stdio)');
|
|
61
62
|
console.error(' install Interactive setup (auth + client config)');
|
|
62
63
|
console.error(' remove Remove Every Design from detected clients');
|
|
63
|
-
console.error(' create Create a design
|
|
64
|
-
console.error('
|
|
65
|
-
console.error('
|
|
66
|
-
console.error(' set-target Select a target draft for a run (CLI helper)');
|
|
67
|
-
console.error(' clear-target Clear the explicit target for a run (CLI helper)');
|
|
68
|
-
console.error(' extract-assets Trigger asset extraction for a run (CLI helper)');
|
|
69
|
-
console.error(' generate-html Trigger HTML generation for a run (CLI helper)');
|
|
70
|
-
console.error(' screenshot <url> Screenshot a URL (CLI helper)');
|
|
71
|
-
console.error(' iterate Iterate on a run using screenshot feedback (CLI helper)');
|
|
72
|
-
console.error(' critique Alias for `iterate`');
|
|
73
|
-
console.error(' watch Watch a design run (CLI helper)');
|
|
74
|
-
console.error(' share status Get run share link status (CLI helper)');
|
|
75
|
-
console.error(' share enable Enable (or rotate) a share link (CLI helper)');
|
|
76
|
-
console.error(' share revoke Revoke the active share link (CLI helper)');
|
|
77
|
-
console.error(' get Fetch a run by id (CLI helper)');
|
|
78
|
-
console.error(' list List recent runs (CLI helper)');
|
|
79
|
-
console.error(' events Fetch run events (CLI helper)');
|
|
80
|
-
console.error(' artifacts list <runId> List run artifacts (CLI helper)');
|
|
81
|
-
console.error(' artifacts download <runId>/<artifactId> Download an artifact (CLI helper)');
|
|
64
|
+
console.error(' create Create a new design (CLI helper)');
|
|
65
|
+
console.error(' sync Wait for completion, extract assets, and sync artifacts (CLI helper)');
|
|
66
|
+
console.error(' check Screenshot and compare implementation against a run (CLI helper)');
|
|
82
67
|
console.error(' auth login Login via approval-link flow and save config');
|
|
83
68
|
console.error(' auth status Check current auth against /api/me');
|
|
84
69
|
console.error(' auth logout Delete the saved config file');
|
|
@@ -90,10 +75,6 @@ function printHelp() {
|
|
|
90
75
|
console.error(` --config <path> Config path (default: ${configPath})`);
|
|
91
76
|
console.error(' --no-open Do not open the approval URL in a browser (auth login)');
|
|
92
77
|
console.error(' --json <string> JSON args payload for CLI helpers (or pipe JSON via stdin)');
|
|
93
|
-
console.error(' --out <path> Output path (screenshot)');
|
|
94
|
-
console.error(' --width <px> Viewport width (screenshot, default 1696)');
|
|
95
|
-
console.error(' --height <px> Viewport height (screenshot, default 2528)');
|
|
96
|
-
console.error(' --wait-ms <ms> Extra wait budget before screenshot (screenshot, default 4000)');
|
|
97
78
|
console.error(' --client <name[,name...]> Install target(s): code,codex,claude-desktop,claude-code,cursor,gemini,qwen,all,auto');
|
|
98
79
|
console.error(' --name <serverName> MCP server name key (default: every-design)');
|
|
99
80
|
console.error(' --launcher <npx|local> How clients launch the MCP server (default for install: local)');
|
|
@@ -119,217 +100,6 @@ async function resolvePackageVersion() {
|
|
|
119
100
|
return '0.0.0';
|
|
120
101
|
}
|
|
121
102
|
}
|
|
122
|
-
function which(bin) {
|
|
123
|
-
try {
|
|
124
|
-
const tool = process.platform === 'win32' ? 'where' : 'which';
|
|
125
|
-
const res = spawnSync(tool, [bin], { encoding: 'utf8' });
|
|
126
|
-
if (res.status !== 0)
|
|
127
|
-
return null;
|
|
128
|
-
const first = String(res.stdout || '').trim().split(/\r?\n/)[0];
|
|
129
|
-
return first ? first.trim() : null;
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
return null;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
function resolveHeadlessBrowserBinary() {
|
|
136
|
-
const explicit = process.env.CHROME_PATH?.trim();
|
|
137
|
-
if (explicit && existsSync(explicit))
|
|
138
|
-
return explicit;
|
|
139
|
-
const candidates = [
|
|
140
|
-
'google-chrome',
|
|
141
|
-
'google-chrome-stable',
|
|
142
|
-
'chromium',
|
|
143
|
-
'chromium-browser',
|
|
144
|
-
'chrome',
|
|
145
|
-
'msedge',
|
|
146
|
-
'microsoft-edge',
|
|
147
|
-
];
|
|
148
|
-
for (const c of candidates) {
|
|
149
|
-
const found = which(c);
|
|
150
|
-
if (found)
|
|
151
|
-
return found;
|
|
152
|
-
}
|
|
153
|
-
if (process.platform === 'darwin') {
|
|
154
|
-
const macCandidates = [
|
|
155
|
-
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
156
|
-
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
157
|
-
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
158
|
-
];
|
|
159
|
-
for (const c of macCandidates) {
|
|
160
|
-
if (existsSync(c))
|
|
161
|
-
return c;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
async function runScreenshotCommand(args) {
|
|
167
|
-
const url = args.url.trim();
|
|
168
|
-
if (!url)
|
|
169
|
-
throw new Error('Missing URL. Usage: every-design screenshot http://127.0.0.1:3000/path');
|
|
170
|
-
const outPath = path.resolve(args.outPath);
|
|
171
|
-
await mkdir(path.dirname(outPath), { recursive: true });
|
|
172
|
-
const browser = resolveHeadlessBrowserBinary();
|
|
173
|
-
if (!browser) {
|
|
174
|
-
throw new Error('No headless Chrome/Chromium/Edge binary found. Install a Chromium-based browser or set CHROME_PATH to an executable path.');
|
|
175
|
-
}
|
|
176
|
-
const tmpProfile = await (async () => {
|
|
177
|
-
const base = path.join(os.tmpdir(), 'every-design-screenshot-');
|
|
178
|
-
const { mkdtemp } = await import('node:fs/promises');
|
|
179
|
-
return mkdtemp(base);
|
|
180
|
-
})();
|
|
181
|
-
try {
|
|
182
|
-
const waitMs = Number.isFinite(args.waitMs) ? Math.max(0, Math.min(120_000, Math.round(args.waitMs))) : 4000;
|
|
183
|
-
const timeoutMs = Math.max(15_000, Math.min(180_000, waitMs + 30_000));
|
|
184
|
-
const argsList = [
|
|
185
|
-
'--headless=new',
|
|
186
|
-
'--disable-gpu',
|
|
187
|
-
'--hide-scrollbars',
|
|
188
|
-
'--mute-audio',
|
|
189
|
-
'--no-first-run',
|
|
190
|
-
'--no-default-browser-check',
|
|
191
|
-
'--disable-background-networking',
|
|
192
|
-
'--disable-sync',
|
|
193
|
-
'--disable-extensions',
|
|
194
|
-
'--metrics-recording-only',
|
|
195
|
-
'--force-device-scale-factor=1',
|
|
196
|
-
`--window-size=${args.width},${args.height}`,
|
|
197
|
-
`--user-data-dir=${tmpProfile}`,
|
|
198
|
-
`--virtual-time-budget=${waitMs || 0}`,
|
|
199
|
-
`--screenshot=${outPath}`,
|
|
200
|
-
url,
|
|
201
|
-
];
|
|
202
|
-
const screenshotOk = () => {
|
|
203
|
-
try {
|
|
204
|
-
return statSync(outPath).size > 0;
|
|
205
|
-
}
|
|
206
|
-
catch {
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
211
|
-
const waitForScreenshotStable = async (signal) => {
|
|
212
|
-
const start = Date.now();
|
|
213
|
-
// Chrome writes the screenshot file as a single operation most of the time, but we've seen
|
|
214
|
-
// occasional flakiness where the file exists briefly at size=0. Wait for a stable >0 size.
|
|
215
|
-
let lastSize = -1;
|
|
216
|
-
let stableTicks = 0;
|
|
217
|
-
while (Date.now() - start < timeoutMs) {
|
|
218
|
-
if (signal.aborted)
|
|
219
|
-
return false;
|
|
220
|
-
let size = -1;
|
|
221
|
-
try {
|
|
222
|
-
size = statSync(outPath).size;
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
size = -1;
|
|
226
|
-
}
|
|
227
|
-
if (size > 0) {
|
|
228
|
-
if (size === lastSize)
|
|
229
|
-
stableTicks += 1;
|
|
230
|
-
else
|
|
231
|
-
stableTicks = 0;
|
|
232
|
-
lastSize = size;
|
|
233
|
-
if (stableTicks >= 2)
|
|
234
|
-
return true;
|
|
235
|
-
}
|
|
236
|
-
await sleep(100);
|
|
237
|
-
}
|
|
238
|
-
if (signal.aborted)
|
|
239
|
-
return false;
|
|
240
|
-
throw new Error('Timed out waiting for screenshot file to be produced.');
|
|
241
|
-
};
|
|
242
|
-
// Use an isolated process group on Unix so we can reliably kill Chrome + its helpers.
|
|
243
|
-
const child = spawn(browser, argsList, {
|
|
244
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
245
|
-
detached: process.platform !== 'win32',
|
|
246
|
-
});
|
|
247
|
-
let stdout = '';
|
|
248
|
-
let stderr = '';
|
|
249
|
-
child.stdout?.setEncoding('utf8');
|
|
250
|
-
child.stderr?.setEncoding('utf8');
|
|
251
|
-
child.stdout?.on('data', (chunk) => {
|
|
252
|
-
stdout += String(chunk);
|
|
253
|
-
});
|
|
254
|
-
child.stderr?.on('data', (chunk) => {
|
|
255
|
-
stderr += String(chunk);
|
|
256
|
-
});
|
|
257
|
-
const killChild = (signal) => {
|
|
258
|
-
if (!child.pid)
|
|
259
|
-
return;
|
|
260
|
-
try {
|
|
261
|
-
if (process.platform !== 'win32') {
|
|
262
|
-
// Kill the whole process group (negative PID) when detached.
|
|
263
|
-
process.kill(-child.pid, signal);
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
process.kill(child.pid, signal);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
catch {
|
|
270
|
-
// ignore
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
const exitPromise = new Promise((resolve, reject) => {
|
|
274
|
-
child.once('error', reject);
|
|
275
|
-
child.once('exit', (code, signal) => resolve({ code, signal }));
|
|
276
|
-
});
|
|
277
|
-
const abort = new AbortController();
|
|
278
|
-
const screenshotPromise = waitForScreenshotStable(abort.signal)
|
|
279
|
-
.then((ok) => ({ type: 'screenshot', ok }))
|
|
280
|
-
.catch((error) => ({ type: 'screenshot_error', error }));
|
|
281
|
-
const exitPromiseHandled = exitPromise
|
|
282
|
-
.then((r) => ({ type: 'exit', ...r }))
|
|
283
|
-
.catch((error) => ({ type: 'exit_error', error }));
|
|
284
|
-
// Hard-kill after the timeout budget; Chrome can hang even after producing a screenshot.
|
|
285
|
-
const timeout = setTimeout(() => {
|
|
286
|
-
killChild('SIGKILL');
|
|
287
|
-
}, timeoutMs);
|
|
288
|
-
try {
|
|
289
|
-
const first = await Promise.race([screenshotPromise, exitPromiseHandled]);
|
|
290
|
-
abort.abort();
|
|
291
|
-
if (first.type === 'exit_error') {
|
|
292
|
-
const message = first.error instanceof Error ? first.error.message : String(first.error);
|
|
293
|
-
throw new Error(`Screenshot failed (spawn error): ${message}`);
|
|
294
|
-
}
|
|
295
|
-
if (first.type === 'screenshot_error') {
|
|
296
|
-
// Still allow a best-effort success if Chrome produced a screenshot before hanging.
|
|
297
|
-
killChild('SIGKILL');
|
|
298
|
-
await Promise.race([exitPromise.catch(() => undefined), sleep(1_500)]);
|
|
299
|
-
}
|
|
300
|
-
// If the screenshot is ready but Chrome is still running, kill it immediately instead of
|
|
301
|
-
// waiting for the timeout (which is often ~waitMs+30s).
|
|
302
|
-
if (first.type === 'screenshot' && first.ok) {
|
|
303
|
-
killChild('SIGKILL');
|
|
304
|
-
// Don't await indefinitely; cleanup is best-effort.
|
|
305
|
-
await Promise.race([exitPromise.catch(() => undefined), sleep(1_500)]);
|
|
306
|
-
}
|
|
307
|
-
const ok = screenshotOk();
|
|
308
|
-
if (!ok) {
|
|
309
|
-
// Chrome may exit without producing a screenshot; include output to help debug.
|
|
310
|
-
const detail = (stderr || stdout).trim();
|
|
311
|
-
const exit = first.type === 'exit' ? first : await exitPromise.catch(() => ({ code: null, signal: null }));
|
|
312
|
-
throw new Error(`Screenshot failed (exit ${exit.code ?? 'unknown'}, signal ${exit.signal ?? 'none'}): ${detail || 'no output'}`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
finally {
|
|
316
|
-
clearTimeout(timeout);
|
|
317
|
-
// Ensure Chrome isn't left running in the background.
|
|
318
|
-
killChild('SIGKILL');
|
|
319
|
-
}
|
|
320
|
-
return {
|
|
321
|
-
screenshotPath: outPath,
|
|
322
|
-
width: args.width,
|
|
323
|
-
height: args.height,
|
|
324
|
-
engine: 'chrome-headless',
|
|
325
|
-
binary: browser,
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
finally {
|
|
329
|
-
const { rm } = await import('node:fs/promises');
|
|
330
|
-
await rm(tmpProfile, { recursive: true, force: true }).catch(() => undefined);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
103
|
function isTtyInteractive() {
|
|
334
104
|
return Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
335
105
|
}
|
|
@@ -381,76 +151,6 @@ async function readJsonArgs(remainingArgs, flags) {
|
|
|
381
151
|
}
|
|
382
152
|
return parsed;
|
|
383
153
|
}
|
|
384
|
-
async function readJsonArgsOptional(remainingArgs, flags) {
|
|
385
|
-
const jsonFlag = resolveFlagString(flags, 'json');
|
|
386
|
-
const hasInline = remainingArgs.some((t) => t.trim().length > 0);
|
|
387
|
-
const hasStdin = !process.stdin.isTTY;
|
|
388
|
-
if (!jsonFlag && !hasInline && !hasStdin)
|
|
389
|
-
return {};
|
|
390
|
-
return readJsonArgs(remainingArgs, flags);
|
|
391
|
-
}
|
|
392
|
-
function pretty(value) {
|
|
393
|
-
return JSON.stringify(value, null, 2);
|
|
394
|
-
}
|
|
395
|
-
function createWatchProgressDisplay(stream, enabled) {
|
|
396
|
-
let lastRendered = '';
|
|
397
|
-
let lastSnapshot = null;
|
|
398
|
-
let spinner = 0;
|
|
399
|
-
function clearLine() {
|
|
400
|
-
stream.write('\r\x1b[2K');
|
|
401
|
-
}
|
|
402
|
-
function render(snapshot) {
|
|
403
|
-
if (!enabled)
|
|
404
|
-
return;
|
|
405
|
-
lastSnapshot = snapshot;
|
|
406
|
-
spinner = (spinner + 1) % 4;
|
|
407
|
-
const stage = snapshot.stage ? String(snapshot.stage) : '';
|
|
408
|
-
const status = snapshot.status ? String(snapshot.status) : '';
|
|
409
|
-
const progress = typeof snapshot.progress === 'number' ? snapshot.progress : null;
|
|
410
|
-
const pct = typeof progress === 'number' ? Math.max(0, Math.min(100, Math.round(progress * 100))) : null;
|
|
411
|
-
const barWidth = 24;
|
|
412
|
-
const filled = pct === null ? 0 : Math.round((pct / 100) * barWidth);
|
|
413
|
-
const bar = `[${'#'.repeat(Math.max(0, Math.min(barWidth, filled)))}${'-'.repeat(Math.max(0, barWidth - filled))}]`;
|
|
414
|
-
const spinChar = ['|', '/', '-', '\\'][spinner] ?? '|';
|
|
415
|
-
const parts = [];
|
|
416
|
-
if (pct === null) {
|
|
417
|
-
parts.push(`${spinChar} ${bar}`);
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
parts.push(`${bar} ${String(pct).padStart(3, ' ')}%`);
|
|
421
|
-
}
|
|
422
|
-
if (stage)
|
|
423
|
-
parts.push(`stage=${stage}`);
|
|
424
|
-
if (status)
|
|
425
|
-
parts.push(`status=${status}`);
|
|
426
|
-
const line = parts.join(' ');
|
|
427
|
-
if (line === lastRendered)
|
|
428
|
-
return;
|
|
429
|
-
lastRendered = line;
|
|
430
|
-
clearLine();
|
|
431
|
-
stream.write(line);
|
|
432
|
-
}
|
|
433
|
-
function log(line) {
|
|
434
|
-
if (!enabled) {
|
|
435
|
-
stream.write(`${line}\n`);
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
clearLine();
|
|
439
|
-
stream.write(`${line}\n`);
|
|
440
|
-
if (lastSnapshot) {
|
|
441
|
-
lastRendered = '';
|
|
442
|
-
render(lastSnapshot);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
function finish() {
|
|
446
|
-
if (!enabled)
|
|
447
|
-
return;
|
|
448
|
-
clearLine();
|
|
449
|
-
lastRendered = '';
|
|
450
|
-
lastSnapshot = null;
|
|
451
|
-
}
|
|
452
|
-
return { render, log, finish, enabled };
|
|
453
|
-
}
|
|
454
154
|
async function promptYesNo(question, defaultYes = false) {
|
|
455
155
|
if (!isTtyInteractive())
|
|
456
156
|
return defaultYes;
|
|
@@ -923,32 +623,6 @@ async function main() {
|
|
|
923
623
|
const loginOrigin = resolveLoginOrigin(flags);
|
|
924
624
|
const verb = command[0] ?? '';
|
|
925
625
|
const sub = command[1] ?? '';
|
|
926
|
-
if (verb === 'screenshot') {
|
|
927
|
-
const url = String(command[1] ?? '').trim();
|
|
928
|
-
const width = Number.parseInt(String(resolveFlagString(flags, 'width') ?? '1696'), 10);
|
|
929
|
-
const height = Number.parseInt(String(resolveFlagString(flags, 'height') ?? '2528'), 10);
|
|
930
|
-
const waitMs = Number.parseInt(String(resolveFlagString(flags, 'wait-ms') ?? '4000'), 10);
|
|
931
|
-
const out = resolveFlagString(flags, 'out')?.trim();
|
|
932
|
-
const outPath = out
|
|
933
|
-
? out
|
|
934
|
-
: path.join(process.cwd(), `every-design-screenshot-${new Date().toISOString().replace(/[:.]/g, '-')}.png`);
|
|
935
|
-
try {
|
|
936
|
-
const result = await runScreenshotCommand({
|
|
937
|
-
url,
|
|
938
|
-
outPath,
|
|
939
|
-
width: Number.isFinite(width) && width > 0 ? width : 1696,
|
|
940
|
-
height: Number.isFinite(height) && height > 0 ? height : 2528,
|
|
941
|
-
waitMs: Number.isFinite(waitMs) ? waitMs : 4000,
|
|
942
|
-
});
|
|
943
|
-
console.log(pretty(result));
|
|
944
|
-
process.exit(0);
|
|
945
|
-
}
|
|
946
|
-
catch (error) {
|
|
947
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
948
|
-
console.error(message);
|
|
949
|
-
process.exit(1);
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
626
|
if (verb === 'install') {
|
|
953
627
|
const yes = Boolean(flags.yes);
|
|
954
628
|
const dryRun = Boolean(flags['dry-run']);
|
|
@@ -1255,22 +929,8 @@ async function main() {
|
|
|
1255
929
|
}
|
|
1256
930
|
process.exit(0);
|
|
1257
931
|
}
|
|
1258
|
-
// CLI helpers (non-MCP).
|
|
1259
|
-
if (verb === 'create'
|
|
1260
|
-
|| verb === 'upload-target'
|
|
1261
|
-
|| verb === 'refine-draft'
|
|
1262
|
-
|| verb === 'set-target'
|
|
1263
|
-
|| verb === 'clear-target'
|
|
1264
|
-
|| verb === 'extract-assets'
|
|
1265
|
-
|| verb === 'generate-html'
|
|
1266
|
-
|| verb === 'iterate'
|
|
1267
|
-
|| verb === 'critique'
|
|
1268
|
-
|| verb === 'watch'
|
|
1269
|
-
|| verb === 'share'
|
|
1270
|
-
|| verb === 'get'
|
|
1271
|
-
|| verb === 'list'
|
|
1272
|
-
|| verb === 'events'
|
|
1273
|
-
|| verb === 'artifacts') {
|
|
932
|
+
// CLI helpers (non-MCP). CLIs must follow: create -> sync -> check.
|
|
933
|
+
if (verb === 'create' || verb === 'sync' || verb === 'check') {
|
|
1274
934
|
const cfg = await loadMergedConfig(configPath, { designOrigin, loginOrigin });
|
|
1275
935
|
const sessionToken = (process.env.DESIGN_MCP_SESSION_TOKEN ?? cfg.sessionToken ?? '').trim();
|
|
1276
936
|
if (!sessionToken) {
|
|
@@ -1288,139 +948,116 @@ async function main() {
|
|
|
1288
948
|
const input = await readJsonArgs(command.slice(1), flags);
|
|
1289
949
|
const { prompt, config, parentRunId } = await buildCreateRunRequest(client, input);
|
|
1290
950
|
const created = await client.createRun({ prompt, config, ...(parentRunId ? { parentRunId } : {}) });
|
|
1291
|
-
console.log(
|
|
951
|
+
console.log(formatToolResponseMarkdown(wrap('design.create', created)));
|
|
1292
952
|
process.exit(0);
|
|
1293
953
|
}
|
|
1294
|
-
if (verb === '
|
|
954
|
+
if (verb === 'sync') {
|
|
1295
955
|
const input = await readJsonArgs(command.slice(1), flags);
|
|
1296
956
|
const runId = String(input.runId ?? '').trim();
|
|
1297
957
|
if (!runId)
|
|
1298
958
|
throw new Error('Missing required argument: runId');
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
if (!runId)
|
|
1313
|
-
throw new Error('Missing required argument: runId');
|
|
1314
|
-
if (!artifactId)
|
|
1315
|
-
throw new Error('Missing required argument: artifactId');
|
|
1316
|
-
if (!prompt)
|
|
1317
|
-
throw new Error('Missing required argument: prompt');
|
|
1318
|
-
const res = await client.refineDraft(runId, artifactId, prompt);
|
|
1319
|
-
console.log(pretty(wrap('design.refineDraft', res, { runId, artifactId })));
|
|
1320
|
-
process.exit(0);
|
|
1321
|
-
}
|
|
1322
|
-
if (verb === 'set-target') {
|
|
1323
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1324
|
-
const runId = String(input.runId ?? '').trim();
|
|
1325
|
-
const artifactId = String(input.artifactId ?? '').trim();
|
|
1326
|
-
if (!runId)
|
|
1327
|
-
throw new Error('Missing required argument: runId');
|
|
1328
|
-
if (!artifactId)
|
|
1329
|
-
throw new Error('Missing required argument: artifactId');
|
|
1330
|
-
const res = await client.setRunTarget(runId, artifactId);
|
|
1331
|
-
console.log(pretty(wrap('design.setTarget', res, { runId, artifactId })));
|
|
1332
|
-
process.exit(0);
|
|
1333
|
-
}
|
|
1334
|
-
if (verb === 'clear-target') {
|
|
1335
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1336
|
-
const runId = String(input.runId ?? '').trim();
|
|
1337
|
-
if (!runId)
|
|
1338
|
-
throw new Error('Missing required argument: runId');
|
|
1339
|
-
const res = await client.clearRunTarget(runId);
|
|
1340
|
-
console.log(pretty(wrap('design.clearTarget', res, { runId })));
|
|
1341
|
-
process.exit(0);
|
|
1342
|
-
}
|
|
1343
|
-
if (verb === 'extract-assets') {
|
|
1344
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1345
|
-
const runId = String(input.runId ?? '').trim();
|
|
1346
|
-
if (!runId)
|
|
1347
|
-
throw new Error('Missing required argument: runId');
|
|
1348
|
-
const targetArtifactId = typeof input.targetArtifactId === 'string' ? input.targetArtifactId.trim() : '';
|
|
1349
|
-
const rawKind = typeof input.designKind === 'string' ? input.designKind.trim().toLowerCase() : '';
|
|
1350
|
-
const designKind = rawKind === 'interface' || rawKind === 'html' ? 'interface' : undefined;
|
|
1351
|
-
if (rawKind && !designKind) {
|
|
1352
|
-
throw new Error('Invalid designKind: expected "interface"');
|
|
959
|
+
const syncDirInput = typeof input.syncDir === 'string' ? input.syncDir.trim() : '';
|
|
960
|
+
const syncDir = syncDirInput || `./every-design/${runId}`;
|
|
961
|
+
const progressLines = [];
|
|
962
|
+
console.log(formatSyncStartMarkdown());
|
|
963
|
+
const tracker = createSyncProgressTracker();
|
|
964
|
+
const emitProgress = (pct) => {
|
|
965
|
+
const line = `Progress: ${pct}%`;
|
|
966
|
+
progressLines.push(line);
|
|
967
|
+
console.log(line);
|
|
968
|
+
};
|
|
969
|
+
const initialPct = tracker.next(0);
|
|
970
|
+
if (initialPct !== null) {
|
|
971
|
+
emitProgress(initialPct);
|
|
1353
972
|
}
|
|
1354
|
-
|
|
1355
|
-
|
|
973
|
+
const run = await watchRun(client, runId, {
|
|
974
|
+
timeoutSeconds: 1800,
|
|
975
|
+
intervalSeconds: 5,
|
|
976
|
+
onUpdate: (snapshot) => {
|
|
977
|
+
const pct = getProgressPct(snapshot.run);
|
|
978
|
+
const mapped = tracker.next(pct);
|
|
979
|
+
if (mapped !== null) {
|
|
980
|
+
emitProgress(mapped);
|
|
981
|
+
}
|
|
982
|
+
},
|
|
983
|
+
onExtractStart: () => {
|
|
984
|
+
const mapped = tracker.markExtractStart();
|
|
985
|
+
if (mapped !== null) {
|
|
986
|
+
emitProgress(mapped);
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
syncDir,
|
|
990
|
+
ensureAssets: true,
|
|
991
|
+
});
|
|
992
|
+
const finalPct = getProgressPct(run);
|
|
993
|
+
const mappedFinal = tracker.finalize(finalPct);
|
|
994
|
+
if (mappedFinal !== null) {
|
|
995
|
+
emitProgress(mappedFinal);
|
|
1356
996
|
}
|
|
1357
|
-
const
|
|
1358
|
-
console.log(
|
|
1359
|
-
|
|
1360
|
-
...(targetArtifactId ? { artifactId: targetArtifactId } : {}),
|
|
1361
|
-
})));
|
|
997
|
+
const payload = typeof run === 'object' && run ? { ...run, progress: progressLines } : { run, progress: progressLines };
|
|
998
|
+
console.log('');
|
|
999
|
+
console.log(formatSyncCompletionMarkdown(wrap('design.sync', payload, { runId, syncDir })));
|
|
1362
1000
|
process.exit(0);
|
|
1363
1001
|
}
|
|
1364
|
-
if (verb === '
|
|
1002
|
+
if (verb === 'check') {
|
|
1365
1003
|
const input = await readJsonArgs(command.slice(1), flags);
|
|
1366
1004
|
const runId = String(input.runId ?? '').trim();
|
|
1367
1005
|
if (!runId)
|
|
1368
1006
|
throw new Error('Missing required argument: runId');
|
|
1369
|
-
const
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1007
|
+
const urlInput = typeof input.url === 'string' ? input.url.trim() : '';
|
|
1008
|
+
const pathInput = typeof input.path === 'string' ? input.path.trim() : '';
|
|
1009
|
+
if (!urlInput && !pathInput)
|
|
1010
|
+
throw new Error('Missing required argument: url or path');
|
|
1011
|
+
if (urlInput && pathInput)
|
|
1012
|
+
throw new Error('Provide only one of url or path');
|
|
1013
|
+
let targetUrl = urlInput;
|
|
1014
|
+
if (pathInput) {
|
|
1015
|
+
const resolved = path.resolve(pathInput);
|
|
1016
|
+
let stat;
|
|
1017
|
+
try {
|
|
1018
|
+
stat = statSync(resolved);
|
|
1019
|
+
}
|
|
1020
|
+
catch {
|
|
1021
|
+
throw new Error(`File not found: ${resolved}`);
|
|
1022
|
+
}
|
|
1023
|
+
if (!stat.isFile()) {
|
|
1024
|
+
throw new Error(`Path must be a file: ${resolved}`);
|
|
1025
|
+
}
|
|
1026
|
+
targetUrl = pathToFileURL(resolved).toString();
|
|
1384
1027
|
}
|
|
1385
|
-
const
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
const
|
|
1394
|
-
const viewport = {
|
|
1395
|
-
width: Number(viewportInput?.width) || 1696,
|
|
1396
|
-
height: Number(viewportInput?.height) || 2528,
|
|
1397
|
-
};
|
|
1398
|
-
const renderRefs = await client.uploadSourceImages(render);
|
|
1399
|
-
const targetRefs = target.length > 0 ? await client.uploadSourceImages(target) : [];
|
|
1028
|
+
const screenshotOut = path.join(os.tmpdir(), `every-design-check-${runId}-${Date.now()}.png`);
|
|
1029
|
+
const screenshot = await runScreenshotCommand({
|
|
1030
|
+
url: targetUrl,
|
|
1031
|
+
outPath: screenshotOut,
|
|
1032
|
+
width: 1696,
|
|
1033
|
+
height: 2528,
|
|
1034
|
+
waitMs: 4000,
|
|
1035
|
+
});
|
|
1036
|
+
const renderRefs = await client.uploadSourceImages([{ type: 'path', path: screenshot.screenshotPath }]);
|
|
1400
1037
|
const created = await client.createRunOperation(runId, {
|
|
1401
1038
|
type: 'iterate',
|
|
1402
|
-
|
|
1403
|
-
viewport,
|
|
1039
|
+
viewport: { width: screenshot.width, height: screenshot.height },
|
|
1404
1040
|
render: renderRefs,
|
|
1405
|
-
...(targetRefs.length > 0 ? { target: targetRefs } : {}),
|
|
1406
1041
|
});
|
|
1407
1042
|
const operationId = String(created?.operation?.id ?? '').trim();
|
|
1408
1043
|
if (!operationId)
|
|
1409
1044
|
throw new Error('Iterate operation created but missing operation id');
|
|
1410
|
-
const
|
|
1411
|
-
|
|
1412
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1413
|
-
const deadlineMs = Date.now() + Math.max(1, timeoutSeconds) * 1000;
|
|
1045
|
+
const deadlineMs = Date.now() + 900 * 1000;
|
|
1046
|
+
let terminalStatus = '';
|
|
1414
1047
|
while (true) {
|
|
1415
1048
|
const op = await client.getOperation(operationId);
|
|
1416
1049
|
const status = String(op?.operation?.status ?? '').trim().toLowerCase();
|
|
1417
1050
|
if (status === 'completed' || status === 'failed' || status === 'cancelled') {
|
|
1051
|
+
terminalStatus = status;
|
|
1418
1052
|
break;
|
|
1419
1053
|
}
|
|
1420
1054
|
if (Date.now() > deadlineMs) {
|
|
1421
1055
|
throw new Error(`Timed out waiting for iterate operation (${operationId})`);
|
|
1422
1056
|
}
|
|
1423
|
-
await
|
|
1057
|
+
await new Promise((r) => setTimeout(r, 5_000));
|
|
1058
|
+
}
|
|
1059
|
+
if (terminalStatus !== 'completed') {
|
|
1060
|
+
throw new Error(`Iterate operation ${operationId} ended with status ${terminalStatus || 'unknown'}`);
|
|
1424
1061
|
}
|
|
1425
1062
|
const detail = await client.getRun(runId);
|
|
1426
1063
|
const outputs = Array.isArray(detail?.run?.outputs) ? detail.run.outputs : [];
|
|
@@ -1443,12 +1080,16 @@ async function main() {
|
|
|
1443
1080
|
runId,
|
|
1444
1081
|
operationId,
|
|
1445
1082
|
status: 'completed',
|
|
1083
|
+
screenshot: {
|
|
1084
|
+
filePath: screenshot.screenshotPath,
|
|
1085
|
+
width: screenshot.width,
|
|
1086
|
+
height: screenshot.height,
|
|
1087
|
+
},
|
|
1446
1088
|
iterate: null,
|
|
1447
|
-
critique: null,
|
|
1448
1089
|
generatedAssets,
|
|
1449
1090
|
error: 'Missing critique artifact for iterate operation',
|
|
1450
1091
|
};
|
|
1451
|
-
console.log(
|
|
1092
|
+
console.log(formatToolResponseMarkdown(wrap('design.check', payload, { runId })));
|
|
1452
1093
|
process.exit(0);
|
|
1453
1094
|
}
|
|
1454
1095
|
const url = typeof critiqueArtifact?.url === 'string' ? String(critiqueArtifact.url) : '';
|
|
@@ -1456,213 +1097,27 @@ async function main() {
|
|
|
1456
1097
|
throw new Error('Critique artifact is missing a download url');
|
|
1457
1098
|
const downloaded = await client.downloadUrlToCache(runId, critiqueArtifact.id, url, { fileNameHint: 'iterate.json' });
|
|
1458
1099
|
const raw = await readFile(downloaded.filePath, 'utf8');
|
|
1459
|
-
|
|
1100
|
+
let parsed;
|
|
1101
|
+
try {
|
|
1102
|
+
parsed = JSON.parse(raw);
|
|
1103
|
+
}
|
|
1104
|
+
catch {
|
|
1105
|
+
parsed = raw;
|
|
1106
|
+
}
|
|
1460
1107
|
const payload = {
|
|
1461
1108
|
runId,
|
|
1462
|
-
|
|
1109
|
+
screenshot: {
|
|
1110
|
+
filePath: screenshot.screenshotPath,
|
|
1111
|
+
width: screenshot.width,
|
|
1112
|
+
height: screenshot.height,
|
|
1113
|
+
},
|
|
1463
1114
|
iterate: parsed,
|
|
1464
|
-
critique: parsed,
|
|
1465
1115
|
artifact: { id: critiqueArtifact.id, filePath: downloaded.filePath },
|
|
1466
1116
|
generatedAssets,
|
|
1467
1117
|
};
|
|
1468
|
-
console.log(
|
|
1118
|
+
console.log(formatToolResponseMarkdown(wrap('design.check', payload, { runId })));
|
|
1469
1119
|
process.exit(0);
|
|
1470
1120
|
}
|
|
1471
|
-
if (verb === 'watch') {
|
|
1472
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1473
|
-
const runId = String(input.runId ?? '').trim();
|
|
1474
|
-
if (!runId)
|
|
1475
|
-
throw new Error('Missing required argument: runId');
|
|
1476
|
-
const timeoutSeconds = typeof input.timeoutSeconds === 'number' ? input.timeoutSeconds : 900;
|
|
1477
|
-
const intervalSeconds = typeof input.intervalSeconds === 'number' ? input.intervalSeconds : 5;
|
|
1478
|
-
const syncDir = typeof input.syncDir === 'string' && input.syncDir.trim() ? input.syncDir.trim() : undefined;
|
|
1479
|
-
const ensureAssets = typeof input.ensureAssets === 'boolean' ? input.ensureAssets : undefined;
|
|
1480
|
-
const targetArtifactId = typeof input.targetArtifactId === 'string' && input.targetArtifactId.trim()
|
|
1481
|
-
? input.targetArtifactId.trim()
|
|
1482
|
-
: undefined;
|
|
1483
|
-
const progressUi = createWatchProgressDisplay(process.stderr, Boolean(process.stderr.isTTY));
|
|
1484
|
-
const log = (line) => {
|
|
1485
|
-
if (progressUi.enabled && line.startsWith('[design.watch] status='))
|
|
1486
|
-
return;
|
|
1487
|
-
progressUi.log(line);
|
|
1488
|
-
};
|
|
1489
|
-
const run = await watchRun(client, runId, {
|
|
1490
|
-
timeoutSeconds,
|
|
1491
|
-
intervalSeconds,
|
|
1492
|
-
log,
|
|
1493
|
-
onUpdate: (snapshot) => {
|
|
1494
|
-
progressUi.render(snapshot);
|
|
1495
|
-
},
|
|
1496
|
-
...(syncDir ? { syncDir } : {}),
|
|
1497
|
-
...(ensureAssets !== undefined ? { ensureAssets } : {}),
|
|
1498
|
-
...(targetArtifactId ? { targetArtifactId } : {}),
|
|
1499
|
-
});
|
|
1500
|
-
progressUi.finish();
|
|
1501
|
-
if (process.stderr.isTTY) {
|
|
1502
|
-
const synced = run?.synced;
|
|
1503
|
-
if (synced && typeof synced === 'object' && typeof synced.dir === 'string') {
|
|
1504
|
-
const dir = String(synced.dir);
|
|
1505
|
-
const downloaded = typeof synced.downloaded === 'number' ? synced.downloaded : 0;
|
|
1506
|
-
const skipped = typeof synced.skipped === 'number' ? synced.skipped : 0;
|
|
1507
|
-
const mapPath = typeof synced.mapPath === 'string' ? String(synced.mapPath) : '';
|
|
1508
|
-
console.error(`Synced: downloaded=${downloaded} skipped=${skipped} dir=${dir}`);
|
|
1509
|
-
if (mapPath)
|
|
1510
|
-
console.error(`Map: ${mapPath}`);
|
|
1511
|
-
try {
|
|
1512
|
-
const mapRaw = mapPath ? await readFile(mapPath, 'utf8') : '';
|
|
1513
|
-
const map = mapRaw ? parseJsonOrThrow(mapRaw) : null;
|
|
1514
|
-
const files = Array.isArray(map?.files) ? map.files : [];
|
|
1515
|
-
const rels = new Set(files
|
|
1516
|
-
.map((f) => (typeof f?.relPath === 'string' ? f.relPath : ''))
|
|
1517
|
-
.filter((p) => Boolean(p)));
|
|
1518
|
-
const hasPrefix = (prefix) => Array.from(rels).some((p) => p === prefix || p.startsWith(prefix + '/'));
|
|
1519
|
-
const keyFiles = [];
|
|
1520
|
-
const sorted = Array.from(rels).sort();
|
|
1521
|
-
const targets = sorted.filter((p) => p.startsWith('target/'));
|
|
1522
|
-
const assetPlan = sorted.filter((p) => p === 'assets/asset-plan.json');
|
|
1523
|
-
const assets = sorted.filter((p) => p.startsWith('assets/') && p !== 'assets/asset-plan.json');
|
|
1524
|
-
const refined = sorted.filter((p) => p.startsWith('refined/'));
|
|
1525
|
-
const drafts = sorted.filter((p) => p.startsWith('drafts/'));
|
|
1526
|
-
if (targets.length)
|
|
1527
|
-
keyFiles.push(targets[0]);
|
|
1528
|
-
if (assetPlan.length)
|
|
1529
|
-
keyFiles.push(assetPlan[0]);
|
|
1530
|
-
if (assets.length)
|
|
1531
|
-
keyFiles.push(...assets.slice(0, 3));
|
|
1532
|
-
if (refined.length)
|
|
1533
|
-
keyFiles.push(...refined.slice(0, 3));
|
|
1534
|
-
if (drafts.length)
|
|
1535
|
-
keyFiles.push(...drafts.slice(0, 3));
|
|
1536
|
-
if (keyFiles.length) {
|
|
1537
|
-
const unique = Array.from(new Set(keyFiles));
|
|
1538
|
-
console.error(`Key files: ${unique.join(', ')}${sorted.length > unique.length ? ` (+${sorted.length - unique.length} more)` : ''}`);
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
catch {
|
|
1542
|
-
// Ignore summary failures.
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
console.log(pretty(wrap('design.watch', run, { runId, ...(syncDir ? { syncDir } : {}) })));
|
|
1547
|
-
process.exit(0);
|
|
1548
|
-
}
|
|
1549
|
-
if (verb === 'share') {
|
|
1550
|
-
const action = (sub || 'status').trim().toLowerCase();
|
|
1551
|
-
const input = await readJsonArgs(command.slice(2), flags);
|
|
1552
|
-
const runId = String(input.runId ?? '').trim();
|
|
1553
|
-
if (!runId)
|
|
1554
|
-
throw new Error('Missing required argument: runId');
|
|
1555
|
-
if (action === 'status') {
|
|
1556
|
-
const res = await client.getRunShareStatus(runId);
|
|
1557
|
-
console.log(pretty(res));
|
|
1558
|
-
process.exit(0);
|
|
1559
|
-
}
|
|
1560
|
-
if (action === 'enable' || action === 'create') {
|
|
1561
|
-
const rotate = input.rotate === true;
|
|
1562
|
-
const res = await client.createRunShare(runId, rotate ? { rotate: true } : {});
|
|
1563
|
-
console.log(pretty(res));
|
|
1564
|
-
process.exit(0);
|
|
1565
|
-
}
|
|
1566
|
-
if (action === 'revoke') {
|
|
1567
|
-
const res = await client.revokeRunShare(runId);
|
|
1568
|
-
console.log(pretty(res));
|
|
1569
|
-
process.exit(0);
|
|
1570
|
-
}
|
|
1571
|
-
throw new Error(`Unknown share action: ${action}. Expected status|enable|revoke.`);
|
|
1572
|
-
}
|
|
1573
|
-
if (verb === 'get') {
|
|
1574
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1575
|
-
const runId = String(input.runId ?? '').trim();
|
|
1576
|
-
if (!runId)
|
|
1577
|
-
throw new Error('Missing required argument: runId');
|
|
1578
|
-
const run = await client.getRun(runId);
|
|
1579
|
-
console.log(pretty(wrap('design.get', run, { runId })));
|
|
1580
|
-
process.exit(0);
|
|
1581
|
-
}
|
|
1582
|
-
if (verb === 'events') {
|
|
1583
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1584
|
-
const runId = String(input.runId ?? '').trim();
|
|
1585
|
-
if (!runId)
|
|
1586
|
-
throw new Error('Missing required argument: runId');
|
|
1587
|
-
const events = await client.getRunEvents(runId);
|
|
1588
|
-
console.log(pretty(wrap('design.events', events, { runId })));
|
|
1589
|
-
process.exit(0);
|
|
1590
|
-
}
|
|
1591
|
-
if (verb === 'list') {
|
|
1592
|
-
const input = await readJsonArgsOptional(command.slice(1), flags);
|
|
1593
|
-
const page = typeof input.page === 'number' ? input.page : undefined;
|
|
1594
|
-
const limit = typeof input.limit === 'number' ? input.limit : undefined;
|
|
1595
|
-
const runs = await client.listRuns({ page, limit });
|
|
1596
|
-
console.log(pretty(wrap('design.list', runs)));
|
|
1597
|
-
process.exit(0);
|
|
1598
|
-
}
|
|
1599
|
-
if (verb === 'artifacts' && sub === 'list') {
|
|
1600
|
-
const positional = parseArtifactsListPositional(command.slice(2));
|
|
1601
|
-
const input = positional ? { runId: positional.runId } : await readJsonArgs(command.slice(2), flags);
|
|
1602
|
-
const runId = String(input.runId ?? '').trim();
|
|
1603
|
-
if (!runId)
|
|
1604
|
-
throw new Error('Missing required argument: runId');
|
|
1605
|
-
const detail = await client.getRun(runId);
|
|
1606
|
-
const outputs = Array.isArray(detail?.run?.outputs) ? detail.run.outputs : [];
|
|
1607
|
-
console.log(pretty(wrap('design.artifacts.list', { artifacts: outputs }, { runId })));
|
|
1608
|
-
process.exit(0);
|
|
1609
|
-
}
|
|
1610
|
-
if (verb === 'artifacts' && sub === 'download') {
|
|
1611
|
-
const positional = parseArtifactsDownloadPositional(command.slice(2));
|
|
1612
|
-
const input = positional ? { runId: positional.runId, artifactId: positional.artifactId } : await readJsonArgs(command.slice(2), flags);
|
|
1613
|
-
const runId = String(input.runId ?? '').trim();
|
|
1614
|
-
const artifactId = String(input.artifactId ?? '').trim();
|
|
1615
|
-
if (!runId)
|
|
1616
|
-
throw new Error('Missing required argument: runId');
|
|
1617
|
-
if (!artifactId)
|
|
1618
|
-
throw new Error('Missing required argument: artifactId');
|
|
1619
|
-
const detail = await client.getRun(runId);
|
|
1620
|
-
const outputs = Array.isArray(detail?.run?.outputs) ? detail.run.outputs : [];
|
|
1621
|
-
const found = outputs.find((entry) => String(entry?.id ?? '') === artifactId) || null;
|
|
1622
|
-
const hint = await (async () => {
|
|
1623
|
-
const url = typeof found?.url === 'string' ? found.url.trim() : '';
|
|
1624
|
-
if (url) {
|
|
1625
|
-
try {
|
|
1626
|
-
const u = url.startsWith('http://') || url.startsWith('https://')
|
|
1627
|
-
? new URL(url)
|
|
1628
|
-
: new URL(url, 'https://example.invalid');
|
|
1629
|
-
return u.pathname.split('/').filter(Boolean).pop() || undefined;
|
|
1630
|
-
}
|
|
1631
|
-
catch {
|
|
1632
|
-
return url.split('/').filter(Boolean).pop() || undefined;
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1635
|
-
try {
|
|
1636
|
-
const artifacts = await client.listArtifacts(runId);
|
|
1637
|
-
const record = Array.isArray(artifacts?.artifacts)
|
|
1638
|
-
? artifacts.artifacts.find((a) => String(a?.id ?? '') === artifactId)
|
|
1639
|
-
: null;
|
|
1640
|
-
const storageKey = typeof record?.storageKey === 'string' ? record.storageKey.trim() : '';
|
|
1641
|
-
return storageKey ? storageKey.split('/').filter(Boolean).pop() || undefined : undefined;
|
|
1642
|
-
}
|
|
1643
|
-
catch {
|
|
1644
|
-
return undefined;
|
|
1645
|
-
}
|
|
1646
|
-
})();
|
|
1647
|
-
const downloaded = await (async () => {
|
|
1648
|
-
try {
|
|
1649
|
-
return await client.downloadArtifactToCache(runId, artifactId, { fileNameHint: hint });
|
|
1650
|
-
}
|
|
1651
|
-
catch (error) {
|
|
1652
|
-
const url = typeof found?.url === 'string' ? found.url.trim() : '';
|
|
1653
|
-
if (url) {
|
|
1654
|
-
return await client.downloadUrlToCache(runId, artifactId, url, { fileNameHint: hint });
|
|
1655
|
-
}
|
|
1656
|
-
throw error;
|
|
1657
|
-
}
|
|
1658
|
-
})();
|
|
1659
|
-
console.log(pretty(wrap('design.artifacts.download', downloaded, { runId, artifactId })));
|
|
1660
|
-
process.exit(0);
|
|
1661
|
-
}
|
|
1662
|
-
if (verb === 'artifacts') {
|
|
1663
|
-
throw new Error('Usage: artifacts list <runId> | artifacts download <runId>/<artifactId>\n' +
|
|
1664
|
-
'Tip: you can still use --json or pipe a JSON object via stdin.');
|
|
1665
|
-
}
|
|
1666
1121
|
// Should be unreachable due to the verb guard.
|
|
1667
1122
|
throw new Error(`Unknown command: ${verb}`);
|
|
1668
1123
|
}
|
|
@@ -1755,6 +1210,11 @@ async function main() {
|
|
|
1755
1210
|
console.log(JSON.stringify(safe, null, 2));
|
|
1756
1211
|
process.exit(0);
|
|
1757
1212
|
}
|
|
1213
|
+
if (verb) {
|
|
1214
|
+
console.error(`Unknown command: ${verb}`);
|
|
1215
|
+
printHelp();
|
|
1216
|
+
process.exit(1);
|
|
1217
|
+
}
|
|
1758
1218
|
// Default: run MCP server.
|
|
1759
1219
|
const cfg = await loadMergedConfig(configPath, { designOrigin, loginOrigin });
|
|
1760
1220
|
const sessionToken = process.env.DESIGN_MCP_SESSION_TOKEN?.trim();
|