@aayambansal/squint 0.2.9 → 0.3.0
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/{cdp-SFWNP7OA.js → cdp-4IZHB3W5.js} +1 -1
- package/dist/{chrome-NVU47PLK.js → chrome-SBV3H77F.js} +2 -2
- package/dist/{chunk-X6MDBKED.js → chunk-A64VVTPU.js} +1 -1
- package/dist/{chunk-RMNG2NTU.js → chunk-CFWKEIJJ.js} +1 -1
- package/dist/{chunk-5VKUJPOL.js → chunk-F6JZSXEO.js} +3 -3
- package/dist/{chunk-B7LOERSP.js → chunk-IMDRXXFU.js} +1 -1
- package/dist/{chunk-BWZFACBT.js → chunk-KVYGPLWW.js} +12 -2
- package/dist/{chunk-OC6RU6XH.js → chunk-QEENJGCL.js} +55 -1
- package/dist/{chunk-GOAFH2VN.js → chunk-RQFFAJFE.js} +17 -7
- package/dist/{chunk-C7WKNJG6.js → chunk-VH7OOFQP.js} +1 -1
- package/dist/cli.js +97 -37
- package/dist/{commands-TERCCWFM.js → commands-QKDWUD4K.js} +1 -1
- package/dist/{preview-VW5DAJNV.js → preview-56UCV5A3.js} +4 -4
- package/dist/{registry-S3PCXJVF.js → registry-MIJ6LSAY.js} +1 -1
- package/dist/{run-S772BPMZ.js → run-NDSNTVYP.js} +2 -2
- package/dist/shots-E2AWBDCN.js +12 -0
- package/dist/{variants-UULPQ7Q5.js → variants-3ZSINJGX.js} +3 -3
- package/package.json +2 -1
- package/dist/shots-FVG4CDVK.js +0 -12
|
@@ -7,7 +7,7 @@ var COMMANDS = [
|
|
|
7
7
|
{ name: "engine", args: "<id>", group: "build", description: "switch backend (new session)" },
|
|
8
8
|
{ name: "engines", group: "build", description: "list installed engines with streaming/resume support" },
|
|
9
9
|
{ name: "model", args: "[name]", group: "build", description: "model override for the engine" },
|
|
10
|
-
{ name: "queue", args: "clear", group: "build", description: "drop queued
|
|
10
|
+
{ name: "queue", args: "clear|drop <n>", group: "build", description: "clear the queue or drop one queued ask" },
|
|
11
11
|
{ name: "check", group: "verify", description: "run all quality gates (typecheck, lint, format, test, build)" },
|
|
12
12
|
{ name: "problems", group: "verify", description: "list open findings from gates, dev server, runtime, a11y" },
|
|
13
13
|
{ name: "fix", args: "[n]", group: "verify", description: "send all open problems to the engine, or just problem n" },
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
variantsRoot
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-A64VVTPU.js";
|
|
5
5
|
import {
|
|
6
6
|
findChrome,
|
|
7
7
|
screenshot
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-IMDRXXFU.js";
|
|
9
9
|
import {
|
|
10
10
|
lineSplitter
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-KVYGPLWW.js";
|
|
12
12
|
|
|
13
13
|
// src/variants/shots.ts
|
|
14
14
|
import path2 from "path";
|
|
@@ -98,8 +98,18 @@ function createClaudeStreamParser(readyLabel) {
|
|
|
98
98
|
durationMs: data.duration_ms
|
|
99
99
|
}
|
|
100
100
|
];
|
|
101
|
-
case "user":
|
|
102
|
-
return [];
|
|
101
|
+
case "user": {
|
|
102
|
+
if (data.parent_tool_use_id) return [];
|
|
103
|
+
const events = [];
|
|
104
|
+
for (const block of data.message?.content ?? []) {
|
|
105
|
+
if (block.type === "tool_result" && block.is_error === true) {
|
|
106
|
+
const raw = Array.isArray(block.content) ? block.content.map((c) => c?.text ?? "").join(" ") : String(block.content ?? "");
|
|
107
|
+
const text = raw.trim().split("\n").at(-1) ?? "";
|
|
108
|
+
if (text) events.push({ type: "status", text: `\u26A0 tool error \xB7 ${truncate(text, 100)}` });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return events;
|
|
112
|
+
}
|
|
103
113
|
case "rate_limit_event":
|
|
104
114
|
return [];
|
|
105
115
|
default:
|
|
@@ -201,11 +201,56 @@ async function pixelDiffPct(chromePath, pngA, pngB) {
|
|
|
201
201
|
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
|
+
var SLOP_AUDIT = `(() => {
|
|
205
|
+
const out = [];
|
|
206
|
+
const bodyFont = (getComputedStyle(document.body).fontFamily || '').toLowerCase();
|
|
207
|
+
const h1 = document.querySelector('h1,h2');
|
|
208
|
+
const displayFont = h1 ? (getComputedStyle(h1).fontFamily || '').toLowerCase() : bodyFont;
|
|
209
|
+
for (const tell of ['inter', 'roboto', 'arial', 'space grotesk']) {
|
|
210
|
+
if (displayFont.includes(tell) || bodyFont.split(',')[0].includes(tell)) {
|
|
211
|
+
out.push('generic font stack: ' + tell + ' (' + (displayFont.includes(tell) ? 'display' : 'body') + ')');
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const vw = innerWidth, vh = innerHeight;
|
|
216
|
+
for (const el of document.querySelectorAll('*')) {
|
|
217
|
+
const r = el.getBoundingClientRect();
|
|
218
|
+
if (r.top > vh || r.width * r.height < vw * vh * 0.2) continue;
|
|
219
|
+
const bg = getComputedStyle(el).backgroundImage || '';
|
|
220
|
+
if (bg.includes('gradient')) {
|
|
221
|
+
const purples = bg.match(/rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)/g) || [];
|
|
222
|
+
if (purples.some((c) => { const [r2, g2, b2] = c.slice(4).split(',').map(Number); return b2 > 150 && r2 > 80 && r2 < 200 && g2 < r2; })) {
|
|
223
|
+
out.push('purple/violet gradient on a hero-scale element');
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
for (const container of document.querySelectorAll('section, div')) {
|
|
229
|
+
const kids = [...container.children];
|
|
230
|
+
if (kids.length < 3 || kids.length > 4) continue;
|
|
231
|
+
const rects = kids.map((k) => k.getBoundingClientRect());
|
|
232
|
+
if (rects[0].width < 150 || rects[0].top > vh * 2) continue;
|
|
233
|
+
const sameSize = rects.every((r) => Math.abs(r.width - rects[0].width) < 4 && Math.abs(r.height - rects[0].height) < 24);
|
|
234
|
+
const cardish = kids.every((k) => k.querySelector('svg, img') && k.querySelector('h2,h3,h4') && k.querySelector('p'));
|
|
235
|
+
if (sameSize && cardish) { out.push('identical icon-card grid (' + kids.length + ' cards)'); break; }
|
|
236
|
+
}
|
|
237
|
+
let emojiBullets = 0;
|
|
238
|
+
for (const li of document.querySelectorAll('li')) {
|
|
239
|
+
if (/^[\\u{1F300}-\\u{1FAFF}\\u{2600}-\\u{27BF}]/u.test((li.textContent || '').trim())) emojiBullets++;
|
|
240
|
+
}
|
|
241
|
+
if (emojiBullets >= 3) out.push(emojiBullets + ' emoji-bulleted list items');
|
|
242
|
+
const rootStyle = getComputedStyle(document.documentElement);
|
|
243
|
+
if (rootStyle.getPropertyValue('--radius').trim() === '0.5rem' && rootStyle.getPropertyValue('--primary').trim() === '222.2 47.4% 11.2%') {
|
|
244
|
+
out.push('untouched shadcn default theme tokens');
|
|
245
|
+
}
|
|
246
|
+
return out.slice(0, 8);
|
|
247
|
+
})()`;
|
|
204
248
|
async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, audit = false) {
|
|
205
249
|
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
206
250
|
const report = { consoleErrors: [], pageErrors: [], failedRequests: [] };
|
|
207
251
|
const shots = [];
|
|
208
252
|
let a11y = [];
|
|
253
|
+
let slop = [];
|
|
209
254
|
const requests = /* @__PURE__ */ new Map();
|
|
210
255
|
let connection = null;
|
|
211
256
|
try {
|
|
@@ -260,6 +305,15 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
260
305
|
if (Array.isArray(result?.value)) a11y = result.value.map(String);
|
|
261
306
|
} catch {
|
|
262
307
|
}
|
|
308
|
+
try {
|
|
309
|
+
const { result } = await connection.send(
|
|
310
|
+
"Runtime.evaluate",
|
|
311
|
+
{ expression: SLOP_AUDIT, returnByValue: true },
|
|
312
|
+
sessionId
|
|
313
|
+
);
|
|
314
|
+
if (Array.isArray(result?.value)) slop = result.value.map(String);
|
|
315
|
+
} catch {
|
|
316
|
+
}
|
|
263
317
|
}
|
|
264
318
|
for (const viewport of viewports) {
|
|
265
319
|
await connection.send(
|
|
@@ -283,7 +337,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
283
337
|
child.kill("SIGKILL");
|
|
284
338
|
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
285
339
|
}
|
|
286
|
-
return { report, shots, a11y };
|
|
340
|
+
return { report, shots, a11y, slop };
|
|
287
341
|
}
|
|
288
342
|
|
|
289
343
|
export {
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
findChrome,
|
|
4
4
|
screenshot
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-IMDRXXFU.js";
|
|
6
6
|
import {
|
|
7
7
|
cdpCapture,
|
|
8
8
|
hasWebSocket
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QEENJGCL.js";
|
|
10
10
|
import {
|
|
11
11
|
ensureSquintIgnore
|
|
12
12
|
} from "./chunk-O2S6PAJE.js";
|
|
@@ -47,7 +47,7 @@ async function captureViewports(cwd, url) {
|
|
|
47
47
|
const base = url.replace(/\/+$/, "");
|
|
48
48
|
if (hasWebSocket()) {
|
|
49
49
|
try {
|
|
50
|
-
const { report, shots: shots2, a11y } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true);
|
|
50
|
+
const { report, shots: shots2, a11y, slop } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true);
|
|
51
51
|
const errors2 = [];
|
|
52
52
|
for (const route of routes.slice(1)) {
|
|
53
53
|
try {
|
|
@@ -62,7 +62,7 @@ async function captureViewports(cwd, url) {
|
|
|
62
62
|
errors2.push(`${route}: capture failed`);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
return { shots: shots2, errors: errors2, runtime: report, a11y };
|
|
65
|
+
return { shots: shots2, errors: errors2, runtime: report, a11y, slop };
|
|
66
66
|
} catch {
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -133,7 +133,7 @@ async function probeRuntime(url, cwd) {
|
|
|
133
133
|
async function comparePulse(previous, current) {
|
|
134
134
|
const chrome = findChrome();
|
|
135
135
|
if (!chrome || !hasWebSocket()) return null;
|
|
136
|
-
const { pixelDiffPct } = await import("./cdp-
|
|
136
|
+
const { pixelDiffPct } = await import("./cdp-4IZHB3W5.js");
|
|
137
137
|
return pixelDiffPct(chrome, previous, current);
|
|
138
138
|
}
|
|
139
139
|
function buildRuntimeFixPrompt(report) {
|
|
@@ -151,13 +151,23 @@ ${findings.join("\n")}
|
|
|
151
151
|
|
|
152
152
|
Fix these as part of the pass \u2014 they are objective defects, not style preferences.`;
|
|
153
153
|
}
|
|
154
|
-
function
|
|
154
|
+
function slopSection(findings) {
|
|
155
|
+
if (!findings || findings.length === 0) return "";
|
|
156
|
+
return `
|
|
157
|
+
|
|
158
|
+
## Distinctiveness debt (detected mechanically)
|
|
159
|
+
|
|
160
|
+
${findings.join("\n")}
|
|
161
|
+
|
|
162
|
+
These patterns make the page read as template output. Rework them within the committed design direction \u2014 this is style debt, not a defect list.`;
|
|
163
|
+
}
|
|
164
|
+
function buildReviewPrompt(shots, extra, runtime, a11y, slop) {
|
|
155
165
|
const list = shots.map((s) => `- ${s.name}: ${s.path}`).join("\n");
|
|
156
166
|
return `Screenshots of the running app were just captured:
|
|
157
167
|
|
|
158
168
|
${list}
|
|
159
169
|
|
|
160
|
-
Read each screenshot and review the rendered UI against the design standards you were given. Check: visual hierarchy and spacing rhythm, typography, color and contrast, alignment, empty-looking or broken regions, and whether the mobile capture shows horizontal overflow or cramped layout. List the concrete issues you can SEE (not hypothetical ones), ranked by visual impact${extra ? `, with special attention to: ${extra}` : ""}. Then fix them and verify the app still builds.${runtimeSection(runtime)}${a11ySection(a11y)}`;
|
|
170
|
+
Read each screenshot and review the rendered UI against the design standards you were given. Check: visual hierarchy and spacing rhythm, typography, color and contrast, alignment, empty-looking or broken regions, and whether the mobile capture shows horizontal overflow or cramped layout. List the concrete issues you can SEE (not hypothetical ones), ranked by visual impact${extra ? `, with special attention to: ${extra}` : ""}. Then fix them and verify the app still builds.${runtimeSection(runtime)}${a11ySection(a11y)}${slopSection(slop)}`;
|
|
161
171
|
}
|
|
162
172
|
|
|
163
173
|
export {
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
completeCommand
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-CFWKEIJJ.js";
|
|
5
5
|
import {
|
|
6
6
|
applySandbox,
|
|
7
7
|
discardSandbox,
|
|
@@ -22,13 +22,13 @@ import {
|
|
|
22
22
|
buildFixPrompt,
|
|
23
23
|
detectDevCommand,
|
|
24
24
|
screenshotVariants
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-F6JZSXEO.js";
|
|
26
26
|
import {
|
|
27
27
|
applyVariant,
|
|
28
28
|
cleanVariants,
|
|
29
29
|
listVariants,
|
|
30
30
|
runVariants
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-A64VVTPU.js";
|
|
32
32
|
import {
|
|
33
33
|
buildGatePrompt,
|
|
34
34
|
detectFastGates,
|
|
@@ -42,18 +42,18 @@ import {
|
|
|
42
42
|
comparePulse,
|
|
43
43
|
probeRuntime,
|
|
44
44
|
runtimeSummary
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-RQFFAJFE.js";
|
|
46
46
|
import {
|
|
47
47
|
runAgent
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-VH7OOFQP.js";
|
|
49
49
|
import {
|
|
50
50
|
findChrome
|
|
51
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-IMDRXXFU.js";
|
|
52
52
|
import {
|
|
53
53
|
detectEngines,
|
|
54
54
|
getEngine
|
|
55
|
-
} from "./chunk-
|
|
56
|
-
import "./chunk-
|
|
55
|
+
} from "./chunk-KVYGPLWW.js";
|
|
56
|
+
import "./chunk-QEENJGCL.js";
|
|
57
57
|
import {
|
|
58
58
|
enrich
|
|
59
59
|
} from "./chunk-4LAU5TGK.js";
|
|
@@ -213,7 +213,7 @@ function registerEnv(program2) {
|
|
|
213
213
|
console.log(`${status} ${engine.name}${binaryPath ? "" : pc2.dim(` \u2014 install: ${engine.install}`)}`);
|
|
214
214
|
}
|
|
215
215
|
if (options.probe) {
|
|
216
|
-
const { runAgent: runAgent2 } = await import("./run-
|
|
216
|
+
const { runAgent: runAgent2 } = await import("./run-NDSNTVYP.js");
|
|
217
217
|
console.log(pc2.dim("\nprobing engines with a one-word prompt (verifies auth end to end)\u2026"));
|
|
218
218
|
for (const { engine, path: binaryPath } of detected) {
|
|
219
219
|
if (!binaryPath) continue;
|
|
@@ -235,8 +235,8 @@ function registerEnv(program2) {
|
|
|
235
235
|
);
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
-
const { findChrome: findChrome2 } = await import("./chrome-
|
|
239
|
-
const { hasWebSocket } = await import("./cdp-
|
|
238
|
+
const { findChrome: findChrome2 } = await import("./chrome-SBV3H77F.js");
|
|
239
|
+
const { hasWebSocket } = await import("./cdp-4IZHB3W5.js");
|
|
240
240
|
const chrome = findChrome2();
|
|
241
241
|
console.log(
|
|
242
242
|
chrome ? `${pc2.green("\u2713")} Chrome ${pc2.dim(chrome)}` : `${pc2.yellow("\u25CB")} Chrome ${pc2.dim("\u2014 screenshots and runtime probing disabled")}`
|
|
@@ -377,7 +377,7 @@ function registerProject(program2) {
|
|
|
377
377
|
process.exitCode = 1;
|
|
378
378
|
return;
|
|
379
379
|
}
|
|
380
|
-
const { runVariants: runVariants2, cleanVariants: cleanVariants2 } = await import("./variants-
|
|
380
|
+
const { runVariants: runVariants2, cleanVariants: cleanVariants2 } = await import("./variants-3ZSINJGX.js");
|
|
381
381
|
const config = loadConfig(defaultPaths(cwd));
|
|
382
382
|
const engineId = resolveEngineId(config, options.engine);
|
|
383
383
|
const engine = getEngine(engineId);
|
|
@@ -395,7 +395,7 @@ function registerProject(program2) {
|
|
|
395
395
|
);
|
|
396
396
|
const succeeded = runs.filter((r) => r.result.ok);
|
|
397
397
|
if (options.shots && succeeded.length > 0) {
|
|
398
|
-
const { screenshotVariants: screenshotVariants2 } = await import("./shots-
|
|
398
|
+
const { screenshotVariants: screenshotVariants2 } = await import("./shots-E2AWBDCN.js");
|
|
399
399
|
console.log(pc3.dim("capturing screenshots\u2026"));
|
|
400
400
|
const shots = await screenshotVariants2(cwd, succeeded.map((r) => r.variant));
|
|
401
401
|
for (const shot of shots) {
|
|
@@ -410,7 +410,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
410
410
|
}
|
|
411
411
|
);
|
|
412
412
|
variantsCommand.command("list").description("List generated variants").action(async () => {
|
|
413
|
-
const { listVariants: listVariants2 } = await import("./variants-
|
|
413
|
+
const { listVariants: listVariants2 } = await import("./variants-3ZSINJGX.js");
|
|
414
414
|
const ids = listVariants2(process.cwd());
|
|
415
415
|
if (ids.length === 0) {
|
|
416
416
|
console.log(pc3.dim('no variants \u2014 squint variants gen <n> "<ask>"'));
|
|
@@ -419,7 +419,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
419
419
|
for (const id of ids) console.log(id);
|
|
420
420
|
});
|
|
421
421
|
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) => {
|
|
422
|
-
const { applyVariant: applyVariant2, cleanVariants: cleanVariants2 } = await import("./variants-
|
|
422
|
+
const { applyVariant: applyVariant2, cleanVariants: cleanVariants2 } = await import("./variants-3ZSINJGX.js");
|
|
423
423
|
const cwd = process.cwd();
|
|
424
424
|
const result = applyVariant2(cwd, id);
|
|
425
425
|
if (!result.ok) {
|
|
@@ -431,7 +431,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
431
431
|
console.log(pc3.green(`\u2713 applied ${id} to the working tree`) + pc3.dim(" \u2014 review with git diff"));
|
|
432
432
|
});
|
|
433
433
|
variantsCommand.command("clean").description("Discard all variants").action(async () => {
|
|
434
|
-
const { cleanVariants: cleanVariants2 } = await import("./variants-
|
|
434
|
+
const { cleanVariants: cleanVariants2 } = await import("./variants-3ZSINJGX.js");
|
|
435
435
|
const count = cleanVariants2(process.cwd());
|
|
436
436
|
console.log(pc3.dim(`removed ${count} variant(s)`));
|
|
437
437
|
});
|
|
@@ -459,7 +459,7 @@ function registerQuality(program2) {
|
|
|
459
459
|
if (results.some((r) => !r.ok)) process.exitCode = 1;
|
|
460
460
|
});
|
|
461
461
|
program2.command("shot").description("Screenshot a running app at mobile/tablet/desktop viewports (+ .squint/routes)").argument("<url>", "URL of the running app (e.g. http://localhost:5173)").action(async (url) => {
|
|
462
|
-
const { captureViewports: captureViewports2 } = await import("./preview-
|
|
462
|
+
const { captureViewports: captureViewports2 } = await import("./preview-56UCV5A3.js");
|
|
463
463
|
const result = await captureViewports2(process.cwd(), url);
|
|
464
464
|
if (!result) {
|
|
465
465
|
console.error(pc4.red("\u2717 no Chrome/Chromium found"));
|
|
@@ -613,7 +613,8 @@ Next: ${pc6.bold(`${cd}${options.install ? "" : "npm install && "}squint`)}`);
|
|
|
613
613
|
import { render } from "ink";
|
|
614
614
|
|
|
615
615
|
// src/tui/App.tsx
|
|
616
|
-
import { Box as
|
|
616
|
+
import { Box as Box3, Static, Text as Text3, useApp, useInput } from "ink";
|
|
617
|
+
import { InkPictureProvider } from "ink-picture";
|
|
617
618
|
import path3 from "path";
|
|
618
619
|
import { useMemo, useRef, useState as useState2, useSyncExternalStore } from "react";
|
|
619
620
|
|
|
@@ -765,6 +766,18 @@ var Session = class {
|
|
|
765
766
|
this.push("status", "queue cleared");
|
|
766
767
|
return;
|
|
767
768
|
}
|
|
769
|
+
const drop = /^\/queue drop (\d+)$/.exec(value);
|
|
770
|
+
if (drop) {
|
|
771
|
+
const index = Number.parseInt(drop[1], 10) - 1;
|
|
772
|
+
if (index >= 0 && index < this.state.queue.length) {
|
|
773
|
+
const removed = this.state.queue[index];
|
|
774
|
+
this.notify({ queue: this.state.queue.filter((_, i) => i !== index) });
|
|
775
|
+
this.push("status", `dropped from queue: ${removed}`);
|
|
776
|
+
} else {
|
|
777
|
+
this.push("status", `queue has ${this.state.queue.length} item(s) \u2014 /queue drop <1-${Math.max(this.state.queue.length, 1)}>`);
|
|
778
|
+
}
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
768
781
|
this.notify({ queue: [...this.state.queue, value] });
|
|
769
782
|
return;
|
|
770
783
|
}
|
|
@@ -950,6 +963,7 @@ var Session = class {
|
|
|
950
963
|
sessionId: this.sessionId,
|
|
951
964
|
model: this.state.model,
|
|
952
965
|
lastAsk: display.length > 80 ? `${display.slice(0, 79)}\u2026` : display,
|
|
966
|
+
totals: this.state.totals,
|
|
953
967
|
at: Date.now()
|
|
954
968
|
});
|
|
955
969
|
}
|
|
@@ -1012,7 +1026,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1012
1026
|
const captureResult = await this.capture();
|
|
1013
1027
|
if (captureResult) {
|
|
1014
1028
|
await this.runTurn(
|
|
1015
|
-
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y),
|
|
1029
|
+
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop),
|
|
1016
1030
|
"\u{1F441} auto-review rendered UI"
|
|
1017
1031
|
);
|
|
1018
1032
|
}
|
|
@@ -1086,6 +1100,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1086
1100
|
this.lastPulse = current;
|
|
1087
1101
|
if (!previous) {
|
|
1088
1102
|
this.push("status", "visual pulse: baseline captured");
|
|
1103
|
+
this.push("image", pulsePath);
|
|
1089
1104
|
return null;
|
|
1090
1105
|
}
|
|
1091
1106
|
const pct = await comparePulse(previous, current);
|
|
@@ -1094,6 +1109,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1094
1109
|
"status",
|
|
1095
1110
|
pct < 0.5 ? "visual pulse: stable vs last turn" : `visual pulse: ${pct.toFixed(1)}% of the page changed vs last turn`
|
|
1096
1111
|
);
|
|
1112
|
+
if (pct >= 0.5) this.push("image", pulsePath);
|
|
1097
1113
|
return pct;
|
|
1098
1114
|
}
|
|
1099
1115
|
/** Screenshot the running app (and watch its runtime where CDP is available). */
|
|
@@ -1115,6 +1131,8 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1115
1131
|
"status",
|
|
1116
1132
|
`captured ${result.shots.map((s) => s.name).join(", ")} \u2192 ${path2.dirname(result.shots[0].path)}`
|
|
1117
1133
|
);
|
|
1134
|
+
const desktop = result.shots.find((s) => s.name === "desktop") ?? result.shots[0];
|
|
1135
|
+
this.push("image", desktop.path);
|
|
1118
1136
|
}
|
|
1119
1137
|
if (result.runtime) {
|
|
1120
1138
|
const summary = runtimeSummary(result.runtime);
|
|
@@ -1127,6 +1145,10 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1127
1145
|
this.push("status", "runtime clean \u2014 no console errors, exceptions, or failed requests");
|
|
1128
1146
|
}
|
|
1129
1147
|
}
|
|
1148
|
+
if (result.slop && result.slop.length > 0) {
|
|
1149
|
+
this.push("status", `distinctiveness: ${result.slop.length} tell(s)
|
|
1150
|
+
${result.slop.join("\n")}`);
|
|
1151
|
+
}
|
|
1130
1152
|
if (result.a11y && result.a11y.length > 0) {
|
|
1131
1153
|
this.push("error", `a11y: ${result.a11y.length} finding(s)
|
|
1132
1154
|
${result.a11y.slice(0, 5).join("\n")}`);
|
|
@@ -1150,7 +1172,7 @@ They are objective defects, not style preferences.`
|
|
|
1150
1172
|
const arg = rest.join(" ").trim();
|
|
1151
1173
|
switch (name) {
|
|
1152
1174
|
case "engines": {
|
|
1153
|
-
void import("./registry-
|
|
1175
|
+
void import("./registry-MIJ6LSAY.js").then(({ detectEngines: detectEngines2 }) => {
|
|
1154
1176
|
const lines = detectEngines2().map(({ engine, path: binaryPath }) => {
|
|
1155
1177
|
const mark = binaryPath ? "\u2713" : "\u2717";
|
|
1156
1178
|
const traits = [engine.createParser ? "stream" : "text", engine.supportsResume ? "resume" : null].filter(Boolean).join(" \xB7 ");
|
|
@@ -1258,6 +1280,9 @@ They are objective defects, not style preferences.`
|
|
|
1258
1280
|
case "tool":
|
|
1259
1281
|
lines.push(`- \u2699 ${item.text}`);
|
|
1260
1282
|
break;
|
|
1283
|
+
case "image":
|
|
1284
|
+
lines.push(``, "");
|
|
1285
|
+
break;
|
|
1261
1286
|
case "thinking":
|
|
1262
1287
|
break;
|
|
1263
1288
|
default:
|
|
@@ -1330,7 +1355,7 @@ They are objective defects, not style preferences.`
|
|
|
1330
1355
|
const result = await this.capture();
|
|
1331
1356
|
if (result) {
|
|
1332
1357
|
await this.runTurn(
|
|
1333
|
-
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y),
|
|
1358
|
+
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop),
|
|
1334
1359
|
`\u{1F441} review rendered UI${arg ? ` \xB7 ${arg}` : ""}`
|
|
1335
1360
|
);
|
|
1336
1361
|
}
|
|
@@ -1380,7 +1405,11 @@ They are objective defects, not style preferences.`
|
|
|
1380
1405
|
break;
|
|
1381
1406
|
}
|
|
1382
1407
|
this.sessionId = saved.sessionId;
|
|
1383
|
-
this.notify({
|
|
1408
|
+
this.notify({
|
|
1409
|
+
engineId: saved.engine,
|
|
1410
|
+
model: saved.model ?? this.state.model,
|
|
1411
|
+
totals: saved.totals ?? this.state.totals
|
|
1412
|
+
});
|
|
1384
1413
|
this.push("status", `resumed ${saved.engine} session${saved.lastAsk ? ` \xB7 "${saved.lastAsk}"` : ""}`);
|
|
1385
1414
|
break;
|
|
1386
1415
|
}
|
|
@@ -1510,7 +1539,7 @@ ${sandboxFiles(this.opts.cwd).join("\n")}`);
|
|
|
1510
1539
|
this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
|
|
1511
1540
|
break;
|
|
1512
1541
|
case "help": {
|
|
1513
|
-
void import("./commands-
|
|
1542
|
+
void import("./commands-QKDWUD4K.js").then(({ commandHelp }) => this.push("status", commandHelp()));
|
|
1514
1543
|
break;
|
|
1515
1544
|
}
|
|
1516
1545
|
case "quit":
|
|
@@ -1772,9 +1801,23 @@ function Markdown({ text }) {
|
|
|
1772
1801
|
}
|
|
1773
1802
|
|
|
1774
1803
|
// src/tui/messages.tsx
|
|
1775
|
-
import { Text as Text2 } from "ink";
|
|
1804
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
1805
|
+
import Image from "ink-picture";
|
|
1776
1806
|
import { useEffect, useState } from "react";
|
|
1807
|
+
|
|
1808
|
+
// src/tui/termImage.ts
|
|
1809
|
+
function supportsInlineImages(env = process.env) {
|
|
1810
|
+
if (env.TMUX) return false;
|
|
1811
|
+
if (env.TERM === "xterm-kitty" || env.KITTY_WINDOW_ID) return true;
|
|
1812
|
+
if (env.TERM === "xterm-ghostty" || env.GHOSTTY_RESOURCES_DIR) return true;
|
|
1813
|
+
if (env.TERM_PROGRAM === "WezTerm") return true;
|
|
1814
|
+
if (env.TERM_PROGRAM === "iTerm.app") return true;
|
|
1815
|
+
return false;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// src/tui/messages.tsx
|
|
1777
1819
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1820
|
+
var INLINE_IMAGES = supportsInlineImages();
|
|
1778
1821
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1779
1822
|
var PHRASES = ["working", "thinking", "squinting", "crafting", "still at it"];
|
|
1780
1823
|
function WorkingLine({ startedAt }) {
|
|
@@ -1839,6 +1882,20 @@ function MessageLine({ message }) {
|
|
|
1839
1882
|
"\u2717 ",
|
|
1840
1883
|
message.text
|
|
1841
1884
|
] });
|
|
1885
|
+
case "image":
|
|
1886
|
+
if (!INLINE_IMAGES) {
|
|
1887
|
+
return /* @__PURE__ */ jsxs2(Text2, { color: theme2.dim, wrap: "wrap", children: [
|
|
1888
|
+
"\u25A3 ",
|
|
1889
|
+
message.text
|
|
1890
|
+
] });
|
|
1891
|
+
}
|
|
1892
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
1893
|
+
/* @__PURE__ */ jsx2(Image, { src: message.text, width: 48, height: 14, alt: "screenshot" }),
|
|
1894
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme2.dim, children: [
|
|
1895
|
+
"\u25A3 ",
|
|
1896
|
+
message.text
|
|
1897
|
+
] })
|
|
1898
|
+
] });
|
|
1842
1899
|
}
|
|
1843
1900
|
}
|
|
1844
1901
|
|
|
@@ -1998,20 +2055,23 @@ function App({
|
|
|
1998
2055
|
});
|
|
1999
2056
|
const devBadge = state.devState === "running" ? ` \xB7 ${state.devUrl ?? "dev running"}` : state.devState === "starting" ? " \xB7 dev starting\u2026" : state.devState === "crashed" ? " \xB7 dev crashed" : "";
|
|
2000
2057
|
const totalsBadge = state.totals.turns > 0 ? ` \xB7 ${state.totals.turns} turn${state.totals.turns === 1 ? "" : "s"}${state.totals.costUsd > 0 ? ` \xB7 $${state.totals.costUsd.toFixed(2)}` : ""}` : "";
|
|
2001
|
-
return /* @__PURE__ */ jsx3(ThemeProvider, { value: theme2, children: /* @__PURE__ */ jsxs3(
|
|
2002
|
-
/* @__PURE__ */ jsx3(Static, { items: state.items, children: (item) => /* @__PURE__ */ jsx3(
|
|
2003
|
-
state.liveText.length > 0 && /* @__PURE__ */ jsx3(
|
|
2004
|
-
state.items.length === 0 && state.liveText.length === 0 && !state.running && /* @__PURE__ */ jsxs3(
|
|
2058
|
+
return /* @__PURE__ */ jsx3(ThemeProvider, { value: theme2, children: /* @__PURE__ */ jsx3(InkPictureProvider, { children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, children: [
|
|
2059
|
+
/* @__PURE__ */ jsx3(Static, { items: state.items, children: (item) => /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(MessageLine, { message: item }) }, item.id) }),
|
|
2060
|
+
state.liveText.length > 0 && /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Markdown, { text: state.liveText }) }),
|
|
2061
|
+
state.items.length === 0 && state.liveText.length === 0 && !state.running && /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
|
|
2005
2062
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: "describe what to build \u2014 or try:" }),
|
|
2006
2063
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: " /dev start the preview server \xB7 /review after a change \xB7 /variants 3 explore wide" }),
|
|
2007
2064
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: " shift+tab cycles plan/safe/yolo \xB7 type while the agent works to queue asks" })
|
|
2008
2065
|
] }),
|
|
2009
|
-
state.running && /* @__PURE__ */ jsx3(
|
|
2010
|
-
state.queue.map((queued, index) => /* @__PURE__ */ jsx3(
|
|
2011
|
-
"\u22EF
|
|
2012
|
-
|
|
2066
|
+
state.running && /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(WorkingLine, { startedAt: state.runStartedAt }) }),
|
|
2067
|
+
state.queue.map((queued, index) => /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: theme2.dim, children: [
|
|
2068
|
+
"\u22EF ",
|
|
2069
|
+
index + 1,
|
|
2070
|
+
". ",
|
|
2071
|
+
queued,
|
|
2072
|
+
index === state.queue.length - 1 ? " (/queue drop <n> removes)" : ""
|
|
2013
2073
|
] }) }, index)),
|
|
2014
|
-
line.text.startsWith("/") && !line.text.includes(" ") && /* @__PURE__ */ jsx3(
|
|
2074
|
+
line.text.startsWith("/") && !line.text.includes(" ") && /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: completeCommand(line.text.slice(1)).slice(0, 5).map((command, index) => /* @__PURE__ */ jsxs3(Text3, { color: index === 0 ? theme2.accent : theme2.dim, children: [
|
|
2015
2075
|
"/",
|
|
2016
2076
|
command.name,
|
|
2017
2077
|
command.args ? ` ${command.args}` : "",
|
|
@@ -2021,13 +2081,13 @@ function App({
|
|
|
2021
2081
|
command.description
|
|
2022
2082
|
] })
|
|
2023
2083
|
] }, command.name)) }),
|
|
2024
|
-
/* @__PURE__ */ jsx3(
|
|
2084
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: state.running ? 0 : 1, children: /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
2025
2085
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.accent, children: "\u276F " }),
|
|
2026
2086
|
line.text.slice(0, line.cursor),
|
|
2027
2087
|
/* @__PURE__ */ jsx3(Text3, { inverse: true, children: line.text[line.cursor] ?? " " }),
|
|
2028
2088
|
line.text.slice(line.cursor + 1)
|
|
2029
2089
|
] }) }),
|
|
2030
|
-
/* @__PURE__ */ jsx3(
|
|
2090
|
+
/* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: theme2.dim, children: [
|
|
2031
2091
|
/* @__PURE__ */ jsxs3(
|
|
2032
2092
|
Text3,
|
|
2033
2093
|
{
|
|
@@ -2057,7 +2117,7 @@ function App({
|
|
|
2057
2117
|
" ",
|
|
2058
2118
|
"\xB7 shift+tab mode \xB7 /help"
|
|
2059
2119
|
] }) })
|
|
2060
|
-
] }) });
|
|
2120
|
+
] }) }) });
|
|
2061
2121
|
}
|
|
2062
2122
|
|
|
2063
2123
|
// src/cli/tui.tsx
|
|
@@ -2095,7 +2155,7 @@ function registerTui(program2) {
|
|
|
2095
2155
|
}
|
|
2096
2156
|
|
|
2097
2157
|
// src/cli.tsx
|
|
2098
|
-
var VERSION = true ? "0.
|
|
2158
|
+
var VERSION = true ? "0.3.0" : "0.0.0-dev";
|
|
2099
2159
|
var program = new Command();
|
|
2100
2160
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
2101
2161
|
registerRun(program);
|
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
probeRuntime,
|
|
11
11
|
routeShotName,
|
|
12
12
|
runtimeSummary
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-RQFFAJFE.js";
|
|
14
|
+
import "./chunk-IMDRXXFU.js";
|
|
15
|
+
import "./chunk-KVYGPLWW.js";
|
|
16
|
+
import "./chunk-QEENJGCL.js";
|
|
17
17
|
import "./chunk-O2S6PAJE.js";
|
|
18
18
|
export {
|
|
19
19
|
VIEWPORTS,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
screenshotVariants
|
|
4
|
+
} from "./chunk-F6JZSXEO.js";
|
|
5
|
+
import "./chunk-A64VVTPU.js";
|
|
6
|
+
import "./chunk-VH7OOFQP.js";
|
|
7
|
+
import "./chunk-IMDRXXFU.js";
|
|
8
|
+
import "./chunk-KVYGPLWW.js";
|
|
9
|
+
import "./chunk-P3H4N2EN.js";
|
|
10
|
+
export {
|
|
11
|
+
screenshotVariants
|
|
12
|
+
};
|
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
runVariants,
|
|
10
10
|
variantPrompt,
|
|
11
11
|
variantsRoot
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-A64VVTPU.js";
|
|
13
|
+
import "./chunk-VH7OOFQP.js";
|
|
14
|
+
import "./chunk-KVYGPLWW.js";
|
|
15
15
|
import "./chunk-P3H4N2EN.js";
|
|
16
16
|
export {
|
|
17
17
|
MAX_VARIANTS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aayambansal/squint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Lovable for your terminal — a frontend harness on top of Claude Code, Codex, and other coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"commander": "^15.0.0",
|
|
51
51
|
"ink": "^7.1.1",
|
|
52
|
+
"ink-picture": "^2.1.0",
|
|
52
53
|
"picocolors": "^1.1.1",
|
|
53
54
|
"react": "^19.1.0",
|
|
54
55
|
"zod": "^4.4.3"
|
package/dist/shots-FVG4CDVK.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
screenshotVariants
|
|
4
|
-
} from "./chunk-5VKUJPOL.js";
|
|
5
|
-
import "./chunk-X6MDBKED.js";
|
|
6
|
-
import "./chunk-C7WKNJG6.js";
|
|
7
|
-
import "./chunk-B7LOERSP.js";
|
|
8
|
-
import "./chunk-BWZFACBT.js";
|
|
9
|
-
import "./chunk-P3H4N2EN.js";
|
|
10
|
-
export {
|
|
11
|
-
screenshotVariants
|
|
12
|
-
};
|