@aayambansal/squint 0.8.2 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{App-C3LOYRTR.js → App-TEZ6E4R6.js} +5 -5
- package/dist/{cdp-6YBMFE7M.js → cdp-E6VAQ7XL.js} +1 -1
- package/dist/{checks-GTKPEQ2L.js → checks-Y73R4SS3.js} +1 -1
- package/dist/{chunk-PPC2KM7P.js → chunk-6M54VGLM.js} +1 -1
- package/dist/{chunk-6RMF4NAW.js → chunk-AYJQYDN6.js} +1 -1
- package/dist/{chunk-53DPUAXN.js → chunk-FQYJ343N.js} +2 -2
- package/dist/{chunk-3ZHETLUC.js → chunk-KPLEM7SV.js} +18 -8
- package/dist/{chunk-4DZAWPS2.js → chunk-MITMSC2W.js} +20 -2
- package/dist/{chunk-B356UR44.js → chunk-MWPZCXN7.js} +14 -14
- package/dist/{chunk-MDLNTOGX.js → chunk-QYK6OO46.js} +54 -3
- package/dist/cli.js +24 -18
- package/dist/{flows-4QS2ER6N.js → flows-EHUNUHED.js} +1 -1
- package/dist/{preview-UV67QLVI.js → preview-M6VBOTQZ.js} +3 -3
- package/dist/{receipts-7PERXBJO.js → receipts-KNOHIVCA.js} +1 -1
- package/dist/{remote-ICNK2PPK.js → remote-VAIQNPAV.js} +7 -6
- package/dist/{server-WOYEQLAA.js → server-V3DHLHPK.js} +8 -7
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
App
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-6M54VGLM.js";
|
|
5
|
+
import "./chunk-MWPZCXN7.js";
|
|
6
6
|
import "./chunk-YKEAKVIS.js";
|
|
7
7
|
import "./chunk-LNJ7O5O4.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-KPLEM7SV.js";
|
|
9
9
|
import "./chunk-UBADCBPT.js";
|
|
10
10
|
import "./chunk-IEQGTVWN.js";
|
|
11
11
|
import "./chunk-AUJJGMZG.js";
|
|
@@ -16,10 +16,10 @@ import "./chunk-K5QJMSJH.js";
|
|
|
16
16
|
import "./chunk-WAJXATCO.js";
|
|
17
17
|
import "./chunk-62JNF5M2.js";
|
|
18
18
|
import "./chunk-7CAGWFAQ.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-MITMSC2W.js";
|
|
20
20
|
import "./chunk-55JZKI2M.js";
|
|
21
21
|
import "./chunk-HIUQ4FXO.js";
|
|
22
|
-
import "./chunk-
|
|
22
|
+
import "./chunk-AYJQYDN6.js";
|
|
23
23
|
export {
|
|
24
24
|
App
|
|
25
25
|
};
|
|
@@ -44,7 +44,7 @@ async function runIntervalSweep(cwd, url, lastRun) {
|
|
|
44
44
|
const chrome = findChrome();
|
|
45
45
|
if (!chrome) return [];
|
|
46
46
|
const os = await import("os");
|
|
47
|
-
const { cdpCapture } = await import("./cdp-
|
|
47
|
+
const { cdpCapture } = await import("./cdp-E6VAQ7XL.js");
|
|
48
48
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "squint-interval-"));
|
|
49
49
|
try {
|
|
50
50
|
const result = await cdpCapture(chrome, url, tmp, [], 800, false, due);
|
|
@@ -143,8 +143,8 @@ function stepExpression(step) {
|
|
|
143
143
|
async function suggestFlows(cwd, baseUrl, chromePath) {
|
|
144
144
|
const fs2 = await import("fs");
|
|
145
145
|
const path2 = await import("path");
|
|
146
|
-
const { cdpCapture } = await import("./cdp-
|
|
147
|
-
const { loadRoutes } = await import("./preview-
|
|
146
|
+
const { cdpCapture } = await import("./cdp-E6VAQ7XL.js");
|
|
147
|
+
const { loadRoutes } = await import("./preview-M6VBOTQZ.js");
|
|
148
148
|
const dir = path2.join(cwd, ".squint", "flows");
|
|
149
149
|
fs2.mkdirSync(dir, { recursive: true });
|
|
150
150
|
const created = [];
|
|
@@ -5,14 +5,14 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
cdpCapture,
|
|
7
7
|
hasWebSocket
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MITMSC2W.js";
|
|
9
9
|
import {
|
|
10
10
|
findChrome,
|
|
11
11
|
screenshot
|
|
12
12
|
} from "./chunk-55JZKI2M.js";
|
|
13
13
|
import {
|
|
14
14
|
loadChecks
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-AYJQYDN6.js";
|
|
16
16
|
|
|
17
17
|
// src/preview/preview.ts
|
|
18
18
|
import fs from "fs";
|
|
@@ -51,7 +51,7 @@ async function captureViewports(cwd, url) {
|
|
|
51
51
|
if (hasWebSocket()) {
|
|
52
52
|
try {
|
|
53
53
|
const checks = loadChecks(cwd);
|
|
54
|
-
const { report, shots: shots2, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank, locale } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true, checks);
|
|
54
|
+
const { report, shots: shots2, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank, locale, speculation } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true, checks);
|
|
55
55
|
const errors2 = [];
|
|
56
56
|
for (const route of routes.slice(1)) {
|
|
57
57
|
try {
|
|
@@ -66,7 +66,7 @@ async function captureViewports(cwd, url) {
|
|
|
66
66
|
errors2.push(`${route}: capture failed`);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
-
return { shots: shots2, errors: errors2, runtime: report, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank, locale };
|
|
69
|
+
return { shots: shots2, errors: errors2, runtime: report, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank, locale, speculation };
|
|
70
70
|
} catch {
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -139,13 +139,13 @@ async function probeRuntime(url, cwd) {
|
|
|
139
139
|
async function comparePulse(previous, current) {
|
|
140
140
|
const chrome = findChrome();
|
|
141
141
|
if (!chrome || !hasWebSocket()) return null;
|
|
142
|
-
const { pixelDiffPct } = await import("./cdp-
|
|
142
|
+
const { pixelDiffPct } = await import("./cdp-E6VAQ7XL.js");
|
|
143
143
|
return pixelDiffPct(chrome, previous, current);
|
|
144
144
|
}
|
|
145
145
|
async function comparePulseAttributed(previous, current, url, outPath) {
|
|
146
146
|
const chrome = findChrome();
|
|
147
147
|
if (!chrome || !hasWebSocket()) return null;
|
|
148
|
-
const { pixelDiffAttributed } = await import("./cdp-
|
|
148
|
+
const { pixelDiffAttributed } = await import("./cdp-E6VAQ7XL.js");
|
|
149
149
|
return pixelDiffAttributed(chrome, previous, current, url, outPath);
|
|
150
150
|
}
|
|
151
151
|
function buildRuntimeFixPrompt(report) {
|
|
@@ -173,6 +173,16 @@ ${narration.join("\n")}
|
|
|
173
173
|
|
|
174
174
|
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.`;
|
|
175
175
|
}
|
|
176
|
+
function speculationSection(speculation) {
|
|
177
|
+
if (!speculation || speculation.length === 0) return "";
|
|
178
|
+
return `
|
|
179
|
+
|
|
180
|
+
## Speculation-rules findings
|
|
181
|
+
|
|
182
|
+
${speculation.join("\n")}
|
|
183
|
+
|
|
184
|
+
Invalid rule sets silently disable speculative loading; failed prefetch/prerender attempts waste the declaration. Fix the rules (or the target pages), don't remove them.`;
|
|
185
|
+
}
|
|
176
186
|
function localeSection(locale) {
|
|
177
187
|
if (!locale || locale.length === 0) return "";
|
|
178
188
|
return `
|
|
@@ -243,13 +253,13 @@ ${findings.join("\n")}
|
|
|
243
253
|
|
|
244
254
|
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.`;
|
|
245
255
|
}
|
|
246
|
-
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration, phantoms, viewTransitions, components, webmcp, jank, locale) {
|
|
256
|
+
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration, phantoms, viewTransitions, components, webmcp, jank, locale, speculation) {
|
|
247
257
|
const list = shots.map((s) => `- ${s.name}: ${s.path}`).join("\n");
|
|
248
258
|
return `Screenshots of the running app were just captured:
|
|
249
259
|
|
|
250
260
|
${list}
|
|
251
261
|
|
|
252
|
-
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)}${jankSection(jank)}${localeSection(locale)}`;
|
|
262
|
+
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)}${jankSection(jank)}${localeSection(locale)}${speculationSection(speculation)}`;
|
|
253
263
|
}
|
|
254
264
|
|
|
255
265
|
export {
|
|
@@ -211,7 +211,7 @@ function summarizeSoftNav(entries) {
|
|
|
211
211
|
});
|
|
212
212
|
}
|
|
213
213
|
async function runFlow(chromePath, baseUrl, flow, outDir) {
|
|
214
|
-
const { stepExpression } = await import("./flows-
|
|
214
|
+
const { stepExpression } = await import("./flows-EHUNUHED.js");
|
|
215
215
|
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
216
216
|
const shots = [];
|
|
217
217
|
let transitions = [];
|
|
@@ -879,6 +879,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
879
879
|
const checkFailures = [];
|
|
880
880
|
let webmcp = [];
|
|
881
881
|
let locale = [];
|
|
882
|
+
const speculation = [];
|
|
882
883
|
let jank = [];
|
|
883
884
|
const requests = /* @__PURE__ */ new Map();
|
|
884
885
|
let connection = null;
|
|
@@ -897,6 +898,22 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
897
898
|
const text = detail?.exception?.description ?? detail?.text;
|
|
898
899
|
if (text) report.pageErrors.push(String(text).split("\n").slice(0, 3).join("\n"));
|
|
899
900
|
});
|
|
901
|
+
connection.on("Preload.ruleSetUpdated", (params) => {
|
|
902
|
+
const rs = params.ruleSet;
|
|
903
|
+
if (rs?.errorType) {
|
|
904
|
+
speculation.push(`speculation: rule set invalid (${rs.errorType}) \u2014 ${String(rs.errorMessage ?? "").slice(0, 120)}`);
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
connection.on("Preload.prefetchStatusUpdated", (params) => {
|
|
908
|
+
if (params.status === "Failure") {
|
|
909
|
+
speculation.push(`speculation: prefetch failed for ${params.prefetchUrl ?? "?"}`);
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
connection.on("Preload.prerenderStatusUpdated", (params) => {
|
|
913
|
+
if (params.status === "Failure") {
|
|
914
|
+
speculation.push(`speculation: prerender failed${params.disallowedMojoInterface ? ` (${params.disallowedMojoInterface})` : ""}${params.prerenderStatus ? ` \u2014 ${params.prerenderStatus}` : ""}`);
|
|
915
|
+
}
|
|
916
|
+
});
|
|
900
917
|
connection.on("Network.requestWillBeSent", (params) => {
|
|
901
918
|
requests.set(params.requestId, params.request?.url ?? "unknown");
|
|
902
919
|
});
|
|
@@ -918,6 +935,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
918
935
|
await connection.send("Runtime.enable", {}, sessionId);
|
|
919
936
|
await connection.send("Network.enable", {}, sessionId);
|
|
920
937
|
await connection.send("Page.enable", {}, sessionId);
|
|
938
|
+
await connection.send("Preload.enable", {}, sessionId).catch(() => null);
|
|
921
939
|
await connection.send("Page.addScriptToEvaluateOnNewDocument", { source: WEBMCP_SHIM }, sessionId).catch(() => null);
|
|
922
940
|
await connection.send("Page.addScriptToEvaluateOnNewDocument", { source: LOAF_SHIM }, sessionId).catch(() => null);
|
|
923
941
|
await connection.send("Page.navigate", { url }, sessionId);
|
|
@@ -1201,7 +1219,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
1201
1219
|
child.kill("SIGKILL");
|
|
1202
1220
|
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
1203
1221
|
}
|
|
1204
|
-
return { report, shots, a11y, slop, perf, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank, locale };
|
|
1222
|
+
return { report, shots, a11y, slop, perf, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank, locale, speculation };
|
|
1205
1223
|
}
|
|
1206
1224
|
|
|
1207
1225
|
export {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
comparePulseAttributed,
|
|
13
13
|
probeRuntime,
|
|
14
14
|
runtimeSummary
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-KPLEM7SV.js";
|
|
16
16
|
import {
|
|
17
17
|
enrich
|
|
18
18
|
} from "./chunk-UBADCBPT.js";
|
|
@@ -117,6 +117,7 @@ var Session = class {
|
|
|
117
117
|
engineId: opts.engineId,
|
|
118
118
|
model: opts.model,
|
|
119
119
|
devState: "stopped",
|
|
120
|
+
pendingApproval: null,
|
|
120
121
|
devUrl: null,
|
|
121
122
|
totals: { costUsd: 0, turns: 0 },
|
|
122
123
|
queue: [],
|
|
@@ -155,7 +156,6 @@ var Session = class {
|
|
|
155
156
|
checkpoints = [];
|
|
156
157
|
fixAttempts = 0;
|
|
157
158
|
reviewTipShown = false;
|
|
158
|
-
pendingApproval = null;
|
|
159
159
|
goal = null;
|
|
160
160
|
laneEnabled = false;
|
|
161
161
|
inLane = false;
|
|
@@ -274,7 +274,7 @@ ${question}`,
|
|
|
274
274
|
return;
|
|
275
275
|
}
|
|
276
276
|
await this.runTurn(
|
|
277
|
-
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp, result.jank, result.locale),
|
|
277
|
+
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp, result.jank, result.locale, result.speculation),
|
|
278
278
|
`\u{1F441} polish round ${round}/${rounds}`
|
|
279
279
|
);
|
|
280
280
|
}
|
|
@@ -496,7 +496,7 @@ ${question}`,
|
|
|
496
496
|
const req = JSON.parse(fs2.readFileSync(reqPath, "utf8"));
|
|
497
497
|
fs2.rmSync(reqPath, { force: true });
|
|
498
498
|
if (typeof req?.summary === "string" && req.summary.length > 0) {
|
|
499
|
-
this.pendingApproval
|
|
499
|
+
this.notify({ pendingApproval: req.summary });
|
|
500
500
|
const shot = typeof req.screenshot === "string" ? path2.resolve(this.execCwd(), req.screenshot) : null;
|
|
501
501
|
if (shot && fs2.existsSync(shot)) this.push("image", shot);
|
|
502
502
|
this.push("status", `\u23F8 approval requested: ${req.summary}
|
|
@@ -665,7 +665,7 @@ ${probe.checkFailures.slice(0, 5).join("\n")}`);
|
|
|
665
665
|
const captureResult = await this.capture();
|
|
666
666
|
if (captureResult) {
|
|
667
667
|
await this.runTurn(
|
|
668
|
-
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration, captureResult.phantoms, captureResult.viewTransitions, captureResult.components, captureResult.webmcp, captureResult.jank, captureResult.locale),
|
|
668
|
+
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration, captureResult.phantoms, captureResult.viewTransitions, captureResult.components, captureResult.webmcp, captureResult.jank, captureResult.locale, captureResult.speculation),
|
|
669
669
|
"\u{1F441} auto-review rendered UI"
|
|
670
670
|
);
|
|
671
671
|
}
|
|
@@ -812,7 +812,7 @@ squint verifies every turn \u2014 gates, runtime probe, page audits. Do not decl
|
|
|
812
812
|
this.push("image", pulsePath);
|
|
813
813
|
return null;
|
|
814
814
|
}
|
|
815
|
-
const { previewDir } = await import("./preview-
|
|
815
|
+
const { previewDir } = await import("./preview-M6VBOTQZ.js");
|
|
816
816
|
const diff = await comparePulseAttributed(
|
|
817
817
|
previous,
|
|
818
818
|
current,
|
|
@@ -1069,12 +1069,12 @@ every ask now carries it; auto-fix presses to ${MAX_GOAL_FIX_ATTEMPTS} attempts
|
|
|
1069
1069
|
}
|
|
1070
1070
|
case "yes":
|
|
1071
1071
|
case "no": {
|
|
1072
|
-
if (!this.pendingApproval) {
|
|
1072
|
+
if (!this.state.pendingApproval) {
|
|
1073
1073
|
this.push("status", `nothing awaiting approval \u2014 /${name} answers an engine's approval request`);
|
|
1074
1074
|
break;
|
|
1075
1075
|
}
|
|
1076
|
-
const summary = this.pendingApproval;
|
|
1077
|
-
this.pendingApproval
|
|
1076
|
+
const summary = this.state.pendingApproval;
|
|
1077
|
+
this.notify({ pendingApproval: null });
|
|
1078
1078
|
const approved = name === "yes";
|
|
1079
1079
|
appendDecision(this.opts.cwd, {
|
|
1080
1080
|
decision: `${approved ? "approved" : "rejected"}: ${summary}${arg ? ` \u2014 ${arg}` : ""}`,
|
|
@@ -1190,7 +1190,7 @@ every ask now carries it; auto-fix presses to ${MAX_GOAL_FIX_ATTEMPTS} attempts
|
|
|
1190
1190
|
this.push("error", "no Chrome/Chromium found");
|
|
1191
1191
|
return;
|
|
1192
1192
|
}
|
|
1193
|
-
const { suggestFlows } = await import("./flows-
|
|
1193
|
+
const { suggestFlows } = await import("./flows-EHUNUHED.js");
|
|
1194
1194
|
this.push("status", "drafting flows from the live routes\u2026");
|
|
1195
1195
|
const { created, skipped } = await suggestFlows(this.opts.cwd, this.state.devUrl, chrome);
|
|
1196
1196
|
if (created.length > 0) this.push("status", `drafted ${created.length} flow(s): ${created.join(", ")} \u2014 edit .squint/flows/*.flow or ask the engine to deepen them`);
|
|
@@ -1200,7 +1200,7 @@ every ask now carries it; auto-fix presses to ${MAX_GOAL_FIX_ATTEMPTS} attempts
|
|
|
1200
1200
|
break;
|
|
1201
1201
|
}
|
|
1202
1202
|
void (async () => {
|
|
1203
|
-
const { loadFlows } = await import("./flows-
|
|
1203
|
+
const { loadFlows } = await import("./flows-EHUNUHED.js");
|
|
1204
1204
|
const flows = loadFlows(this.opts.cwd);
|
|
1205
1205
|
if (flows.length === 0) {
|
|
1206
1206
|
this.push("status", "no flows \u2014 add .squint/flows/<name>.flow (goto/click/fill/press/expect/shot lines), or ask the engine to write one");
|
|
@@ -1211,8 +1211,8 @@ every ask now carries it; auto-fix presses to ${MAX_GOAL_FIX_ATTEMPTS} attempts
|
|
|
1211
1211
|
this.push("error", "no Chrome/Chromium found for flows");
|
|
1212
1212
|
return;
|
|
1213
1213
|
}
|
|
1214
|
-
const { runFlow } = await import("./cdp-
|
|
1215
|
-
const { previewDir } = await import("./preview-
|
|
1214
|
+
const { runFlow } = await import("./cdp-E6VAQ7XL.js");
|
|
1215
|
+
const { previewDir } = await import("./preview-M6VBOTQZ.js");
|
|
1216
1216
|
this.push("status", `replaying ${flows.length} flow(s)\u2026`);
|
|
1217
1217
|
this.notify({ running: true, runStartedAt: Date.now() });
|
|
1218
1218
|
const failures = [];
|
|
@@ -1352,7 +1352,7 @@ every ask now carries it; auto-fix presses to ${MAX_GOAL_FIX_ATTEMPTS} attempts
|
|
|
1352
1352
|
const result = await this.capture();
|
|
1353
1353
|
if (result) {
|
|
1354
1354
|
await this.runTurn(
|
|
1355
|
-
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp, result.jank, result.locale),
|
|
1355
|
+
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp, result.jank, result.locale, result.speculation),
|
|
1356
1356
|
`\u{1F441} review rendered UI${arg ? ` \xB7 ${arg}` : ""}`
|
|
1357
1357
|
);
|
|
1358
1358
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
Session
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-MWPZCXN7.js";
|
|
5
5
|
|
|
6
6
|
// src/daemon/client.ts
|
|
7
7
|
import net from "net";
|
|
@@ -57,7 +57,7 @@ function socketPath(cwd) {
|
|
|
57
57
|
const hash = crypto.createHash("sha256").update(cwd).digest("hex").slice(0, 12);
|
|
58
58
|
return path.join(os.tmpdir(), `squint-${hash}.sock`);
|
|
59
59
|
}
|
|
60
|
-
function startDaemon(opts) {
|
|
60
|
+
async function startDaemon(opts) {
|
|
61
61
|
const session = new Session(opts);
|
|
62
62
|
const sock = socketPath(opts.cwd);
|
|
63
63
|
fs.mkdirSync(path.dirname(sock), { recursive: true });
|
|
@@ -122,7 +122,7 @@ function startDaemon(opts) {
|
|
|
122
122
|
const state = session.getState();
|
|
123
123
|
if (!state.devUrl || state.running) return;
|
|
124
124
|
try {
|
|
125
|
-
const { runIntervalSweep } = await import("./checks-
|
|
125
|
+
const { runIntervalSweep } = await import("./checks-Y73R4SS3.js");
|
|
126
126
|
const failures = await runIntervalSweep(opts.cwd, state.devUrl, lastRun);
|
|
127
127
|
if (failures.length > 0) {
|
|
128
128
|
session.note(`\u23F0 interval check(s) failing:
|
|
@@ -132,6 +132,54 @@ ${failures.join("\n")}`);
|
|
|
132
132
|
}
|
|
133
133
|
}, opts.intervalSweepMs ?? 6e4);
|
|
134
134
|
sweep.unref?.();
|
|
135
|
+
let relayServer = null;
|
|
136
|
+
let relayUrl;
|
|
137
|
+
let relayUnsub = null;
|
|
138
|
+
if (opts.approvalWebhook) {
|
|
139
|
+
const http = await import("http");
|
|
140
|
+
const crypto2 = await import("crypto");
|
|
141
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
142
|
+
relayServer = http.createServer((req, res) => {
|
|
143
|
+
const token = (req.url ?? "").replace(/^\//, "");
|
|
144
|
+
const verdict = tokens.get(token);
|
|
145
|
+
if (!verdict) {
|
|
146
|
+
res.writeHead(404).end("unknown or already-used token");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
tokens.clear();
|
|
150
|
+
session.command(verdict === "yes" ? "/yes approved via webhook" : "/no rejected via webhook");
|
|
151
|
+
res.writeHead(200, { "content-type": "text/plain" });
|
|
152
|
+
res.end(verdict === "yes" ? "approved \u2014 the engine proceeds" : "rejected \u2014 the engine stands down");
|
|
153
|
+
});
|
|
154
|
+
await new Promise((resolve) => relayServer?.listen(opts.relayPort ?? 0, "127.0.0.1", resolve));
|
|
155
|
+
const addr = relayServer.address();
|
|
156
|
+
relayUrl = `http://127.0.0.1:${addr.port}`;
|
|
157
|
+
let lastSeen = null;
|
|
158
|
+
relayUnsub = session.subscribe(() => {
|
|
159
|
+
const pending = session.getState().pendingApproval;
|
|
160
|
+
if (!pending || pending === lastSeen) {
|
|
161
|
+
if (!pending) lastSeen = null;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
lastSeen = pending;
|
|
165
|
+
const approve = crypto2.randomBytes(16).toString("hex");
|
|
166
|
+
const reject = crypto2.randomBytes(16).toString("hex");
|
|
167
|
+
tokens.clear();
|
|
168
|
+
tokens.set(approve, "yes");
|
|
169
|
+
tokens.set(reject, "no");
|
|
170
|
+
fetch(opts.approvalWebhook, {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: { "content-type": "application/json" },
|
|
173
|
+
body: JSON.stringify({
|
|
174
|
+
text: `squint approval requested: ${pending}`,
|
|
175
|
+
summary: pending,
|
|
176
|
+
approveUrl: `${relayUrl}/${approve}`,
|
|
177
|
+
rejectUrl: `${relayUrl}/${reject}`
|
|
178
|
+
}),
|
|
179
|
+
signal: AbortSignal.timeout(5e3)
|
|
180
|
+
}).catch(() => null);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
135
183
|
const unsubscribe = session.subscribe(() => {
|
|
136
184
|
const payload = serialize();
|
|
137
185
|
for (const client of clients) {
|
|
@@ -143,9 +191,12 @@ ${failures.join("\n")}`);
|
|
|
143
191
|
server.listen(sock, () => {
|
|
144
192
|
resolve({
|
|
145
193
|
session,
|
|
194
|
+
relayUrl,
|
|
146
195
|
clientCount: () => clients.length,
|
|
147
196
|
close: () => {
|
|
148
197
|
clearInterval(sweep);
|
|
198
|
+
relayUnsub?.();
|
|
199
|
+
relayServer?.close();
|
|
149
200
|
unsubscribe();
|
|
150
201
|
for (const client of clients) client.destroy();
|
|
151
202
|
server.close();
|
package/dist/cli.js
CHANGED
|
@@ -3,14 +3,14 @@ import {
|
|
|
3
3
|
connectDaemon,
|
|
4
4
|
socketPath,
|
|
5
5
|
startDaemon
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QYK6OO46.js";
|
|
7
7
|
import {
|
|
8
8
|
App
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-6M54VGLM.js";
|
|
10
|
+
import "./chunk-MWPZCXN7.js";
|
|
11
11
|
import "./chunk-YKEAKVIS.js";
|
|
12
12
|
import "./chunk-LNJ7O5O4.js";
|
|
13
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-KPLEM7SV.js";
|
|
14
14
|
import "./chunk-UBADCBPT.js";
|
|
15
15
|
import "./chunk-IEQGTVWN.js";
|
|
16
16
|
import "./chunk-AUJJGMZG.js";
|
|
@@ -25,13 +25,13 @@ import {
|
|
|
25
25
|
} from "./chunk-WAJXATCO.js";
|
|
26
26
|
import "./chunk-62JNF5M2.js";
|
|
27
27
|
import "./chunk-7CAGWFAQ.js";
|
|
28
|
-
import "./chunk-
|
|
28
|
+
import "./chunk-MITMSC2W.js";
|
|
29
29
|
import "./chunk-55JZKI2M.js";
|
|
30
30
|
import {
|
|
31
31
|
detectEngines,
|
|
32
32
|
getEngine
|
|
33
33
|
} from "./chunk-HIUQ4FXO.js";
|
|
34
|
-
import "./chunk-
|
|
34
|
+
import "./chunk-AYJQYDN6.js";
|
|
35
35
|
|
|
36
36
|
// src/cli.tsx
|
|
37
37
|
import { Command } from "commander";
|
|
@@ -66,7 +66,9 @@ var ConfigSchema = z.object({
|
|
|
66
66
|
/** Cheaper model used for auto-fix and /fix turns (mechanical work). */
|
|
67
67
|
fixModel: z.string().optional(),
|
|
68
68
|
/** TUI theme name (amber, ocean, moss, rose, mono). */
|
|
69
|
-
theme: z.string().optional()
|
|
69
|
+
theme: z.string().optional(),
|
|
70
|
+
/** POSTed when an engine requests visual approval under squint serve. */
|
|
71
|
+
approvalWebhook: z.string().url().optional()
|
|
70
72
|
});
|
|
71
73
|
function defaultPaths(cwd) {
|
|
72
74
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
@@ -114,6 +116,8 @@ function setConfigValue(file, key, value) {
|
|
|
114
116
|
throw new Error(`"${key}" must be true or false`);
|
|
115
117
|
}
|
|
116
118
|
next = { ...current, [key]: value === "true" };
|
|
119
|
+
} else if (key === "approvalWebhook") {
|
|
120
|
+
next = { ...current, approvalWebhook: value };
|
|
117
121
|
} else if (key === "budgetUsd") {
|
|
118
122
|
const budget = Number.parseFloat(value);
|
|
119
123
|
if (!Number.isFinite(budget) || budget <= 0) {
|
|
@@ -126,7 +130,7 @@ function setConfigValue(file, key, value) {
|
|
|
126
130
|
next = { ...current, models: { ...current.models, [engineId]: value } };
|
|
127
131
|
} else {
|
|
128
132
|
throw new Error(
|
|
129
|
-
`Unknown config key "${key}". Supported: engine, theme, autoDev, autoFix, autoProbe, autoCheck, autoReview, bell, budgetUsd, fixModel, models.<engineId>`
|
|
133
|
+
`Unknown config key "${key}". Supported: engine, theme, autoDev, autoFix, autoProbe, autoCheck, autoReview, bell, budgetUsd, fixModel, approvalWebhook, models.<engineId>`
|
|
130
134
|
);
|
|
131
135
|
}
|
|
132
136
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
@@ -173,6 +177,7 @@ function registerDaemon(program2) {
|
|
|
173
177
|
autoReview: config.autoReview,
|
|
174
178
|
fixModel: config.fixModel,
|
|
175
179
|
budgetUsd: config.budgetUsd,
|
|
180
|
+
approvalWebhook: config.approvalWebhook,
|
|
176
181
|
onQuit: () => {
|
|
177
182
|
daemon.close();
|
|
178
183
|
process.exit(0);
|
|
@@ -180,6 +185,7 @@ function registerDaemon(program2) {
|
|
|
180
185
|
});
|
|
181
186
|
console.log(`squint daemon on ${socketPath(cwd)} (engine: ${engineId})`);
|
|
182
187
|
console.log("attach from another terminal with: squint attach");
|
|
188
|
+
if (daemon.relayUrl) console.log(`approval relay on ${daemon.relayUrl} \u2192 ${config.approvalWebhook}`);
|
|
183
189
|
const stop = () => {
|
|
184
190
|
daemon.close();
|
|
185
191
|
process.exit(0);
|
|
@@ -188,18 +194,18 @@ function registerDaemon(program2) {
|
|
|
188
194
|
process.on("SIGTERM", stop);
|
|
189
195
|
});
|
|
190
196
|
program2.command("mcp").description("serve the gates as MCP tools over stdio (check, shot, flows, context)").action(async () => {
|
|
191
|
-
const { runMcpServer } = await import("./server-
|
|
197
|
+
const { runMcpServer } = await import("./server-V3DHLHPK.js");
|
|
192
198
|
runMcpServer(process.cwd());
|
|
193
199
|
});
|
|
194
200
|
program2.command("attach").description("attach this terminal to a running squint daemon (full TUI; --plain for line mode)").option("--plain", "line-mode attach instead of the full TUI").action(async (opts) => {
|
|
195
201
|
const cwd = process.cwd();
|
|
196
202
|
if (!opts.plain) {
|
|
197
203
|
try {
|
|
198
|
-
const { RemoteSession } = await import("./remote-
|
|
204
|
+
const { RemoteSession } = await import("./remote-VAIQNPAV.js");
|
|
199
205
|
const remote = await RemoteSession.connect(cwd);
|
|
200
206
|
const config = loadConfig(defaultPaths(cwd));
|
|
201
207
|
const { render: render2 } = await import("ink");
|
|
202
|
-
const { App: App2 } = await import("./App-
|
|
208
|
+
const { App: App2 } = await import("./App-TEZ6E4R6.js");
|
|
203
209
|
const React = await import("react");
|
|
204
210
|
render2(
|
|
205
211
|
React.createElement(App2, {
|
|
@@ -316,7 +322,7 @@ function registerEnv(program2) {
|
|
|
316
322
|
}
|
|
317
323
|
}
|
|
318
324
|
const { findChrome } = await import("./chrome-BQ4WOCOH.js");
|
|
319
|
-
const { hasWebSocket } = await import("./cdp-
|
|
325
|
+
const { hasWebSocket } = await import("./cdp-E6VAQ7XL.js");
|
|
320
326
|
const chrome = findChrome();
|
|
321
327
|
console.log(
|
|
322
328
|
chrome ? `${pc3.green("\u2713")} Chrome ${pc3.dim(chrome)}` : `${pc3.yellow("\u25CB")} Chrome ${pc3.dim("\u2014 screenshots and runtime probing disabled")}`
|
|
@@ -552,7 +558,7 @@ function registerQuality(program2) {
|
|
|
552
558
|
report.gates = gateResults.map((r) => ({ id: r.gate.id, ok: r.ok, durationMs: r.durationMs, outputTail: r.ok ? void 0 : r.outputTail }));
|
|
553
559
|
if (gateResults.some((r) => !r.ok)) failed = true;
|
|
554
560
|
if (opts.url) {
|
|
555
|
-
const { captureViewports, runtimeSummary, previewDir } = await import("./preview-
|
|
561
|
+
const { captureViewports, runtimeSummary, previewDir } = await import("./preview-M6VBOTQZ.js");
|
|
556
562
|
const capture = await captureViewports(cwd, opts.url);
|
|
557
563
|
if (!capture) {
|
|
558
564
|
console.log(pc5.red("\u2717 audit: capture failed (Chrome missing or URL unreachable)"));
|
|
@@ -584,14 +590,14 @@ function registerQuality(program2) {
|
|
|
584
590
|
console.log(`${pc5.green("\u2713")} audit ${pc5.dim(`(${advisories.a11y.length} a11y, ${advisories.slop.length} slop, ${advisories.jank.length} jank advisories)`)}`);
|
|
585
591
|
}
|
|
586
592
|
}
|
|
587
|
-
const { loadFlows } = await import("./flows-
|
|
593
|
+
const { loadFlows } = await import("./flows-EHUNUHED.js");
|
|
588
594
|
const flows = loadFlows(cwd);
|
|
589
595
|
if (flows.length > 0) {
|
|
590
596
|
const { findChrome } = await import("./chrome-BQ4WOCOH.js");
|
|
591
597
|
const chrome = findChrome();
|
|
592
598
|
const flowReport = [];
|
|
593
599
|
if (chrome) {
|
|
594
|
-
const { runFlow } = await import("./cdp-
|
|
600
|
+
const { runFlow } = await import("./cdp-E6VAQ7XL.js");
|
|
595
601
|
for (const flow of flows) {
|
|
596
602
|
const result = await runFlow(chrome, opts.url, flow, previewDir(cwd));
|
|
597
603
|
flowReport.push({ name: flow.name, ok: result.ok, detail: result.ok ? void 0 : `step ${result.failedStep}: ${result.detail}` });
|
|
@@ -605,7 +611,7 @@ function registerQuality(program2) {
|
|
|
605
611
|
report.ok = !failed;
|
|
606
612
|
report.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
607
613
|
try {
|
|
608
|
-
const { writeReceipt } = await import("./receipts-
|
|
614
|
+
const { writeReceipt } = await import("./receipts-KNOHIVCA.js");
|
|
609
615
|
console.log(pc5.dim(`receipt \u2192 ${writeReceipt(cwd, report)}`));
|
|
610
616
|
} catch {
|
|
611
617
|
}
|
|
@@ -617,7 +623,7 @@ function registerQuality(program2) {
|
|
|
617
623
|
if (failed) process.exitCode = 1;
|
|
618
624
|
});
|
|
619
625
|
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) => {
|
|
620
|
-
const { captureViewports } = await import("./preview-
|
|
626
|
+
const { captureViewports } = await import("./preview-M6VBOTQZ.js");
|
|
621
627
|
const result = await captureViewports(process.cwd(), url);
|
|
622
628
|
if (!result) {
|
|
623
629
|
console.error(pc5.red("\u2717 no Chrome/Chromium found"));
|
|
@@ -804,7 +810,7 @@ function registerTui(program2) {
|
|
|
804
810
|
}
|
|
805
811
|
|
|
806
812
|
// src/cli.tsx
|
|
807
|
-
var VERSION = true ? "0.
|
|
813
|
+
var VERSION = true ? "0.9.1" : "0.0.0-dev";
|
|
808
814
|
var program = new Command();
|
|
809
815
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
810
816
|
registerRun(program);
|
|
@@ -11,12 +11,12 @@ import {
|
|
|
11
11
|
probeRuntime,
|
|
12
12
|
routeShotName,
|
|
13
13
|
runtimeSummary
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-KPLEM7SV.js";
|
|
15
15
|
import "./chunk-7CAGWFAQ.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-MITMSC2W.js";
|
|
17
17
|
import "./chunk-55JZKI2M.js";
|
|
18
18
|
import "./chunk-HIUQ4FXO.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-AYJQYDN6.js";
|
|
20
20
|
export {
|
|
21
21
|
VIEWPORTS,
|
|
22
22
|
buildReviewPrompt,
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
connectDaemon,
|
|
4
4
|
socketPath
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-QYK6OO46.js";
|
|
6
|
+
import "./chunk-MWPZCXN7.js";
|
|
7
7
|
import "./chunk-YKEAKVIS.js";
|
|
8
|
-
import "./chunk-
|
|
8
|
+
import "./chunk-KPLEM7SV.js";
|
|
9
9
|
import "./chunk-UBADCBPT.js";
|
|
10
10
|
import "./chunk-IEQGTVWN.js";
|
|
11
11
|
import "./chunk-AUJJGMZG.js";
|
|
@@ -16,10 +16,10 @@ import "./chunk-K5QJMSJH.js";
|
|
|
16
16
|
import "./chunk-WAJXATCO.js";
|
|
17
17
|
import "./chunk-62JNF5M2.js";
|
|
18
18
|
import "./chunk-7CAGWFAQ.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-MITMSC2W.js";
|
|
20
20
|
import "./chunk-55JZKI2M.js";
|
|
21
21
|
import "./chunk-HIUQ4FXO.js";
|
|
22
|
-
import "./chunk-
|
|
22
|
+
import "./chunk-AYJQYDN6.js";
|
|
23
23
|
|
|
24
24
|
// src/daemon/remote.ts
|
|
25
25
|
var noteId = 0;
|
|
@@ -38,7 +38,8 @@ var RemoteSession = class _RemoteSession {
|
|
|
38
38
|
queue: [],
|
|
39
39
|
mode: "safe",
|
|
40
40
|
problems: [],
|
|
41
|
-
sandbox: false
|
|
41
|
+
sandbox: false,
|
|
42
|
+
pendingApproval: null
|
|
42
43
|
};
|
|
43
44
|
client.onMessage((msg) => {
|
|
44
45
|
if (msg.type === "state" && msg.state) {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
captureViewports,
|
|
8
8
|
previewDir,
|
|
9
9
|
runtimeSummary
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-KPLEM7SV.js";
|
|
11
11
|
import "./chunk-UBADCBPT.js";
|
|
12
12
|
import "./chunk-IEQGTVWN.js";
|
|
13
13
|
import "./chunk-WAJXATCO.js";
|
|
@@ -18,13 +18,13 @@ import {
|
|
|
18
18
|
import "./chunk-7CAGWFAQ.js";
|
|
19
19
|
import {
|
|
20
20
|
loadFlows
|
|
21
|
-
} from "./chunk-
|
|
22
|
-
import "./chunk-
|
|
21
|
+
} from "./chunk-FQYJ343N.js";
|
|
22
|
+
import "./chunk-MITMSC2W.js";
|
|
23
23
|
import {
|
|
24
24
|
findChrome
|
|
25
25
|
} from "./chunk-55JZKI2M.js";
|
|
26
26
|
import "./chunk-HIUQ4FXO.js";
|
|
27
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-AYJQYDN6.js";
|
|
28
28
|
|
|
29
29
|
// src/mcp/server.ts
|
|
30
30
|
import readline from "readline";
|
|
@@ -59,6 +59,7 @@ ${r.outputTail.slice(-1500)}`}`).join("\n");
|
|
|
59
59
|
["view transitions", result.viewTransitions],
|
|
60
60
|
["jank", result.jank],
|
|
61
61
|
["locale", result.locale],
|
|
62
|
+
["speculation", result.speculation],
|
|
62
63
|
["components", result.components]
|
|
63
64
|
]) {
|
|
64
65
|
if (list && list.length > 0) sections.push(`${label}:
|
|
@@ -77,7 +78,7 @@ ${list.join("\n")}`);
|
|
|
77
78
|
if (flows.length === 0) return "no flows declared \u2014 add .squint/flows/<name>.flow";
|
|
78
79
|
const chrome = findChrome();
|
|
79
80
|
if (!chrome) return "no Chrome/Chromium found";
|
|
80
|
-
const { runFlow } = await import("./cdp-
|
|
81
|
+
const { runFlow } = await import("./cdp-E6VAQ7XL.js");
|
|
81
82
|
const lines = [];
|
|
82
83
|
for (const flow of flows) {
|
|
83
84
|
const result = await runFlow(chrome, url, flow, previewDir(cwd));
|
|
@@ -97,7 +98,7 @@ ${result.leaks.map((l) => ` ${l}`).join("\n")}` : ""}` : `\u2717 ${flow.name} a
|
|
|
97
98
|
async run(args, cwd) {
|
|
98
99
|
const chrome = findChrome();
|
|
99
100
|
if (!chrome) return "no Chrome/Chromium found";
|
|
100
|
-
const { suggestFlows } = await import("./flows-
|
|
101
|
+
const { suggestFlows } = await import("./flows-EHUNUHED.js");
|
|
101
102
|
const { created, skipped } = await suggestFlows(cwd, String(args.url ?? ""), chrome);
|
|
102
103
|
return [
|
|
103
104
|
created.length > 0 ? `drafted: ${created.join(", ")}` : "nothing drafted",
|
|
@@ -112,7 +113,7 @@ ${result.leaks.map((l) => ` ${l}`).join("\n")}` : ""}` : `\u2717 ${flow.name} a
|
|
|
112
113
|
async run(args, cwd) {
|
|
113
114
|
const fs = await import("fs");
|
|
114
115
|
const path = await import("path");
|
|
115
|
-
const { verifyReceipt } = await import("./receipts-
|
|
116
|
+
const { verifyReceipt } = await import("./receipts-KNOHIVCA.js");
|
|
116
117
|
const file = path.isAbsolute(String(args.path)) ? String(args.path) : path.join(cwd, String(args.path));
|
|
117
118
|
let receipt;
|
|
118
119
|
try {
|
package/package.json
CHANGED