@just-every/design 0.1.32 → 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 -657
- 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,221 +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
|
-
if (typeof process.getuid === 'function' && process.getuid() === 0) {
|
|
203
|
-
// Chromium requires no-sandbox flags when running as root.
|
|
204
|
-
argsList.unshift('--disable-setuid-sandbox', '--no-sandbox');
|
|
205
|
-
}
|
|
206
|
-
const screenshotOk = () => {
|
|
207
|
-
try {
|
|
208
|
-
return statSync(outPath).size > 0;
|
|
209
|
-
}
|
|
210
|
-
catch {
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
215
|
-
const waitForScreenshotStable = async (signal) => {
|
|
216
|
-
const start = Date.now();
|
|
217
|
-
// Chrome writes the screenshot file as a single operation most of the time, but we've seen
|
|
218
|
-
// occasional flakiness where the file exists briefly at size=0. Wait for a stable >0 size.
|
|
219
|
-
let lastSize = -1;
|
|
220
|
-
let stableTicks = 0;
|
|
221
|
-
while (Date.now() - start < timeoutMs) {
|
|
222
|
-
if (signal.aborted)
|
|
223
|
-
return false;
|
|
224
|
-
let size = -1;
|
|
225
|
-
try {
|
|
226
|
-
size = statSync(outPath).size;
|
|
227
|
-
}
|
|
228
|
-
catch {
|
|
229
|
-
size = -1;
|
|
230
|
-
}
|
|
231
|
-
if (size > 0) {
|
|
232
|
-
if (size === lastSize)
|
|
233
|
-
stableTicks += 1;
|
|
234
|
-
else
|
|
235
|
-
stableTicks = 0;
|
|
236
|
-
lastSize = size;
|
|
237
|
-
if (stableTicks >= 2)
|
|
238
|
-
return true;
|
|
239
|
-
}
|
|
240
|
-
await sleep(100);
|
|
241
|
-
}
|
|
242
|
-
if (signal.aborted)
|
|
243
|
-
return false;
|
|
244
|
-
throw new Error('Timed out waiting for screenshot file to be produced.');
|
|
245
|
-
};
|
|
246
|
-
// Use an isolated process group on Unix so we can reliably kill Chrome + its helpers.
|
|
247
|
-
const child = spawn(browser, argsList, {
|
|
248
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
249
|
-
detached: process.platform !== 'win32',
|
|
250
|
-
});
|
|
251
|
-
let stdout = '';
|
|
252
|
-
let stderr = '';
|
|
253
|
-
child.stdout?.setEncoding('utf8');
|
|
254
|
-
child.stderr?.setEncoding('utf8');
|
|
255
|
-
child.stdout?.on('data', (chunk) => {
|
|
256
|
-
stdout += String(chunk);
|
|
257
|
-
});
|
|
258
|
-
child.stderr?.on('data', (chunk) => {
|
|
259
|
-
stderr += String(chunk);
|
|
260
|
-
});
|
|
261
|
-
const killChild = (signal) => {
|
|
262
|
-
if (!child.pid)
|
|
263
|
-
return;
|
|
264
|
-
try {
|
|
265
|
-
if (process.platform !== 'win32') {
|
|
266
|
-
// Kill the whole process group (negative PID) when detached.
|
|
267
|
-
process.kill(-child.pid, signal);
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
process.kill(child.pid, signal);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
catch {
|
|
274
|
-
// ignore
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
const exitPromise = new Promise((resolve, reject) => {
|
|
278
|
-
child.once('error', reject);
|
|
279
|
-
child.once('exit', (code, signal) => resolve({ code, signal }));
|
|
280
|
-
});
|
|
281
|
-
const abort = new AbortController();
|
|
282
|
-
const screenshotPromise = waitForScreenshotStable(abort.signal)
|
|
283
|
-
.then((ok) => ({ type: 'screenshot', ok }))
|
|
284
|
-
.catch((error) => ({ type: 'screenshot_error', error }));
|
|
285
|
-
const exitPromiseHandled = exitPromise
|
|
286
|
-
.then((r) => ({ type: 'exit', ...r }))
|
|
287
|
-
.catch((error) => ({ type: 'exit_error', error }));
|
|
288
|
-
// Hard-kill after the timeout budget; Chrome can hang even after producing a screenshot.
|
|
289
|
-
const timeout = setTimeout(() => {
|
|
290
|
-
killChild('SIGKILL');
|
|
291
|
-
}, timeoutMs);
|
|
292
|
-
try {
|
|
293
|
-
const first = await Promise.race([screenshotPromise, exitPromiseHandled]);
|
|
294
|
-
abort.abort();
|
|
295
|
-
if (first.type === 'exit_error') {
|
|
296
|
-
const message = first.error instanceof Error ? first.error.message : String(first.error);
|
|
297
|
-
throw new Error(`Screenshot failed (spawn error): ${message}`);
|
|
298
|
-
}
|
|
299
|
-
if (first.type === 'screenshot_error') {
|
|
300
|
-
// Still allow a best-effort success if Chrome produced a screenshot before hanging.
|
|
301
|
-
killChild('SIGKILL');
|
|
302
|
-
await Promise.race([exitPromise.catch(() => undefined), sleep(1_500)]);
|
|
303
|
-
}
|
|
304
|
-
// If the screenshot is ready but Chrome is still running, kill it immediately instead of
|
|
305
|
-
// waiting for the timeout (which is often ~waitMs+30s).
|
|
306
|
-
if (first.type === 'screenshot' && first.ok) {
|
|
307
|
-
killChild('SIGKILL');
|
|
308
|
-
// Don't await indefinitely; cleanup is best-effort.
|
|
309
|
-
await Promise.race([exitPromise.catch(() => undefined), sleep(1_500)]);
|
|
310
|
-
}
|
|
311
|
-
const ok = screenshotOk();
|
|
312
|
-
if (!ok) {
|
|
313
|
-
// Chrome may exit without producing a screenshot; include output to help debug.
|
|
314
|
-
const detail = (stderr || stdout).trim();
|
|
315
|
-
const exit = first.type === 'exit' ? first : await exitPromise.catch(() => ({ code: null, signal: null }));
|
|
316
|
-
throw new Error(`Screenshot failed (exit ${exit.code ?? 'unknown'}, signal ${exit.signal ?? 'none'}): ${detail || 'no output'}`);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
finally {
|
|
320
|
-
clearTimeout(timeout);
|
|
321
|
-
// Ensure Chrome isn't left running in the background.
|
|
322
|
-
killChild('SIGKILL');
|
|
323
|
-
}
|
|
324
|
-
return {
|
|
325
|
-
screenshotPath: outPath,
|
|
326
|
-
width: args.width,
|
|
327
|
-
height: args.height,
|
|
328
|
-
engine: 'chrome-headless',
|
|
329
|
-
binary: browser,
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
finally {
|
|
333
|
-
const { rm } = await import('node:fs/promises');
|
|
334
|
-
await rm(tmpProfile, { recursive: true, force: true }).catch(() => undefined);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
103
|
function isTtyInteractive() {
|
|
338
104
|
return Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
339
105
|
}
|
|
@@ -385,76 +151,6 @@ async function readJsonArgs(remainingArgs, flags) {
|
|
|
385
151
|
}
|
|
386
152
|
return parsed;
|
|
387
153
|
}
|
|
388
|
-
async function readJsonArgsOptional(remainingArgs, flags) {
|
|
389
|
-
const jsonFlag = resolveFlagString(flags, 'json');
|
|
390
|
-
const hasInline = remainingArgs.some((t) => t.trim().length > 0);
|
|
391
|
-
const hasStdin = !process.stdin.isTTY;
|
|
392
|
-
if (!jsonFlag && !hasInline && !hasStdin)
|
|
393
|
-
return {};
|
|
394
|
-
return readJsonArgs(remainingArgs, flags);
|
|
395
|
-
}
|
|
396
|
-
function pretty(value) {
|
|
397
|
-
return JSON.stringify(value, null, 2);
|
|
398
|
-
}
|
|
399
|
-
function createWatchProgressDisplay(stream, enabled) {
|
|
400
|
-
let lastRendered = '';
|
|
401
|
-
let lastSnapshot = null;
|
|
402
|
-
let spinner = 0;
|
|
403
|
-
function clearLine() {
|
|
404
|
-
stream.write('\r\x1b[2K');
|
|
405
|
-
}
|
|
406
|
-
function render(snapshot) {
|
|
407
|
-
if (!enabled)
|
|
408
|
-
return;
|
|
409
|
-
lastSnapshot = snapshot;
|
|
410
|
-
spinner = (spinner + 1) % 4;
|
|
411
|
-
const stage = snapshot.stage ? String(snapshot.stage) : '';
|
|
412
|
-
const status = snapshot.status ? String(snapshot.status) : '';
|
|
413
|
-
const progress = typeof snapshot.progress === 'number' ? snapshot.progress : null;
|
|
414
|
-
const pct = typeof progress === 'number' ? Math.max(0, Math.min(100, Math.round(progress * 100))) : null;
|
|
415
|
-
const barWidth = 24;
|
|
416
|
-
const filled = pct === null ? 0 : Math.round((pct / 100) * barWidth);
|
|
417
|
-
const bar = `[${'#'.repeat(Math.max(0, Math.min(barWidth, filled)))}${'-'.repeat(Math.max(0, barWidth - filled))}]`;
|
|
418
|
-
const spinChar = ['|', '/', '-', '\\'][spinner] ?? '|';
|
|
419
|
-
const parts = [];
|
|
420
|
-
if (pct === null) {
|
|
421
|
-
parts.push(`${spinChar} ${bar}`);
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
parts.push(`${bar} ${String(pct).padStart(3, ' ')}%`);
|
|
425
|
-
}
|
|
426
|
-
if (stage)
|
|
427
|
-
parts.push(`stage=${stage}`);
|
|
428
|
-
if (status)
|
|
429
|
-
parts.push(`status=${status}`);
|
|
430
|
-
const line = parts.join(' ');
|
|
431
|
-
if (line === lastRendered)
|
|
432
|
-
return;
|
|
433
|
-
lastRendered = line;
|
|
434
|
-
clearLine();
|
|
435
|
-
stream.write(line);
|
|
436
|
-
}
|
|
437
|
-
function log(line) {
|
|
438
|
-
if (!enabled) {
|
|
439
|
-
stream.write(`${line}\n`);
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
clearLine();
|
|
443
|
-
stream.write(`${line}\n`);
|
|
444
|
-
if (lastSnapshot) {
|
|
445
|
-
lastRendered = '';
|
|
446
|
-
render(lastSnapshot);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
function finish() {
|
|
450
|
-
if (!enabled)
|
|
451
|
-
return;
|
|
452
|
-
clearLine();
|
|
453
|
-
lastRendered = '';
|
|
454
|
-
lastSnapshot = null;
|
|
455
|
-
}
|
|
456
|
-
return { render, log, finish, enabled };
|
|
457
|
-
}
|
|
458
154
|
async function promptYesNo(question, defaultYes = false) {
|
|
459
155
|
if (!isTtyInteractive())
|
|
460
156
|
return defaultYes;
|
|
@@ -927,32 +623,6 @@ async function main() {
|
|
|
927
623
|
const loginOrigin = resolveLoginOrigin(flags);
|
|
928
624
|
const verb = command[0] ?? '';
|
|
929
625
|
const sub = command[1] ?? '';
|
|
930
|
-
if (verb === 'screenshot') {
|
|
931
|
-
const url = String(command[1] ?? '').trim();
|
|
932
|
-
const width = Number.parseInt(String(resolveFlagString(flags, 'width') ?? '1696'), 10);
|
|
933
|
-
const height = Number.parseInt(String(resolveFlagString(flags, 'height') ?? '2528'), 10);
|
|
934
|
-
const waitMs = Number.parseInt(String(resolveFlagString(flags, 'wait-ms') ?? '4000'), 10);
|
|
935
|
-
const out = resolveFlagString(flags, 'out')?.trim();
|
|
936
|
-
const outPath = out
|
|
937
|
-
? out
|
|
938
|
-
: path.join(process.cwd(), `every-design-screenshot-${new Date().toISOString().replace(/[:.]/g, '-')}.png`);
|
|
939
|
-
try {
|
|
940
|
-
const result = await runScreenshotCommand({
|
|
941
|
-
url,
|
|
942
|
-
outPath,
|
|
943
|
-
width: Number.isFinite(width) && width > 0 ? width : 1696,
|
|
944
|
-
height: Number.isFinite(height) && height > 0 ? height : 2528,
|
|
945
|
-
waitMs: Number.isFinite(waitMs) ? waitMs : 4000,
|
|
946
|
-
});
|
|
947
|
-
console.log(pretty(result));
|
|
948
|
-
process.exit(0);
|
|
949
|
-
}
|
|
950
|
-
catch (error) {
|
|
951
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
952
|
-
console.error(message);
|
|
953
|
-
process.exit(1);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
626
|
if (verb === 'install') {
|
|
957
627
|
const yes = Boolean(flags.yes);
|
|
958
628
|
const dryRun = Boolean(flags['dry-run']);
|
|
@@ -1259,22 +929,8 @@ async function main() {
|
|
|
1259
929
|
}
|
|
1260
930
|
process.exit(0);
|
|
1261
931
|
}
|
|
1262
|
-
// CLI helpers (non-MCP).
|
|
1263
|
-
if (verb === 'create'
|
|
1264
|
-
|| verb === 'upload-target'
|
|
1265
|
-
|| verb === 'refine-draft'
|
|
1266
|
-
|| verb === 'set-target'
|
|
1267
|
-
|| verb === 'clear-target'
|
|
1268
|
-
|| verb === 'extract-assets'
|
|
1269
|
-
|| verb === 'generate-html'
|
|
1270
|
-
|| verb === 'iterate'
|
|
1271
|
-
|| verb === 'critique'
|
|
1272
|
-
|| verb === 'watch'
|
|
1273
|
-
|| verb === 'share'
|
|
1274
|
-
|| verb === 'get'
|
|
1275
|
-
|| verb === 'list'
|
|
1276
|
-
|| verb === 'events'
|
|
1277
|
-
|| verb === 'artifacts') {
|
|
932
|
+
// CLI helpers (non-MCP). CLIs must follow: create -> sync -> check.
|
|
933
|
+
if (verb === 'create' || verb === 'sync' || verb === 'check') {
|
|
1278
934
|
const cfg = await loadMergedConfig(configPath, { designOrigin, loginOrigin });
|
|
1279
935
|
const sessionToken = (process.env.DESIGN_MCP_SESSION_TOKEN ?? cfg.sessionToken ?? '').trim();
|
|
1280
936
|
if (!sessionToken) {
|
|
@@ -1292,139 +948,116 @@ async function main() {
|
|
|
1292
948
|
const input = await readJsonArgs(command.slice(1), flags);
|
|
1293
949
|
const { prompt, config, parentRunId } = await buildCreateRunRequest(client, input);
|
|
1294
950
|
const created = await client.createRun({ prompt, config, ...(parentRunId ? { parentRunId } : {}) });
|
|
1295
|
-
console.log(
|
|
1296
|
-
process.exit(0);
|
|
1297
|
-
}
|
|
1298
|
-
if (verb === 'upload-target') {
|
|
1299
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1300
|
-
const runId = String(input.runId ?? '').trim();
|
|
1301
|
-
if (!runId)
|
|
1302
|
-
throw new Error('Missing required argument: runId');
|
|
1303
|
-
const sourceImage = input.sourceImage && typeof input.sourceImage === 'object' ? input.sourceImage : null;
|
|
1304
|
-
if (!sourceImage) {
|
|
1305
|
-
throw new Error('Missing required argument: sourceImage');
|
|
1306
|
-
}
|
|
1307
|
-
const res = await client.uploadRunTarget(runId, sourceImage);
|
|
1308
|
-
console.log(pretty(wrap('design.uploadTarget', res, { runId })));
|
|
1309
|
-
process.exit(0);
|
|
1310
|
-
}
|
|
1311
|
-
if (verb === 'refine-draft') {
|
|
1312
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1313
|
-
const runId = String(input.runId ?? '').trim();
|
|
1314
|
-
const artifactId = String(input.artifactId ?? '').trim();
|
|
1315
|
-
const prompt = String(input.prompt ?? '').trim();
|
|
1316
|
-
if (!runId)
|
|
1317
|
-
throw new Error('Missing required argument: runId');
|
|
1318
|
-
if (!artifactId)
|
|
1319
|
-
throw new Error('Missing required argument: artifactId');
|
|
1320
|
-
if (!prompt)
|
|
1321
|
-
throw new Error('Missing required argument: prompt');
|
|
1322
|
-
const res = await client.refineDraft(runId, artifactId, prompt);
|
|
1323
|
-
console.log(pretty(wrap('design.refineDraft', res, { runId, artifactId })));
|
|
1324
|
-
process.exit(0);
|
|
1325
|
-
}
|
|
1326
|
-
if (verb === 'set-target') {
|
|
1327
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1328
|
-
const runId = String(input.runId ?? '').trim();
|
|
1329
|
-
const artifactId = String(input.artifactId ?? '').trim();
|
|
1330
|
-
if (!runId)
|
|
1331
|
-
throw new Error('Missing required argument: runId');
|
|
1332
|
-
if (!artifactId)
|
|
1333
|
-
throw new Error('Missing required argument: artifactId');
|
|
1334
|
-
const res = await client.setRunTarget(runId, artifactId);
|
|
1335
|
-
console.log(pretty(wrap('design.setTarget', res, { runId, artifactId })));
|
|
1336
|
-
process.exit(0);
|
|
1337
|
-
}
|
|
1338
|
-
if (verb === 'clear-target') {
|
|
1339
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1340
|
-
const runId = String(input.runId ?? '').trim();
|
|
1341
|
-
if (!runId)
|
|
1342
|
-
throw new Error('Missing required argument: runId');
|
|
1343
|
-
const res = await client.clearRunTarget(runId);
|
|
1344
|
-
console.log(pretty(wrap('design.clearTarget', res, { runId })));
|
|
951
|
+
console.log(formatToolResponseMarkdown(wrap('design.create', created)));
|
|
1345
952
|
process.exit(0);
|
|
1346
953
|
}
|
|
1347
|
-
if (verb === '
|
|
954
|
+
if (verb === 'sync') {
|
|
1348
955
|
const input = await readJsonArgs(command.slice(1), flags);
|
|
1349
956
|
const runId = String(input.runId ?? '').trim();
|
|
1350
957
|
if (!runId)
|
|
1351
958
|
throw new Error('Missing required argument: runId');
|
|
1352
|
-
const
|
|
1353
|
-
const
|
|
1354
|
-
const
|
|
1355
|
-
|
|
1356
|
-
|
|
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);
|
|
1357
972
|
}
|
|
1358
|
-
|
|
1359
|
-
|
|
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);
|
|
1360
996
|
}
|
|
1361
|
-
const
|
|
1362
|
-
console.log(
|
|
1363
|
-
|
|
1364
|
-
...(targetArtifactId ? { artifactId: targetArtifactId } : {}),
|
|
1365
|
-
})));
|
|
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 })));
|
|
1366
1000
|
process.exit(0);
|
|
1367
1001
|
}
|
|
1368
|
-
if (verb === '
|
|
1002
|
+
if (verb === 'check') {
|
|
1369
1003
|
const input = await readJsonArgs(command.slice(1), flags);
|
|
1370
1004
|
const runId = String(input.runId ?? '').trim();
|
|
1371
1005
|
if (!runId)
|
|
1372
1006
|
throw new Error('Missing required argument: runId');
|
|
1373
|
-
const
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
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();
|
|
1388
1027
|
}
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
const
|
|
1398
|
-
const viewport = {
|
|
1399
|
-
width: Number(viewportInput?.width) || 1696,
|
|
1400
|
-
height: Number(viewportInput?.height) || 2528,
|
|
1401
|
-
};
|
|
1402
|
-
const renderRefs = await client.uploadSourceImages(render);
|
|
1403
|
-
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 }]);
|
|
1404
1037
|
const created = await client.createRunOperation(runId, {
|
|
1405
1038
|
type: 'iterate',
|
|
1406
|
-
|
|
1407
|
-
viewport,
|
|
1039
|
+
viewport: { width: screenshot.width, height: screenshot.height },
|
|
1408
1040
|
render: renderRefs,
|
|
1409
|
-
...(targetRefs.length > 0 ? { target: targetRefs } : {}),
|
|
1410
1041
|
});
|
|
1411
1042
|
const operationId = String(created?.operation?.id ?? '').trim();
|
|
1412
1043
|
if (!operationId)
|
|
1413
1044
|
throw new Error('Iterate operation created but missing operation id');
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1416
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1417
|
-
const deadlineMs = Date.now() + Math.max(1, timeoutSeconds) * 1000;
|
|
1045
|
+
const deadlineMs = Date.now() + 900 * 1000;
|
|
1046
|
+
let terminalStatus = '';
|
|
1418
1047
|
while (true) {
|
|
1419
1048
|
const op = await client.getOperation(operationId);
|
|
1420
1049
|
const status = String(op?.operation?.status ?? '').trim().toLowerCase();
|
|
1421
1050
|
if (status === 'completed' || status === 'failed' || status === 'cancelled') {
|
|
1051
|
+
terminalStatus = status;
|
|
1422
1052
|
break;
|
|
1423
1053
|
}
|
|
1424
1054
|
if (Date.now() > deadlineMs) {
|
|
1425
1055
|
throw new Error(`Timed out waiting for iterate operation (${operationId})`);
|
|
1426
1056
|
}
|
|
1427
|
-
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'}`);
|
|
1428
1061
|
}
|
|
1429
1062
|
const detail = await client.getRun(runId);
|
|
1430
1063
|
const outputs = Array.isArray(detail?.run?.outputs) ? detail.run.outputs : [];
|
|
@@ -1447,12 +1080,16 @@ async function main() {
|
|
|
1447
1080
|
runId,
|
|
1448
1081
|
operationId,
|
|
1449
1082
|
status: 'completed',
|
|
1083
|
+
screenshot: {
|
|
1084
|
+
filePath: screenshot.screenshotPath,
|
|
1085
|
+
width: screenshot.width,
|
|
1086
|
+
height: screenshot.height,
|
|
1087
|
+
},
|
|
1450
1088
|
iterate: null,
|
|
1451
|
-
critique: null,
|
|
1452
1089
|
generatedAssets,
|
|
1453
1090
|
error: 'Missing critique artifact for iterate operation',
|
|
1454
1091
|
};
|
|
1455
|
-
console.log(
|
|
1092
|
+
console.log(formatToolResponseMarkdown(wrap('design.check', payload, { runId })));
|
|
1456
1093
|
process.exit(0);
|
|
1457
1094
|
}
|
|
1458
1095
|
const url = typeof critiqueArtifact?.url === 'string' ? String(critiqueArtifact.url) : '';
|
|
@@ -1460,213 +1097,27 @@ async function main() {
|
|
|
1460
1097
|
throw new Error('Critique artifact is missing a download url');
|
|
1461
1098
|
const downloaded = await client.downloadUrlToCache(runId, critiqueArtifact.id, url, { fileNameHint: 'iterate.json' });
|
|
1462
1099
|
const raw = await readFile(downloaded.filePath, 'utf8');
|
|
1463
|
-
|
|
1100
|
+
let parsed;
|
|
1101
|
+
try {
|
|
1102
|
+
parsed = JSON.parse(raw);
|
|
1103
|
+
}
|
|
1104
|
+
catch {
|
|
1105
|
+
parsed = raw;
|
|
1106
|
+
}
|
|
1464
1107
|
const payload = {
|
|
1465
1108
|
runId,
|
|
1466
|
-
|
|
1109
|
+
screenshot: {
|
|
1110
|
+
filePath: screenshot.screenshotPath,
|
|
1111
|
+
width: screenshot.width,
|
|
1112
|
+
height: screenshot.height,
|
|
1113
|
+
},
|
|
1467
1114
|
iterate: parsed,
|
|
1468
|
-
critique: parsed,
|
|
1469
1115
|
artifact: { id: critiqueArtifact.id, filePath: downloaded.filePath },
|
|
1470
1116
|
generatedAssets,
|
|
1471
1117
|
};
|
|
1472
|
-
console.log(
|
|
1473
|
-
process.exit(0);
|
|
1474
|
-
}
|
|
1475
|
-
if (verb === 'watch') {
|
|
1476
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1477
|
-
const runId = String(input.runId ?? '').trim();
|
|
1478
|
-
if (!runId)
|
|
1479
|
-
throw new Error('Missing required argument: runId');
|
|
1480
|
-
const timeoutSeconds = typeof input.timeoutSeconds === 'number' ? input.timeoutSeconds : 900;
|
|
1481
|
-
const intervalSeconds = typeof input.intervalSeconds === 'number' ? input.intervalSeconds : 5;
|
|
1482
|
-
const syncDir = typeof input.syncDir === 'string' && input.syncDir.trim() ? input.syncDir.trim() : undefined;
|
|
1483
|
-
const ensureAssets = typeof input.ensureAssets === 'boolean' ? input.ensureAssets : undefined;
|
|
1484
|
-
const targetArtifactId = typeof input.targetArtifactId === 'string' && input.targetArtifactId.trim()
|
|
1485
|
-
? input.targetArtifactId.trim()
|
|
1486
|
-
: undefined;
|
|
1487
|
-
const progressUi = createWatchProgressDisplay(process.stderr, Boolean(process.stderr.isTTY));
|
|
1488
|
-
const log = (line) => {
|
|
1489
|
-
if (progressUi.enabled && line.startsWith('[design.watch] status='))
|
|
1490
|
-
return;
|
|
1491
|
-
progressUi.log(line);
|
|
1492
|
-
};
|
|
1493
|
-
const run = await watchRun(client, runId, {
|
|
1494
|
-
timeoutSeconds,
|
|
1495
|
-
intervalSeconds,
|
|
1496
|
-
log,
|
|
1497
|
-
onUpdate: (snapshot) => {
|
|
1498
|
-
progressUi.render(snapshot);
|
|
1499
|
-
},
|
|
1500
|
-
...(syncDir ? { syncDir } : {}),
|
|
1501
|
-
...(ensureAssets !== undefined ? { ensureAssets } : {}),
|
|
1502
|
-
...(targetArtifactId ? { targetArtifactId } : {}),
|
|
1503
|
-
});
|
|
1504
|
-
progressUi.finish();
|
|
1505
|
-
if (process.stderr.isTTY) {
|
|
1506
|
-
const synced = run?.synced;
|
|
1507
|
-
if (synced && typeof synced === 'object' && typeof synced.dir === 'string') {
|
|
1508
|
-
const dir = String(synced.dir);
|
|
1509
|
-
const downloaded = typeof synced.downloaded === 'number' ? synced.downloaded : 0;
|
|
1510
|
-
const skipped = typeof synced.skipped === 'number' ? synced.skipped : 0;
|
|
1511
|
-
const mapPath = typeof synced.mapPath === 'string' ? String(synced.mapPath) : '';
|
|
1512
|
-
console.error(`Synced: downloaded=${downloaded} skipped=${skipped} dir=${dir}`);
|
|
1513
|
-
if (mapPath)
|
|
1514
|
-
console.error(`Map: ${mapPath}`);
|
|
1515
|
-
try {
|
|
1516
|
-
const mapRaw = mapPath ? await readFile(mapPath, 'utf8') : '';
|
|
1517
|
-
const map = mapRaw ? parseJsonOrThrow(mapRaw) : null;
|
|
1518
|
-
const files = Array.isArray(map?.files) ? map.files : [];
|
|
1519
|
-
const rels = new Set(files
|
|
1520
|
-
.map((f) => (typeof f?.relPath === 'string' ? f.relPath : ''))
|
|
1521
|
-
.filter((p) => Boolean(p)));
|
|
1522
|
-
const hasPrefix = (prefix) => Array.from(rels).some((p) => p === prefix || p.startsWith(prefix + '/'));
|
|
1523
|
-
const keyFiles = [];
|
|
1524
|
-
const sorted = Array.from(rels).sort();
|
|
1525
|
-
const targets = sorted.filter((p) => p.startsWith('target/'));
|
|
1526
|
-
const assetPlan = sorted.filter((p) => p === 'assets/asset-plan.json');
|
|
1527
|
-
const assets = sorted.filter((p) => p.startsWith('assets/') && p !== 'assets/asset-plan.json');
|
|
1528
|
-
const refined = sorted.filter((p) => p.startsWith('refined/'));
|
|
1529
|
-
const drafts = sorted.filter((p) => p.startsWith('drafts/'));
|
|
1530
|
-
if (targets.length)
|
|
1531
|
-
keyFiles.push(targets[0]);
|
|
1532
|
-
if (assetPlan.length)
|
|
1533
|
-
keyFiles.push(assetPlan[0]);
|
|
1534
|
-
if (assets.length)
|
|
1535
|
-
keyFiles.push(...assets.slice(0, 3));
|
|
1536
|
-
if (refined.length)
|
|
1537
|
-
keyFiles.push(...refined.slice(0, 3));
|
|
1538
|
-
if (drafts.length)
|
|
1539
|
-
keyFiles.push(...drafts.slice(0, 3));
|
|
1540
|
-
if (keyFiles.length) {
|
|
1541
|
-
const unique = Array.from(new Set(keyFiles));
|
|
1542
|
-
console.error(`Key files: ${unique.join(', ')}${sorted.length > unique.length ? ` (+${sorted.length - unique.length} more)` : ''}`);
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
catch {
|
|
1546
|
-
// Ignore summary failures.
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
1550
|
-
console.log(pretty(wrap('design.watch', run, { runId, ...(syncDir ? { syncDir } : {}) })));
|
|
1551
|
-
process.exit(0);
|
|
1552
|
-
}
|
|
1553
|
-
if (verb === 'share') {
|
|
1554
|
-
const action = (sub || 'status').trim().toLowerCase();
|
|
1555
|
-
const input = await readJsonArgs(command.slice(2), flags);
|
|
1556
|
-
const runId = String(input.runId ?? '').trim();
|
|
1557
|
-
if (!runId)
|
|
1558
|
-
throw new Error('Missing required argument: runId');
|
|
1559
|
-
if (action === 'status') {
|
|
1560
|
-
const res = await client.getRunShareStatus(runId);
|
|
1561
|
-
console.log(pretty(res));
|
|
1562
|
-
process.exit(0);
|
|
1563
|
-
}
|
|
1564
|
-
if (action === 'enable' || action === 'create') {
|
|
1565
|
-
const rotate = input.rotate === true;
|
|
1566
|
-
const res = await client.createRunShare(runId, rotate ? { rotate: true } : {});
|
|
1567
|
-
console.log(pretty(res));
|
|
1568
|
-
process.exit(0);
|
|
1569
|
-
}
|
|
1570
|
-
if (action === 'revoke') {
|
|
1571
|
-
const res = await client.revokeRunShare(runId);
|
|
1572
|
-
console.log(pretty(res));
|
|
1573
|
-
process.exit(0);
|
|
1574
|
-
}
|
|
1575
|
-
throw new Error(`Unknown share action: ${action}. Expected status|enable|revoke.`);
|
|
1576
|
-
}
|
|
1577
|
-
if (verb === 'get') {
|
|
1578
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1579
|
-
const runId = String(input.runId ?? '').trim();
|
|
1580
|
-
if (!runId)
|
|
1581
|
-
throw new Error('Missing required argument: runId');
|
|
1582
|
-
const run = await client.getRun(runId);
|
|
1583
|
-
console.log(pretty(wrap('design.get', run, { runId })));
|
|
1584
|
-
process.exit(0);
|
|
1585
|
-
}
|
|
1586
|
-
if (verb === 'events') {
|
|
1587
|
-
const input = await readJsonArgs(command.slice(1), flags);
|
|
1588
|
-
const runId = String(input.runId ?? '').trim();
|
|
1589
|
-
if (!runId)
|
|
1590
|
-
throw new Error('Missing required argument: runId');
|
|
1591
|
-
const events = await client.getRunEvents(runId);
|
|
1592
|
-
console.log(pretty(wrap('design.events', events, { runId })));
|
|
1118
|
+
console.log(formatToolResponseMarkdown(wrap('design.check', payload, { runId })));
|
|
1593
1119
|
process.exit(0);
|
|
1594
1120
|
}
|
|
1595
|
-
if (verb === 'list') {
|
|
1596
|
-
const input = await readJsonArgsOptional(command.slice(1), flags);
|
|
1597
|
-
const page = typeof input.page === 'number' ? input.page : undefined;
|
|
1598
|
-
const limit = typeof input.limit === 'number' ? input.limit : undefined;
|
|
1599
|
-
const runs = await client.listRuns({ page, limit });
|
|
1600
|
-
console.log(pretty(wrap('design.list', runs)));
|
|
1601
|
-
process.exit(0);
|
|
1602
|
-
}
|
|
1603
|
-
if (verb === 'artifacts' && sub === 'list') {
|
|
1604
|
-
const positional = parseArtifactsListPositional(command.slice(2));
|
|
1605
|
-
const input = positional ? { runId: positional.runId } : await readJsonArgs(command.slice(2), flags);
|
|
1606
|
-
const runId = String(input.runId ?? '').trim();
|
|
1607
|
-
if (!runId)
|
|
1608
|
-
throw new Error('Missing required argument: runId');
|
|
1609
|
-
const detail = await client.getRun(runId);
|
|
1610
|
-
const outputs = Array.isArray(detail?.run?.outputs) ? detail.run.outputs : [];
|
|
1611
|
-
console.log(pretty(wrap('design.artifacts.list', { artifacts: outputs }, { runId })));
|
|
1612
|
-
process.exit(0);
|
|
1613
|
-
}
|
|
1614
|
-
if (verb === 'artifacts' && sub === 'download') {
|
|
1615
|
-
const positional = parseArtifactsDownloadPositional(command.slice(2));
|
|
1616
|
-
const input = positional ? { runId: positional.runId, artifactId: positional.artifactId } : await readJsonArgs(command.slice(2), flags);
|
|
1617
|
-
const runId = String(input.runId ?? '').trim();
|
|
1618
|
-
const artifactId = String(input.artifactId ?? '').trim();
|
|
1619
|
-
if (!runId)
|
|
1620
|
-
throw new Error('Missing required argument: runId');
|
|
1621
|
-
if (!artifactId)
|
|
1622
|
-
throw new Error('Missing required argument: artifactId');
|
|
1623
|
-
const detail = await client.getRun(runId);
|
|
1624
|
-
const outputs = Array.isArray(detail?.run?.outputs) ? detail.run.outputs : [];
|
|
1625
|
-
const found = outputs.find((entry) => String(entry?.id ?? '') === artifactId) || null;
|
|
1626
|
-
const hint = await (async () => {
|
|
1627
|
-
const url = typeof found?.url === 'string' ? found.url.trim() : '';
|
|
1628
|
-
if (url) {
|
|
1629
|
-
try {
|
|
1630
|
-
const u = url.startsWith('http://') || url.startsWith('https://')
|
|
1631
|
-
? new URL(url)
|
|
1632
|
-
: new URL(url, 'https://example.invalid');
|
|
1633
|
-
return u.pathname.split('/').filter(Boolean).pop() || undefined;
|
|
1634
|
-
}
|
|
1635
|
-
catch {
|
|
1636
|
-
return url.split('/').filter(Boolean).pop() || undefined;
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
try {
|
|
1640
|
-
const artifacts = await client.listArtifacts(runId);
|
|
1641
|
-
const record = Array.isArray(artifacts?.artifacts)
|
|
1642
|
-
? artifacts.artifacts.find((a) => String(a?.id ?? '') === artifactId)
|
|
1643
|
-
: null;
|
|
1644
|
-
const storageKey = typeof record?.storageKey === 'string' ? record.storageKey.trim() : '';
|
|
1645
|
-
return storageKey ? storageKey.split('/').filter(Boolean).pop() || undefined : undefined;
|
|
1646
|
-
}
|
|
1647
|
-
catch {
|
|
1648
|
-
return undefined;
|
|
1649
|
-
}
|
|
1650
|
-
})();
|
|
1651
|
-
const downloaded = await (async () => {
|
|
1652
|
-
try {
|
|
1653
|
-
return await client.downloadArtifactToCache(runId, artifactId, { fileNameHint: hint });
|
|
1654
|
-
}
|
|
1655
|
-
catch (error) {
|
|
1656
|
-
const url = typeof found?.url === 'string' ? found.url.trim() : '';
|
|
1657
|
-
if (url) {
|
|
1658
|
-
return await client.downloadUrlToCache(runId, artifactId, url, { fileNameHint: hint });
|
|
1659
|
-
}
|
|
1660
|
-
throw error;
|
|
1661
|
-
}
|
|
1662
|
-
})();
|
|
1663
|
-
console.log(pretty(wrap('design.artifacts.download', downloaded, { runId, artifactId })));
|
|
1664
|
-
process.exit(0);
|
|
1665
|
-
}
|
|
1666
|
-
if (verb === 'artifacts') {
|
|
1667
|
-
throw new Error('Usage: artifacts list <runId> | artifacts download <runId>/<artifactId>\n' +
|
|
1668
|
-
'Tip: you can still use --json or pipe a JSON object via stdin.');
|
|
1669
|
-
}
|
|
1670
1121
|
// Should be unreachable due to the verb guard.
|
|
1671
1122
|
throw new Error(`Unknown command: ${verb}`);
|
|
1672
1123
|
}
|
|
@@ -1759,6 +1210,11 @@ async function main() {
|
|
|
1759
1210
|
console.log(JSON.stringify(safe, null, 2));
|
|
1760
1211
|
process.exit(0);
|
|
1761
1212
|
}
|
|
1213
|
+
if (verb) {
|
|
1214
|
+
console.error(`Unknown command: ${verb}`);
|
|
1215
|
+
printHelp();
|
|
1216
|
+
process.exit(1);
|
|
1217
|
+
}
|
|
1762
1218
|
// Default: run MCP server.
|
|
1763
1219
|
const cfg = await loadMergedConfig(configPath, { designOrigin, loginOrigin });
|
|
1764
1220
|
const sessionToken = process.env.DESIGN_MCP_SESSION_TOKEN?.trim();
|