@aayambansal/squint 0.3.2 → 0.3.4
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-2XAU5MEA.js → cdp-UOIP4ZQZ.js} +1 -1
- package/dist/{chunk-S2ODU4MN.js → chunk-BNJGHNXB.js} +1 -0
- package/dist/{chunk-4LAU5TGK.js → chunk-H5K55LXY.js} +40 -5
- package/dist/{chunk-OTCH66M7.js → chunk-SXF7CVRW.js} +16 -6
- package/dist/{chunk-DAETGO2M.js → chunk-XOVOQJFL.js} +43 -1
- package/dist/cli.js +68 -18
- package/dist/{commands-Y5RUKBPS.js → commands-3HHBHBQV.js} +1 -1
- package/dist/{preview-PS7WJ2KO.js → preview-S6FOTRRR.js} +2 -2
- package/dist/{skills-DNBHZAAU.js → skills-ODOVNE6E.js} +1 -1
- package/package.json +1 -1
|
@@ -23,6 +23,7 @@ var COMMANDS = [
|
|
|
23
23
|
{ name: "btw", args: "<question>", group: "session", description: "read-only side question; the main thread is untouched" },
|
|
24
24
|
{ name: "copy", group: "session", description: "copy the last reply to the clipboard" },
|
|
25
25
|
{ name: "save", group: "session", description: "export the transcript to .squint/transcripts/" },
|
|
26
|
+
{ name: "find", args: "<term>", group: "session", description: "search this session and saved transcripts" },
|
|
26
27
|
{ name: "resume", group: "session", description: "pick up the previous session for this repo" },
|
|
27
28
|
{ name: "clear", group: "session", description: "new session (transcript, totals, persisted state)" },
|
|
28
29
|
{ name: "help", group: "session", description: "list commands" },
|
|
@@ -1,8 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/prompt/skills.ts
|
|
4
|
+
import fs2 from "fs";
|
|
5
|
+
import path2 from "path";
|
|
6
|
+
|
|
7
|
+
// src/prompt/registry.ts
|
|
4
8
|
import fs from "fs";
|
|
5
9
|
import path from "path";
|
|
10
|
+
function loadComponentInventory(cwd) {
|
|
11
|
+
let config;
|
|
12
|
+
try {
|
|
13
|
+
config = JSON.parse(fs.readFileSync(path.join(cwd, "components.json"), "utf8"));
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const alias = config.aliases?.ui ?? (config.aliases?.components ? `${config.aliases.components}/ui` : "@/components/ui");
|
|
18
|
+
const relative = alias.replace(/^@\//, "src/").replace(/^~\//, "");
|
|
19
|
+
const uiDir = path.join(cwd, relative);
|
|
20
|
+
let entries;
|
|
21
|
+
try {
|
|
22
|
+
entries = fs.readdirSync(uiDir);
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const components = entries.filter((f) => /\.(tsx|jsx|vue|svelte)$/.test(f)).map((f) => f.replace(/\.[^.]+$/, "")).sort();
|
|
27
|
+
if (components.length === 0) return null;
|
|
28
|
+
return { components, uiDir: relative };
|
|
29
|
+
}
|
|
30
|
+
function inventorySection(inventory) {
|
|
31
|
+
return `## Installed UI components (${inventory.uiDir})
|
|
32
|
+
|
|
33
|
+
${inventory.components.join(" \xB7 ")}
|
|
34
|
+
|
|
35
|
+
Compose from these before writing new primitives. More are one command away: \`npx shadcn@latest add <name>\`. Never invent props for these components \u2014 read the file when unsure.`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/prompt/skills.ts
|
|
6
39
|
var FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
|
|
7
40
|
function parseSkill(name, raw) {
|
|
8
41
|
const match = FRONTMATTER_RE.exec(raw);
|
|
@@ -22,17 +55,17 @@ function parseSkill(name, raw) {
|
|
|
22
55
|
return { name, triggers, body };
|
|
23
56
|
}
|
|
24
57
|
function loadSkills(cwd) {
|
|
25
|
-
const dir =
|
|
58
|
+
const dir = path2.join(cwd, ".squint", "skills");
|
|
26
59
|
let entries;
|
|
27
60
|
try {
|
|
28
|
-
entries =
|
|
61
|
+
entries = fs2.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
29
62
|
} catch {
|
|
30
63
|
return [];
|
|
31
64
|
}
|
|
32
65
|
const skills = [];
|
|
33
66
|
for (const entry of entries.sort()) {
|
|
34
67
|
try {
|
|
35
|
-
const skill = parseSkill(entry.replace(/\.md$/, ""),
|
|
68
|
+
const skill = parseSkill(entry.replace(/\.md$/, ""), fs2.readFileSync(path2.join(dir, entry), "utf8"));
|
|
36
69
|
if (skill) skills.push(skill);
|
|
37
70
|
} catch {
|
|
38
71
|
}
|
|
@@ -41,7 +74,7 @@ function loadSkills(cwd) {
|
|
|
41
74
|
}
|
|
42
75
|
function loadRules(cwd) {
|
|
43
76
|
try {
|
|
44
|
-
const text =
|
|
77
|
+
const text = fs2.readFileSync(path2.join(cwd, ".squint", "rules.md"), "utf8").trim();
|
|
45
78
|
return text.length > 0 ? text : null;
|
|
46
79
|
} catch {
|
|
47
80
|
return null;
|
|
@@ -53,7 +86,7 @@ function matchSkills(skills, ask) {
|
|
|
53
86
|
}
|
|
54
87
|
function loadLocks(cwd) {
|
|
55
88
|
try {
|
|
56
|
-
return
|
|
89
|
+
return fs2.readFileSync(path2.join(cwd, ".squint", "locks"), "utf8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
57
90
|
} catch {
|
|
58
91
|
return [];
|
|
59
92
|
}
|
|
@@ -64,6 +97,8 @@ function enrich(cwd, ask) {
|
|
|
64
97
|
if (rules) parts.push(`## Project rules (always apply)
|
|
65
98
|
|
|
66
99
|
${rules}`);
|
|
100
|
+
const inventory = loadComponentInventory(cwd);
|
|
101
|
+
if (inventory) parts.push(inventorySection(inventory));
|
|
67
102
|
const locks = loadLocks(cwd);
|
|
68
103
|
if (locks.length > 0) {
|
|
69
104
|
parts.push(
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import {
|
|
7
7
|
cdpCapture,
|
|
8
8
|
hasWebSocket
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-XOVOQJFL.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, slop } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true);
|
|
50
|
+
const { report, shots: shots2, a11y, slop, narration } = 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 };
|
|
65
|
+
return { shots: shots2, errors: errors2, runtime: report, a11y, slop, narration };
|
|
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-UOIP4ZQZ.js");
|
|
137
137
|
return pixelDiffPct(chrome, previous, current);
|
|
138
138
|
}
|
|
139
139
|
function buildRuntimeFixPrompt(report) {
|
|
@@ -151,6 +151,16 @@ ${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 narrationSection(narration) {
|
|
155
|
+
if (!narration || narration.length === 0) return "";
|
|
156
|
+
return `
|
|
157
|
+
|
|
158
|
+
## What a screen reader would say (accessibility tree, in order)
|
|
159
|
+
|
|
160
|
+
${narration.join("\n")}
|
|
161
|
+
|
|
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
|
+
}
|
|
154
164
|
function slopSection(findings) {
|
|
155
165
|
if (!findings || findings.length === 0) return "";
|
|
156
166
|
return `
|
|
@@ -161,13 +171,13 @@ ${findings.join("\n")}
|
|
|
161
171
|
|
|
162
172
|
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
173
|
}
|
|
164
|
-
function buildReviewPrompt(shots, extra, runtime, a11y, slop) {
|
|
174
|
+
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration) {
|
|
165
175
|
const list = shots.map((s) => `- ${s.name}: ${s.path}`).join("\n");
|
|
166
176
|
return `Screenshots of the running app were just captured:
|
|
167
177
|
|
|
168
178
|
${list}
|
|
169
179
|
|
|
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)}`;
|
|
180
|
+
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)}`;
|
|
171
181
|
}
|
|
172
182
|
|
|
173
183
|
export {
|
|
@@ -278,6 +278,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
278
278
|
let a11y = [];
|
|
279
279
|
let slop = [];
|
|
280
280
|
let perf = {};
|
|
281
|
+
let narration = [];
|
|
281
282
|
const requests = /* @__PURE__ */ new Map();
|
|
282
283
|
let connection = null;
|
|
283
284
|
try {
|
|
@@ -350,6 +351,47 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
350
351
|
if (Array.isArray(result?.value)) slop = result.value.map(String);
|
|
351
352
|
} catch {
|
|
352
353
|
}
|
|
354
|
+
try {
|
|
355
|
+
await connection.send("Accessibility.enable", {}, sessionId);
|
|
356
|
+
const { nodes } = await connection.send("Accessibility.getFullAXTree", {}, sessionId);
|
|
357
|
+
const interesting = /* @__PURE__ */ new Set([
|
|
358
|
+
"heading",
|
|
359
|
+
"link",
|
|
360
|
+
"button",
|
|
361
|
+
"textbox",
|
|
362
|
+
"checkbox",
|
|
363
|
+
"radio",
|
|
364
|
+
"combobox",
|
|
365
|
+
"listbox",
|
|
366
|
+
"img",
|
|
367
|
+
"image",
|
|
368
|
+
"navigation",
|
|
369
|
+
"main",
|
|
370
|
+
"banner",
|
|
371
|
+
"contentinfo",
|
|
372
|
+
"form",
|
|
373
|
+
"search",
|
|
374
|
+
"tab",
|
|
375
|
+
"menuitem",
|
|
376
|
+
"switch",
|
|
377
|
+
"slider",
|
|
378
|
+
"alert",
|
|
379
|
+
"dialog",
|
|
380
|
+
"list"
|
|
381
|
+
]);
|
|
382
|
+
if (Array.isArray(nodes)) {
|
|
383
|
+
for (const node of nodes) {
|
|
384
|
+
if (node.ignored) continue;
|
|
385
|
+
const role = node.role?.value;
|
|
386
|
+
if (!role || !interesting.has(role)) continue;
|
|
387
|
+
const name = (node.name?.value ?? "").toString().trim().replace(/\s+/g, " ").slice(0, 80);
|
|
388
|
+
const level = node.properties?.find((p) => p.name === "level")?.value?.value;
|
|
389
|
+
narration.push(`${role}${level ? ` ${level}` : ""}${name ? `: "${name}"` : " (no accessible name)"}`);
|
|
390
|
+
if (narration.length >= 80) break;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} catch {
|
|
394
|
+
}
|
|
353
395
|
}
|
|
354
396
|
for (const viewport of viewports) {
|
|
355
397
|
await connection.send(
|
|
@@ -373,7 +415,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
373
415
|
child.kill("SIGKILL");
|
|
374
416
|
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
375
417
|
}
|
|
376
|
-
return { report, shots, a11y, slop, perf };
|
|
418
|
+
return { report, shots, a11y, slop, perf, narration };
|
|
377
419
|
}
|
|
378
420
|
|
|
379
421
|
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-BNJGHNXB.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-SXF7CVRW.js";
|
|
46
46
|
import {
|
|
47
47
|
runAgent
|
|
48
48
|
} from "./chunk-VH7OOFQP.js";
|
|
@@ -53,10 +53,10 @@ import {
|
|
|
53
53
|
detectEngines,
|
|
54
54
|
getEngine
|
|
55
55
|
} from "./chunk-KVYGPLWW.js";
|
|
56
|
-
import "./chunk-
|
|
56
|
+
import "./chunk-XOVOQJFL.js";
|
|
57
57
|
import {
|
|
58
58
|
enrich
|
|
59
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-H5K55LXY.js";
|
|
60
60
|
import {
|
|
61
61
|
composePrompt
|
|
62
62
|
} from "./chunk-P3H4N2EN.js";
|
|
@@ -96,6 +96,8 @@ var ConfigSchema = z.object({
|
|
|
96
96
|
budgetUsd: z.number().positive().optional(),
|
|
97
97
|
/** Auto-run /review when the visual pulse shows a big change (default off). */
|
|
98
98
|
autoReview: z.boolean().optional(),
|
|
99
|
+
/** Cheaper model used for auto-fix and /fix turns (mechanical work). */
|
|
100
|
+
fixModel: z.string().optional(),
|
|
99
101
|
/** TUI theme name (amber, ocean, moss, rose, mono). */
|
|
100
102
|
theme: z.string().optional()
|
|
101
103
|
});
|
|
@@ -138,7 +140,7 @@ function resolveModel(config, engineId, override) {
|
|
|
138
140
|
function setConfigValue(file, key, value) {
|
|
139
141
|
const current = readConfigFile(file);
|
|
140
142
|
let next;
|
|
141
|
-
if (key === "engine" || key === "theme") {
|
|
143
|
+
if (key === "engine" || key === "theme" || key === "fixModel") {
|
|
142
144
|
next = { ...current, [key]: value };
|
|
143
145
|
} else if (key === "autoDev" || key === "autoFix" || key === "autoProbe" || key === "autoCheck" || key === "autoReview" || key === "bell") {
|
|
144
146
|
if (value !== "true" && value !== "false") {
|
|
@@ -157,7 +159,7 @@ function setConfigValue(file, key, value) {
|
|
|
157
159
|
next = { ...current, models: { ...current.models, [engineId]: value } };
|
|
158
160
|
} else {
|
|
159
161
|
throw new Error(
|
|
160
|
-
`Unknown config key "${key}". Supported: engine, theme, autoDev, autoFix, autoProbe, autoCheck, autoReview, bell, budgetUsd, models.<engineId>`
|
|
162
|
+
`Unknown config key "${key}". Supported: engine, theme, autoDev, autoFix, autoProbe, autoCheck, autoReview, bell, budgetUsd, fixModel, models.<engineId>`
|
|
161
163
|
);
|
|
162
164
|
}
|
|
163
165
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
@@ -236,7 +238,7 @@ function registerEnv(program2) {
|
|
|
236
238
|
}
|
|
237
239
|
}
|
|
238
240
|
const { findChrome: findChrome2 } = await import("./chrome-SBV3H77F.js");
|
|
239
|
-
const { hasWebSocket } = await import("./cdp-
|
|
241
|
+
const { hasWebSocket } = await import("./cdp-UOIP4ZQZ.js");
|
|
240
242
|
const chrome = findChrome2();
|
|
241
243
|
console.log(
|
|
242
244
|
chrome ? `${pc2.green("\u2713")} Chrome ${pc2.dim(chrome)}` : `${pc2.yellow("\u25CB")} Chrome ${pc2.dim("\u2014 screenshots and runtime probing disabled")}`
|
|
@@ -260,7 +262,7 @@ import pc3 from "picocolors";
|
|
|
260
262
|
function registerProject(program2) {
|
|
261
263
|
const skillsCommand = program2.command("skills").description("Project knowledge injected into asks (.squint/rules.md + .squint/skills/)");
|
|
262
264
|
skillsCommand.command("list").description("Show always-on rules and trigger-matched skills").action(async () => {
|
|
263
|
-
const { loadRules, loadSkills } = await import("./skills-
|
|
265
|
+
const { loadRules, loadSkills } = await import("./skills-ODOVNE6E.js");
|
|
264
266
|
const cwd = process.cwd();
|
|
265
267
|
const rules = loadRules(cwd);
|
|
266
268
|
console.log(
|
|
@@ -459,7 +461,7 @@ function registerQuality(program2) {
|
|
|
459
461
|
if (results.some((r) => !r.ok)) process.exitCode = 1;
|
|
460
462
|
});
|
|
461
463
|
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-
|
|
464
|
+
const { captureViewports: captureViewports2 } = await import("./preview-S6FOTRRR.js");
|
|
463
465
|
const result = await captureViewports2(process.cwd(), url);
|
|
464
466
|
if (!result) {
|
|
465
467
|
console.error(pc4.red("\u2717 no Chrome/Chromium found"));
|
|
@@ -756,10 +758,11 @@ var Session = class {
|
|
|
756
758
|
dispatchFix(problems) {
|
|
757
759
|
if (problems.length === 0) return;
|
|
758
760
|
const prompt = this.combinedFixPrompt(problems);
|
|
759
|
-
const
|
|
761
|
+
const fixModel = this.opts.fixModel;
|
|
762
|
+
const display = `\u26D1 fix: ${problems.map((p) => p.source).join(" + ")}${fixModel ? ` \xB7 ${fixModel}` : ""}`;
|
|
760
763
|
for (const problem of problems) this.problemPrompts.delete(problem.id);
|
|
761
764
|
this.notify({ problems: this.state.problems.filter((p) => !problems.includes(p)) });
|
|
762
|
-
void this.runTurn(prompt, display);
|
|
765
|
+
void this.runTurn(prompt, display, fixModel);
|
|
763
766
|
}
|
|
764
767
|
/** Launch a capped auto-fix turn over all open problems. Returns true if launched. */
|
|
765
768
|
maybeAutoFix() {
|
|
@@ -820,7 +823,7 @@ ${question}`,
|
|
|
820
823
|
return;
|
|
821
824
|
}
|
|
822
825
|
await this.runTurn(
|
|
823
|
-
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop),
|
|
826
|
+
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration),
|
|
824
827
|
`\u{1F441} polish round ${round}/${rounds}`
|
|
825
828
|
);
|
|
826
829
|
}
|
|
@@ -1006,7 +1009,7 @@ ${question}`,
|
|
|
1006
1009
|
}
|
|
1007
1010
|
};
|
|
1008
1011
|
/** Run one engine turn. `display` is what the transcript shows as the ask. */
|
|
1009
|
-
async runTurn(prompt, display) {
|
|
1012
|
+
async runTurn(prompt, display, modelOverride) {
|
|
1010
1013
|
this.push("user", display);
|
|
1011
1014
|
this.turnEdits = 0;
|
|
1012
1015
|
this.turnTools = 0;
|
|
@@ -1019,7 +1022,7 @@ ${question}`,
|
|
|
1019
1022
|
{
|
|
1020
1023
|
prompt,
|
|
1021
1024
|
cwd: this.execCwd(),
|
|
1022
|
-
model: this.state.model,
|
|
1025
|
+
model: modelOverride ?? this.state.model,
|
|
1023
1026
|
mode: this.state.mode,
|
|
1024
1027
|
sessionId: engine.supportsResume ? this.sessionId : void 0
|
|
1025
1028
|
},
|
|
@@ -1138,7 +1141,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1138
1141
|
const captureResult = await this.capture();
|
|
1139
1142
|
if (captureResult) {
|
|
1140
1143
|
await this.runTurn(
|
|
1141
|
-
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop),
|
|
1144
|
+
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration),
|
|
1142
1145
|
"\u{1F441} auto-review rendered UI"
|
|
1143
1146
|
);
|
|
1144
1147
|
}
|
|
@@ -1392,6 +1395,50 @@ They are objective defects, not style preferences.`
|
|
|
1392
1395
|
}
|
|
1393
1396
|
break;
|
|
1394
1397
|
}
|
|
1398
|
+
case "find": {
|
|
1399
|
+
if (!arg) {
|
|
1400
|
+
this.push("status", "usage: /find <term> \u2014 searches this session and saved transcripts");
|
|
1401
|
+
break;
|
|
1402
|
+
}
|
|
1403
|
+
const needle = arg.toLowerCase();
|
|
1404
|
+
const matches = [];
|
|
1405
|
+
for (const item of this.state.items) {
|
|
1406
|
+
if ((item.role === "user" || item.role === "assistant") && item.text.toLowerCase().includes(needle)) {
|
|
1407
|
+
const line = item.text.split("\n").find((l) => l.toLowerCase().includes(needle)) ?? item.text;
|
|
1408
|
+
matches.push(`[live] ${item.role === "user" ? "\u276F " : ""}${line.trim().slice(0, 90)}`);
|
|
1409
|
+
if (matches.length >= 8) break;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
if (matches.length < 8) {
|
|
1413
|
+
void (async () => {
|
|
1414
|
+
try {
|
|
1415
|
+
const fs3 = await import("fs");
|
|
1416
|
+
const path5 = await import("path");
|
|
1417
|
+
const dir = path5.join(this.opts.cwd, ".squint", "transcripts");
|
|
1418
|
+
const files = fs3.readdirSync(dir).filter((f) => f.endsWith(".md")).sort().reverse().slice(0, 20);
|
|
1419
|
+
for (const file of files) {
|
|
1420
|
+
if (matches.length >= 8) break;
|
|
1421
|
+
const text = fs3.readFileSync(path5.join(dir, file), "utf8");
|
|
1422
|
+
for (const line of text.split("\n")) {
|
|
1423
|
+
if (line.toLowerCase().includes(needle)) {
|
|
1424
|
+
matches.push(`[${file.replace(/\.md$/, "")}] ${line.trim().slice(0, 90)}`);
|
|
1425
|
+
break;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
} catch {
|
|
1430
|
+
}
|
|
1431
|
+
this.push(
|
|
1432
|
+
"status",
|
|
1433
|
+
matches.length > 0 ? `${matches.join("\n")}
|
|
1434
|
+
/checkpoints can rewind \xB7 /save archives this session` : `no matches for "${arg}"`
|
|
1435
|
+
);
|
|
1436
|
+
})();
|
|
1437
|
+
} else {
|
|
1438
|
+
this.push("status", matches.join("\n"));
|
|
1439
|
+
}
|
|
1440
|
+
break;
|
|
1441
|
+
}
|
|
1395
1442
|
case "save": {
|
|
1396
1443
|
void (async () => {
|
|
1397
1444
|
const fs3 = await import("fs");
|
|
@@ -1508,7 +1555,7 @@ They are objective defects, not style preferences.`
|
|
|
1508
1555
|
const result = await this.capture();
|
|
1509
1556
|
if (result) {
|
|
1510
1557
|
await this.runTurn(
|
|
1511
|
-
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop),
|
|
1558
|
+
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration),
|
|
1512
1559
|
`\u{1F441} review rendered UI${arg ? ` \xB7 ${arg}` : ""}`
|
|
1513
1560
|
);
|
|
1514
1561
|
}
|
|
@@ -1692,7 +1739,7 @@ ${sandboxFiles(this.opts.cwd).join("\n")}`);
|
|
|
1692
1739
|
this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
|
|
1693
1740
|
break;
|
|
1694
1741
|
case "help": {
|
|
1695
|
-
void import("./commands-
|
|
1742
|
+
void import("./commands-3HHBHBQV.js").then(({ commandHelp }) => this.push("status", commandHelp()));
|
|
1696
1743
|
break;
|
|
1697
1744
|
}
|
|
1698
1745
|
case "quit":
|
|
@@ -2063,6 +2110,7 @@ function App({
|
|
|
2063
2110
|
autoProbe,
|
|
2064
2111
|
autoCheck,
|
|
2065
2112
|
autoReview,
|
|
2113
|
+
fixModel,
|
|
2066
2114
|
bell,
|
|
2067
2115
|
budgetUsd,
|
|
2068
2116
|
initialTheme
|
|
@@ -2081,6 +2129,7 @@ function App({
|
|
|
2081
2129
|
autoProbe,
|
|
2082
2130
|
autoCheck,
|
|
2083
2131
|
autoReview,
|
|
2132
|
+
fixModel,
|
|
2084
2133
|
budgetUsd,
|
|
2085
2134
|
// Delay lets the goodbye summary land in the Static scrollback.
|
|
2086
2135
|
onQuit: () => setTimeout(() => exit(), 60)
|
|
@@ -2298,6 +2347,7 @@ function registerTui(program2) {
|
|
|
2298
2347
|
autoProbe: config.autoProbe,
|
|
2299
2348
|
autoCheck: config.autoCheck,
|
|
2300
2349
|
autoReview: config.autoReview,
|
|
2350
|
+
fixModel: config.fixModel,
|
|
2301
2351
|
bell: config.bell,
|
|
2302
2352
|
budgetUsd: config.budgetUsd,
|
|
2303
2353
|
initialTheme: theme2
|
|
@@ -2308,7 +2358,7 @@ function registerTui(program2) {
|
|
|
2308
2358
|
}
|
|
2309
2359
|
|
|
2310
2360
|
// src/cli.tsx
|
|
2311
|
-
var VERSION = true ? "0.3.
|
|
2361
|
+
var VERSION = true ? "0.3.4" : "0.0.0-dev";
|
|
2312
2362
|
var program = new Command();
|
|
2313
2363
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
2314
2364
|
registerRun(program);
|
|
@@ -10,10 +10,10 @@ import {
|
|
|
10
10
|
probeRuntime,
|
|
11
11
|
routeShotName,
|
|
12
12
|
runtimeSummary
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-SXF7CVRW.js";
|
|
14
14
|
import "./chunk-IMDRXXFU.js";
|
|
15
15
|
import "./chunk-KVYGPLWW.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-XOVOQJFL.js";
|
|
17
17
|
import "./chunk-O2S6PAJE.js";
|
|
18
18
|
export {
|
|
19
19
|
VIEWPORTS,
|
package/package.json
CHANGED