@aayambansal/squint 0.2.9 → 0.3.1
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-2XAU5MEA.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-OC6RU6XH.js → chunk-DAETGO2M.js} +91 -1
- package/dist/{chunk-5VKUJPOL.js → chunk-F6JZSXEO.js} +3 -3
- package/dist/{chunk-RMNG2NTU.js → chunk-IH4L2KR6.js} +2 -1
- package/dist/{chunk-B7LOERSP.js → chunk-IMDRXXFU.js} +1 -1
- package/dist/{chunk-BWZFACBT.js → chunk-KVYGPLWW.js} +12 -2
- package/dist/{chunk-GOAFH2VN.js → chunk-OTCH66M7.js} +19 -9
- package/dist/{chunk-C7WKNJG6.js → chunk-VH7OOFQP.js} +1 -1
- package/dist/cli.js +172 -37
- package/dist/{commands-TERCCWFM.js → commands-E7JIVJDP.js} +1 -1
- package/dist/{preview-VW5DAJNV.js → preview-PS7WJ2KO.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/tokens-XYGRRAXC.js +104 -0
- package/dist/{variants-UULPQ7Q5.js → variants-3ZSINJGX.js} +3 -3
- package/package.json +2 -1
- package/dist/shots-FVG4CDVK.js +0 -12
|
@@ -5,6 +5,32 @@ import { spawn } from "child_process";
|
|
|
5
5
|
import fs from "fs";
|
|
6
6
|
import os from "os";
|
|
7
7
|
import path from "path";
|
|
8
|
+
var PERF_PROBE = `(() => {
|
|
9
|
+
const out = {};
|
|
10
|
+
const buffered = (type) => {
|
|
11
|
+
try {
|
|
12
|
+
const po = new PerformanceObserver(() => {});
|
|
13
|
+
po.observe({ type, buffered: true });
|
|
14
|
+
const records = po.takeRecords();
|
|
15
|
+
po.disconnect();
|
|
16
|
+
return records;
|
|
17
|
+
} catch { return []; }
|
|
18
|
+
};
|
|
19
|
+
const lcp = buffered('largest-contentful-paint');
|
|
20
|
+
if (lcp.length > 0) out.lcpMs = Math.round(lcp[lcp.length - 1].startTime);
|
|
21
|
+
let cls = 0;
|
|
22
|
+
for (const e of buffered('layout-shift')) { if (!e.hadRecentInput) cls += e.value; }
|
|
23
|
+
out.cls = Math.round(cls * 1000) / 1000;
|
|
24
|
+
try {
|
|
25
|
+
const resources = performance.getEntriesByType('resource');
|
|
26
|
+
const nav = performance.getEntriesByType('navigation')[0];
|
|
27
|
+
let bytes = nav ? (nav.transferSize || 0) : 0;
|
|
28
|
+
for (const r of resources) bytes += r.transferSize || 0;
|
|
29
|
+
out.transferBytes = bytes;
|
|
30
|
+
out.requests = resources.length + (nav ? 1 : 0);
|
|
31
|
+
} catch {}
|
|
32
|
+
return out;
|
|
33
|
+
})()`;
|
|
8
34
|
var A11Y_AUDIT = `(() => {
|
|
9
35
|
const out = [];
|
|
10
36
|
const name = (el) => (el.getAttribute('aria-label') || el.textContent || el.getAttribute('title') || '').trim();
|
|
@@ -201,11 +227,57 @@ async function pixelDiffPct(chromePath, pngA, pngB) {
|
|
|
201
227
|
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
202
228
|
}
|
|
203
229
|
}
|
|
230
|
+
var SLOP_AUDIT = `(() => {
|
|
231
|
+
const out = [];
|
|
232
|
+
const bodyFont = (getComputedStyle(document.body).fontFamily || '').toLowerCase();
|
|
233
|
+
const h1 = document.querySelector('h1,h2');
|
|
234
|
+
const displayFont = h1 ? (getComputedStyle(h1).fontFamily || '').toLowerCase() : bodyFont;
|
|
235
|
+
for (const tell of ['inter', 'roboto', 'arial', 'space grotesk']) {
|
|
236
|
+
if (displayFont.includes(tell) || bodyFont.split(',')[0].includes(tell)) {
|
|
237
|
+
out.push('generic font stack: ' + tell + ' (' + (displayFont.includes(tell) ? 'display' : 'body') + ')');
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const vw = innerWidth, vh = innerHeight;
|
|
242
|
+
for (const el of document.querySelectorAll('*')) {
|
|
243
|
+
const r = el.getBoundingClientRect();
|
|
244
|
+
if (r.top > vh || r.width * r.height < vw * vh * 0.2) continue;
|
|
245
|
+
const bg = getComputedStyle(el).backgroundImage || '';
|
|
246
|
+
if (bg.includes('gradient')) {
|
|
247
|
+
const purples = bg.match(/rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)/g) || [];
|
|
248
|
+
if (purples.some((c) => { const [r2, g2, b2] = c.slice(4).split(',').map(Number); return b2 > 150 && r2 > 80 && r2 < 200 && g2 < r2; })) {
|
|
249
|
+
out.push('purple/violet gradient on a hero-scale element');
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (const container of document.querySelectorAll('section, div')) {
|
|
255
|
+
const kids = [...container.children];
|
|
256
|
+
if (kids.length < 3 || kids.length > 4) continue;
|
|
257
|
+
const rects = kids.map((k) => k.getBoundingClientRect());
|
|
258
|
+
if (rects[0].width < 150 || rects[0].top > vh * 2) continue;
|
|
259
|
+
const sameSize = rects.every((r) => Math.abs(r.width - rects[0].width) < 4 && Math.abs(r.height - rects[0].height) < 24);
|
|
260
|
+
const cardish = kids.every((k) => k.querySelector('svg, img') && k.querySelector('h2,h3,h4') && k.querySelector('p'));
|
|
261
|
+
if (sameSize && cardish) { out.push('identical icon-card grid (' + kids.length + ' cards)'); break; }
|
|
262
|
+
}
|
|
263
|
+
let emojiBullets = 0;
|
|
264
|
+
for (const li of document.querySelectorAll('li')) {
|
|
265
|
+
if (/^[\\u{1F300}-\\u{1FAFF}\\u{2600}-\\u{27BF}]/u.test((li.textContent || '').trim())) emojiBullets++;
|
|
266
|
+
}
|
|
267
|
+
if (emojiBullets >= 3) out.push(emojiBullets + ' emoji-bulleted list items');
|
|
268
|
+
const rootStyle = getComputedStyle(document.documentElement);
|
|
269
|
+
if (rootStyle.getPropertyValue('--radius').trim() === '0.5rem' && rootStyle.getPropertyValue('--primary').trim() === '222.2 47.4% 11.2%') {
|
|
270
|
+
out.push('untouched shadcn default theme tokens');
|
|
271
|
+
}
|
|
272
|
+
return out.slice(0, 8);
|
|
273
|
+
})()`;
|
|
204
274
|
async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, audit = false) {
|
|
205
275
|
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
206
276
|
const report = { consoleErrors: [], pageErrors: [], failedRequests: [] };
|
|
207
277
|
const shots = [];
|
|
208
278
|
let a11y = [];
|
|
279
|
+
let slop = [];
|
|
280
|
+
let perf = {};
|
|
209
281
|
const requests = /* @__PURE__ */ new Map();
|
|
210
282
|
let connection = null;
|
|
211
283
|
try {
|
|
@@ -250,6 +322,15 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
250
322
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
251
323
|
}
|
|
252
324
|
await new Promise((resolve) => setTimeout(resolve, settleMs));
|
|
325
|
+
try {
|
|
326
|
+
const { result } = await connection.send(
|
|
327
|
+
"Runtime.evaluate",
|
|
328
|
+
{ expression: PERF_PROBE, returnByValue: true },
|
|
329
|
+
sessionId
|
|
330
|
+
);
|
|
331
|
+
if (result?.value && typeof result.value === "object") perf = result.value;
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
253
334
|
if (audit) {
|
|
254
335
|
try {
|
|
255
336
|
const { result } = await connection.send(
|
|
@@ -260,6 +341,15 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
260
341
|
if (Array.isArray(result?.value)) a11y = result.value.map(String);
|
|
261
342
|
} catch {
|
|
262
343
|
}
|
|
344
|
+
try {
|
|
345
|
+
const { result } = await connection.send(
|
|
346
|
+
"Runtime.evaluate",
|
|
347
|
+
{ expression: SLOP_AUDIT, returnByValue: true },
|
|
348
|
+
sessionId
|
|
349
|
+
);
|
|
350
|
+
if (Array.isArray(result?.value)) slop = result.value.map(String);
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
263
353
|
}
|
|
264
354
|
for (const viewport of viewports) {
|
|
265
355
|
await connection.send(
|
|
@@ -283,7 +373,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
283
373
|
child.kill("SIGKILL");
|
|
284
374
|
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
285
375
|
}
|
|
286
|
-
return { report, shots, a11y };
|
|
376
|
+
return { report, shots, a11y, slop, perf };
|
|
287
377
|
}
|
|
288
378
|
|
|
289
379
|
export {
|
|
@@ -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";
|
|
@@ -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" },
|
|
@@ -19,6 +19,7 @@ var COMMANDS = [
|
|
|
19
19
|
{ name: "checkpoints", group: "explore", description: "list per-ask checkpoints" },
|
|
20
20
|
{ name: "restore", args: "<n>", group: "explore", description: "rewind files to before ask n" },
|
|
21
21
|
{ name: "theme", args: "[name]", group: "session", description: "switch the TUI theme", viewLevel: true },
|
|
22
|
+
{ name: "btw", args: "<question>", group: "session", description: "read-only side question; the main thread is untouched" },
|
|
22
23
|
{ name: "copy", group: "session", description: "copy the last reply to the clipboard" },
|
|
23
24
|
{ name: "save", group: "session", description: "export the transcript to .squint/transcripts/" },
|
|
24
25
|
{ name: "resume", group: "session", description: "pick up the previous session for this repo" },
|
|
@@ -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:
|
|
@@ -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-DAETGO2M.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
|
}
|
|
@@ -118,14 +118,14 @@ async function probeRuntime(url, cwd) {
|
|
|
118
118
|
if (!chrome || !hasWebSocket()) return null;
|
|
119
119
|
try {
|
|
120
120
|
const dir = cwd ? previewDir(cwd) : os.tmpdir();
|
|
121
|
-
const { report, shots } = await cdpCapture(
|
|
121
|
+
const { report, shots, perf } = await cdpCapture(
|
|
122
122
|
chrome,
|
|
123
123
|
url,
|
|
124
124
|
dir,
|
|
125
125
|
cwd ? [{ name: "pulse", width: 1280, height: 800 }] : [],
|
|
126
126
|
1500
|
|
127
127
|
);
|
|
128
|
-
return { report, pulsePath: shots[0]?.path };
|
|
128
|
+
return { report, pulsePath: shots[0]?.path, perf };
|
|
129
129
|
} catch {
|
|
130
130
|
return null;
|
|
131
131
|
}
|
|
@@ -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-2XAU5MEA.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-IH4L2KR6.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-OTCH66M7.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-DAETGO2M.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-2XAU5MEA.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-PS7WJ2KO.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
|
|
|
@@ -671,6 +672,7 @@ var Session = class {
|
|
|
671
672
|
fixAttempts = 0;
|
|
672
673
|
reviewTipShown = false;
|
|
673
674
|
lastPulse = null;
|
|
675
|
+
lastPerf = null;
|
|
674
676
|
autoReviewedThisAsk = false;
|
|
675
677
|
startedAt = Date.now();
|
|
676
678
|
subscribe(listener) {
|
|
@@ -732,6 +734,41 @@ var Session = class {
|
|
|
732
734
|
this.dispatchFix([...this.state.problems]);
|
|
733
735
|
return true;
|
|
734
736
|
}
|
|
737
|
+
/**
|
|
738
|
+
* A side question: read-only, no session resume, so the main thread's
|
|
739
|
+
* context is untouched (Cursor's /btw). Costs count; loops don't run.
|
|
740
|
+
*/
|
|
741
|
+
async btw(question) {
|
|
742
|
+
this.push("user", `\u{1F4AC} btw: ${question}`);
|
|
743
|
+
this.notify({ running: true, runStartedAt: Date.now() });
|
|
744
|
+
const engine = getEngine(this.state.engineId);
|
|
745
|
+
this.abort = new AbortController();
|
|
746
|
+
const result = await runAgent(
|
|
747
|
+
engine,
|
|
748
|
+
{
|
|
749
|
+
prompt: `Answer this question about the repository. Investigate as needed but make no changes:
|
|
750
|
+
|
|
751
|
+
${question}`,
|
|
752
|
+
cwd: this.execCwd(),
|
|
753
|
+
model: this.state.model,
|
|
754
|
+
mode: "plan"
|
|
755
|
+
},
|
|
756
|
+
this.handleEvent,
|
|
757
|
+
this.abort.signal
|
|
758
|
+
);
|
|
759
|
+
this.abort = null;
|
|
760
|
+
this.commitLive();
|
|
761
|
+
this.flushToolCollapse();
|
|
762
|
+
if (result.ok) {
|
|
763
|
+
const cost = result.costUsd !== void 0 ? ` \xB7 $${result.costUsd.toFixed(2)}` : "";
|
|
764
|
+
this.push("status", `btw answered${cost}`);
|
|
765
|
+
this.notify({
|
|
766
|
+
totals: { costUsd: this.state.totals.costUsd + (result.costUsd ?? 0), turns: this.state.totals.turns }
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
this.notify({ running: false });
|
|
770
|
+
this.drainQueue();
|
|
771
|
+
}
|
|
735
772
|
setMode(mode) {
|
|
736
773
|
this.notify({ mode });
|
|
737
774
|
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";
|
|
@@ -765,6 +802,18 @@ var Session = class {
|
|
|
765
802
|
this.push("status", "queue cleared");
|
|
766
803
|
return;
|
|
767
804
|
}
|
|
805
|
+
const drop = /^\/queue drop (\d+)$/.exec(value);
|
|
806
|
+
if (drop) {
|
|
807
|
+
const index = Number.parseInt(drop[1], 10) - 1;
|
|
808
|
+
if (index >= 0 && index < this.state.queue.length) {
|
|
809
|
+
const removed = this.state.queue[index];
|
|
810
|
+
this.notify({ queue: this.state.queue.filter((_, i) => i !== index) });
|
|
811
|
+
this.push("status", `dropped from queue: ${removed}`);
|
|
812
|
+
} else {
|
|
813
|
+
this.push("status", `queue has ${this.state.queue.length} item(s) \u2014 /queue drop <1-${Math.max(this.state.queue.length, 1)}>`);
|
|
814
|
+
}
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
768
817
|
this.notify({ queue: [...this.state.queue, value] });
|
|
769
818
|
return;
|
|
770
819
|
}
|
|
@@ -930,6 +979,18 @@ var Session = class {
|
|
|
930
979
|
const stat = checkpoint ? diffStatSince(this.opts.cwd, checkpoint.snapshot) : null;
|
|
931
980
|
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"}` : "";
|
|
932
981
|
this.push("status", `done${secs}${cost}${work}`);
|
|
982
|
+
if (checkpoint) {
|
|
983
|
+
try {
|
|
984
|
+
const { driftSummary, loadTokenIndex, scanDrift } = await import("./tokens-XYGRRAXC.js");
|
|
985
|
+
const index = loadTokenIndex(this.execCwd());
|
|
986
|
+
const drift = scanDrift(this.execCwd(), checkpoint.snapshot.stashHash ?? "HEAD", index);
|
|
987
|
+
if (drift.length > 0) {
|
|
988
|
+
this.push("status", `token drift: ${drift.length} hardcoded color(s)
|
|
989
|
+
${driftSummary(drift)}`);
|
|
990
|
+
}
|
|
991
|
+
} catch {
|
|
992
|
+
}
|
|
993
|
+
}
|
|
933
994
|
const before = this.state.totals.costUsd;
|
|
934
995
|
this.notify({
|
|
935
996
|
totals: {
|
|
@@ -950,6 +1011,7 @@ var Session = class {
|
|
|
950
1011
|
sessionId: this.sessionId,
|
|
951
1012
|
model: this.state.model,
|
|
952
1013
|
lastAsk: display.length > 80 ? `${display.slice(0, 79)}\u2026` : display,
|
|
1014
|
+
totals: this.state.totals,
|
|
953
1015
|
at: Date.now()
|
|
954
1016
|
});
|
|
955
1017
|
}
|
|
@@ -1004,6 +1066,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1004
1066
|
this.push("status", "/fix sends open problems to the engine \xB7 /problems lists them");
|
|
1005
1067
|
} else if (probe) {
|
|
1006
1068
|
this.clearProblems("runtime");
|
|
1069
|
+
this.perfPulse(probe.perf);
|
|
1007
1070
|
const pct = await this.visualPulse(probe.pulsePath);
|
|
1008
1071
|
if (this.opts.autoReview && pct !== null && pct >= 10 && !this.autoReviewedThisAsk) {
|
|
1009
1072
|
this.autoReviewedThisAsk = true;
|
|
@@ -1012,7 +1075,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1012
1075
|
const captureResult = await this.capture();
|
|
1013
1076
|
if (captureResult) {
|
|
1014
1077
|
await this.runTurn(
|
|
1015
|
-
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y),
|
|
1078
|
+
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop),
|
|
1016
1079
|
"\u{1F441} auto-review rendered UI"
|
|
1017
1080
|
);
|
|
1018
1081
|
}
|
|
@@ -1069,6 +1132,25 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1069
1132
|
this.push("error", `restore failed: ${result.detail ?? "unknown error"}`);
|
|
1070
1133
|
}
|
|
1071
1134
|
}
|
|
1135
|
+
/** Cross-turn load-performance deltas: the perf twin of the visual pulse. */
|
|
1136
|
+
perfPulse(perf) {
|
|
1137
|
+
if (!perf || perf.lcpMs === void 0 && perf.transferBytes === void 0) return;
|
|
1138
|
+
const previous = this.lastPerf;
|
|
1139
|
+
this.lastPerf = perf;
|
|
1140
|
+
const mb = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
1141
|
+
const parts = [];
|
|
1142
|
+
if (perf.lcpMs !== void 0) {
|
|
1143
|
+
const delta = previous?.lcpMs !== void 0 && Math.abs(perf.lcpMs - previous.lcpMs) >= 100 ? ` (${perf.lcpMs > previous.lcpMs ? "+" : "\u2212"}${Math.abs(perf.lcpMs - previous.lcpMs)}ms)` : "";
|
|
1144
|
+
parts.push(`LCP ${perf.lcpMs}ms${delta}`);
|
|
1145
|
+
}
|
|
1146
|
+
if (perf.cls !== void 0 && perf.cls > 0.05) parts.push(`CLS ${perf.cls}`);
|
|
1147
|
+
if (perf.transferBytes !== void 0) {
|
|
1148
|
+
const delta = previous?.transferBytes !== void 0 && Math.abs(perf.transferBytes - previous.transferBytes) > 100 * 1024 ? ` (${perf.transferBytes > previous.transferBytes ? "+" : "\u2212"}${mb(Math.abs(perf.transferBytes - previous.transferBytes))})` : "";
|
|
1149
|
+
parts.push(`${mb(perf.transferBytes)}${delta}`);
|
|
1150
|
+
}
|
|
1151
|
+
if (perf.requests !== void 0) parts.push(`${perf.requests} req`);
|
|
1152
|
+
if (parts.length > 0) this.push("status", `perf: ${parts.join(" \xB7 ")}`);
|
|
1153
|
+
}
|
|
1072
1154
|
/**
|
|
1073
1155
|
* Cross-turn visual drift check: compare this turn's pulse screenshot
|
|
1074
1156
|
* with the previous one and report how much of the page changed.
|
|
@@ -1086,6 +1168,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1086
1168
|
this.lastPulse = current;
|
|
1087
1169
|
if (!previous) {
|
|
1088
1170
|
this.push("status", "visual pulse: baseline captured");
|
|
1171
|
+
this.push("image", pulsePath);
|
|
1089
1172
|
return null;
|
|
1090
1173
|
}
|
|
1091
1174
|
const pct = await comparePulse(previous, current);
|
|
@@ -1094,6 +1177,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1094
1177
|
"status",
|
|
1095
1178
|
pct < 0.5 ? "visual pulse: stable vs last turn" : `visual pulse: ${pct.toFixed(1)}% of the page changed vs last turn`
|
|
1096
1179
|
);
|
|
1180
|
+
if (pct >= 0.5) this.push("image", pulsePath);
|
|
1097
1181
|
return pct;
|
|
1098
1182
|
}
|
|
1099
1183
|
/** Screenshot the running app (and watch its runtime where CDP is available). */
|
|
@@ -1115,6 +1199,8 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1115
1199
|
"status",
|
|
1116
1200
|
`captured ${result.shots.map((s) => s.name).join(", ")} \u2192 ${path2.dirname(result.shots[0].path)}`
|
|
1117
1201
|
);
|
|
1202
|
+
const desktop = result.shots.find((s) => s.name === "desktop") ?? result.shots[0];
|
|
1203
|
+
this.push("image", desktop.path);
|
|
1118
1204
|
}
|
|
1119
1205
|
if (result.runtime) {
|
|
1120
1206
|
const summary = runtimeSummary(result.runtime);
|
|
@@ -1127,6 +1213,10 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1127
1213
|
this.push("status", "runtime clean \u2014 no console errors, exceptions, or failed requests");
|
|
1128
1214
|
}
|
|
1129
1215
|
}
|
|
1216
|
+
if (result.slop && result.slop.length > 0) {
|
|
1217
|
+
this.push("status", `distinctiveness: ${result.slop.length} tell(s)
|
|
1218
|
+
${result.slop.join("\n")}`);
|
|
1219
|
+
}
|
|
1130
1220
|
if (result.a11y && result.a11y.length > 0) {
|
|
1131
1221
|
this.push("error", `a11y: ${result.a11y.length} finding(s)
|
|
1132
1222
|
${result.a11y.slice(0, 5).join("\n")}`);
|
|
@@ -1150,7 +1240,7 @@ They are objective defects, not style preferences.`
|
|
|
1150
1240
|
const arg = rest.join(" ").trim();
|
|
1151
1241
|
switch (name) {
|
|
1152
1242
|
case "engines": {
|
|
1153
|
-
void import("./registry-
|
|
1243
|
+
void import("./registry-MIJ6LSAY.js").then(({ detectEngines: detectEngines2 }) => {
|
|
1154
1244
|
const lines = detectEngines2().map(({ engine, path: binaryPath }) => {
|
|
1155
1245
|
const mark = binaryPath ? "\u2713" : "\u2717";
|
|
1156
1246
|
const traits = [engine.createParser ? "stream" : "text", engine.supportsResume ? "resume" : null].filter(Boolean).join(" \xB7 ");
|
|
@@ -1258,6 +1348,9 @@ They are objective defects, not style preferences.`
|
|
|
1258
1348
|
case "tool":
|
|
1259
1349
|
lines.push(`- \u2699 ${item.text}`);
|
|
1260
1350
|
break;
|
|
1351
|
+
case "image":
|
|
1352
|
+
lines.push(``, "");
|
|
1353
|
+
break;
|
|
1261
1354
|
case "thinking":
|
|
1262
1355
|
break;
|
|
1263
1356
|
default:
|
|
@@ -1270,6 +1363,13 @@ They are objective defects, not style preferences.`
|
|
|
1270
1363
|
})();
|
|
1271
1364
|
break;
|
|
1272
1365
|
}
|
|
1366
|
+
case "btw":
|
|
1367
|
+
if (!arg) {
|
|
1368
|
+
this.push("status", "usage: /btw <question> \u2014 read-only side question, main thread untouched");
|
|
1369
|
+
} else {
|
|
1370
|
+
void this.btw(arg);
|
|
1371
|
+
}
|
|
1372
|
+
break;
|
|
1273
1373
|
case "copy": {
|
|
1274
1374
|
const last = this.state.items.findLast((i) => i.role === "assistant");
|
|
1275
1375
|
if (!last) {
|
|
@@ -1330,7 +1430,7 @@ They are objective defects, not style preferences.`
|
|
|
1330
1430
|
const result = await this.capture();
|
|
1331
1431
|
if (result) {
|
|
1332
1432
|
await this.runTurn(
|
|
1333
|
-
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y),
|
|
1433
|
+
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop),
|
|
1334
1434
|
`\u{1F441} review rendered UI${arg ? ` \xB7 ${arg}` : ""}`
|
|
1335
1435
|
);
|
|
1336
1436
|
}
|
|
@@ -1380,7 +1480,11 @@ They are objective defects, not style preferences.`
|
|
|
1380
1480
|
break;
|
|
1381
1481
|
}
|
|
1382
1482
|
this.sessionId = saved.sessionId;
|
|
1383
|
-
this.notify({
|
|
1483
|
+
this.notify({
|
|
1484
|
+
engineId: saved.engine,
|
|
1485
|
+
model: saved.model ?? this.state.model,
|
|
1486
|
+
totals: saved.totals ?? this.state.totals
|
|
1487
|
+
});
|
|
1384
1488
|
this.push("status", `resumed ${saved.engine} session${saved.lastAsk ? ` \xB7 "${saved.lastAsk}"` : ""}`);
|
|
1385
1489
|
break;
|
|
1386
1490
|
}
|
|
@@ -1510,7 +1614,7 @@ ${sandboxFiles(this.opts.cwd).join("\n")}`);
|
|
|
1510
1614
|
this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
|
|
1511
1615
|
break;
|
|
1512
1616
|
case "help": {
|
|
1513
|
-
void import("./commands-
|
|
1617
|
+
void import("./commands-E7JIVJDP.js").then(({ commandHelp }) => this.push("status", commandHelp()));
|
|
1514
1618
|
break;
|
|
1515
1619
|
}
|
|
1516
1620
|
case "quit":
|
|
@@ -1772,9 +1876,23 @@ function Markdown({ text }) {
|
|
|
1772
1876
|
}
|
|
1773
1877
|
|
|
1774
1878
|
// src/tui/messages.tsx
|
|
1775
|
-
import { Text as Text2 } from "ink";
|
|
1879
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
1880
|
+
import Image from "ink-picture";
|
|
1776
1881
|
import { useEffect, useState } from "react";
|
|
1882
|
+
|
|
1883
|
+
// src/tui/termImage.ts
|
|
1884
|
+
function supportsInlineImages(env = process.env) {
|
|
1885
|
+
if (env.TMUX) return false;
|
|
1886
|
+
if (env.TERM === "xterm-kitty" || env.KITTY_WINDOW_ID) return true;
|
|
1887
|
+
if (env.TERM === "xterm-ghostty" || env.GHOSTTY_RESOURCES_DIR) return true;
|
|
1888
|
+
if (env.TERM_PROGRAM === "WezTerm") return true;
|
|
1889
|
+
if (env.TERM_PROGRAM === "iTerm.app") return true;
|
|
1890
|
+
return false;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
// src/tui/messages.tsx
|
|
1777
1894
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1895
|
+
var INLINE_IMAGES = supportsInlineImages();
|
|
1778
1896
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1779
1897
|
var PHRASES = ["working", "thinking", "squinting", "crafting", "still at it"];
|
|
1780
1898
|
function WorkingLine({ startedAt }) {
|
|
@@ -1839,6 +1957,20 @@ function MessageLine({ message }) {
|
|
|
1839
1957
|
"\u2717 ",
|
|
1840
1958
|
message.text
|
|
1841
1959
|
] });
|
|
1960
|
+
case "image":
|
|
1961
|
+
if (!INLINE_IMAGES) {
|
|
1962
|
+
return /* @__PURE__ */ jsxs2(Text2, { color: theme2.dim, wrap: "wrap", children: [
|
|
1963
|
+
"\u25A3 ",
|
|
1964
|
+
message.text
|
|
1965
|
+
] });
|
|
1966
|
+
}
|
|
1967
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
1968
|
+
/* @__PURE__ */ jsx2(Image, { src: message.text, width: 48, height: 14, alt: "screenshot" }),
|
|
1969
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme2.dim, children: [
|
|
1970
|
+
"\u25A3 ",
|
|
1971
|
+
message.text
|
|
1972
|
+
] })
|
|
1973
|
+
] });
|
|
1842
1974
|
}
|
|
1843
1975
|
}
|
|
1844
1976
|
|
|
@@ -1998,20 +2130,23 @@ function App({
|
|
|
1998
2130
|
});
|
|
1999
2131
|
const devBadge = state.devState === "running" ? ` \xB7 ${state.devUrl ?? "dev running"}` : state.devState === "starting" ? " \xB7 dev starting\u2026" : state.devState === "crashed" ? " \xB7 dev crashed" : "";
|
|
2000
2132
|
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(
|
|
2133
|
+
return /* @__PURE__ */ jsx3(ThemeProvider, { value: theme2, children: /* @__PURE__ */ jsx3(InkPictureProvider, { children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, children: [
|
|
2134
|
+
/* @__PURE__ */ jsx3(Static, { items: state.items, children: (item) => /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(MessageLine, { message: item }) }, item.id) }),
|
|
2135
|
+
state.liveText.length > 0 && /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Markdown, { text: state.liveText }) }),
|
|
2136
|
+
state.items.length === 0 && state.liveText.length === 0 && !state.running && /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
|
|
2005
2137
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: "describe what to build \u2014 or try:" }),
|
|
2006
2138
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: " /dev start the preview server \xB7 /review after a change \xB7 /variants 3 explore wide" }),
|
|
2007
2139
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: " shift+tab cycles plan/safe/yolo \xB7 type while the agent works to queue asks" })
|
|
2008
2140
|
] }),
|
|
2009
|
-
state.running && /* @__PURE__ */ jsx3(
|
|
2010
|
-
state.queue.map((queued, index) => /* @__PURE__ */ jsx3(
|
|
2011
|
-
"\u22EF
|
|
2012
|
-
|
|
2141
|
+
state.running && /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(WorkingLine, { startedAt: state.runStartedAt }) }),
|
|
2142
|
+
state.queue.map((queued, index) => /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: theme2.dim, children: [
|
|
2143
|
+
"\u22EF ",
|
|
2144
|
+
index + 1,
|
|
2145
|
+
". ",
|
|
2146
|
+
queued,
|
|
2147
|
+
index === state.queue.length - 1 ? " (/queue drop <n> removes)" : ""
|
|
2013
2148
|
] }) }, index)),
|
|
2014
|
-
line.text.startsWith("/") && !line.text.includes(" ") && /* @__PURE__ */ jsx3(
|
|
2149
|
+
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
2150
|
"/",
|
|
2016
2151
|
command.name,
|
|
2017
2152
|
command.args ? ` ${command.args}` : "",
|
|
@@ -2021,13 +2156,13 @@ function App({
|
|
|
2021
2156
|
command.description
|
|
2022
2157
|
] })
|
|
2023
2158
|
] }, command.name)) }),
|
|
2024
|
-
/* @__PURE__ */ jsx3(
|
|
2159
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: state.running ? 0 : 1, children: /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
2025
2160
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.accent, children: "\u276F " }),
|
|
2026
2161
|
line.text.slice(0, line.cursor),
|
|
2027
2162
|
/* @__PURE__ */ jsx3(Text3, { inverse: true, children: line.text[line.cursor] ?? " " }),
|
|
2028
2163
|
line.text.slice(line.cursor + 1)
|
|
2029
2164
|
] }) }),
|
|
2030
|
-
/* @__PURE__ */ jsx3(
|
|
2165
|
+
/* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: theme2.dim, children: [
|
|
2031
2166
|
/* @__PURE__ */ jsxs3(
|
|
2032
2167
|
Text3,
|
|
2033
2168
|
{
|
|
@@ -2057,7 +2192,7 @@ function App({
|
|
|
2057
2192
|
" ",
|
|
2058
2193
|
"\xB7 shift+tab mode \xB7 /help"
|
|
2059
2194
|
] }) })
|
|
2060
|
-
] }) });
|
|
2195
|
+
] }) }) });
|
|
2061
2196
|
}
|
|
2062
2197
|
|
|
2063
2198
|
// src/cli/tui.tsx
|
|
@@ -2095,7 +2230,7 @@ function registerTui(program2) {
|
|
|
2095
2230
|
}
|
|
2096
2231
|
|
|
2097
2232
|
// src/cli.tsx
|
|
2098
|
-
var VERSION = true ? "0.
|
|
2233
|
+
var VERSION = true ? "0.3.1" : "0.0.0-dev";
|
|
2099
2234
|
var program = new Command();
|
|
2100
2235
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
2101
2236
|
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-OTCH66M7.js";
|
|
14
|
+
import "./chunk-IMDRXXFU.js";
|
|
15
|
+
import "./chunk-KVYGPLWW.js";
|
|
16
|
+
import "./chunk-DAETGO2M.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
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/quality/tokens.ts
|
|
4
|
+
import { execFileSync } from "child_process";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
var HEX_RE = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/g;
|
|
8
|
+
var VAR_DEF_RE = /(--[a-zA-Z0-9-_]+)\s*:\s*([^;]+);/g;
|
|
9
|
+
function parseHex(hex) {
|
|
10
|
+
const raw = hex.replace("#", "");
|
|
11
|
+
const full = raw.length === 3 ? raw.split("").map((c) => c + c).join("") : raw;
|
|
12
|
+
if (full.length !== 6) return null;
|
|
13
|
+
const n = Number.parseInt(full, 16);
|
|
14
|
+
if (Number.isNaN(n)) return null;
|
|
15
|
+
return [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
16
|
+
}
|
|
17
|
+
function loadTokenIndex(cwd) {
|
|
18
|
+
const colors = /* @__PURE__ */ new Map();
|
|
19
|
+
const cssFiles = [];
|
|
20
|
+
const walk = (dir, depth) => {
|
|
21
|
+
if (depth > 4 || cssFiles.length > 30) return;
|
|
22
|
+
let entries;
|
|
23
|
+
try {
|
|
24
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
25
|
+
} catch {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
30
|
+
const full = path.join(dir, entry.name);
|
|
31
|
+
if (entry.isDirectory()) walk(full, depth + 1);
|
|
32
|
+
else if (entry.name.endsWith(".css")) cssFiles.push(full);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
walk(path.join(cwd, "src"), 0);
|
|
36
|
+
walk(cwd, 1);
|
|
37
|
+
for (const file of cssFiles.slice(0, 30)) {
|
|
38
|
+
let text;
|
|
39
|
+
try {
|
|
40
|
+
text = fs.readFileSync(file, "utf8");
|
|
41
|
+
} catch {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
for (const match of text.matchAll(VAR_DEF_RE)) {
|
|
45
|
+
const value = match[2].trim();
|
|
46
|
+
const hex = value.match(/#[0-9a-fA-F]{3,6}\b/)?.[0];
|
|
47
|
+
const rgbFn = /rgb\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/.exec(value);
|
|
48
|
+
let rgb = null;
|
|
49
|
+
if (hex) rgb = parseHex(hex);
|
|
50
|
+
else if (rgbFn) rgb = [Number(rgbFn[1]), Number(rgbFn[2]), Number(rgbFn[3])];
|
|
51
|
+
if (rgb) colors.set(match[1], { value, rgb });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { colors };
|
|
55
|
+
}
|
|
56
|
+
var distance = (a, b) => Math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2);
|
|
57
|
+
function scanDrift(cwd, source, index) {
|
|
58
|
+
if (index.colors.size === 0) return [];
|
|
59
|
+
let diff;
|
|
60
|
+
try {
|
|
61
|
+
diff = execFileSync("git", ["diff", "-U0", source, "--", "*.css", "*.tsx", "*.jsx", "*.ts", "*.html"], {
|
|
62
|
+
cwd,
|
|
63
|
+
encoding: "utf8",
|
|
64
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
65
|
+
});
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const findings = [];
|
|
70
|
+
let currentFile = "";
|
|
71
|
+
for (const line of diff.split("\n")) {
|
|
72
|
+
if (line.startsWith("+++ b/")) {
|
|
73
|
+
currentFile = line.slice(6);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (!line.startsWith("+") || line.startsWith("+++")) continue;
|
|
77
|
+
if (/--[a-zA-Z0-9-_]+\s*:/.test(line)) continue;
|
|
78
|
+
for (const match of line.matchAll(HEX_RE)) {
|
|
79
|
+
const rgb = parseHex(match[0]);
|
|
80
|
+
if (!rgb) continue;
|
|
81
|
+
let best = null;
|
|
82
|
+
for (const [token, entry] of index.colors) {
|
|
83
|
+
const d = distance(rgb, entry.rgb);
|
|
84
|
+
if (!best || d < best.d) best = { token, d };
|
|
85
|
+
}
|
|
86
|
+
if (best) {
|
|
87
|
+
findings.push({ file: currentFile, literal: match[0], token: best.token, distance: Math.round(best.d) });
|
|
88
|
+
}
|
|
89
|
+
if (findings.length >= 10) return findings;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return findings;
|
|
93
|
+
}
|
|
94
|
+
function driftSummary(findings) {
|
|
95
|
+
return findings.map(
|
|
96
|
+
(f) => `${f.file}: ${f.literal} \u2192 use var(${f.token})${f.distance === 0 ? " (exact match)" : ` (closest, \u0394${f.distance})`}`
|
|
97
|
+
).join("\n");
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
driftSummary,
|
|
101
|
+
loadTokenIndex,
|
|
102
|
+
parseHex,
|
|
103
|
+
scanDrift
|
|
104
|
+
};
|
|
@@ -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.1",
|
|
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
|
-
};
|