@pro-vi/designer 0.3.4 → 0.3.6
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/cli.js +34 -9
- package/dist/designer-controller.js +19 -6
- package/dist/mcp-server.js +6 -2
- package/dist/setup.js +2 -2
- package/dist/ui-anchors.js +10 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -66,7 +66,8 @@ async function main() {
|
|
|
66
66
|
const res = await c.iterate(prompt, {
|
|
67
67
|
file: flags.file,
|
|
68
68
|
timeoutMs: flags.timeoutMs ? Number(flags.timeoutMs) : undefined,
|
|
69
|
-
stabilityMs: flags.stabilityMs ? Number(flags.stabilityMs) : undefined
|
|
69
|
+
stabilityMs: flags.stabilityMs ? Number(flags.stabilityMs) : undefined,
|
|
70
|
+
decisive: Boolean(flags.decisive)
|
|
70
71
|
});
|
|
71
72
|
if (res.url)
|
|
72
73
|
console.log(`\nTaste here: ${res.url}\n`);
|
|
@@ -343,6 +344,7 @@ Flags:
|
|
|
343
344
|
--file <f.html> switch to this file before prompting
|
|
344
345
|
--timeoutMs <n> default 20 minutes
|
|
345
346
|
--stabilityMs <n> default 4 seconds
|
|
347
|
+
--decisive tell Claude not to stop on clarifying questions; pick defaults and proceed
|
|
346
348
|
|
|
347
349
|
Output: prints 'Taste here: <url>' then JSON metadata (done, newFiles, htmlPath, screenshotPath,
|
|
348
350
|
htmlHash, chatReply). HTML is written to disk (read htmlPath if needed); it's not inline.
|
|
@@ -350,6 +352,10 @@ htmlHash, chatReply). HTML is written to disk (read htmlPath if needed); it's no
|
|
|
350
352
|
Auto-appended to every prompt: 'Keep all generated files at the project root; no subfolders.'
|
|
351
353
|
Override by explicitly contradicting it in your prompt text.
|
|
352
354
|
|
|
355
|
+
With --decisive, also append: 'If you would otherwise stop to ask clarifying questions, do not.
|
|
356
|
+
Choose the most defensible answer for each axis yourself and proceed.' Use when you want Claude
|
|
357
|
+
to commit to a direction instead of blocking on the clarifying-questions affordance.
|
|
358
|
+
|
|
353
359
|
Examples:
|
|
354
360
|
designer prompt "add a Remember-me checkbox" --key feat-x
|
|
355
361
|
designer prompt --prompt-file ./brief.md --key feat-x
|
|
@@ -536,18 +542,37 @@ async function checkOnDesignSurface() {
|
|
|
536
542
|
const port = process.env.DESIGNER_CDP || '9222';
|
|
537
543
|
try {
|
|
538
544
|
const res = await fetch(`http://127.0.0.1:${port}/json/list`);
|
|
539
|
-
if (!res.ok)
|
|
540
|
-
return {
|
|
545
|
+
if (!res.ok) {
|
|
546
|
+
return {
|
|
547
|
+
name: 'claude.ai/design tab',
|
|
548
|
+
status: 'fail',
|
|
549
|
+
detail: `CDP HTTP ${res.status} — debug Chrome may have died. Run \`designer setup\` to relaunch it.`
|
|
550
|
+
};
|
|
551
|
+
}
|
|
541
552
|
const tabs = await res.json();
|
|
542
553
|
const onDesign = tabs.find((t) => t.url && /claude\.ai\/design/.test(t.url));
|
|
543
|
-
if (!onDesign)
|
|
544
|
-
return {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
554
|
+
if (!onDesign) {
|
|
555
|
+
return {
|
|
556
|
+
name: 'claude.ai/design tab',
|
|
557
|
+
status: 'warn',
|
|
558
|
+
detail: 'no tab on claude.ai/design in the debug Chrome window. Open https://claude.ai/design THERE (not in your normal Chrome — they are separate profiles with separate cookies).'
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
if (/login|sign in/i.test(onDesign.title || '')) {
|
|
562
|
+
return {
|
|
563
|
+
name: 'signed in to claude.ai/design',
|
|
564
|
+
status: 'fail',
|
|
565
|
+
detail: 'on a login page. Sign in INSIDE the debug Chrome window (not your normal Chrome — that profile is signed in but its cookies are not shared).'
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
return { name: 'signed in to claude.ai/design', status: 'ok', detail: onDesign.url };
|
|
548
569
|
}
|
|
549
570
|
catch {
|
|
550
|
-
return {
|
|
571
|
+
return {
|
|
572
|
+
name: 'claude.ai/design tab',
|
|
573
|
+
status: 'fail',
|
|
574
|
+
detail: 'debug Chrome unreachable. Run `designer setup` to launch it.'
|
|
575
|
+
};
|
|
551
576
|
}
|
|
552
577
|
}
|
|
553
578
|
function checkSelectors() {
|
|
@@ -10,6 +10,7 @@ import { REPO_ROOT } from "./repo-root.js";
|
|
|
10
10
|
import { ensureCdpUp } from "./cdp-ensure.js";
|
|
11
11
|
const DESIGN_HOME = 'https://claude.ai/design';
|
|
12
12
|
const FLAT_LAYOUT_SUFFIX = '\n\nFile layout: keep all generated files at the project root. No subfolders.';
|
|
13
|
+
const DECISIVE_SUFFIX = '\n\nIf you would otherwise stop to ask clarifying questions, do not. Choose the most defensible answer for each axis yourself and proceed. Note your assumption in a one-line `<!-- assumed: ... -->` comment at the top of the relevant file so I can override on the next turn.';
|
|
13
14
|
function loadSelectors() {
|
|
14
15
|
const base = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'selectors.json'), 'utf8'));
|
|
15
16
|
const overridePath = path.join(os.homedir(), '.designer', 'selectors.override.json');
|
|
@@ -59,15 +60,26 @@ export class DesignerController {
|
|
|
59
60
|
const url = await this.currentUrl();
|
|
60
61
|
const inSession = /\/design\/p\/[a-f0-9-]+/i.test(url);
|
|
61
62
|
const availableFiles = inSession ? await this.listFiles().catch(() => []) : [];
|
|
63
|
+
const awaitingClarification = inSession ? await this.detectAwaitingClarification() : false;
|
|
62
64
|
return {
|
|
63
65
|
key: this.key,
|
|
64
66
|
stored,
|
|
65
67
|
currentUrl: url,
|
|
66
68
|
inSession,
|
|
67
69
|
onHome: /\/design\/?$/.test(url) || url.endsWith('/design'),
|
|
68
|
-
availableFiles
|
|
70
|
+
availableFiles,
|
|
71
|
+
awaitingClarification
|
|
69
72
|
};
|
|
70
73
|
}
|
|
74
|
+
async detectAwaitingClarification() {
|
|
75
|
+
const turns = await this.getChatTurns().catch(() => []);
|
|
76
|
+
if (turns.length === 0)
|
|
77
|
+
return false;
|
|
78
|
+
const last = turns[turns.length - 1];
|
|
79
|
+
if (!last || last.role !== 'assistant')
|
|
80
|
+
return false;
|
|
81
|
+
return /Claude has some questions/i.test(last.text);
|
|
82
|
+
}
|
|
71
83
|
async session({ action = 'status', name, fidelity = 'wireframe' } = {}) {
|
|
72
84
|
if (action === 'status')
|
|
73
85
|
return this.getStatus();
|
|
@@ -204,12 +216,13 @@ export class DesignerController {
|
|
|
204
216
|
return true;
|
|
205
217
|
})()`);
|
|
206
218
|
}
|
|
207
|
-
async sendPrompt(prompt) {
|
|
219
|
+
async sendPrompt(prompt, { decisive = false } = {}) {
|
|
208
220
|
const before = await this.fetchServedHtml();
|
|
209
221
|
this._preSendHtml = before.html;
|
|
210
|
-
const effective = prompt + FLAT_LAYOUT_SUFFIX;
|
|
222
|
+
const effective = prompt + FLAT_LAYOUT_SUFFIX + (decisive ? DECISIVE_SUFFIX : '');
|
|
211
223
|
await this._submitPrompt(effective);
|
|
212
|
-
|
|
224
|
+
const suffixApplied = decisive ? 'flat_layout+decisive' : 'flat_layout';
|
|
225
|
+
appendHistory(this.key, { kind: 'prompt', prompt, suffixApplied });
|
|
213
226
|
return { ok: true };
|
|
214
227
|
}
|
|
215
228
|
async waitForGenerationDone({ timeoutMs = 20 * 60_000, stabilityMs = 4000, pollMs = 1500 } = {}) {
|
|
@@ -274,13 +287,13 @@ export class DesignerController {
|
|
|
274
287
|
throw new Error(`No active session for key=${this.key}. Call createSession first.`);
|
|
275
288
|
await this.resumeSession();
|
|
276
289
|
}
|
|
277
|
-
async iterate(prompt, { file, timeoutMs, stabilityMs } = {}) {
|
|
290
|
+
async iterate(prompt, { file, timeoutMs, stabilityMs, decisive } = {}) {
|
|
278
291
|
await this._ensureInSession();
|
|
279
292
|
if (file)
|
|
280
293
|
await this.openFile(file);
|
|
281
294
|
const preFiles = await this.listFiles().catch(() => []);
|
|
282
295
|
const preChatCount = (await this.getChatTurns()).length;
|
|
283
|
-
await this.sendPrompt(prompt);
|
|
296
|
+
await this.sendPrompt(prompt, { decisive });
|
|
284
297
|
const done = await this.waitForGenerationDone({ timeoutMs, stabilityMs });
|
|
285
298
|
const postFiles = await this.listFiles().catch(() => []);
|
|
286
299
|
const postTurns = await this.getChatTurns();
|
package/dist/mcp-server.js
CHANGED
|
@@ -37,9 +37,13 @@ server.registerTool('designer_prompt', {
|
|
|
37
37
|
prompt: z.string(),
|
|
38
38
|
file: z.string().optional().describe('Switch to this file before sending (targets the prompt at it).'),
|
|
39
39
|
timeoutMs: z.number().optional().describe('Default 20m. Hi-fi generations can take 15+ min; bump this for complex multi-variant prompts.'),
|
|
40
|
-
stabilityMs: z.number().optional().describe('Default 4s.')
|
|
40
|
+
stabilityMs: z.number().optional().describe('Default 4s.'),
|
|
41
|
+
decisive: z
|
|
42
|
+
.boolean()
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("Append a 'do not stop to ask clarifying questions' instruction. Use when you want Claude to commit to a direction (pick defensible defaults itself, document the assumption inline) instead of blocking on the ephemeral clarifying-questions affordance — which disappears on refresh and has no stable DOM contract to scrape.")
|
|
41
45
|
}
|
|
42
|
-
}, async ({ key, prompt, file, timeoutMs, stabilityMs }) => textResult(await getController(key).iterate(prompt, { file, timeoutMs, stabilityMs })));
|
|
46
|
+
}, async ({ key, prompt, file, timeoutMs, stabilityMs, decisive }) => textResult(await getController(key).iterate(prompt, { file, timeoutMs, stabilityMs, decisive })));
|
|
43
47
|
server.registerTool('designer_ask', {
|
|
44
48
|
description: "Q&A with the design assistant — text-only, doesn't change any file. Use for 'why did you choose X?', 'compare A vs B', 'suggest 3 alternatives before I commit'. Returns the assistant's reply.",
|
|
45
49
|
inputSchema: {
|
package/dist/setup.js
CHANGED
|
@@ -169,11 +169,11 @@ async function step4SignIn(port) {
|
|
|
169
169
|
log('login', 'ok', `Signed in. Tab on ${tab.url.replace(/\?.*$/, '')}`);
|
|
170
170
|
return true;
|
|
171
171
|
}
|
|
172
|
-
log('login', 'wait', 'Sign in to Claude in the
|
|
172
|
+
log('login', 'wait', 'Sign in to Claude in the DEBUG Chrome window I just opened (it is a separate window with no extensions/bookmarks — NOT your normal Chrome; the two have separate cookie jars). Then navigate to claude.ai/design. I am polling.');
|
|
173
173
|
const ok = await pollUntil('login', async () => (await getDesignTab(port)) !== null, {
|
|
174
174
|
intervalMs: 2000,
|
|
175
175
|
timeoutMs: 10 * 60_000,
|
|
176
|
-
reminder: 'Still waiting for a tab on claude.ai/design (not
|
|
176
|
+
reminder: 'Still waiting for a tab on claude.ai/design in the debug window (signing into your normal Chrome will not help — different profile).',
|
|
177
177
|
hint60s: "If Chrome shows a Google 'new device' or 2FA prompt, complete that first — setup is waiting on you."
|
|
178
178
|
});
|
|
179
179
|
if (!ok) {
|
package/dist/ui-anchors.js
CHANGED
|
@@ -77,7 +77,11 @@ export const UI_ANCHORS = [
|
|
|
77
77
|
category: 'session',
|
|
78
78
|
description: 'html-viewer-iframe (design preview)',
|
|
79
79
|
requires: 'session',
|
|
80
|
-
check: async (b) =>
|
|
80
|
+
check: async (b, url) => {
|
|
81
|
+
if (!/[?&]file=/.test(url))
|
|
82
|
+
return { ok: true, detail: '(no file open — iframe not expected)' };
|
|
83
|
+
return { ok: await hasSelector(b, '[data-testid="html-viewer-iframe"]') };
|
|
84
|
+
}
|
|
81
85
|
},
|
|
82
86
|
{
|
|
83
87
|
id: 'session.chatMessages',
|
|
@@ -91,10 +95,12 @@ export const UI_ANCHORS = [
|
|
|
91
95
|
category: 'pattern',
|
|
92
96
|
description: 'iframe src is claudeusercontent.com with signed ?t= token',
|
|
93
97
|
requires: 'session',
|
|
94
|
-
check: async (b) => {
|
|
98
|
+
check: async (b, url) => {
|
|
99
|
+
if (!/[?&]file=/.test(url))
|
|
100
|
+
return { ok: true, detail: '(no file open — iframe not expected)' };
|
|
95
101
|
const src = await b.evalValue(`(() => { const el = document.querySelector('[data-testid="html-viewer-iframe"]'); return (el && el.src) || ''; })()`).catch(() => '');
|
|
96
102
|
if (!src)
|
|
97
|
-
return { ok: false, detail: '
|
|
103
|
+
return { ok: false, detail: 'file param present but iframe missing src' };
|
|
98
104
|
const ok = /claudeusercontent\.com/.test(src) && /[?&]t=/.test(src);
|
|
99
105
|
return { ok, detail: ok ? undefined : `src=${src.slice(0, 120)}...` };
|
|
100
106
|
}
|
|
@@ -182,7 +188,7 @@ export const UI_ANCHORS = [
|
|
|
182
188
|
}
|
|
183
189
|
const match = url.match(/[?&]file=([^&]+)/);
|
|
184
190
|
if (match && match[1]) {
|
|
185
|
-
const activeFile = decodeURIComponent(match[1]);
|
|
191
|
+
const activeFile = decodeURIComponent(match[1].replace(/\+/g, ' '));
|
|
186
192
|
if (!files.includes(activeFile)) {
|
|
187
193
|
return {
|
|
188
194
|
ok: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pro-vi/designer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP + CLI for autonomous iteration of claude.ai/design — drives the design surface via agent-browser, downloads handoff bundles, and exposes a tasting harness for full-viewport variant comparison.",
|
|
6
6
|
"license": "MIT",
|