@aayambansal/squint 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/{cdp-C6MOKVEN.js → cdp-OYRHOPVP.js} +1 -1
- package/dist/{chunk-O7NWGLS3.js → chunk-43NQNIJY.js} +2 -0
- package/dist/{chunk-YT6K2YIS.js → chunk-ARDV4XH6.js} +9 -0
- package/dist/{chunk-ZUEGDTAM.js → chunk-D7Q3M6PA.js} +64 -14
- package/dist/{chunk-JFFNQIBU.js → chunk-X2RZJT4L.js} +91 -2
- package/dist/cli.js +87 -36
- package/dist/{commands-VDICJJT2.js → commands-BY44HDQ6.js} +1 -1
- package/dist/{contextDoctor-M3R3PICP.js → contextDoctor-A26C2ZDN.js} +2 -1
- package/dist/{preview-Q43LOK6P.js → preview-XREFPMHX.js} +2 -2
- package/dist/{skills-GTTF4PNU.js → skills-POB4ZZY5.js} +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -174,6 +174,11 @@ squint doctor --probe # run every engine end to end, verify auth act
|
|
|
174
174
|
flagged from the live page, and on Next 16+ the framework's own `/_next/mcp`
|
|
175
175
|
channel feeds structured errors straight into the fix loop. `/context` itemizes
|
|
176
176
|
the injected-context bill per source, with staleness warnings.
|
|
177
|
+
- **Persistent checks**: assertions the engine verifies once persist as
|
|
178
|
+
`.squint/checks/*.js` and replay against the live page every turn — one-off
|
|
179
|
+
verifications compound into repo-versioned regression checks.
|
|
180
|
+
- **Visual approval**: engines ask before contested changes — the request renders
|
|
181
|
+
with its screenshot, `/yes` / `/no` answer it, the ledger remembers.
|
|
177
182
|
- **The design ledger**: `/decide` (plus chosen variants, rollbacks, accepted
|
|
178
183
|
sandboxes) appends to a committed `.squint/design-log.jsonl`; recent decisions ride
|
|
179
184
|
into every ask so they stop getting silently undone between sessions.
|
|
@@ -28,6 +28,8 @@ var COMMANDS = [
|
|
|
28
28
|
{ name: "find", args: "<term>", group: "session", description: "search this session and saved transcripts" },
|
|
29
29
|
{ name: "decide", args: "<text>", group: "session", description: "record a design decision; injected into every future ask" },
|
|
30
30
|
{ name: "context", group: "session", description: "what squint injects per ask, token-costed, with staleness warnings" },
|
|
31
|
+
{ name: "yes", args: "[note]", group: "build", description: "approve the engine's pending visual-approval request" },
|
|
32
|
+
{ name: "no", args: "[note]", group: "build", description: "reject the engine's pending visual-approval request" },
|
|
31
33
|
{ name: "resume", group: "session", description: "pick up the previous session for this repo" },
|
|
32
34
|
{ name: "clear", group: "session", description: "new session (transcript, totals, persisted state)" },
|
|
33
35
|
{ name: "help", group: "session", description: "list commands" },
|
|
@@ -156,6 +156,15 @@ ${locks.map((l) => `- ${l}`).join("\n")}
|
|
|
156
156
|
If the task appears to require changing them, stop and explain instead.`
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
|
+
parts.push(
|
|
160
|
+
`## Requesting visual approval
|
|
161
|
+
|
|
162
|
+
For a visual decision you should not make alone (a redesign direction, removing something deliberate, reversing a design decision on record): write .squint/approval-request.json containing {"summary": "<one line>", "screenshot": "<path, optional>"} and end your turn immediately without making the change. The user's verdict arrives as the next message.
|
|
163
|
+
|
|
164
|
+
## Persistent checks
|
|
165
|
+
|
|
166
|
+
When you verify something about the page that should stay true (an element exists, a state renders, a metric holds), persist it as .squint/checks/<name>.js \u2014 plain JS that evaluates IN THE PAGE to an array of failure strings (empty array = pass). squint replays every check against the live page after each turn.`
|
|
167
|
+
);
|
|
159
168
|
const matched = matchSkills(loadSkills(cwd), ask);
|
|
160
169
|
for (const skill of matched) {
|
|
161
170
|
parts.push(`## Project notes: ${skill.name}
|
|
@@ -9,12 +9,39 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
cdpCapture,
|
|
11
11
|
hasWebSocket
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-X2RZJT4L.js";
|
|
13
13
|
|
|
14
14
|
// src/preview/preview.ts
|
|
15
|
-
import
|
|
15
|
+
import fs2 from "fs";
|
|
16
16
|
import os from "os";
|
|
17
|
+
import path2 from "path";
|
|
18
|
+
|
|
19
|
+
// src/preview/checks.ts
|
|
20
|
+
import fs from "fs";
|
|
17
21
|
import path from "path";
|
|
22
|
+
var MAX_CHECKS = 20;
|
|
23
|
+
var MAX_BYTES = 1e4;
|
|
24
|
+
function loadChecks(cwd) {
|
|
25
|
+
const dir = path.join(cwd, ".squint", "checks");
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = fs.readdirSync(dir).filter((f) => f.endsWith(".js"));
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const checks = [];
|
|
33
|
+
for (const entry of entries.sort().slice(0, MAX_CHECKS)) {
|
|
34
|
+
try {
|
|
35
|
+
const source = fs.readFileSync(path.join(dir, entry), "utf8");
|
|
36
|
+
if (source.trim().length === 0 || Buffer.byteLength(source) > MAX_BYTES) continue;
|
|
37
|
+
checks.push({ name: entry.replace(/\.js$/, ""), source });
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return checks;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/preview/preview.ts
|
|
18
45
|
var VIEWPORTS = [
|
|
19
46
|
{ name: "mobile", width: 390, height: 844 },
|
|
20
47
|
{ name: "tablet", width: 768, height: 1024 },
|
|
@@ -23,7 +50,7 @@ var VIEWPORTS = [
|
|
|
23
50
|
function loadRoutes(cwd) {
|
|
24
51
|
let lines = [];
|
|
25
52
|
try {
|
|
26
|
-
lines =
|
|
53
|
+
lines = fs2.readFileSync(path2.join(cwd, ".squint", "routes"), "utf8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
27
54
|
} catch {
|
|
28
55
|
}
|
|
29
56
|
const routes = ["/", ...lines.filter((l) => l !== "/")];
|
|
@@ -34,8 +61,8 @@ function routeShotName(route) {
|
|
|
34
61
|
return clean.length > 0 ? clean : "root";
|
|
35
62
|
}
|
|
36
63
|
function previewDir(cwd) {
|
|
37
|
-
const dir =
|
|
38
|
-
|
|
64
|
+
const dir = path2.join(cwd, ".squint", "preview");
|
|
65
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
39
66
|
ensureSquintIgnore(cwd);
|
|
40
67
|
return dir;
|
|
41
68
|
}
|
|
@@ -47,7 +74,8 @@ async function captureViewports(cwd, url) {
|
|
|
47
74
|
const base = url.replace(/\/+$/, "");
|
|
48
75
|
if (hasWebSocket()) {
|
|
49
76
|
try {
|
|
50
|
-
const
|
|
77
|
+
const checks = loadChecks(cwd);
|
|
78
|
+
const { report, shots: shots2, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true, checks);
|
|
51
79
|
const errors2 = [];
|
|
52
80
|
for (const route of routes.slice(1)) {
|
|
53
81
|
try {
|
|
@@ -62,14 +90,14 @@ async function captureViewports(cwd, url) {
|
|
|
62
90
|
errors2.push(`${route}: capture failed`);
|
|
63
91
|
}
|
|
64
92
|
}
|
|
65
|
-
return { shots: shots2, errors: errors2, runtime: report, a11y, slop, narration, phantoms, viewTransitions };
|
|
93
|
+
return { shots: shots2, errors: errors2, runtime: report, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp };
|
|
66
94
|
} catch {
|
|
67
95
|
}
|
|
68
96
|
}
|
|
69
97
|
const shots = [];
|
|
70
98
|
const errors = [];
|
|
71
99
|
for (const viewport of VIEWPORTS) {
|
|
72
|
-
const outPath =
|
|
100
|
+
const outPath = path2.join(dir, `${viewport.name}.png`);
|
|
73
101
|
const result = await screenshot(chrome, url, outPath, {
|
|
74
102
|
width: viewport.width,
|
|
75
103
|
height: viewport.height
|
|
@@ -118,14 +146,16 @@ async function probeRuntime(url, cwd) {
|
|
|
118
146
|
if (!chrome || !hasWebSocket()) return null;
|
|
119
147
|
try {
|
|
120
148
|
const dir = cwd ? previewDir(cwd) : os.tmpdir();
|
|
121
|
-
const { report, shots, perf } = await cdpCapture(
|
|
149
|
+
const { report, shots, perf, checkFailures } = await cdpCapture(
|
|
122
150
|
chrome,
|
|
123
151
|
url,
|
|
124
152
|
dir,
|
|
125
153
|
cwd ? [{ name: "pulse", width: 1280, height: 800 }] : [],
|
|
126
|
-
1500
|
|
154
|
+
1500,
|
|
155
|
+
false,
|
|
156
|
+
cwd ? loadChecks(cwd) : []
|
|
127
157
|
);
|
|
128
|
-
return { report, pulsePath: shots[0]?.path, perf };
|
|
158
|
+
return { report, pulsePath: shots[0]?.path, perf, checkFailures };
|
|
129
159
|
} catch {
|
|
130
160
|
return null;
|
|
131
161
|
}
|
|
@@ -133,7 +163,7 @@ async function probeRuntime(url, cwd) {
|
|
|
133
163
|
async function comparePulse(previous, current) {
|
|
134
164
|
const chrome = findChrome();
|
|
135
165
|
if (!chrome || !hasWebSocket()) return null;
|
|
136
|
-
const { pixelDiffPct } = await import("./cdp-
|
|
166
|
+
const { pixelDiffPct } = await import("./cdp-OYRHOPVP.js");
|
|
137
167
|
return pixelDiffPct(chrome, previous, current);
|
|
138
168
|
}
|
|
139
169
|
function buildRuntimeFixPrompt(report) {
|
|
@@ -161,6 +191,26 @@ ${narration.join("\n")}
|
|
|
161
191
|
|
|
162
192
|
Judge this narration as an experience: does the reading order make sense? do names actually describe their targets? is anything announced as "(no accessible name)"? Fix real incoherence \u2014 this is how non-visual users meet the page.`;
|
|
163
193
|
}
|
|
194
|
+
function webmcpSection(webmcp) {
|
|
195
|
+
if (!webmcp || webmcp.length === 0) return "";
|
|
196
|
+
return `
|
|
197
|
+
|
|
198
|
+
## Page-declared WebMCP tools
|
|
199
|
+
|
|
200
|
+
${webmcp.join("\n")}
|
|
201
|
+
|
|
202
|
+
The page registers these for agents via navigator.modelContext \u2014 keep them working, and prefer extending them over inventing parallel affordances.`;
|
|
203
|
+
}
|
|
204
|
+
function componentSection(components) {
|
|
205
|
+
if (!components || components.length === 0) return "";
|
|
206
|
+
return `
|
|
207
|
+
|
|
208
|
+
## Component map (from React fibers)
|
|
209
|
+
|
|
210
|
+
${components.join("\n")}
|
|
211
|
+
|
|
212
|
+
Use these owner chains to name the component (and file) an issue lives in instead of describing regions.`;
|
|
213
|
+
}
|
|
164
214
|
function vtSection(viewTransitions) {
|
|
165
215
|
if (!viewTransitions || viewTransitions.length === 0) return "";
|
|
166
216
|
return `
|
|
@@ -191,13 +241,13 @@ ${findings.join("\n")}
|
|
|
191
241
|
|
|
192
242
|
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.`;
|
|
193
243
|
}
|
|
194
|
-
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration, phantoms, viewTransitions) {
|
|
244
|
+
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration, phantoms, viewTransitions, components, webmcp) {
|
|
195
245
|
const list = shots.map((s) => `- ${s.name}: ${s.path}`).join("\n");
|
|
196
246
|
return `Screenshots of the running app were just captured:
|
|
197
247
|
|
|
198
248
|
${list}
|
|
199
249
|
|
|
200
|
-
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)}${narrationSection(narration)}${phantomSection(phantoms)}${vtSection(viewTransitions)}`;
|
|
250
|
+
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)}${narrationSection(narration)}${phantomSection(phantoms)}${vtSection(viewTransitions)}${componentSection(components)}${webmcpSection(webmcp)}`;
|
|
201
251
|
}
|
|
202
252
|
|
|
203
253
|
export {
|
|
@@ -188,6 +188,7 @@ async function runFlow(chromePath, baseUrl, flow, outDir) {
|
|
|
188
188
|
const { targetId } = await connection.send("Target.createTarget", { url: "about:blank" });
|
|
189
189
|
const { sessionId } = await connection.send("Target.attachToTarget", { targetId, flatten: true });
|
|
190
190
|
await connection.send("Page.enable", {}, sessionId);
|
|
191
|
+
await connection.send("Page.addScriptToEvaluateOnNewDocument", { source: WEBMCP_SHIM }, sessionId).catch(() => null);
|
|
191
192
|
await connection.send(
|
|
192
193
|
"Emulation.setDeviceMetricsOverride",
|
|
193
194
|
{ width: 1280, height: 800, deviceScaleFactor: 1, mobile: false },
|
|
@@ -400,7 +401,57 @@ var VT_AUDIT = `(() => {
|
|
|
400
401
|
}
|
|
401
402
|
return findings;
|
|
402
403
|
})()`;
|
|
403
|
-
|
|
404
|
+
var FIBER_AUDIT = `(() => {
|
|
405
|
+
const fiberKey = (el) => Object.keys(el).find((k) => k.startsWith('__reactFiber$'));
|
|
406
|
+
const all = document.querySelectorAll('*');
|
|
407
|
+
let reactSeen = false;
|
|
408
|
+
for (let i = 0; i < all.length && i < 300; i++) {
|
|
409
|
+
if (fiberKey(all[i])) { reactSeen = true; break; }
|
|
410
|
+
}
|
|
411
|
+
if (!reactSeen) return [];
|
|
412
|
+
const nameOf = (t) => {
|
|
413
|
+
if (typeof t === 'function') return t.displayName || t.name || '';
|
|
414
|
+
if (t && typeof t === 'object') return t.displayName || (t.render && (t.render.displayName || t.render.name)) || '';
|
|
415
|
+
return '';
|
|
416
|
+
};
|
|
417
|
+
const chainFor = (el) => {
|
|
418
|
+
const key = fiberKey(el);
|
|
419
|
+
if (!key) return null;
|
|
420
|
+
let fiber = el[key];
|
|
421
|
+
const names = [];
|
|
422
|
+
let hops = 0;
|
|
423
|
+
while (fiber && hops < 50 && names.length < 3) {
|
|
424
|
+
const n = nameOf(fiber.type);
|
|
425
|
+
if (n && !names.includes(n)) names.push(n);
|
|
426
|
+
fiber = fiber.return;
|
|
427
|
+
hops++;
|
|
428
|
+
}
|
|
429
|
+
return names.length > 0 ? names.join(' < ') : null;
|
|
430
|
+
};
|
|
431
|
+
const out = [];
|
|
432
|
+
for (const sel of ['header', 'nav', 'main', 'footer', 'h1', 'h2', 'form', 'aside', '[role="dialog"]', 'table']) {
|
|
433
|
+
const el = document.querySelector(sel);
|
|
434
|
+
if (!el) continue;
|
|
435
|
+
const chain = chainFor(el);
|
|
436
|
+
if (chain) out.push(sel + ' \u2014 ' + chain);
|
|
437
|
+
if (out.length >= 10) break;
|
|
438
|
+
}
|
|
439
|
+
return out;
|
|
440
|
+
})()`;
|
|
441
|
+
var WEBMCP_SHIM = `(() => {
|
|
442
|
+
window.__squintWebMcp = [];
|
|
443
|
+
const record = (tools) => {
|
|
444
|
+
for (const t of tools || []) {
|
|
445
|
+
if (t && t.name) window.__squintWebMcp.push(t.name + (t.description ? ' \u2014 ' + t.description : ''));
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
const target = navigator.modelContext || (navigator.modelContext = {});
|
|
449
|
+
const provide = target.provideContext && target.provideContext.bind(target);
|
|
450
|
+
target.provideContext = (params) => { record(params && params.tools); return provide ? provide(params) : undefined; };
|
|
451
|
+
const register = target.registerTool && target.registerTool.bind(target);
|
|
452
|
+
target.registerTool = (tool) => { record([tool]); return register ? register(tool) : undefined; };
|
|
453
|
+
})()`;
|
|
454
|
+
async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, audit = false, checks = []) {
|
|
404
455
|
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
405
456
|
const report = { consoleErrors: [], pageErrors: [], failedRequests: [] };
|
|
406
457
|
const shots = [];
|
|
@@ -410,6 +461,9 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
410
461
|
let narration = [];
|
|
411
462
|
let phantoms = [];
|
|
412
463
|
let viewTransitions = [];
|
|
464
|
+
let components = [];
|
|
465
|
+
const checkFailures = [];
|
|
466
|
+
let webmcp = [];
|
|
413
467
|
const requests = /* @__PURE__ */ new Map();
|
|
414
468
|
let connection = null;
|
|
415
469
|
try {
|
|
@@ -448,6 +502,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
448
502
|
await connection.send("Runtime.enable", {}, sessionId);
|
|
449
503
|
await connection.send("Network.enable", {}, sessionId);
|
|
450
504
|
await connection.send("Page.enable", {}, sessionId);
|
|
505
|
+
await connection.send("Page.addScriptToEvaluateOnNewDocument", { source: WEBMCP_SHIM }, sessionId).catch(() => null);
|
|
451
506
|
await connection.send("Page.navigate", { url }, sessionId);
|
|
452
507
|
const deadline = Date.now() + 12e3;
|
|
453
508
|
while (!loaded && Date.now() < deadline) {
|
|
@@ -463,6 +518,31 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
463
518
|
if (result?.value && typeof result.value === "object") perf = result.value;
|
|
464
519
|
} catch {
|
|
465
520
|
}
|
|
521
|
+
try {
|
|
522
|
+
const { result } = await connection.send(
|
|
523
|
+
"Runtime.evaluate",
|
|
524
|
+
{ expression: "window.__squintWebMcp || []", returnByValue: true },
|
|
525
|
+
sessionId
|
|
526
|
+
);
|
|
527
|
+
if (Array.isArray(result?.value)) webmcp = result.value.map(String).slice(0, 12);
|
|
528
|
+
} catch {
|
|
529
|
+
}
|
|
530
|
+
for (const check of checks) {
|
|
531
|
+
try {
|
|
532
|
+
const { result, exceptionDetails } = await connection.send(
|
|
533
|
+
"Runtime.evaluate",
|
|
534
|
+
{ expression: check.source, returnByValue: true, timeout: 2e3 },
|
|
535
|
+
sessionId
|
|
536
|
+
);
|
|
537
|
+
if (exceptionDetails) {
|
|
538
|
+
checkFailures.push(`${check.name}: threw ${exceptionDetails.exception?.description?.split("\n")[0] ?? exceptionDetails.text ?? "an error"}`);
|
|
539
|
+
} else if (Array.isArray(result?.value)) {
|
|
540
|
+
for (const finding of result.value.slice(0, 5)) checkFailures.push(`${check.name}: ${String(finding)}`);
|
|
541
|
+
}
|
|
542
|
+
} catch {
|
|
543
|
+
}
|
|
544
|
+
if (checkFailures.length >= 15) break;
|
|
545
|
+
}
|
|
466
546
|
if (audit) {
|
|
467
547
|
try {
|
|
468
548
|
const { result } = await connection.send(
|
|
@@ -500,6 +580,15 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
500
580
|
if (Array.isArray(result?.value)) viewTransitions = result.value.map(String);
|
|
501
581
|
} catch {
|
|
502
582
|
}
|
|
583
|
+
try {
|
|
584
|
+
const { result } = await connection.send(
|
|
585
|
+
"Runtime.evaluate",
|
|
586
|
+
{ expression: FIBER_AUDIT, returnByValue: true },
|
|
587
|
+
sessionId
|
|
588
|
+
);
|
|
589
|
+
if (Array.isArray(result?.value)) components = result.value.map(String);
|
|
590
|
+
} catch {
|
|
591
|
+
}
|
|
503
592
|
try {
|
|
504
593
|
await connection.send("Accessibility.enable", {}, sessionId);
|
|
505
594
|
const { nodes } = await connection.send("Accessibility.getFullAXTree", {}, sessionId);
|
|
@@ -564,7 +653,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
564
653
|
child.kill("SIGKILL");
|
|
565
654
|
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
566
655
|
}
|
|
567
|
-
return { report, shots, a11y, slop, perf, narration, phantoms, viewTransitions };
|
|
656
|
+
return { report, shots, a11y, slop, perf, narration, phantoms, viewTransitions, components, checkFailures, webmcp };
|
|
568
657
|
}
|
|
569
658
|
|
|
570
659
|
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-43NQNIJY.js";
|
|
5
5
|
import {
|
|
6
6
|
applySandbox,
|
|
7
7
|
discardSandbox,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
comparePulse,
|
|
43
43
|
probeRuntime,
|
|
44
44
|
runtimeSummary
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-D7Q3M6PA.js";
|
|
46
46
|
import {
|
|
47
47
|
clearState,
|
|
48
48
|
loadState,
|
|
@@ -58,11 +58,11 @@ import {
|
|
|
58
58
|
detectEngines,
|
|
59
59
|
getEngine
|
|
60
60
|
} from "./chunk-KVYGPLWW.js";
|
|
61
|
-
import "./chunk-
|
|
61
|
+
import "./chunk-X2RZJT4L.js";
|
|
62
62
|
import {
|
|
63
63
|
appendDecision,
|
|
64
64
|
enrich
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-ARDV4XH6.js";
|
|
66
66
|
import "./chunk-K5QJMSJH.js";
|
|
67
67
|
import {
|
|
68
68
|
composePrompt
|
|
@@ -240,7 +240,7 @@ function registerEnv(program2) {
|
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
const { findChrome: findChrome2 } = await import("./chrome-SBV3H77F.js");
|
|
243
|
-
const { hasWebSocket } = await import("./cdp-
|
|
243
|
+
const { hasWebSocket } = await import("./cdp-OYRHOPVP.js");
|
|
244
244
|
const chrome = findChrome2();
|
|
245
245
|
console.log(
|
|
246
246
|
chrome ? `${pc2.green("\u2713")} Chrome ${pc2.dim(chrome)}` : `${pc2.yellow("\u25CB")} Chrome ${pc2.dim("\u2014 screenshots and runtime probing disabled")}`
|
|
@@ -264,7 +264,7 @@ import pc3 from "picocolors";
|
|
|
264
264
|
function registerProject(program2) {
|
|
265
265
|
const skillsCommand = program2.command("skills").description("Project knowledge injected into asks (.squint/rules.md + .squint/skills/)");
|
|
266
266
|
skillsCommand.command("list").description("Show always-on rules and trigger-matched skills").action(async () => {
|
|
267
|
-
const { loadRules, loadSkills } = await import("./skills-
|
|
267
|
+
const { loadRules, loadSkills } = await import("./skills-POB4ZZY5.js");
|
|
268
268
|
const cwd = process.cwd();
|
|
269
269
|
const rules = loadRules(cwd);
|
|
270
270
|
console.log(
|
|
@@ -280,22 +280,22 @@ function registerProject(program2) {
|
|
|
280
280
|
}
|
|
281
281
|
});
|
|
282
282
|
skillsCommand.command("init").description("Scaffold .squint/rules.md and an example skill").action(async () => {
|
|
283
|
-
const
|
|
283
|
+
const fs4 = await import("fs");
|
|
284
284
|
const nodePath = await import("path");
|
|
285
285
|
const cwd = process.cwd();
|
|
286
286
|
const skillsDir = nodePath.join(cwd, ".squint", "skills");
|
|
287
|
-
|
|
287
|
+
fs4.mkdirSync(skillsDir, { recursive: true });
|
|
288
288
|
const rules = nodePath.join(cwd, ".squint", "rules.md");
|
|
289
|
-
if (!
|
|
290
|
-
|
|
289
|
+
if (!fs4.existsSync(rules)) {
|
|
290
|
+
fs4.writeFileSync(
|
|
291
291
|
rules,
|
|
292
292
|
"# 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"
|
|
293
293
|
);
|
|
294
294
|
console.log(pc3.green("\u2713 .squint/rules.md"));
|
|
295
295
|
}
|
|
296
296
|
const example = nodePath.join(skillsDir, "example.md");
|
|
297
|
-
if (!
|
|
298
|
-
|
|
297
|
+
if (!fs4.existsSync(example)) {
|
|
298
|
+
fs4.writeFileSync(
|
|
299
299
|
example,
|
|
300
300
|
"---\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"
|
|
301
301
|
);
|
|
@@ -304,7 +304,7 @@ function registerProject(program2) {
|
|
|
304
304
|
console.log(pc3.dim("rules are always-on; skills inject when an ask mentions a trigger"));
|
|
305
305
|
});
|
|
306
306
|
program2.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) => {
|
|
307
|
-
const
|
|
307
|
+
const fs4 = await import("fs");
|
|
308
308
|
const nodePath = await import("path");
|
|
309
309
|
const { FAMILIES, getFamily, renderFamilyBrief } = await import("./families-3ARYRBMH.js");
|
|
310
310
|
if (!familyId) {
|
|
@@ -322,13 +322,13 @@ function registerProject(program2) {
|
|
|
322
322
|
return;
|
|
323
323
|
}
|
|
324
324
|
const target = nodePath.join(process.cwd(), ".squint", "brief.md");
|
|
325
|
-
if (
|
|
325
|
+
if (fs4.existsSync(target) && !options.force) {
|
|
326
326
|
console.error(pc3.red(`\u2717 ${target} exists \u2014 use --force to overwrite`));
|
|
327
327
|
process.exitCode = 1;
|
|
328
328
|
return;
|
|
329
329
|
}
|
|
330
|
-
|
|
331
|
-
|
|
330
|
+
fs4.mkdirSync(nodePath.dirname(target), { recursive: true });
|
|
331
|
+
fs4.writeFileSync(target, renderFamilyBrief(family) + "\n");
|
|
332
332
|
console.log(pc3.green(`\u2713 ${family.name} direction written to .squint/brief.md`));
|
|
333
333
|
console.log(pc3.dim("every squint ask in this repo now holds this direction \u2014 edit the file to remix"));
|
|
334
334
|
});
|
|
@@ -463,7 +463,7 @@ function registerQuality(program2) {
|
|
|
463
463
|
if (results.some((r) => !r.ok)) process.exitCode = 1;
|
|
464
464
|
});
|
|
465
465
|
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) => {
|
|
466
|
-
const { captureViewports: captureViewports2 } = await import("./preview-
|
|
466
|
+
const { captureViewports: captureViewports2 } = await import("./preview-XREFPMHX.js");
|
|
467
467
|
const result = await captureViewports2(process.cwd(), url);
|
|
468
468
|
if (!result) {
|
|
469
469
|
console.error(pc4.red("\u2717 no Chrome/Chromium found"));
|
|
@@ -581,21 +581,21 @@ Next: ${pc6.bold(`${cd}${options.install ? "" : "npm install && "}squint`)}`);
|
|
|
581
581
|
console.log(pc6.dim("then describe what to build \u2014 /dev starts the preview server"));
|
|
582
582
|
});
|
|
583
583
|
program2.command("tag").description("Add the element picker to this Vite app (Alt+S in the browser \u2192 click \u2192 file:line:col)").action(async () => {
|
|
584
|
-
const
|
|
584
|
+
const fs4 = await import("fs");
|
|
585
585
|
const nodePath = await import("path");
|
|
586
586
|
const { patchViteConfig, TAGGER_FILENAME, TAGGER_SOURCE } = await import("./source-ZZU245VN.js");
|
|
587
587
|
const cwd = process.cwd();
|
|
588
588
|
const taggerPath = nodePath.join(cwd, TAGGER_FILENAME);
|
|
589
|
-
|
|
589
|
+
fs4.writeFileSync(taggerPath, TAGGER_SOURCE);
|
|
590
590
|
console.log(pc6.green(`\u2713 ${TAGGER_FILENAME} written`));
|
|
591
|
-
const configPath = ["vite.config.ts", "vite.config.js", "vite.config.mjs"].map((name) => nodePath.join(cwd, name)).find((candidate) =>
|
|
591
|
+
const configPath = ["vite.config.ts", "vite.config.js", "vite.config.mjs"].map((name) => nodePath.join(cwd, name)).find((candidate) => fs4.existsSync(candidate));
|
|
592
592
|
if (!configPath) {
|
|
593
593
|
console.log(pc6.yellow("\u25CB no vite config found \u2014 add the plugin manually:"));
|
|
594
594
|
console.log(pc6.dim(` import squintTagger from './${TAGGER_FILENAME}'
|
|
595
595
|
plugins: [squintTagger(), \u2026]`));
|
|
596
596
|
return;
|
|
597
597
|
}
|
|
598
|
-
const source =
|
|
598
|
+
const source = fs4.readFileSync(configPath, "utf8");
|
|
599
599
|
const patched = patchViteConfig(source);
|
|
600
600
|
if (patched === "already") {
|
|
601
601
|
console.log(pc6.dim("vite config already wired"));
|
|
@@ -604,7 +604,7 @@ Next: ${pc6.bold(`${cd}${options.install ? "" : "npm install && "}squint`)}`);
|
|
|
604
604
|
console.log(pc6.dim(` import squintTagger from './${TAGGER_FILENAME}'
|
|
605
605
|
plugins: [squintTagger(), \u2026]`));
|
|
606
606
|
} else {
|
|
607
|
-
|
|
607
|
+
fs4.writeFileSync(configPath, patched);
|
|
608
608
|
console.log(pc6.green(`\u2713 ${nodePath.basename(configPath)} wired`));
|
|
609
609
|
}
|
|
610
610
|
console.log(
|
|
@@ -623,6 +623,7 @@ import path4 from "path";
|
|
|
623
623
|
import { useMemo, useRef, useState as useState2, useSyncExternalStore } from "react";
|
|
624
624
|
|
|
625
625
|
// src/session/engine.ts
|
|
626
|
+
import fs3 from "fs";
|
|
626
627
|
import path3 from "path";
|
|
627
628
|
|
|
628
629
|
// src/session/hooks.ts
|
|
@@ -711,6 +712,7 @@ var Session = class {
|
|
|
711
712
|
checkpoints = [];
|
|
712
713
|
fixAttempts = 0;
|
|
713
714
|
reviewTipShown = false;
|
|
715
|
+
pendingApproval = null;
|
|
714
716
|
lastPulse = null;
|
|
715
717
|
lastPerf = null;
|
|
716
718
|
autoReviewedThisAsk = false;
|
|
@@ -825,7 +827,7 @@ ${question}`,
|
|
|
825
827
|
return;
|
|
826
828
|
}
|
|
827
829
|
await this.runTurn(
|
|
828
|
-
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions),
|
|
830
|
+
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp),
|
|
829
831
|
`\u{1F441} polish round ${round}/${rounds}`
|
|
830
832
|
);
|
|
831
833
|
}
|
|
@@ -1041,6 +1043,21 @@ ${question}`,
|
|
|
1041
1043
|
const stat = checkpoint ? diffStatSince(this.opts.cwd, checkpoint.snapshot) : null;
|
|
1042
1044
|
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"}` : "";
|
|
1043
1045
|
this.push("status", `done${secs}${cost}${work}`);
|
|
1046
|
+
try {
|
|
1047
|
+
const reqPath = path3.join(this.opts.cwd, ".squint", "approval-request.json");
|
|
1048
|
+
if (fs3.existsSync(reqPath)) {
|
|
1049
|
+
const req = JSON.parse(fs3.readFileSync(reqPath, "utf8"));
|
|
1050
|
+
fs3.rmSync(reqPath, { force: true });
|
|
1051
|
+
if (typeof req?.summary === "string" && req.summary.length > 0) {
|
|
1052
|
+
this.pendingApproval = req.summary;
|
|
1053
|
+
const shot = typeof req.screenshot === "string" ? path3.resolve(this.execCwd(), req.screenshot) : null;
|
|
1054
|
+
if (shot && fs3.existsSync(shot)) this.push("image", shot);
|
|
1055
|
+
this.push("status", `\u23F8 approval requested: ${req.summary}
|
|
1056
|
+
/yes approves \xB7 /no rejects \xB7 or type feedback`);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
} catch {
|
|
1060
|
+
}
|
|
1044
1061
|
if (checkpoint) {
|
|
1045
1062
|
try {
|
|
1046
1063
|
const { driftSummary, loadTokenIndex, scanDrift } = await import("./tokens-XYGRRAXC.js");
|
|
@@ -1158,6 +1175,21 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1158
1175
|
this.clearProblems("dev");
|
|
1159
1176
|
if (this.opts.autoProbe !== false && this.state.devUrl) {
|
|
1160
1177
|
const probe = await probeRuntime(this.state.devUrl, this.opts.cwd);
|
|
1178
|
+
if (probe?.checkFailures && probe.checkFailures.length > 0) {
|
|
1179
|
+
this.addProblem(
|
|
1180
|
+
"check",
|
|
1181
|
+
`${probe.checkFailures.length} persistent check failure(s)`,
|
|
1182
|
+
`Repo checks in .squint/checks/ failed against the live page. Each line is <check>: <failure>:
|
|
1183
|
+
|
|
1184
|
+
${probe.checkFailures.join("\n")}
|
|
1185
|
+
|
|
1186
|
+
Fix the page (or, only if the assertion itself is genuinely outdated, update that check file and say so).`
|
|
1187
|
+
);
|
|
1188
|
+
this.push("error", `checks: ${probe.checkFailures.length} failure(s)
|
|
1189
|
+
${probe.checkFailures.slice(0, 5).join("\n")}`);
|
|
1190
|
+
} else {
|
|
1191
|
+
this.clearProblems("check");
|
|
1192
|
+
}
|
|
1161
1193
|
const summary = probe ? runtimeSummary(probe.report) : null;
|
|
1162
1194
|
if (probe && summary) {
|
|
1163
1195
|
this.addProblem("runtime", summary, buildRuntimeFixPrompt(probe.report));
|
|
@@ -1175,7 +1207,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1175
1207
|
const captureResult = await this.capture();
|
|
1176
1208
|
if (captureResult) {
|
|
1177
1209
|
await this.runTurn(
|
|
1178
|
-
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration, captureResult.phantoms, captureResult.viewTransitions),
|
|
1210
|
+
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration, captureResult.phantoms, captureResult.viewTransitions, captureResult.components, captureResult.webmcp),
|
|
1179
1211
|
"\u{1F441} auto-review rendered UI"
|
|
1180
1212
|
);
|
|
1181
1213
|
}
|
|
@@ -1463,13 +1495,32 @@ They are objective defects, not style preferences.`
|
|
|
1463
1495
|
break;
|
|
1464
1496
|
}
|
|
1465
1497
|
case "context": {
|
|
1466
|
-
import("./contextDoctor-
|
|
1498
|
+
import("./contextDoctor-A26C2ZDN.js").then(({ contextReport, formatContextReport }) => {
|
|
1467
1499
|
this.push("status", formatContextReport(contextReport(this.execCwd())));
|
|
1468
1500
|
}).catch((error) => {
|
|
1469
1501
|
this.push("status", `context report failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1470
1502
|
});
|
|
1471
1503
|
break;
|
|
1472
1504
|
}
|
|
1505
|
+
case "yes":
|
|
1506
|
+
case "no": {
|
|
1507
|
+
if (!this.pendingApproval) {
|
|
1508
|
+
this.push("status", `nothing awaiting approval \u2014 /${name} answers an engine's approval request`);
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
const summary = this.pendingApproval;
|
|
1512
|
+
this.pendingApproval = null;
|
|
1513
|
+
const approved = name === "yes";
|
|
1514
|
+
appendDecision(this.opts.cwd, {
|
|
1515
|
+
decision: `${approved ? "approved" : "rejected"}: ${summary}${arg ? ` \u2014 ${arg}` : ""}`,
|
|
1516
|
+
source: "approval"
|
|
1517
|
+
});
|
|
1518
|
+
void this.runTurn(
|
|
1519
|
+
approved ? `Approved: "${summary}". Proceed.${arg ? ` Note from the user: ${arg}` : ""}` : `Rejected: "${summary}". Do not proceed with it.${arg ? ` The user says: ${arg}` : " Stop and await direction."}`,
|
|
1520
|
+
approved ? `\u2713 approved${arg ? ` \u2014 ${arg}` : ""}` : `\u2717 rejected${arg ? ` \u2014 ${arg}` : ""}`
|
|
1521
|
+
);
|
|
1522
|
+
break;
|
|
1523
|
+
}
|
|
1473
1524
|
case "decide": {
|
|
1474
1525
|
if (!arg) {
|
|
1475
1526
|
this.push("status", "usage: /decide <the decision> \u2014 recorded in .squint/design-log.jsonl and injected into every future ask");
|
|
@@ -1500,13 +1551,13 @@ They are objective defects, not style preferences.`
|
|
|
1500
1551
|
if (matches.length < 8) {
|
|
1501
1552
|
void (async () => {
|
|
1502
1553
|
try {
|
|
1503
|
-
const
|
|
1554
|
+
const fs4 = await import("fs");
|
|
1504
1555
|
const path5 = await import("path");
|
|
1505
1556
|
const dir = path5.join(this.opts.cwd, ".squint", "transcripts");
|
|
1506
|
-
const files =
|
|
1557
|
+
const files = fs4.readdirSync(dir).filter((f) => f.endsWith(".md")).sort().reverse().slice(0, 20);
|
|
1507
1558
|
for (const file of files) {
|
|
1508
1559
|
if (matches.length >= 8) break;
|
|
1509
|
-
const text =
|
|
1560
|
+
const text = fs4.readFileSync(path5.join(dir, file), "utf8");
|
|
1510
1561
|
for (const line of text.split("\n")) {
|
|
1511
1562
|
if (line.toLowerCase().includes(needle)) {
|
|
1512
1563
|
matches.push(`[${file.replace(/\.md$/, "")}] ${line.trim().slice(0, 90)}`);
|
|
@@ -1529,10 +1580,10 @@ They are objective defects, not style preferences.`
|
|
|
1529
1580
|
}
|
|
1530
1581
|
case "save": {
|
|
1531
1582
|
void (async () => {
|
|
1532
|
-
const
|
|
1583
|
+
const fs4 = await import("fs");
|
|
1533
1584
|
const path5 = await import("path");
|
|
1534
1585
|
const dir = path5.join(this.opts.cwd, ".squint", "transcripts");
|
|
1535
|
-
|
|
1586
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1536
1587
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
1537
1588
|
const file = path5.join(dir, `${stamp}.md`);
|
|
1538
1589
|
const lines = [`# squint session \u2014 ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`, ""];
|
|
@@ -1557,7 +1608,7 @@ They are objective defects, not style preferences.`
|
|
|
1557
1608
|
}
|
|
1558
1609
|
}
|
|
1559
1610
|
lines.push("", `> ${this.summary()}`);
|
|
1560
|
-
|
|
1611
|
+
fs4.writeFileSync(file, lines.join("\n") + "\n");
|
|
1561
1612
|
this.push("status", `saved transcript \u2192 ${path5.relative(this.opts.cwd, file)}`);
|
|
1562
1613
|
})();
|
|
1563
1614
|
break;
|
|
@@ -1579,8 +1630,8 @@ They are objective defects, not style preferences.`
|
|
|
1579
1630
|
this.push("error", "no Chrome/Chromium found for flows");
|
|
1580
1631
|
return;
|
|
1581
1632
|
}
|
|
1582
|
-
const { runFlow } = await import("./cdp-
|
|
1583
|
-
const { previewDir } = await import("./preview-
|
|
1633
|
+
const { runFlow } = await import("./cdp-OYRHOPVP.js");
|
|
1634
|
+
const { previewDir } = await import("./preview-XREFPMHX.js");
|
|
1584
1635
|
this.push("status", `replaying ${flows.length} flow(s)\u2026`);
|
|
1585
1636
|
this.notify({ running: true, runStartedAt: Date.now() });
|
|
1586
1637
|
const failures = [];
|
|
@@ -1718,7 +1769,7 @@ They are objective defects, not style preferences.`
|
|
|
1718
1769
|
const result = await this.capture();
|
|
1719
1770
|
if (result) {
|
|
1720
1771
|
await this.runTurn(
|
|
1721
|
-
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions),
|
|
1772
|
+
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp),
|
|
1722
1773
|
`\u{1F441} review rendered UI${arg ? ` \xB7 ${arg}` : ""}`
|
|
1723
1774
|
);
|
|
1724
1775
|
}
|
|
@@ -1904,7 +1955,7 @@ ${sandboxFiles(this.opts.cwd).join("\n")}`);
|
|
|
1904
1955
|
this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
|
|
1905
1956
|
break;
|
|
1906
1957
|
case "help": {
|
|
1907
|
-
void import("./commands-
|
|
1958
|
+
void import("./commands-BY44HDQ6.js").then(({ commandHelp }) => this.push("status", commandHelp()));
|
|
1908
1959
|
break;
|
|
1909
1960
|
}
|
|
1910
1961
|
case "quit":
|
|
@@ -2523,7 +2574,7 @@ function registerTui(program2) {
|
|
|
2523
2574
|
}
|
|
2524
2575
|
|
|
2525
2576
|
// src/cli.tsx
|
|
2526
|
-
var VERSION = true ? "0.4.
|
|
2577
|
+
var VERSION = true ? "0.4.6" : "0.0.0-dev";
|
|
2527
2578
|
var program = new Command();
|
|
2528
2579
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
2529
2580
|
registerRun(program);
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
loadLocks,
|
|
8
8
|
loadRules,
|
|
9
9
|
loadSkills
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ARDV4XH6.js";
|
|
11
11
|
import {
|
|
12
12
|
loadBrief
|
|
13
13
|
} from "./chunk-WAJXATCO.js";
|
|
@@ -54,6 +54,7 @@ function contextReport(cwd) {
|
|
|
54
54
|
warnings.push(`skill "${skill.name}" has trigger(s) ${generic.map((t) => `"${t}"`).join(", ")} short enough to match almost any ask \u2014 make them more specific`);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
lines.push({ source: "approval protocol (built-in)", tokens: 70, when: "every ask" });
|
|
57
58
|
const totalAlways = lines.filter((l) => l.when === "every ask").reduce((sum, l) => sum + l.tokens, 0);
|
|
58
59
|
if (totalAlways > 3e3) warnings.push(`~${totalAlways} tokens ride on every single ask \u2014 that is real money and real attention; trim the always-on set`);
|
|
59
60
|
return { lines, warnings, totalAlways };
|
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
probeRuntime,
|
|
11
11
|
routeShotName,
|
|
12
12
|
runtimeSummary
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-D7Q3M6PA.js";
|
|
14
14
|
import "./chunk-O2S6PAJE.js";
|
|
15
15
|
import "./chunk-IMDRXXFU.js";
|
|
16
16
|
import "./chunk-KVYGPLWW.js";
|
|
17
|
-
import "./chunk-
|
|
17
|
+
import "./chunk-X2RZJT4L.js";
|
|
18
18
|
export {
|
|
19
19
|
VIEWPORTS,
|
|
20
20
|
buildReviewPrompt,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aayambansal/squint",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Lovable for your terminal
|
|
3
|
+
"version": "0.4.6",
|
|
4
|
+
"description": "Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and other coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Aayam Bansal",
|