@aayambansal/squint 0.2.0 → 0.2.2
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 +34 -7
- package/dist/cdp-SFWNP7OA.js +11 -0
- package/dist/{chrome-K4BKFOYS.js → chrome-RD7XQ767.js} +2 -2
- package/dist/{chunk-ZJOU46X4.js → chunk-2IW2MMCY.js} +59 -2
- package/dist/{chunk-4MMIJXYD.js → chunk-73ULM6HF.js} +19 -5
- package/dist/{chunk-7XUORM3K.js → chunk-LCS47EAG.js} +3 -5
- package/dist/{chunk-VJGE7HSP.js → chunk-OC6RU6XH.js} +50 -0
- package/dist/{chunk-FPTBBHOH.js → chunk-OTG4ZB4R.js} +1 -1
- package/dist/{chunk-5IR2AZVK.js → chunk-P3H4N2EN.js} +58 -3
- package/dist/{chunk-Z46MXU64.js → chunk-P3V5CWH5.js} +1 -1
- package/dist/{chunk-W6MYXQIU.js → chunk-VNLM53RJ.js} +60 -10
- package/dist/chunk-XZKQZKEE.js +80 -0
- package/dist/{chunk-WTA6YYBY.js → chunk-YHRAOBI2.js} +17 -0
- package/dist/cli.js +360 -93
- package/dist/{families-RVP5BWQD.js → families-2G5SLLY7.js} +1 -2
- package/dist/{init-7AYGAKOS.js → init-LSYB32NS.js} +1 -1
- package/dist/{preview-LTWJK3LF.js → preview-JJJLDF7H.js} +6 -4
- package/dist/run-XHLCTF6V.js +8 -0
- package/dist/shots-LRYYMTPK.js +12 -0
- package/dist/skills-UGHU22BS.js +15 -0
- package/dist/{snapshot-KKUY5RR6.js → snapshot-H6VYFHLN.js} +3 -1
- package/dist/{source-MEXHWVP4.js → source-ZZU245VN.js} +1 -1
- package/dist/{variants-4NCQXTP7.js → variants-7A7723L7.js} +4 -5
- package/package.json +1 -1
- package/dist/cdp-RTWPFIX5.js +0 -9
- package/dist/chunk-NT2HR4RD.js +0 -63
- package/dist/shots-VJBLDMF3.js +0 -66
- package/dist/{chunk-MCYQXOE7.js → chunk-2LRIKWBU.js} +18 -18
package/dist/cli.js
CHANGED
|
@@ -2,14 +2,21 @@
|
|
|
2
2
|
import {
|
|
3
3
|
DevServer,
|
|
4
4
|
buildFixPrompt,
|
|
5
|
-
detectDevCommand
|
|
6
|
-
|
|
5
|
+
detectDevCommand,
|
|
6
|
+
screenshotVariants
|
|
7
|
+
} from "./chunk-2IW2MMCY.js";
|
|
7
8
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
applyVariant,
|
|
10
|
+
cleanVariants,
|
|
11
|
+
listVariants,
|
|
12
|
+
runVariants
|
|
13
|
+
} from "./chunk-LCS47EAG.js";
|
|
10
14
|
import {
|
|
11
15
|
composePrompt
|
|
12
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-P3H4N2EN.js";
|
|
17
|
+
import {
|
|
18
|
+
runAgent
|
|
19
|
+
} from "./chunk-OTG4ZB4R.js";
|
|
13
20
|
import {
|
|
14
21
|
buildGatePrompt,
|
|
15
22
|
detectFastGates,
|
|
@@ -21,21 +28,29 @@ import {
|
|
|
21
28
|
buildRuntimeFixPrompt,
|
|
22
29
|
captureViewports,
|
|
23
30
|
clearState,
|
|
31
|
+
comparePulse,
|
|
24
32
|
loadState,
|
|
25
33
|
probeRuntime,
|
|
26
34
|
runtimeSummary,
|
|
27
35
|
saveState
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import "./chunk-
|
|
30
|
-
import
|
|
36
|
+
} from "./chunk-73ULM6HF.js";
|
|
37
|
+
import "./chunk-OC6RU6XH.js";
|
|
38
|
+
import {
|
|
39
|
+
findChrome
|
|
40
|
+
} from "./chunk-P3V5CWH5.js";
|
|
31
41
|
import {
|
|
32
42
|
detectEngines,
|
|
33
43
|
getEngine
|
|
34
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-2LRIKWBU.js";
|
|
45
|
+
import {
|
|
46
|
+
enrich
|
|
47
|
+
} from "./chunk-XZKQZKEE.js";
|
|
35
48
|
import {
|
|
49
|
+
diffStatSince,
|
|
50
|
+
isGitRepo,
|
|
36
51
|
restoreSnapshot,
|
|
37
52
|
takeSnapshot
|
|
38
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-YHRAOBI2.js";
|
|
39
54
|
|
|
40
55
|
// src/cli.tsx
|
|
41
56
|
import { Command } from "commander";
|
|
@@ -148,7 +163,8 @@ var Session = class {
|
|
|
148
163
|
devUrl: null,
|
|
149
164
|
totals: { costUsd: 0, turns: 0 },
|
|
150
165
|
queue: [],
|
|
151
|
-
mode: "safe"
|
|
166
|
+
mode: "safe",
|
|
167
|
+
problems: []
|
|
152
168
|
};
|
|
153
169
|
if (opts.autoDev && detectDevCommand(opts.cwd)) {
|
|
154
170
|
this.devServer().start();
|
|
@@ -175,10 +191,13 @@ var Session = class {
|
|
|
175
191
|
sessionId;
|
|
176
192
|
dev = null;
|
|
177
193
|
abort = null;
|
|
178
|
-
|
|
194
|
+
/** Full problem records; state carries the summaries. */
|
|
195
|
+
problemPrompts = /* @__PURE__ */ new Map();
|
|
196
|
+
nextProblemId = 0;
|
|
179
197
|
checkpoints = [];
|
|
180
198
|
fixAttempts = 0;
|
|
181
199
|
reviewTipShown = false;
|
|
200
|
+
lastPulse = null;
|
|
182
201
|
startedAt = Date.now();
|
|
183
202
|
subscribe(listener) {
|
|
184
203
|
this.listeners.add(listener);
|
|
@@ -198,6 +217,47 @@ var Session = class {
|
|
|
198
217
|
note(text) {
|
|
199
218
|
this.push("status", text);
|
|
200
219
|
}
|
|
220
|
+
/** Register a finding; a fresh finding from a source supersedes its old one. */
|
|
221
|
+
addProblem(source, summary, prompt) {
|
|
222
|
+
const kept = this.state.problems.filter((p) => p.source !== source);
|
|
223
|
+
for (const gone of this.state.problems) {
|
|
224
|
+
if (gone.source === source) this.problemPrompts.delete(gone.id);
|
|
225
|
+
}
|
|
226
|
+
this.nextProblemId += 1;
|
|
227
|
+
this.problemPrompts.set(this.nextProblemId, prompt);
|
|
228
|
+
this.notify({ problems: [...kept, { id: this.nextProblemId, source, summary }] });
|
|
229
|
+
}
|
|
230
|
+
clearProblems(source) {
|
|
231
|
+
const removed = this.state.problems.filter((p) => p.source === source);
|
|
232
|
+
if (removed.length === 0) return;
|
|
233
|
+
for (const problem of removed) this.problemPrompts.delete(problem.id);
|
|
234
|
+
this.notify({ problems: this.state.problems.filter((p) => p.source !== source) });
|
|
235
|
+
}
|
|
236
|
+
/** One prompt covering the given problems, oldest first. */
|
|
237
|
+
combinedFixPrompt(problems) {
|
|
238
|
+
const sections = problems.map(
|
|
239
|
+
(p) => this.problemPrompts.get(p.id) ?? `Fix this reported problem: ${p.summary}`
|
|
240
|
+
);
|
|
241
|
+
return sections.join("\n\n---\n\n");
|
|
242
|
+
}
|
|
243
|
+
dispatchFix(problems) {
|
|
244
|
+
if (problems.length === 0) return;
|
|
245
|
+
const prompt = this.combinedFixPrompt(problems);
|
|
246
|
+
const display = `\u26D1 fix: ${problems.map((p) => p.source).join(" + ")}`;
|
|
247
|
+
for (const problem of problems) this.problemPrompts.delete(problem.id);
|
|
248
|
+
this.notify({ problems: this.state.problems.filter((p) => !problems.includes(p)) });
|
|
249
|
+
void this.runTurn(prompt, display);
|
|
250
|
+
}
|
|
251
|
+
/** Launch a capped auto-fix turn over all open problems. Returns true if launched. */
|
|
252
|
+
maybeAutoFix() {
|
|
253
|
+
if (!this.opts.autoFix || this.fixAttempts >= MAX_AUTO_FIX_ATTEMPTS) return false;
|
|
254
|
+
if (this.state.problems.length === 0) return false;
|
|
255
|
+
this.fixAttempts += 1;
|
|
256
|
+
this.push("status", `auto-fix attempt ${this.fixAttempts}/${MAX_AUTO_FIX_ATTEMPTS}`);
|
|
257
|
+
this.notify({ running: false });
|
|
258
|
+
this.dispatchFix([...this.state.problems]);
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
201
261
|
setMode(mode) {
|
|
202
262
|
this.notify({ mode });
|
|
203
263
|
const hint = mode === "plan" ? "read-only: the engine investigates and proposes, edits nothing" : mode === "yolo" ? "no approval friction \u2014 the engine can do anything" : "edits auto-approved inside the workspace";
|
|
@@ -350,7 +410,9 @@ var Session = class {
|
|
|
350
410
|
if (result.ok) {
|
|
351
411
|
const cost = result.costUsd !== void 0 ? ` \xB7 $${result.costUsd.toFixed(2)}` : "";
|
|
352
412
|
const secs = result.durationMs !== void 0 ? ` \xB7 ${(result.durationMs / 1e3).toFixed(0)}s` : "";
|
|
353
|
-
const
|
|
413
|
+
const checkpoint = this.checkpoints.at(-1);
|
|
414
|
+
const stat = checkpoint ? diffStatSince(this.opts.cwd, checkpoint.snapshot) : null;
|
|
415
|
+
const work = stat ? ` \xB7 ${stat}` : this.turnEdits > 0 ? ` \xB7 ${this.turnEdits} edit${this.turnEdits === 1 ? "" : "s"}` : this.turnTools > 0 ? ` \xB7 ${this.turnTools} tool call${this.turnTools === 1 ? "" : "s"}` : "";
|
|
354
416
|
this.push("status", `done${secs}${cost}${work}`);
|
|
355
417
|
this.notify({
|
|
356
418
|
totals: {
|
|
@@ -374,26 +436,22 @@ var Session = class {
|
|
|
374
436
|
const gateResults = await runGates(this.opts.cwd, fastGates);
|
|
375
437
|
const failures = gateResults.filter((r) => !r.ok);
|
|
376
438
|
if (failures.length > 0) {
|
|
377
|
-
this.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
439
|
+
this.addProblem(
|
|
440
|
+
"gates",
|
|
441
|
+
failures.map((f) => f.gate.id).join(" + "),
|
|
442
|
+
buildGatePrompt(failures)
|
|
443
|
+
);
|
|
381
444
|
this.push(
|
|
382
445
|
"error",
|
|
383
446
|
failures.map((f) => `\u2717 ${f.gate.id} \xB7 ${f.outputTail.split("\n").slice(-3).join("\n")}`).join("\n")
|
|
384
447
|
);
|
|
385
|
-
if (this.
|
|
386
|
-
|
|
387
|
-
this.push("status", `auto-fix attempt ${this.fixAttempts}/${MAX_AUTO_FIX_ATTEMPTS}`);
|
|
388
|
-
this.notify({ running: false });
|
|
389
|
-
await this.runTurn(this.pendingFix.prompt, this.pendingFix.display);
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
this.push("status", "type /fix to send them to the engine");
|
|
448
|
+
if (this.maybeAutoFix()) return;
|
|
449
|
+
this.push("status", "/fix sends open problems to the engine \xB7 /problems lists them");
|
|
393
450
|
this.notify({ running: false });
|
|
394
451
|
this.drainQueue();
|
|
395
452
|
return;
|
|
396
453
|
}
|
|
454
|
+
this.clearProblems("gates");
|
|
397
455
|
}
|
|
398
456
|
}
|
|
399
457
|
const dev = this.dev;
|
|
@@ -401,39 +459,28 @@ var Session = class {
|
|
|
401
459
|
await delay(1500);
|
|
402
460
|
const errors = dev.errorsSince(runStart);
|
|
403
461
|
if (errors.length > 0) {
|
|
404
|
-
this.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
462
|
+
this.addProblem(
|
|
463
|
+
"dev",
|
|
464
|
+
`${errors.length} dev server error line(s)`,
|
|
465
|
+
buildFixPrompt(errors, dev.tail(30))
|
|
466
|
+
);
|
|
408
467
|
this.push("error", `dev server: ${errors.length} error line(s)
|
|
409
468
|
${errors.slice(-5).join("\n")}`);
|
|
410
|
-
if (this.
|
|
411
|
-
|
|
412
|
-
this.push("status", `auto-fix attempt ${this.fixAttempts}/${MAX_AUTO_FIX_ATTEMPTS}`);
|
|
413
|
-
this.notify({ running: false });
|
|
414
|
-
await this.runTurn(this.pendingFix.prompt, this.pendingFix.display);
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
this.push("status", "type /fix to send them to the engine");
|
|
469
|
+
if (this.maybeAutoFix()) return;
|
|
470
|
+
this.push("status", "/fix sends open problems to the engine \xB7 /problems lists them");
|
|
418
471
|
} else {
|
|
419
|
-
this.
|
|
472
|
+
this.clearProblems("dev");
|
|
420
473
|
if (this.opts.autoProbe !== false && this.state.devUrl) {
|
|
421
|
-
const
|
|
422
|
-
const summary =
|
|
423
|
-
if (
|
|
424
|
-
this.
|
|
425
|
-
prompt: buildRuntimeFixPrompt(report),
|
|
426
|
-
display: "\u26D1 fix runtime errors"
|
|
427
|
-
};
|
|
474
|
+
const probe = await probeRuntime(this.state.devUrl, this.opts.cwd);
|
|
475
|
+
const summary = probe ? runtimeSummary(probe.report) : null;
|
|
476
|
+
if (probe && summary) {
|
|
477
|
+
this.addProblem("runtime", summary, buildRuntimeFixPrompt(probe.report));
|
|
428
478
|
this.push("error", `runtime: ${summary}`);
|
|
429
|
-
if (this.
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
this.push("status", "type /fix to send them to the engine");
|
|
479
|
+
if (this.maybeAutoFix()) return;
|
|
480
|
+
this.push("status", "/fix sends open problems to the engine \xB7 /problems lists them");
|
|
481
|
+
} else if (probe) {
|
|
482
|
+
this.clearProblems("runtime");
|
|
483
|
+
await this.visualPulse(probe.pulsePath);
|
|
437
484
|
}
|
|
438
485
|
}
|
|
439
486
|
if (!this.reviewTipShown && this.state.devUrl) {
|
|
@@ -457,7 +504,12 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
457
504
|
if (this.checkpoints.length > 20) this.checkpoints.shift();
|
|
458
505
|
}
|
|
459
506
|
const isFirstTurn = this.sessionId === void 0;
|
|
460
|
-
|
|
507
|
+
let prompt = isFirstTurn ? composePrompt(ask, { cwd: this.opts.cwd, firstTurn: true }) : ask;
|
|
508
|
+
const enrichment = enrich(this.opts.cwd, ask);
|
|
509
|
+
if (enrichment.matchedSkills.length > 0) {
|
|
510
|
+
this.push("status", `skills: ${enrichment.matchedSkills.join(", ")}`);
|
|
511
|
+
}
|
|
512
|
+
prompt += enrichment.sections;
|
|
461
513
|
await this.runTurn(prompt, ask);
|
|
462
514
|
}
|
|
463
515
|
/** Restore files to the state before checkpoint `index`; drop it and everything after. */
|
|
@@ -479,6 +531,32 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
479
531
|
this.push("error", `restore failed: ${result.detail ?? "unknown error"}`);
|
|
480
532
|
}
|
|
481
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* Cross-turn visual drift check: compare this turn's pulse screenshot
|
|
536
|
+
* with the previous one and report how much of the page changed.
|
|
537
|
+
* Informational — changes are usually intended; surprises shouldn't be.
|
|
538
|
+
*/
|
|
539
|
+
async visualPulse(pulsePath) {
|
|
540
|
+
if (!pulsePath) return;
|
|
541
|
+
let current;
|
|
542
|
+
try {
|
|
543
|
+
current = (await import("fs")).readFileSync(pulsePath);
|
|
544
|
+
} catch {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const previous = this.lastPulse;
|
|
548
|
+
this.lastPulse = current;
|
|
549
|
+
if (!previous) {
|
|
550
|
+
this.push("status", "visual pulse: baseline captured");
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const pct = await comparePulse(previous, current);
|
|
554
|
+
if (pct === null) return;
|
|
555
|
+
this.push(
|
|
556
|
+
"status",
|
|
557
|
+
pct < 0.5 ? "visual pulse: stable vs last turn" : `visual pulse: ${pct.toFixed(1)}% of the page changed vs last turn`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
482
560
|
/** Screenshot the running app (and watch its runtime where CDP is available). */
|
|
483
561
|
async capture() {
|
|
484
562
|
if (!this.state.devUrl) {
|
|
@@ -502,16 +580,28 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
502
580
|
const summary = runtimeSummary(result.runtime);
|
|
503
581
|
if (summary) {
|
|
504
582
|
this.push("error", `runtime: ${summary}`);
|
|
505
|
-
this.
|
|
506
|
-
this.push("status", "
|
|
583
|
+
this.addProblem("runtime", summary, buildRuntimeFixPrompt(result.runtime));
|
|
584
|
+
this.push("status", "/fix sends open problems to the engine");
|
|
507
585
|
} else {
|
|
586
|
+
this.clearProblems("runtime");
|
|
508
587
|
this.push("status", "runtime clean \u2014 no console errors, exceptions, or failed requests");
|
|
509
588
|
}
|
|
510
589
|
}
|
|
511
590
|
if (result.a11y && result.a11y.length > 0) {
|
|
512
591
|
this.push("error", `a11y: ${result.a11y.length} finding(s)
|
|
513
592
|
${result.a11y.slice(0, 5).join("\n")}`);
|
|
514
|
-
this.
|
|
593
|
+
this.addProblem(
|
|
594
|
+
"a11y",
|
|
595
|
+
`${result.a11y.length} accessibility finding(s)`,
|
|
596
|
+
`Fix these accessibility defects found by an automated sweep of the running app:
|
|
597
|
+
|
|
598
|
+
${result.a11y.join("\n")}
|
|
599
|
+
|
|
600
|
+
They are objective defects, not style preferences.`
|
|
601
|
+
);
|
|
602
|
+
this.push("status", "/review folds these into the fix pass \xB7 /fix sends them directly");
|
|
603
|
+
} else if (result.runtime) {
|
|
604
|
+
this.clearProblems("a11y");
|
|
515
605
|
}
|
|
516
606
|
return result.shots.length > 0 ? result : null;
|
|
517
607
|
}
|
|
@@ -561,11 +651,47 @@ ${result.a11y.slice(0, 5).join("\n")}`);
|
|
|
561
651
|
}
|
|
562
652
|
break;
|
|
563
653
|
}
|
|
564
|
-
case "fix":
|
|
565
|
-
if (
|
|
566
|
-
this.push("status", "nothing to fix \u2014 no
|
|
654
|
+
case "fix": {
|
|
655
|
+
if (this.state.problems.length === 0) {
|
|
656
|
+
this.push("status", "nothing to fix \u2014 no open problems");
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
const index = Number.parseInt(arg, 10);
|
|
660
|
+
if (arg && Number.isInteger(index)) {
|
|
661
|
+
const target = this.state.problems[index - 1];
|
|
662
|
+
if (!target) {
|
|
663
|
+
this.push("status", `usage: /fix [1\u2013${this.state.problems.length}] \u2014 see /problems`);
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
this.dispatchFix([target]);
|
|
567
667
|
} else {
|
|
568
|
-
|
|
668
|
+
this.dispatchFix([...this.state.problems]);
|
|
669
|
+
}
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
case "copy": {
|
|
673
|
+
const last = this.state.items.findLast((i) => i.role === "assistant");
|
|
674
|
+
if (!last) {
|
|
675
|
+
this.push("status", "nothing to copy yet");
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
void import("child_process").then(({ spawn }) => {
|
|
679
|
+
const cmd = process.platform === "darwin" ? spawn("pbcopy") : process.platform === "win32" ? spawn("clip") : spawn("xclip", ["-selection", "clipboard"]);
|
|
680
|
+
cmd.on("error", () => this.push("error", "no clipboard tool found"));
|
|
681
|
+
cmd.on("close", (code) => {
|
|
682
|
+
if (code === 0) this.push("status", `copied last reply (${last.text.length} chars)`);
|
|
683
|
+
});
|
|
684
|
+
cmd.stdin?.end(last.text);
|
|
685
|
+
});
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
case "problems":
|
|
689
|
+
if (this.state.problems.length === 0) {
|
|
690
|
+
this.push("status", "no open problems");
|
|
691
|
+
} else {
|
|
692
|
+
const lines = this.state.problems.map((p, i) => `${i + 1}. [${p.source}] ${p.summary}`);
|
|
693
|
+
this.push("status", `${lines.join("\n")}
|
|
694
|
+
/fix sends all \xB7 /fix <n> targets one`);
|
|
569
695
|
}
|
|
570
696
|
break;
|
|
571
697
|
case "check":
|
|
@@ -587,12 +713,10 @@ ${result.a11y.slice(0, 5).join("\n")}`);
|
|
|
587
713
|
this.drainQueue();
|
|
588
714
|
const failures = results.filter((r) => !r.ok);
|
|
589
715
|
if (failures.length > 0) {
|
|
590
|
-
this.
|
|
591
|
-
|
|
592
|
-
display: `\u26D1 fix failing gates: ${failures.map((f) => f.gate.id).join(", ")}`
|
|
593
|
-
};
|
|
594
|
-
this.push("status", "type /fix to send failures to the engine");
|
|
716
|
+
this.addProblem("gates", failures.map((f) => f.gate.id).join(" + "), buildGatePrompt(failures));
|
|
717
|
+
this.push("status", "/fix sends open problems to the engine \xB7 /problems lists them");
|
|
595
718
|
} else {
|
|
719
|
+
this.clearProblems("gates");
|
|
596
720
|
this.push("status", "all gates passed");
|
|
597
721
|
}
|
|
598
722
|
})();
|
|
@@ -659,6 +783,75 @@ ${result.a11y.slice(0, 5).join("\n")}`);
|
|
|
659
783
|
this.push("status", `resumed ${saved.engine} session${saved.lastAsk ? ` \xB7 "${saved.lastAsk}"` : ""}`);
|
|
660
784
|
break;
|
|
661
785
|
}
|
|
786
|
+
case "variants": {
|
|
787
|
+
const [sub, ...subRest] = arg.split(/\s+/);
|
|
788
|
+
if (sub === "apply") {
|
|
789
|
+
const id = subRest[0];
|
|
790
|
+
if (!id) {
|
|
791
|
+
this.push("status", "usage: /variants apply <id>");
|
|
792
|
+
break;
|
|
793
|
+
}
|
|
794
|
+
const applied = applyVariant(this.opts.cwd, id);
|
|
795
|
+
if (applied.ok) {
|
|
796
|
+
cleanVariants(this.opts.cwd);
|
|
797
|
+
this.push("status", `applied ${id} to the working tree \u2014 review with git diff`);
|
|
798
|
+
} else {
|
|
799
|
+
this.push("error", applied.detail ?? "apply failed");
|
|
800
|
+
}
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
if (sub === "clean") {
|
|
804
|
+
this.push("status", `removed ${cleanVariants(this.opts.cwd)} variant(s)`);
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
if (sub === "list") {
|
|
808
|
+
const ids = listVariants(this.opts.cwd);
|
|
809
|
+
this.push("status", ids.length > 0 ? ids.join(" \xB7 ") : "no variants \u2014 /variants <2-4> <ask>");
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
const n = Number.parseInt(sub ?? "", 10);
|
|
813
|
+
const ask = subRest.join(" ").trim();
|
|
814
|
+
if (!Number.isInteger(n) || n < 2 || n > 4 || ask.length === 0) {
|
|
815
|
+
this.push("status", "usage: /variants <2-4> <ask> \xB7 /variants apply <id> \xB7 list \xB7 clean");
|
|
816
|
+
break;
|
|
817
|
+
}
|
|
818
|
+
if (!isGitRepo(this.opts.cwd)) {
|
|
819
|
+
this.push("error", "variants need a git repo with at least one commit");
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
void (async () => {
|
|
823
|
+
cleanVariants(this.opts.cwd);
|
|
824
|
+
this.push("status", `generating ${n} directions in parallel \u2014 this runs ${n} engine sessions`);
|
|
825
|
+
this.notify({ running: true, runStartedAt: Date.now() });
|
|
826
|
+
const engine = getEngine(this.state.engineId);
|
|
827
|
+
const runs = await runVariants(
|
|
828
|
+
this.opts.cwd,
|
|
829
|
+
ask,
|
|
830
|
+
n,
|
|
831
|
+
engine,
|
|
832
|
+
this.state.model,
|
|
833
|
+
(familyId, text) => this.push("status", `[${familyId}] ${text}`)
|
|
834
|
+
);
|
|
835
|
+
const succeeded = runs.filter((r) => r.result.ok);
|
|
836
|
+
if (succeeded.length > 0 && findChrome() && detectDevCommand(this.opts.cwd)) {
|
|
837
|
+
this.push("status", "capturing one screenshot per variant\u2026");
|
|
838
|
+
const shots = await screenshotVariants(this.opts.cwd, succeeded.map((r) => r.variant));
|
|
839
|
+
for (const shot of shots) {
|
|
840
|
+
this.push(
|
|
841
|
+
shot.path ? "status" : "error",
|
|
842
|
+
`[${shot.familyId}] ${shot.path ?? shot.error ?? "screenshot failed"}`
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
this.notify({ running: false });
|
|
847
|
+
this.push(
|
|
848
|
+
succeeded.length > 0 ? "status" : "error",
|
|
849
|
+
`${succeeded.length}/${runs.length} variants ready \u2014 /variants apply <id> keeps one, /variants clean discards`
|
|
850
|
+
);
|
|
851
|
+
this.drainQueue();
|
|
852
|
+
})();
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
662
855
|
case "clear":
|
|
663
856
|
this.sessionId = void 0;
|
|
664
857
|
clearState(this.opts.cwd);
|
|
@@ -667,7 +860,7 @@ ${result.a11y.slice(0, 5).join("\n")}`);
|
|
|
667
860
|
case "help":
|
|
668
861
|
this.push(
|
|
669
862
|
"status",
|
|
670
|
-
"/engine <id> \xB7 /model <name> \xB7 /
|
|
863
|
+
"/engine <id> \xB7 /model <name> \xB7 /mode plan|safe|yolo \xB7 /dev \xB7 /check (gates) \xB7 /problems \xB7 /fix [n] \xB7 /shot \xB7 /review [focus] \xB7 /variants <2-4> <ask> \xB7 /undo \xB7 /checkpoints \xB7 /restore <n> \xB7 /copy (last reply) \xB7 /resume \xB7 /clear \xB7 /quit"
|
|
671
864
|
);
|
|
672
865
|
break;
|
|
673
866
|
case "quit":
|
|
@@ -1138,6 +1331,11 @@ function App({
|
|
|
1138
1331
|
return /* @__PURE__ */ jsx3(ThemeProvider, { value: theme2, children: /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
1139
1332
|
/* @__PURE__ */ jsx3(Static, { items: state.items, children: (item) => /* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsx3(MessageLine, { message: item }) }, item.id) }),
|
|
1140
1333
|
state.liveText.length > 0 && /* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsx3(Markdown, { text: state.liveText }) }),
|
|
1334
|
+
state.items.length === 0 && state.liveText.length === 0 && !state.running && /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", marginTop: 1, children: [
|
|
1335
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: "describe what to build \u2014 or try:" }),
|
|
1336
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: " /dev start the preview server \xB7 /review after a change \xB7 /variants 3 explore wide" }),
|
|
1337
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: " shift+tab cycles plan/safe/yolo \xB7 type while the agent works to queue asks" })
|
|
1338
|
+
] }),
|
|
1141
1339
|
state.running && /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(WorkingLine, { startedAt: state.runStartedAt }) }),
|
|
1142
1340
|
state.queue.map((queued, index) => /* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsxs3(Text3, { color: theme2.dim, children: [
|
|
1143
1341
|
"\u22EF queued: ",
|
|
@@ -1169,14 +1367,21 @@ function App({
|
|
|
1169
1367
|
path3.basename(cwd),
|
|
1170
1368
|
devBadge,
|
|
1171
1369
|
totalsBadge,
|
|
1172
|
-
|
|
1370
|
+
state.problems.length > 0 && /* @__PURE__ */ jsxs3(Text3, { color: theme2.error, children: [
|
|
1371
|
+
" \xB7 ",
|
|
1372
|
+
state.problems.length,
|
|
1373
|
+
" problem",
|
|
1374
|
+
state.problems.length === 1 ? "" : "s"
|
|
1375
|
+
] }),
|
|
1376
|
+
" ",
|
|
1377
|
+
"\xB7 shift+tab mode \xB7 /help"
|
|
1173
1378
|
] }) })
|
|
1174
1379
|
] }) });
|
|
1175
1380
|
}
|
|
1176
1381
|
|
|
1177
1382
|
// src/cli.tsx
|
|
1178
1383
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1179
|
-
var VERSION = "0.2.
|
|
1384
|
+
var VERSION = "0.2.2";
|
|
1180
1385
|
var program = new Command();
|
|
1181
1386
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
1182
1387
|
program.command("run").description("Run one prompt headlessly and stream the result").argument("<prompt...>", "what to build or change").option("-e, --engine <id>", "engine to use (claude, codex, gemini, opencode)").option("-m, --model <name>", "model override for the engine").option("--no-brief", "send the prompt without the squint design brief").option("--json", "emit normalized agent events as ndjson").action(
|
|
@@ -1210,18 +1415,41 @@ program.command("engines").description("List engines and whether they are instal
|
|
|
1210
1415
|
console.log(`${status} ${engine.id.padEnd(10)} ${engine.name.padEnd(14)} ${location}`);
|
|
1211
1416
|
}
|
|
1212
1417
|
});
|
|
1213
|
-
program.command("doctor").description("Check squint prerequisites and engine availability").action(async () => {
|
|
1418
|
+
program.command("doctor").description("Check squint prerequisites and engine availability").option("--probe", "run each detected engine with a one-word prompt to verify auth works").action(async (options) => {
|
|
1214
1419
|
const [major] = process.versions.node.split(".");
|
|
1215
|
-
const nodeOk = Number(major) >=
|
|
1216
|
-
console.log(`${nodeOk ? pc.green("\u2713") : pc.red("\u2717")} node ${process.versions.node}${nodeOk ? "" : " (need >=
|
|
1420
|
+
const nodeOk = Number(major) >= 22;
|
|
1421
|
+
console.log(`${nodeOk ? pc.green("\u2713") : pc.red("\u2717")} node ${process.versions.node}${nodeOk ? "" : " (need >= 22)"}`);
|
|
1217
1422
|
const detected = detectEngines();
|
|
1218
1423
|
for (const { engine, path: binaryPath } of detected) {
|
|
1219
1424
|
const status = binaryPath ? pc.green("\u2713") : pc.yellow("\u25CB");
|
|
1220
1425
|
console.log(`${status} ${engine.name}${binaryPath ? "" : pc.dim(` \u2014 install: ${engine.install}`)}`);
|
|
1221
1426
|
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1427
|
+
if (options.probe) {
|
|
1428
|
+
const { runAgent: runAgent2 } = await import("./run-XHLCTF6V.js");
|
|
1429
|
+
console.log(pc.dim("\nprobing engines with a one-word prompt (verifies auth end to end)\u2026"));
|
|
1430
|
+
for (const { engine, path: binaryPath } of detected) {
|
|
1431
|
+
if (!binaryPath) continue;
|
|
1432
|
+
const startedAt = Date.now();
|
|
1433
|
+
const abort = new AbortController();
|
|
1434
|
+
const timer = setTimeout(() => abort.abort(), 9e4);
|
|
1435
|
+
const result = await runAgent2(
|
|
1436
|
+
engine,
|
|
1437
|
+
{ prompt: "Reply with exactly: ok", cwd: process.cwd() },
|
|
1438
|
+
() => {
|
|
1439
|
+
},
|
|
1440
|
+
abort.signal
|
|
1441
|
+
);
|
|
1442
|
+
clearTimeout(timer);
|
|
1443
|
+
const secs = ((Date.now() - startedAt) / 1e3).toFixed(1);
|
|
1444
|
+
const detail = (result.error ?? "failed").split("\n").filter((l) => l.trim()).at(-1) ?? "failed";
|
|
1445
|
+
console.log(
|
|
1446
|
+
result.ok ? `${pc.green("\u2713")} ${engine.id.padEnd(10)} responded in ${secs}s` : `${pc.red("\u2717")} ${engine.id.padEnd(10)} ${pc.dim(detail.slice(0, 110))}`
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
const { findChrome: findChrome2 } = await import("./chrome-RD7XQ767.js");
|
|
1451
|
+
const { hasWebSocket } = await import("./cdp-SFWNP7OA.js");
|
|
1452
|
+
const chrome = findChrome2();
|
|
1225
1453
|
console.log(
|
|
1226
1454
|
chrome ? `${pc.green("\u2713")} Chrome ${pc.dim(chrome)}` : `${pc.yellow("\u25CB")} Chrome ${pc.dim("\u2014 screenshots and runtime probing disabled")}`
|
|
1227
1455
|
);
|
|
@@ -1238,7 +1466,7 @@ ${available.length} engine(s) ready.`));
|
|
|
1238
1466
|
}
|
|
1239
1467
|
});
|
|
1240
1468
|
program.command("init").description("Scaffold a new Vite + React + TS + Tailwind app with token-first CSS").argument("[dir]", "target directory", ".").option("--force", "write into a non-empty directory").option("--no-install", "skip npm install").action(async (dir, options) => {
|
|
1241
|
-
const { installDependencies, writeTemplate } = await import("./init-
|
|
1469
|
+
const { installDependencies, writeTemplate } = await import("./init-LSYB32NS.js");
|
|
1242
1470
|
let result;
|
|
1243
1471
|
try {
|
|
1244
1472
|
result = writeTemplate(dir, { force: options.force });
|
|
@@ -1262,10 +1490,49 @@ program.command("init").description("Scaffold a new Vite + React + TS + Tailwind
|
|
|
1262
1490
|
Next: ${pc.bold(`${cd}${options.install ? "" : "npm install && "}squint`)}`);
|
|
1263
1491
|
console.log(pc.dim("then describe what to build \u2014 /dev starts the preview server"));
|
|
1264
1492
|
});
|
|
1493
|
+
var skillsCommand = program.command("skills").description("Project knowledge injected into asks (.squint/rules.md + .squint/skills/)");
|
|
1494
|
+
skillsCommand.command("list").description("Show always-on rules and trigger-matched skills").action(async () => {
|
|
1495
|
+
const { loadRules, loadSkills } = await import("./skills-UGHU22BS.js");
|
|
1496
|
+
const cwd = process.cwd();
|
|
1497
|
+
const rules = loadRules(cwd);
|
|
1498
|
+
console.log(rules ? `${pc.green("\u2713")} rules.md ${pc.dim(`(${rules.split("\n").length} lines, always on)`)}` : pc.dim("\u25CB no .squint/rules.md"));
|
|
1499
|
+
const skills = loadSkills(cwd);
|
|
1500
|
+
if (skills.length === 0) {
|
|
1501
|
+
console.log(pc.dim("\u25CB no skills \u2014 squint skills init writes an example"));
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
for (const skill of skills) {
|
|
1505
|
+
console.log(`${pc.green("\u2713")} ${skill.name.padEnd(20)} ${pc.dim(`triggers: ${skill.triggers.join(", ")}`)}`);
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
skillsCommand.command("init").description("Scaffold .squint/rules.md and an example skill").action(async () => {
|
|
1509
|
+
const fs2 = await import("fs");
|
|
1510
|
+
const nodePath = await import("path");
|
|
1511
|
+
const cwd = process.cwd();
|
|
1512
|
+
const skillsDir = nodePath.join(cwd, ".squint", "skills");
|
|
1513
|
+
fs2.mkdirSync(skillsDir, { recursive: true });
|
|
1514
|
+
const rules = nodePath.join(cwd, ".squint", "rules.md");
|
|
1515
|
+
if (!fs2.existsSync(rules)) {
|
|
1516
|
+
fs2.writeFileSync(
|
|
1517
|
+
rules,
|
|
1518
|
+
"# Project rules\n\nThese ride along on every squint ask. Keep them short \u2014 cut anything that would not cause a mistake if removed.\n"
|
|
1519
|
+
);
|
|
1520
|
+
console.log(pc.green("\u2713 .squint/rules.md"));
|
|
1521
|
+
}
|
|
1522
|
+
const example = nodePath.join(skillsDir, "example.md");
|
|
1523
|
+
if (!fs2.existsSync(example)) {
|
|
1524
|
+
fs2.writeFileSync(
|
|
1525
|
+
example,
|
|
1526
|
+
"---\ntriggers: example, sample\n---\n\nThis note is injected only when an ask mentions one of the triggers above.\nDocument the parts of this repo an agent would otherwise rediscover every time:\nwhere state lives, which helpers to reuse, what not to touch.\n"
|
|
1527
|
+
);
|
|
1528
|
+
console.log(pc.green("\u2713 .squint/skills/example.md"));
|
|
1529
|
+
}
|
|
1530
|
+
console.log(pc.dim("rules are always-on; skills inject when an ask mentions a trigger"));
|
|
1531
|
+
});
|
|
1265
1532
|
program.command("tag").description("Add the element picker to this Vite app (Alt+S in the browser \u2192 click \u2192 file:line:col)").action(async () => {
|
|
1266
1533
|
const fs2 = await import("fs");
|
|
1267
1534
|
const nodePath = await import("path");
|
|
1268
|
-
const { patchViteConfig, TAGGER_FILENAME, TAGGER_SOURCE } = await import("./source-
|
|
1535
|
+
const { patchViteConfig, TAGGER_FILENAME, TAGGER_SOURCE } = await import("./source-ZZU245VN.js");
|
|
1269
1536
|
const cwd = process.cwd();
|
|
1270
1537
|
const taggerPath = nodePath.join(cwd, TAGGER_FILENAME);
|
|
1271
1538
|
fs2.writeFileSync(taggerPath, TAGGER_SOURCE);
|
|
@@ -1301,21 +1568,21 @@ variantsCommand.command("gen").description("Generate n variants of one ask in pa
|
|
|
1301
1568
|
process.exitCode = 1;
|
|
1302
1569
|
return;
|
|
1303
1570
|
}
|
|
1304
|
-
const { isGitRepo } = await import("./snapshot-
|
|
1305
|
-
if (!
|
|
1571
|
+
const { isGitRepo: isGitRepo2 } = await import("./snapshot-H6VYFHLN.js");
|
|
1572
|
+
if (!isGitRepo2(cwd)) {
|
|
1306
1573
|
console.error(pc.red("\u2717 variants need a git repo with at least one commit"));
|
|
1307
1574
|
process.exitCode = 1;
|
|
1308
1575
|
return;
|
|
1309
1576
|
}
|
|
1310
|
-
const { runVariants, cleanVariants } = await import("./variants-
|
|
1577
|
+
const { runVariants: runVariants2, cleanVariants: cleanVariants2 } = await import("./variants-7A7723L7.js");
|
|
1311
1578
|
const config = loadConfig(defaultPaths(cwd));
|
|
1312
1579
|
const engineId = resolveEngineId(config, options.engine);
|
|
1313
1580
|
const engine = getEngine(engineId);
|
|
1314
1581
|
const model = resolveModel(config, engineId, options.model);
|
|
1315
1582
|
const ask = promptWords.join(" ");
|
|
1316
|
-
|
|
1583
|
+
cleanVariants2(cwd);
|
|
1317
1584
|
console.log(pc.dim(`generating ${n} directions in parallel via ${engine.id} \u2014 this runs ${n} engine sessions`));
|
|
1318
|
-
const runs = await
|
|
1585
|
+
const runs = await runVariants2(
|
|
1319
1586
|
cwd,
|
|
1320
1587
|
ask,
|
|
1321
1588
|
n,
|
|
@@ -1325,9 +1592,9 @@ variantsCommand.command("gen").description("Generate n variants of one ask in pa
|
|
|
1325
1592
|
);
|
|
1326
1593
|
const succeeded = runs.filter((r) => r.result.ok);
|
|
1327
1594
|
if (options.shots && succeeded.length > 0) {
|
|
1328
|
-
const { screenshotVariants } = await import("./shots-
|
|
1595
|
+
const { screenshotVariants: screenshotVariants2 } = await import("./shots-LRYYMTPK.js");
|
|
1329
1596
|
console.log(pc.dim("capturing screenshots\u2026"));
|
|
1330
|
-
const shots = await
|
|
1597
|
+
const shots = await screenshotVariants2(cwd, succeeded.map((r) => r.variant));
|
|
1331
1598
|
for (const shot of shots) {
|
|
1332
1599
|
console.log(`${pc.green("\u2713")} ${shot.familyId.padEnd(18)} ${shot.path ?? pc.dim(shot.error ?? "")}`);
|
|
1333
1600
|
}
|
|
@@ -1340,8 +1607,8 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
1340
1607
|
}
|
|
1341
1608
|
);
|
|
1342
1609
|
variantsCommand.command("list").description("List generated variants").action(async () => {
|
|
1343
|
-
const { listVariants } = await import("./variants-
|
|
1344
|
-
const ids =
|
|
1610
|
+
const { listVariants: listVariants2 } = await import("./variants-7A7723L7.js");
|
|
1611
|
+
const ids = listVariants2(process.cwd());
|
|
1345
1612
|
if (ids.length === 0) {
|
|
1346
1613
|
console.log(pc.dim('no variants \u2014 squint variants gen <n> "<ask>"'));
|
|
1347
1614
|
return;
|
|
@@ -1349,26 +1616,26 @@ variantsCommand.command("list").description("List generated variants").action(as
|
|
|
1349
1616
|
for (const id of ids) console.log(id);
|
|
1350
1617
|
});
|
|
1351
1618
|
variantsCommand.command("apply").description("Apply one variant\u2019s changes to the main tree and discard the rest").argument("<id>", "family id of the winning variant").action(async (id) => {
|
|
1352
|
-
const { applyVariant, cleanVariants } = await import("./variants-
|
|
1619
|
+
const { applyVariant: applyVariant2, cleanVariants: cleanVariants2 } = await import("./variants-7A7723L7.js");
|
|
1353
1620
|
const cwd = process.cwd();
|
|
1354
|
-
const result =
|
|
1621
|
+
const result = applyVariant2(cwd, id);
|
|
1355
1622
|
if (!result.ok) {
|
|
1356
1623
|
console.error(pc.red(`\u2717 ${result.detail}`));
|
|
1357
1624
|
process.exitCode = 1;
|
|
1358
1625
|
return;
|
|
1359
1626
|
}
|
|
1360
|
-
|
|
1627
|
+
cleanVariants2(cwd);
|
|
1361
1628
|
console.log(pc.green(`\u2713 applied ${id} to the working tree`) + pc.dim(" \u2014 review with git diff"));
|
|
1362
1629
|
});
|
|
1363
1630
|
variantsCommand.command("clean").description("Discard all variants").action(async () => {
|
|
1364
|
-
const { cleanVariants } = await import("./variants-
|
|
1365
|
-
const count =
|
|
1631
|
+
const { cleanVariants: cleanVariants2 } = await import("./variants-7A7723L7.js");
|
|
1632
|
+
const count = cleanVariants2(process.cwd());
|
|
1366
1633
|
console.log(pc.dim(`removed ${count} variant(s)`));
|
|
1367
1634
|
});
|
|
1368
1635
|
program.command("brief").description("Set a committed design direction for this project (.squint/brief.md)").argument("[family]", "aesthetic family id (omit to list)").option("--force", "overwrite an existing project brief").action(async (familyId, options) => {
|
|
1369
1636
|
const fs2 = await import("fs");
|
|
1370
1637
|
const nodePath = await import("path");
|
|
1371
|
-
const { FAMILIES, getFamily, renderFamilyBrief } = await import("./families-
|
|
1638
|
+
const { FAMILIES, getFamily, renderFamilyBrief } = await import("./families-2G5SLLY7.js");
|
|
1372
1639
|
if (!familyId) {
|
|
1373
1640
|
console.log(pc.bold("Aesthetic families") + pc.dim(" \u2014 squint brief <id>\n"));
|
|
1374
1641
|
for (const family2 of FAMILIES) {
|
|
@@ -1411,7 +1678,7 @@ program.command("check").description("Run this project\u2019s quality gates (typ
|
|
|
1411
1678
|
if (results.some((r) => !r.ok)) process.exitCode = 1;
|
|
1412
1679
|
});
|
|
1413
1680
|
program.command("shot").description("Screenshot a running app at mobile/tablet/desktop viewports").argument("<url>", "URL of the running app (e.g. http://localhost:5173)").action(async (url) => {
|
|
1414
|
-
const { captureViewports: captureViewports2 } = await import("./preview-
|
|
1681
|
+
const { captureViewports: captureViewports2 } = await import("./preview-JJJLDF7H.js");
|
|
1415
1682
|
const result = await captureViewports2(process.cwd(), url);
|
|
1416
1683
|
if (!result) {
|
|
1417
1684
|
console.error(pc.red("\u2717 no Chrome/Chromium found"));
|