@aayambansal/squint 0.3.2 → 0.3.3
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-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 +10 -10
- 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
|
@@ -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
|
@@ -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";
|
|
@@ -236,7 +236,7 @@ function registerEnv(program2) {
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
const { findChrome: findChrome2 } = await import("./chrome-SBV3H77F.js");
|
|
239
|
-
const { hasWebSocket } = await import("./cdp-
|
|
239
|
+
const { hasWebSocket } = await import("./cdp-UOIP4ZQZ.js");
|
|
240
240
|
const chrome = findChrome2();
|
|
241
241
|
console.log(
|
|
242
242
|
chrome ? `${pc2.green("\u2713")} Chrome ${pc2.dim(chrome)}` : `${pc2.yellow("\u25CB")} Chrome ${pc2.dim("\u2014 screenshots and runtime probing disabled")}`
|
|
@@ -260,7 +260,7 @@ import pc3 from "picocolors";
|
|
|
260
260
|
function registerProject(program2) {
|
|
261
261
|
const skillsCommand = program2.command("skills").description("Project knowledge injected into asks (.squint/rules.md + .squint/skills/)");
|
|
262
262
|
skillsCommand.command("list").description("Show always-on rules and trigger-matched skills").action(async () => {
|
|
263
|
-
const { loadRules, loadSkills } = await import("./skills-
|
|
263
|
+
const { loadRules, loadSkills } = await import("./skills-ODOVNE6E.js");
|
|
264
264
|
const cwd = process.cwd();
|
|
265
265
|
const rules = loadRules(cwd);
|
|
266
266
|
console.log(
|
|
@@ -459,7 +459,7 @@ function registerQuality(program2) {
|
|
|
459
459
|
if (results.some((r) => !r.ok)) process.exitCode = 1;
|
|
460
460
|
});
|
|
461
461
|
program2.command("shot").description("Screenshot a running app at mobile/tablet/desktop viewports (+ .squint/routes)").argument("<url>", "URL of the running app (e.g. http://localhost:5173)").action(async (url) => {
|
|
462
|
-
const { captureViewports: captureViewports2 } = await import("./preview-
|
|
462
|
+
const { captureViewports: captureViewports2 } = await import("./preview-S6FOTRRR.js");
|
|
463
463
|
const result = await captureViewports2(process.cwd(), url);
|
|
464
464
|
if (!result) {
|
|
465
465
|
console.error(pc4.red("\u2717 no Chrome/Chromium found"));
|
|
@@ -820,7 +820,7 @@ ${question}`,
|
|
|
820
820
|
return;
|
|
821
821
|
}
|
|
822
822
|
await this.runTurn(
|
|
823
|
-
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop),
|
|
823
|
+
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration),
|
|
824
824
|
`\u{1F441} polish round ${round}/${rounds}`
|
|
825
825
|
);
|
|
826
826
|
}
|
|
@@ -1138,7 +1138,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1138
1138
|
const captureResult = await this.capture();
|
|
1139
1139
|
if (captureResult) {
|
|
1140
1140
|
await this.runTurn(
|
|
1141
|
-
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop),
|
|
1141
|
+
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration),
|
|
1142
1142
|
"\u{1F441} auto-review rendered UI"
|
|
1143
1143
|
);
|
|
1144
1144
|
}
|
|
@@ -1508,7 +1508,7 @@ They are objective defects, not style preferences.`
|
|
|
1508
1508
|
const result = await this.capture();
|
|
1509
1509
|
if (result) {
|
|
1510
1510
|
await this.runTurn(
|
|
1511
|
-
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop),
|
|
1511
|
+
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration),
|
|
1512
1512
|
`\u{1F441} review rendered UI${arg ? ` \xB7 ${arg}` : ""}`
|
|
1513
1513
|
);
|
|
1514
1514
|
}
|
|
@@ -2308,7 +2308,7 @@ function registerTui(program2) {
|
|
|
2308
2308
|
}
|
|
2309
2309
|
|
|
2310
2310
|
// src/cli.tsx
|
|
2311
|
-
var VERSION = true ? "0.3.
|
|
2311
|
+
var VERSION = true ? "0.3.3" : "0.0.0-dev";
|
|
2312
2312
|
var program = new Command();
|
|
2313
2313
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
2314
2314
|
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