@aayambansal/squint 0.4.3 → 0.4.5
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 +7 -1
- package/dist/{cdp-TPP2RGSR.js → cdp-Q4H6ZHPT.js} +1 -1
- package/dist/{chunk-O7NWGLS3.js → chunk-43NQNIJY.js} +2 -0
- package/dist/{chunk-WCLQZFOR.js → chunk-CV5WVKHU.js} +26 -6
- package/dist/{chunk-Y47X4ENN.js → chunk-RQHOE5MV.js} +98 -1
- package/dist/{chunk-YT6K2YIS.js → chunk-ZLEP2TWF.js} +5 -0
- package/dist/cli.js +102 -36
- package/dist/{commands-VDICJJT2.js → commands-BY44HDQ6.js} +1 -1
- package/dist/{contextDoctor-M3R3PICP.js → contextDoctor-U3YTDFVG.js} +2 -1
- package/dist/nextMcp-42Y63M7W.js +97 -0
- package/dist/{preview-SKHIXVCE.js → preview-ZUCVWGZG.js} +2 -2
- package/dist/{skills-GTTF4PNU.js → skills-6NZP67QT.js} +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -169,7 +169,13 @@ squint doctor --probe # run every engine end to end, verify auth act
|
|
|
169
169
|
design token, and the mechanical anti-slop sweep flags generic-AI tells as
|
|
170
170
|
distinctiveness debt in `/review`. The phantom-class check diffs every DOM class
|
|
171
171
|
against the compiled CSS — hallucinated utilities surface as named problems instead
|
|
172
|
-
of silently unstyled elements
|
|
172
|
+
of silently unstyled elements — and version-aware rule-packs catch Tailwind v3
|
|
173
|
+
muscle memory in v4 projects at gate time, rename in hand. view-transition breakage (duplicate names, missing reduced-motion handling) is
|
|
174
|
+
flagged from the live page, and on Next 16+ the framework's own `/_next/mcp`
|
|
175
|
+
channel feeds structured errors straight into the fix loop. `/context` itemizes
|
|
176
|
+
the injected-context bill per source, with staleness warnings.
|
|
177
|
+
- **Visual approval**: engines ask before contested changes — the request renders
|
|
178
|
+
with its screenshot, `/yes` / `/no` answer it, the ledger remembers.
|
|
173
179
|
- **The design ledger**: `/decide` (plus chosen variants, rollbacks, accepted
|
|
174
180
|
sandboxes) appends to a committed `.squint/design-log.jsonl`; recent decisions ride
|
|
175
181
|
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" },
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
cdpCapture,
|
|
11
11
|
hasWebSocket
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-RQHOE5MV.js";
|
|
13
13
|
|
|
14
14
|
// src/preview/preview.ts
|
|
15
15
|
import fs from "fs";
|
|
@@ -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, slop, narration, phantoms } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true);
|
|
50
|
+
const { report, shots: shots2, a11y, slop, narration, phantoms, viewTransitions, components } = 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, slop, narration, phantoms };
|
|
65
|
+
return { shots: shots2, errors: errors2, runtime: report, a11y, slop, narration, phantoms, viewTransitions, components };
|
|
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-Q4H6ZHPT.js");
|
|
137
137
|
return pixelDiffPct(chrome, previous, current);
|
|
138
138
|
}
|
|
139
139
|
function buildRuntimeFixPrompt(report) {
|
|
@@ -161,6 +161,26 @@ ${narration.join("\n")}
|
|
|
161
161
|
|
|
162
162
|
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
163
|
}
|
|
164
|
+
function componentSection(components) {
|
|
165
|
+
if (!components || components.length === 0) return "";
|
|
166
|
+
return `
|
|
167
|
+
|
|
168
|
+
## Component map (from React fibers)
|
|
169
|
+
|
|
170
|
+
${components.join("\n")}
|
|
171
|
+
|
|
172
|
+
Use these owner chains to name the component (and file) an issue lives in instead of describing regions.`;
|
|
173
|
+
}
|
|
174
|
+
function vtSection(viewTransitions) {
|
|
175
|
+
if (!viewTransitions || viewTransitions.length === 0) return "";
|
|
176
|
+
return `
|
|
177
|
+
|
|
178
|
+
## View-transition findings
|
|
179
|
+
|
|
180
|
+
${viewTransitions.join("\n")}
|
|
181
|
+
|
|
182
|
+
Duplicate names abort the whole transition at runtime; missing reduced-motion handling animates for users who opted out. Fix the names / add the media query rather than removing the transitions.`;
|
|
183
|
+
}
|
|
164
184
|
function phantomSection(phantoms) {
|
|
165
185
|
if (!phantoms || phantoms.length === 0) return "";
|
|
166
186
|
return `
|
|
@@ -181,13 +201,13 @@ ${findings.join("\n")}
|
|
|
181
201
|
|
|
182
202
|
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.`;
|
|
183
203
|
}
|
|
184
|
-
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration, phantoms) {
|
|
204
|
+
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration, phantoms, viewTransitions, components) {
|
|
185
205
|
const list = shots.map((s) => `- ${s.name}: ${s.path}`).join("\n");
|
|
186
206
|
return `Screenshots of the running app were just captured:
|
|
187
207
|
|
|
188
208
|
${list}
|
|
189
209
|
|
|
190
|
-
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)}`;
|
|
210
|
+
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)}`;
|
|
191
211
|
}
|
|
192
212
|
|
|
193
213
|
export {
|
|
@@ -360,6 +360,83 @@ var PHANTOM_AUDIT = `(() => {
|
|
|
360
360
|
}
|
|
361
361
|
return out;
|
|
362
362
|
})()`;
|
|
363
|
+
var VT_AUDIT = `(() => {
|
|
364
|
+
const findings = [];
|
|
365
|
+
const names = new Map();
|
|
366
|
+
for (const el of document.querySelectorAll('*')) {
|
|
367
|
+
const name = getComputedStyle(el).viewTransitionName;
|
|
368
|
+
if (name && name !== 'none' && name !== 'auto') {
|
|
369
|
+
const label = el.tagName.toLowerCase() + (el.id ? '#' + el.id : '');
|
|
370
|
+
const list = names.get(name) || [];
|
|
371
|
+
list.push(label);
|
|
372
|
+
names.set(name, list);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
for (const [name, els] of names) {
|
|
376
|
+
if (els.length > 1) {
|
|
377
|
+
findings.push('duplicate view-transition-name "' + name + '" on ' + els.length + ' elements (' + els.slice(0, 3).join(', ') + ') \u2014 the browser skips the entire transition');
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
let vtCss = false;
|
|
381
|
+
let reducedGuard = false;
|
|
382
|
+
const walk = (list, inReduced) => {
|
|
383
|
+
for (const rule of list) {
|
|
384
|
+
const media = rule.media && rule.media.mediaText || '';
|
|
385
|
+
const nowReduced = inReduced || /prefers-reduced-motion/.test(media);
|
|
386
|
+
if (rule.cssText && rule.cssText.includes('::view-transition')) {
|
|
387
|
+
vtCss = true;
|
|
388
|
+
if (nowReduced) reducedGuard = true;
|
|
389
|
+
}
|
|
390
|
+
if (nowReduced && rule.cssText && /animation|transition/.test(rule.cssText)) reducedGuard = true;
|
|
391
|
+
if (rule.cssRules) walk(rule.cssRules, nowReduced);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
for (const sheet of document.styleSheets) {
|
|
395
|
+
let rules; try { rules = sheet.cssRules; } catch { continue; }
|
|
396
|
+
try { walk(rules, false); } catch {}
|
|
397
|
+
}
|
|
398
|
+
if ((names.size > 0 || vtCss) && !reducedGuard) {
|
|
399
|
+
findings.push('view transitions declared with no prefers-reduced-motion handling anywhere in the CSS \u2014 motion-sensitive users get the full animation');
|
|
400
|
+
}
|
|
401
|
+
return findings;
|
|
402
|
+
})()`;
|
|
403
|
+
var FIBER_AUDIT = `(() => {
|
|
404
|
+
const fiberKey = (el) => Object.keys(el).find((k) => k.startsWith('__reactFiber$'));
|
|
405
|
+
const all = document.querySelectorAll('*');
|
|
406
|
+
let reactSeen = false;
|
|
407
|
+
for (let i = 0; i < all.length && i < 300; i++) {
|
|
408
|
+
if (fiberKey(all[i])) { reactSeen = true; break; }
|
|
409
|
+
}
|
|
410
|
+
if (!reactSeen) return [];
|
|
411
|
+
const nameOf = (t) => {
|
|
412
|
+
if (typeof t === 'function') return t.displayName || t.name || '';
|
|
413
|
+
if (t && typeof t === 'object') return t.displayName || (t.render && (t.render.displayName || t.render.name)) || '';
|
|
414
|
+
return '';
|
|
415
|
+
};
|
|
416
|
+
const chainFor = (el) => {
|
|
417
|
+
const key = fiberKey(el);
|
|
418
|
+
if (!key) return null;
|
|
419
|
+
let fiber = el[key];
|
|
420
|
+
const names = [];
|
|
421
|
+
let hops = 0;
|
|
422
|
+
while (fiber && hops < 50 && names.length < 3) {
|
|
423
|
+
const n = nameOf(fiber.type);
|
|
424
|
+
if (n && !names.includes(n)) names.push(n);
|
|
425
|
+
fiber = fiber.return;
|
|
426
|
+
hops++;
|
|
427
|
+
}
|
|
428
|
+
return names.length > 0 ? names.join(' < ') : null;
|
|
429
|
+
};
|
|
430
|
+
const out = [];
|
|
431
|
+
for (const sel of ['header', 'nav', 'main', 'footer', 'h1', 'h2', 'form', 'aside', '[role="dialog"]', 'table']) {
|
|
432
|
+
const el = document.querySelector(sel);
|
|
433
|
+
if (!el) continue;
|
|
434
|
+
const chain = chainFor(el);
|
|
435
|
+
if (chain) out.push(sel + ' \u2014 ' + chain);
|
|
436
|
+
if (out.length >= 10) break;
|
|
437
|
+
}
|
|
438
|
+
return out;
|
|
439
|
+
})()`;
|
|
363
440
|
async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, audit = false) {
|
|
364
441
|
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
365
442
|
const report = { consoleErrors: [], pageErrors: [], failedRequests: [] };
|
|
@@ -369,6 +446,8 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
369
446
|
let perf = {};
|
|
370
447
|
let narration = [];
|
|
371
448
|
let phantoms = [];
|
|
449
|
+
let viewTransitions = [];
|
|
450
|
+
let components = [];
|
|
372
451
|
const requests = /* @__PURE__ */ new Map();
|
|
373
452
|
let connection = null;
|
|
374
453
|
try {
|
|
@@ -450,6 +529,24 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
450
529
|
if (Array.isArray(result?.value)) phantoms = result.value.map(String);
|
|
451
530
|
} catch {
|
|
452
531
|
}
|
|
532
|
+
try {
|
|
533
|
+
const { result } = await connection.send(
|
|
534
|
+
"Runtime.evaluate",
|
|
535
|
+
{ expression: VT_AUDIT, returnByValue: true },
|
|
536
|
+
sessionId
|
|
537
|
+
);
|
|
538
|
+
if (Array.isArray(result?.value)) viewTransitions = result.value.map(String);
|
|
539
|
+
} catch {
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
const { result } = await connection.send(
|
|
543
|
+
"Runtime.evaluate",
|
|
544
|
+
{ expression: FIBER_AUDIT, returnByValue: true },
|
|
545
|
+
sessionId
|
|
546
|
+
);
|
|
547
|
+
if (Array.isArray(result?.value)) components = result.value.map(String);
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
453
550
|
try {
|
|
454
551
|
await connection.send("Accessibility.enable", {}, sessionId);
|
|
455
552
|
const { nodes } = await connection.send("Accessibility.getFullAXTree", {}, sessionId);
|
|
@@ -514,7 +611,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
514
611
|
child.kill("SIGKILL");
|
|
515
612
|
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
516
613
|
}
|
|
517
|
-
return { report, shots, a11y, slop, perf, narration, phantoms };
|
|
614
|
+
return { report, shots, a11y, slop, perf, narration, phantoms, viewTransitions, components };
|
|
518
615
|
}
|
|
519
616
|
|
|
520
617
|
export {
|
|
@@ -156,6 +156,11 @@ ${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
|
+
);
|
|
159
164
|
const matched = matchSkills(loadSkills(cwd), ask);
|
|
160
165
|
for (const skill of matched) {
|
|
161
166
|
parts.push(`## Project notes: ${skill.name}
|
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-CV5WVKHU.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-RQHOE5MV.js";
|
|
62
62
|
import {
|
|
63
63
|
appendDecision,
|
|
64
64
|
enrich
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-ZLEP2TWF.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-Q4H6ZHPT.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-6NZP67QT.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-ZUCVWGZG.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),
|
|
830
|
+
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components),
|
|
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");
|
|
@@ -1132,6 +1149,18 @@ ${rulePackSummary(soft)}`);
|
|
|
1132
1149
|
if (result.error !== "interrupted" && dev && (dev.state === "running" || dev.state === "starting")) {
|
|
1133
1150
|
await delay(1500);
|
|
1134
1151
|
const errors = dev.errorsSince(runStart);
|
|
1152
|
+
if (this.state.devUrl) {
|
|
1153
|
+
try {
|
|
1154
|
+
const { hasNextMcp, probeNextMcp } = await import("./nextMcp-42Y63M7W.js");
|
|
1155
|
+
if (hasNextMcp(this.execCwd())) {
|
|
1156
|
+
const mcp = await probeNextMcp(this.state.devUrl);
|
|
1157
|
+
if (mcp.available && mcp.errors.length > 0) {
|
|
1158
|
+
for (const err of mcp.errors) errors.push(`[next mcp] ${err}`);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
} catch {
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1135
1164
|
if (errors.length > 0) {
|
|
1136
1165
|
this.addProblem(
|
|
1137
1166
|
"dev",
|
|
@@ -1163,7 +1192,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1163
1192
|
const captureResult = await this.capture();
|
|
1164
1193
|
if (captureResult) {
|
|
1165
1194
|
await this.runTurn(
|
|
1166
|
-
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration, captureResult.phantoms),
|
|
1195
|
+
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration, captureResult.phantoms, captureResult.viewTransitions, captureResult.components),
|
|
1167
1196
|
"\u{1F441} auto-review rendered UI"
|
|
1168
1197
|
);
|
|
1169
1198
|
}
|
|
@@ -1303,6 +1332,24 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1303
1332
|
this.push("status", "runtime clean \u2014 no console errors, exceptions, or failed requests");
|
|
1304
1333
|
}
|
|
1305
1334
|
}
|
|
1335
|
+
const vtHard = (result.viewTransitions ?? []).filter((f) => f.startsWith("duplicate"));
|
|
1336
|
+
if (vtHard.length > 0) {
|
|
1337
|
+
this.push("error", `view transitions: ${vtHard.length} broken
|
|
1338
|
+
${vtHard.join("\n")}`);
|
|
1339
|
+
this.addProblem(
|
|
1340
|
+
"runtime",
|
|
1341
|
+
`${vtHard.length} view transition(s) the browser will skip`,
|
|
1342
|
+
`Duplicate view-transition-name values make the browser abort the entire transition at runtime:
|
|
1343
|
+
|
|
1344
|
+
${vtHard.join("\n")}
|
|
1345
|
+
|
|
1346
|
+
Give each simultaneously rendered element a unique name (or scope names per item, e.g. per list key). Do not remove the transitions.`
|
|
1347
|
+
);
|
|
1348
|
+
}
|
|
1349
|
+
const vtSoft = (result.viewTransitions ?? []).filter((f) => !f.startsWith("duplicate"));
|
|
1350
|
+
if (vtSoft.length > 0) {
|
|
1351
|
+
this.push("status", `view transitions: ${vtSoft.join("; ")}`);
|
|
1352
|
+
}
|
|
1306
1353
|
if (result.phantoms && result.phantoms.length > 0) {
|
|
1307
1354
|
this.push("error", `phantom classes: ${result.phantoms.length} (in the DOM, absent from CSS)
|
|
1308
1355
|
${result.phantoms.slice(0, 5).join("\n")}`);
|
|
@@ -1433,13 +1480,32 @@ They are objective defects, not style preferences.`
|
|
|
1433
1480
|
break;
|
|
1434
1481
|
}
|
|
1435
1482
|
case "context": {
|
|
1436
|
-
import("./contextDoctor-
|
|
1483
|
+
import("./contextDoctor-U3YTDFVG.js").then(({ contextReport, formatContextReport }) => {
|
|
1437
1484
|
this.push("status", formatContextReport(contextReport(this.execCwd())));
|
|
1438
1485
|
}).catch((error) => {
|
|
1439
1486
|
this.push("status", `context report failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1440
1487
|
});
|
|
1441
1488
|
break;
|
|
1442
1489
|
}
|
|
1490
|
+
case "yes":
|
|
1491
|
+
case "no": {
|
|
1492
|
+
if (!this.pendingApproval) {
|
|
1493
|
+
this.push("status", `nothing awaiting approval \u2014 /${name} answers an engine's approval request`);
|
|
1494
|
+
break;
|
|
1495
|
+
}
|
|
1496
|
+
const summary = this.pendingApproval;
|
|
1497
|
+
this.pendingApproval = null;
|
|
1498
|
+
const approved = name === "yes";
|
|
1499
|
+
appendDecision(this.opts.cwd, {
|
|
1500
|
+
decision: `${approved ? "approved" : "rejected"}: ${summary}${arg ? ` \u2014 ${arg}` : ""}`,
|
|
1501
|
+
source: "approval"
|
|
1502
|
+
});
|
|
1503
|
+
void this.runTurn(
|
|
1504
|
+
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."}`,
|
|
1505
|
+
approved ? `\u2713 approved${arg ? ` \u2014 ${arg}` : ""}` : `\u2717 rejected${arg ? ` \u2014 ${arg}` : ""}`
|
|
1506
|
+
);
|
|
1507
|
+
break;
|
|
1508
|
+
}
|
|
1443
1509
|
case "decide": {
|
|
1444
1510
|
if (!arg) {
|
|
1445
1511
|
this.push("status", "usage: /decide <the decision> \u2014 recorded in .squint/design-log.jsonl and injected into every future ask");
|
|
@@ -1470,13 +1536,13 @@ They are objective defects, not style preferences.`
|
|
|
1470
1536
|
if (matches.length < 8) {
|
|
1471
1537
|
void (async () => {
|
|
1472
1538
|
try {
|
|
1473
|
-
const
|
|
1539
|
+
const fs4 = await import("fs");
|
|
1474
1540
|
const path5 = await import("path");
|
|
1475
1541
|
const dir = path5.join(this.opts.cwd, ".squint", "transcripts");
|
|
1476
|
-
const files =
|
|
1542
|
+
const files = fs4.readdirSync(dir).filter((f) => f.endsWith(".md")).sort().reverse().slice(0, 20);
|
|
1477
1543
|
for (const file of files) {
|
|
1478
1544
|
if (matches.length >= 8) break;
|
|
1479
|
-
const text =
|
|
1545
|
+
const text = fs4.readFileSync(path5.join(dir, file), "utf8");
|
|
1480
1546
|
for (const line of text.split("\n")) {
|
|
1481
1547
|
if (line.toLowerCase().includes(needle)) {
|
|
1482
1548
|
matches.push(`[${file.replace(/\.md$/, "")}] ${line.trim().slice(0, 90)}`);
|
|
@@ -1499,10 +1565,10 @@ They are objective defects, not style preferences.`
|
|
|
1499
1565
|
}
|
|
1500
1566
|
case "save": {
|
|
1501
1567
|
void (async () => {
|
|
1502
|
-
const
|
|
1568
|
+
const fs4 = await import("fs");
|
|
1503
1569
|
const path5 = await import("path");
|
|
1504
1570
|
const dir = path5.join(this.opts.cwd, ".squint", "transcripts");
|
|
1505
|
-
|
|
1571
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1506
1572
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
1507
1573
|
const file = path5.join(dir, `${stamp}.md`);
|
|
1508
1574
|
const lines = [`# squint session \u2014 ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`, ""];
|
|
@@ -1527,7 +1593,7 @@ They are objective defects, not style preferences.`
|
|
|
1527
1593
|
}
|
|
1528
1594
|
}
|
|
1529
1595
|
lines.push("", `> ${this.summary()}`);
|
|
1530
|
-
|
|
1596
|
+
fs4.writeFileSync(file, lines.join("\n") + "\n");
|
|
1531
1597
|
this.push("status", `saved transcript \u2192 ${path5.relative(this.opts.cwd, file)}`);
|
|
1532
1598
|
})();
|
|
1533
1599
|
break;
|
|
@@ -1549,8 +1615,8 @@ They are objective defects, not style preferences.`
|
|
|
1549
1615
|
this.push("error", "no Chrome/Chromium found for flows");
|
|
1550
1616
|
return;
|
|
1551
1617
|
}
|
|
1552
|
-
const { runFlow } = await import("./cdp-
|
|
1553
|
-
const { previewDir } = await import("./preview-
|
|
1618
|
+
const { runFlow } = await import("./cdp-Q4H6ZHPT.js");
|
|
1619
|
+
const { previewDir } = await import("./preview-ZUCVWGZG.js");
|
|
1554
1620
|
this.push("status", `replaying ${flows.length} flow(s)\u2026`);
|
|
1555
1621
|
this.notify({ running: true, runStartedAt: Date.now() });
|
|
1556
1622
|
const failures = [];
|
|
@@ -1688,7 +1754,7 @@ They are objective defects, not style preferences.`
|
|
|
1688
1754
|
const result = await this.capture();
|
|
1689
1755
|
if (result) {
|
|
1690
1756
|
await this.runTurn(
|
|
1691
|
-
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms),
|
|
1757
|
+
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components),
|
|
1692
1758
|
`\u{1F441} review rendered UI${arg ? ` \xB7 ${arg}` : ""}`
|
|
1693
1759
|
);
|
|
1694
1760
|
}
|
|
@@ -1874,7 +1940,7 @@ ${sandboxFiles(this.opts.cwd).join("\n")}`);
|
|
|
1874
1940
|
this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
|
|
1875
1941
|
break;
|
|
1876
1942
|
case "help": {
|
|
1877
|
-
void import("./commands-
|
|
1943
|
+
void import("./commands-BY44HDQ6.js").then(({ commandHelp }) => this.push("status", commandHelp()));
|
|
1878
1944
|
break;
|
|
1879
1945
|
}
|
|
1880
1946
|
case "quit":
|
|
@@ -2493,7 +2559,7 @@ function registerTui(program2) {
|
|
|
2493
2559
|
}
|
|
2494
2560
|
|
|
2495
2561
|
// src/cli.tsx
|
|
2496
|
-
var VERSION = true ? "0.4.
|
|
2562
|
+
var VERSION = true ? "0.4.5" : "0.0.0-dev";
|
|
2497
2563
|
var program = new Command();
|
|
2498
2564
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
2499
2565
|
registerRun(program);
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
loadLocks,
|
|
8
8
|
loadRules,
|
|
9
9
|
loadSkills
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ZLEP2TWF.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 };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/preview/nextMcp.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
function parseBody(contentType, text) {
|
|
7
|
+
try {
|
|
8
|
+
if (contentType.includes("text/event-stream")) {
|
|
9
|
+
const events = text.split("\n").filter((l) => l.startsWith("data:"));
|
|
10
|
+
const last = events.at(-1);
|
|
11
|
+
return last ? JSON.parse(last.slice(5).trim()) : null;
|
|
12
|
+
}
|
|
13
|
+
return text.trim().length > 0 ? JSON.parse(text) : null;
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function rpc(url, sessionId, body, timeoutMs) {
|
|
19
|
+
const headers = {
|
|
20
|
+
"content-type": "application/json",
|
|
21
|
+
accept: "application/json, text/event-stream"
|
|
22
|
+
};
|
|
23
|
+
if (sessionId) headers["mcp-session-id"] = sessionId;
|
|
24
|
+
const res = await fetch(url, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers,
|
|
27
|
+
body: JSON.stringify(body),
|
|
28
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
29
|
+
});
|
|
30
|
+
const newSession = res.headers.get("mcp-session-id") ?? sessionId;
|
|
31
|
+
if (res.status === 202) return { response: {}, sessionId: newSession };
|
|
32
|
+
if (!res.ok) return { response: null, sessionId: newSession };
|
|
33
|
+
const text = await res.text();
|
|
34
|
+
return { response: parseBody(res.headers.get("content-type") ?? "", text), sessionId: newSession };
|
|
35
|
+
}
|
|
36
|
+
function extractText(result) {
|
|
37
|
+
const content = result?.content;
|
|
38
|
+
if (!Array.isArray(content)) return [];
|
|
39
|
+
return content.map((c) => c && typeof c === "object" && "text" in c ? String(c.text) : "").filter((t) => t.trim().length > 0);
|
|
40
|
+
}
|
|
41
|
+
async function probeNextMcp(baseUrl, timeoutMs = 4e3) {
|
|
42
|
+
const url = new URL("/_next/mcp", baseUrl).toString();
|
|
43
|
+
const none = { available: false, tools: [], errors: [] };
|
|
44
|
+
let id = 0;
|
|
45
|
+
try {
|
|
46
|
+
const init = await rpc(url, null, {
|
|
47
|
+
jsonrpc: "2.0",
|
|
48
|
+
id: ++id,
|
|
49
|
+
method: "initialize",
|
|
50
|
+
params: {
|
|
51
|
+
protocolVersion: "2025-06-18",
|
|
52
|
+
capabilities: {},
|
|
53
|
+
clientInfo: { name: "squint", version: "0" }
|
|
54
|
+
}
|
|
55
|
+
}, timeoutMs);
|
|
56
|
+
if (!init.response?.result) return none;
|
|
57
|
+
const session = init.sessionId;
|
|
58
|
+
await rpc(url, session, { jsonrpc: "2.0", method: "notifications/initialized" }, timeoutMs).catch(() => null);
|
|
59
|
+
const list = await rpc(url, session, { jsonrpc: "2.0", id: ++id, method: "tools/list" }, timeoutMs);
|
|
60
|
+
const toolDefs = list.response?.result?.tools ?? [];
|
|
61
|
+
const tools = toolDefs.map((t) => String(t.name ?? "")).filter((n) => n.length > 0);
|
|
62
|
+
const errors = [];
|
|
63
|
+
for (const name of tools.filter((n) => /error|issue|diagnostic/i.test(n)).slice(0, 3)) {
|
|
64
|
+
const call = await rpc(url, session, {
|
|
65
|
+
jsonrpc: "2.0",
|
|
66
|
+
id: ++id,
|
|
67
|
+
method: "tools/call",
|
|
68
|
+
params: { name, arguments: {} }
|
|
69
|
+
}, timeoutMs);
|
|
70
|
+
for (const text of extractText(call.response?.result)) {
|
|
71
|
+
if (!/no (build |runtime )?errors|^\s*\[\]\s*$|no issues/i.test(text)) {
|
|
72
|
+
errors.push(`[${name}] ${text.slice(0, 600)}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (errors.length >= 5) break;
|
|
76
|
+
}
|
|
77
|
+
return { available: true, tools, errors };
|
|
78
|
+
} catch {
|
|
79
|
+
return none;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function hasNextMcp(cwd) {
|
|
83
|
+
try {
|
|
84
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
85
|
+
for (const key of ["dependencies", "devDependencies"]) {
|
|
86
|
+
const range = pkg?.[key]?.next;
|
|
87
|
+
const major = range?.match(/(\d+)/)?.[1];
|
|
88
|
+
if (major) return Number.parseInt(major, 10) >= 16;
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
hasNextMcp,
|
|
96
|
+
probeNextMcp
|
|
97
|
+
};
|
|
@@ -10,11 +10,11 @@ import {
|
|
|
10
10
|
probeRuntime,
|
|
11
11
|
routeShotName,
|
|
12
12
|
runtimeSummary
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-CV5WVKHU.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-RQHOE5MV.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.5",
|
|
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",
|