@aayambansal/squint 0.4.7 → 0.5.0
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 +6 -0
- package/dist/{App-HWMVSRMF.js → App-MC2XEM7K.js} +9 -9
- package/dist/{cdp-PBQKXC6R.js → cdp-WSWOSDEX.js} +3 -1
- package/dist/{chrome-SBV3H77F.js → chrome-EEXJCG6W.js} +2 -2
- package/dist/{chunk-QCHBDP46.js → chunk-2I3YHT6P.js} +3 -3
- package/dist/{flows-6LK7NGWS.js → chunk-4XSHHGA6.js} +2 -1
- package/dist/{chunk-VH7OOFQP.js → chunk-6NPUVPQD.js} +2 -2
- package/dist/{contextDoctor-A26C2ZDN.js → chunk-BKOGVFCO.js} +1 -0
- package/dist/{chunk-ZSDCMYZR.js → chunk-CRDRWTON.js} +24 -7
- package/dist/{chunk-HC4E42SV.js → chunk-DCFNX2CQ.js} +228 -3
- package/dist/{chunk-M25IQ7C7.js → chunk-EB4OSUB7.js} +1 -1
- package/dist/{chunk-KVYGPLWW.js → chunk-GV2HAALP.js} +4 -0
- package/dist/{chunk-YGSF2TSO.js → chunk-J7ITMK6X.js} +1 -1
- package/dist/{chunk-IMDRXXFU.js → chunk-PYC53J7D.js} +1 -1
- package/dist/{chunk-7MOKOZOR.js → chunk-WASLLXEZ.js} +31 -18
- package/dist/{chunk-NX7XQY2X.js → chunk-YK5DEB7T.js} +1 -1
- package/dist/cli.js +99 -22
- package/dist/contextDoctor-3Y36YURE.js +11 -0
- package/dist/flows-MXJL45HW.js +11 -0
- package/dist/{preview-WT34NVCH.js → preview-CDEMPHWV.js} +6 -4
- package/dist/{registry-MIJ6LSAY.js → registry-JBP7J3JB.js} +1 -1
- package/dist/{remote-6O6FPTUF.js → remote-C4BACCKX.js} +9 -9
- package/dist/{run-NDSNTVYP.js → run-GDDIYEBP.js} +2 -2
- package/dist/sentinel-42NZRTTS.js +0 -0
- package/dist/server-ENT2OFNL.js +155 -0
- package/dist/shots-6DRTXFDI.js +13 -0
- package/dist/{variants-3IEP7DFY.js → variants-VURXVIQO.js} +3 -3
- package/package.json +1 -1
- package/dist/shots-OKWOYF7F.js +0 -13
package/README.md
CHANGED
|
@@ -174,6 +174,12 @@ squint doctor --probe # run every engine end to end, verify auth act
|
|
|
174
174
|
flagged from the live page, and on Next 16+ the framework's own `/_next/mcp`
|
|
175
175
|
channel feeds structured errors straight into the fix loop. `/context` itemizes
|
|
176
176
|
the injected-context bill per source, with staleness warnings.
|
|
177
|
+
- **Two more doors in**: `squint mcp` serves the gates as MCP tools (any
|
|
178
|
+
MCP-speaking agent calls squint's verification directly); `squint ci` runs the
|
|
179
|
+
whole loop headlessly in a pipeline — JSON report, non-zero exit on hard findings.
|
|
180
|
+
- **The sentinel**: gate evasion (deleted tests, added skips, suppressed
|
|
181
|
+
diagnostics, weakened checks, locked-path touches) detected deterministically per
|
|
182
|
+
turn and reported to you — never sent back to the engine that did it.
|
|
177
183
|
- **Detachable sessions**: `squint serve` owns the session on a unix socket;
|
|
178
184
|
`squint attach` joins from any terminal (observer seats for teammates, driver
|
|
179
185
|
seat inherited on detach). Crash-proof, ssh-friendly, no cloud.
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
App
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-EB4OSUB7.js";
|
|
5
5
|
import "./chunk-43NQNIJY.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-WASLLXEZ.js";
|
|
7
7
|
import "./chunk-ARDV4XH6.js";
|
|
8
8
|
import "./chunk-AUJJGMZG.js";
|
|
9
9
|
import "./chunk-YHRAOBI2.js";
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-2I3YHT6P.js";
|
|
11
|
+
import "./chunk-J7ITMK6X.js";
|
|
12
|
+
import "./chunk-6NPUVPQD.js";
|
|
13
13
|
import "./chunk-K5QJMSJH.js";
|
|
14
14
|
import "./chunk-WAJXATCO.js";
|
|
15
15
|
import "./chunk-62JNF5M2.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-CRDRWTON.js";
|
|
17
17
|
import "./chunk-7CAGWFAQ.js";
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
20
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-DCFNX2CQ.js";
|
|
19
|
+
import "./chunk-PYC53J7D.js";
|
|
20
|
+
import "./chunk-GV2HAALP.js";
|
|
21
21
|
export {
|
|
22
22
|
App
|
|
23
23
|
};
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
import {
|
|
3
3
|
cdpCapture,
|
|
4
4
|
hasWebSocket,
|
|
5
|
+
pixelDiffAttributed,
|
|
5
6
|
pixelDiffPct,
|
|
6
7
|
runFlow
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DCFNX2CQ.js";
|
|
8
9
|
export {
|
|
9
10
|
cdpCapture,
|
|
10
11
|
hasWebSocket,
|
|
12
|
+
pixelDiffAttributed,
|
|
11
13
|
pixelDiffPct,
|
|
12
14
|
runFlow
|
|
13
15
|
};
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
variantsRoot
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-J7ITMK6X.js";
|
|
5
5
|
import {
|
|
6
6
|
findChrome,
|
|
7
7
|
screenshot
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PYC53J7D.js";
|
|
9
9
|
import {
|
|
10
10
|
lineSplitter
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-GV2HAALP.js";
|
|
12
12
|
|
|
13
13
|
// src/variants/shots.ts
|
|
14
14
|
import path2 from "path";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
findEngineBinary,
|
|
4
4
|
lineSplitter,
|
|
5
5
|
truncate
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GV2HAALP.js";
|
|
7
7
|
|
|
8
8
|
// src/runner/run.ts
|
|
9
9
|
import { spawn } from "child_process";
|
|
@@ -21,7 +21,7 @@ function runAgent(engine, opts, onEvent, signal) {
|
|
|
21
21
|
let stderrTail = "";
|
|
22
22
|
const child = spawn(binaryPath, engine.buildArgs(opts), {
|
|
23
23
|
cwd: opts.cwd,
|
|
24
|
-
env: process.env,
|
|
24
|
+
env: { ...process.env, ...engine.env },
|
|
25
25
|
stdio: ["ignore", "pipe", "pipe"]
|
|
26
26
|
});
|
|
27
27
|
const emit = (event) => {
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
cdpCapture,
|
|
7
7
|
hasWebSocket
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DCFNX2CQ.js";
|
|
9
9
|
import {
|
|
10
10
|
findChrome,
|
|
11
11
|
screenshot
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-PYC53J7D.js";
|
|
13
13
|
|
|
14
14
|
// src/preview/preview.ts
|
|
15
15
|
import fs2 from "fs";
|
|
@@ -75,7 +75,7 @@ async function captureViewports(cwd, url) {
|
|
|
75
75
|
if (hasWebSocket()) {
|
|
76
76
|
try {
|
|
77
77
|
const checks = loadChecks(cwd);
|
|
78
|
-
const { report, shots: shots2, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true, checks);
|
|
78
|
+
const { report, shots: shots2, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true, checks);
|
|
79
79
|
const errors2 = [];
|
|
80
80
|
for (const route of routes.slice(1)) {
|
|
81
81
|
try {
|
|
@@ -90,7 +90,7 @@ async function captureViewports(cwd, url) {
|
|
|
90
90
|
errors2.push(`${route}: capture failed`);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
-
return { shots: shots2, errors: errors2, runtime: report, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp };
|
|
93
|
+
return { shots: shots2, errors: errors2, runtime: report, a11y, slop, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank };
|
|
94
94
|
} catch {
|
|
95
95
|
}
|
|
96
96
|
}
|
|
@@ -163,9 +163,15 @@ async function probeRuntime(url, cwd) {
|
|
|
163
163
|
async function comparePulse(previous, current) {
|
|
164
164
|
const chrome = findChrome();
|
|
165
165
|
if (!chrome || !hasWebSocket()) return null;
|
|
166
|
-
const { pixelDiffPct } = await import("./cdp-
|
|
166
|
+
const { pixelDiffPct } = await import("./cdp-WSWOSDEX.js");
|
|
167
167
|
return pixelDiffPct(chrome, previous, current);
|
|
168
168
|
}
|
|
169
|
+
async function comparePulseAttributed(previous, current, url) {
|
|
170
|
+
const chrome = findChrome();
|
|
171
|
+
if (!chrome || !hasWebSocket()) return null;
|
|
172
|
+
const { pixelDiffAttributed } = await import("./cdp-WSWOSDEX.js");
|
|
173
|
+
return pixelDiffAttributed(chrome, previous, current, url);
|
|
174
|
+
}
|
|
169
175
|
function buildRuntimeFixPrompt(report) {
|
|
170
176
|
return `The running app has runtime problems.${runtimeSection(report)}
|
|
171
177
|
|
|
@@ -191,6 +197,16 @@ ${narration.join("\n")}
|
|
|
191
197
|
|
|
192
198
|
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.`;
|
|
193
199
|
}
|
|
200
|
+
function jankSection(jank) {
|
|
201
|
+
if (!jank || jank.length === 0) return "";
|
|
202
|
+
return `
|
|
203
|
+
|
|
204
|
+
## Jank attribution (main-thread frames \u226550ms)
|
|
205
|
+
|
|
206
|
+
${jank.join("\n")}
|
|
207
|
+
|
|
208
|
+
Each line names the function behind a long animation frame observed during load and a scripted scroll. Fix the work (memoize, virtualize, move off the main thread) \u2014 do not remove the animation.`;
|
|
209
|
+
}
|
|
194
210
|
function webmcpSection(webmcp) {
|
|
195
211
|
if (!webmcp || webmcp.length === 0) return "";
|
|
196
212
|
return `
|
|
@@ -241,13 +257,13 @@ ${findings.join("\n")}
|
|
|
241
257
|
|
|
242
258
|
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.`;
|
|
243
259
|
}
|
|
244
|
-
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration, phantoms, viewTransitions, components, webmcp) {
|
|
260
|
+
function buildReviewPrompt(shots, extra, runtime, a11y, slop, narration, phantoms, viewTransitions, components, webmcp, jank) {
|
|
245
261
|
const list = shots.map((s) => `- ${s.name}: ${s.path}`).join("\n");
|
|
246
262
|
return `Screenshots of the running app were just captured:
|
|
247
263
|
|
|
248
264
|
${list}
|
|
249
265
|
|
|
250
|
-
Read each screenshot and review the rendered UI against the design standards you were given. Check: visual hierarchy and spacing rhythm, typography, color and contrast, alignment, empty-looking or broken regions, and whether the mobile capture shows horizontal overflow or cramped layout. List the concrete issues you can SEE (not hypothetical ones), ranked by visual impact${extra ? `, with special attention to: ${extra}` : ""}. Then fix them and verify the app still builds.${runtimeSection(runtime)}${a11ySection(a11y)}${slopSection(slop)}${narrationSection(narration)}${phantomSection(phantoms)}${vtSection(viewTransitions)}${componentSection(components)}${webmcpSection(webmcp)}`;
|
|
266
|
+
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)}`;
|
|
251
267
|
}
|
|
252
268
|
|
|
253
269
|
export {
|
|
@@ -259,6 +275,7 @@ export {
|
|
|
259
275
|
runtimeSummary,
|
|
260
276
|
probeRuntime,
|
|
261
277
|
comparePulse,
|
|
278
|
+
comparePulseAttributed,
|
|
262
279
|
buildRuntimeFixPrompt,
|
|
263
280
|
buildReviewPrompt
|
|
264
281
|
};
|
|
@@ -179,7 +179,7 @@ var describe = (value) => {
|
|
|
179
179
|
return String(value);
|
|
180
180
|
};
|
|
181
181
|
async function runFlow(chromePath, baseUrl, flow, outDir) {
|
|
182
|
-
const { stepExpression } = await import("./flows-
|
|
182
|
+
const { stepExpression } = await import("./flows-MXJL45HW.js");
|
|
183
183
|
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
184
184
|
const shots = [];
|
|
185
185
|
let connection = null;
|
|
@@ -189,6 +189,7 @@ async function runFlow(chromePath, baseUrl, flow, outDir) {
|
|
|
189
189
|
const { sessionId } = await connection.send("Target.attachToTarget", { targetId, flatten: true });
|
|
190
190
|
await connection.send("Page.enable", {}, sessionId);
|
|
191
191
|
await connection.send("Page.addScriptToEvaluateOnNewDocument", { source: WEBMCP_SHIM }, sessionId).catch(() => null);
|
|
192
|
+
await connection.send("Page.addScriptToEvaluateOnNewDocument", { source: LOAF_SHIM }, sessionId).catch(() => null);
|
|
192
193
|
await connection.send(
|
|
193
194
|
"Emulation.setDeviceMetricsOverride",
|
|
194
195
|
{ width: 1280, height: 800, deviceScaleFactor: 1, mobile: false },
|
|
@@ -237,6 +238,145 @@ async function runFlow(chromePath, baseUrl, flow, outDir) {
|
|
|
237
238
|
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
238
239
|
}
|
|
239
240
|
}
|
|
241
|
+
async function pixelDiffAttributed(chromePath, pngA, pngB, url) {
|
|
242
|
+
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
243
|
+
let connection = null;
|
|
244
|
+
try {
|
|
245
|
+
connection = await CdpConnection.connect(wsUrl, 1e4);
|
|
246
|
+
const { targetId } = await connection.send("Target.createTarget", { url: "about:blank" });
|
|
247
|
+
const { sessionId } = await connection.send("Target.attachToTarget", { targetId, flatten: true });
|
|
248
|
+
await connection.send("Runtime.enable", {}, sessionId);
|
|
249
|
+
const expression = `(async () => {
|
|
250
|
+
const load = (src) => new Promise((resolve, reject) => {
|
|
251
|
+
const img = new Image();
|
|
252
|
+
img.onload = () => resolve(img);
|
|
253
|
+
img.onerror = () => reject(new Error('decode'));
|
|
254
|
+
img.src = src;
|
|
255
|
+
});
|
|
256
|
+
const a = await load('data:image/png;base64,${pngA.toString("base64")}');
|
|
257
|
+
const b = await load('data:image/png;base64,${pngB.toString("base64")}');
|
|
258
|
+
const w = Math.min(a.width, b.width), h = Math.min(a.height, b.height);
|
|
259
|
+
if (w === 0 || h === 0) return null;
|
|
260
|
+
const draw = (img) => {
|
|
261
|
+
const c = document.createElement('canvas');
|
|
262
|
+
c.width = w; c.height = h;
|
|
263
|
+
const ctx = c.getContext('2d');
|
|
264
|
+
ctx.drawImage(img, 0, 0);
|
|
265
|
+
return ctx.getImageData(0, 0, w, h).data;
|
|
266
|
+
};
|
|
267
|
+
const da = draw(a), db = draw(b);
|
|
268
|
+
const CELL = 32;
|
|
269
|
+
const gw = Math.ceil(w / CELL), gh = Math.ceil(h / CELL);
|
|
270
|
+
const marked = new Uint8Array(gw * gh);
|
|
271
|
+
let differ = 0, total = 0;
|
|
272
|
+
for (let y = 0; y < h; y += 2) {
|
|
273
|
+
for (let x = 0; x < w; x += 2) {
|
|
274
|
+
total++;
|
|
275
|
+
const i = (y * w + x) * 4;
|
|
276
|
+
if (Math.abs(da[i] - db[i]) > 8 || Math.abs(da[i + 1] - db[i + 1]) > 8 || Math.abs(da[i + 2] - db[i + 2]) > 8) {
|
|
277
|
+
differ++;
|
|
278
|
+
marked[Math.floor(y / CELL) * gw + Math.floor(x / CELL)] = 1;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Flood-fill marked cells into region bounding boxes.
|
|
283
|
+
const seen = new Uint8Array(gw * gh);
|
|
284
|
+
const regions = [];
|
|
285
|
+
for (let cy = 0; cy < gh; cy++) {
|
|
286
|
+
for (let cx = 0; cx < gw; cx++) {
|
|
287
|
+
const start = cy * gw + cx;
|
|
288
|
+
if (!marked[start] || seen[start]) continue;
|
|
289
|
+
let minX = cx, maxX = cx, minY = cy, maxY = cy, cells = 0;
|
|
290
|
+
const stack = [start];
|
|
291
|
+
seen[start] = 1;
|
|
292
|
+
while (stack.length) {
|
|
293
|
+
const cell = stack.pop();
|
|
294
|
+
const x = cell % gw, y = Math.floor(cell / gw);
|
|
295
|
+
cells++;
|
|
296
|
+
if (x < minX) minX = x; if (x > maxX) maxX = x;
|
|
297
|
+
if (y < minY) minY = y; if (y > maxY) maxY = y;
|
|
298
|
+
for (const [dx, dy] of [[1,0],[-1,0],[0,1],[0,-1]]) {
|
|
299
|
+
const nx = x + dx, ny = y + dy;
|
|
300
|
+
if (nx < 0 || ny < 0 || nx >= gw || ny >= gh) continue;
|
|
301
|
+
const n = ny * gw + nx;
|
|
302
|
+
if (marked[n] && !seen[n]) { seen[n] = 1; stack.push(n); }
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
regions.push({
|
|
306
|
+
x: minX * CELL, y: minY * CELL,
|
|
307
|
+
w: Math.min(w, (maxX + 1) * CELL) - minX * CELL,
|
|
308
|
+
h: Math.min(h, (maxY + 1) * CELL) - minY * CELL,
|
|
309
|
+
cells,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
regions.sort((p, q) => q.cells - p.cells);
|
|
314
|
+
const sizePenalty = (a.width !== b.width || a.height !== b.height) ? 1 : 0;
|
|
315
|
+
return { pct: Math.min(100, (differ / total) * 100 + sizePenalty), regions: regions.slice(0, 5), w, h };
|
|
316
|
+
})()`;
|
|
317
|
+
const { result } = await connection.send(
|
|
318
|
+
"Runtime.evaluate",
|
|
319
|
+
{ expression, awaitPromise: true, returnByValue: true },
|
|
320
|
+
sessionId
|
|
321
|
+
);
|
|
322
|
+
const value = result?.value;
|
|
323
|
+
if (!value || typeof value.pct !== "number") return null;
|
|
324
|
+
if (!url || value.regions.length === 0 || value.pct < 0.5) return { pct: value.pct, sentences: [] };
|
|
325
|
+
let sentences = [];
|
|
326
|
+
try {
|
|
327
|
+
const page = await connection.send("Target.createTarget", { url: "about:blank" });
|
|
328
|
+
const attach = await connection.send("Target.attachToTarget", { targetId: page.targetId, flatten: true });
|
|
329
|
+
const pageSession = attach.sessionId;
|
|
330
|
+
await connection.send("Runtime.enable", {}, pageSession);
|
|
331
|
+
await connection.send("Page.enable", {}, pageSession);
|
|
332
|
+
await connection.send(
|
|
333
|
+
"Emulation.setDeviceMetricsOverride",
|
|
334
|
+
{ width: value.w, height: value.h, deviceScaleFactor: 1, mobile: false },
|
|
335
|
+
pageSession
|
|
336
|
+
);
|
|
337
|
+
await connection.send("Page.navigate", { url }, pageSession);
|
|
338
|
+
await new Promise((resolve) => setTimeout(resolve, 1200));
|
|
339
|
+
const hitTest = `((regions) => regions.map((r) => {
|
|
340
|
+
const el = document.elementsFromPoint(r.x + r.w / 2, r.y + r.h / 2)
|
|
341
|
+
.find((e) => e !== document.documentElement && e !== document.body);
|
|
342
|
+
const where = r.w + '\xD7' + r.h + ' region @ (' + r.x + ',' + r.y + ')';
|
|
343
|
+
if (!el) return where + ' changed';
|
|
344
|
+
let label = el.tagName.toLowerCase();
|
|
345
|
+
if (el.id) label += '#' + el.id;
|
|
346
|
+
else if (el.classList[0]) label += '.' + el.classList[0];
|
|
347
|
+
const key = Object.keys(el).find((k) => k.startsWith('__reactFiber$'));
|
|
348
|
+
let chain = '';
|
|
349
|
+
if (key) {
|
|
350
|
+
let fiber = el[key];
|
|
351
|
+
const names = [];
|
|
352
|
+
let hops = 0;
|
|
353
|
+
while (fiber && hops < 50 && names.length < 2) {
|
|
354
|
+
const t = fiber.type;
|
|
355
|
+
const n = typeof t === 'function' ? (t.displayName || t.name || '') : '';
|
|
356
|
+
if (n && !names.includes(n)) names.push(n);
|
|
357
|
+
fiber = fiber.return; hops++;
|
|
358
|
+
}
|
|
359
|
+
chain = names.join(' < ');
|
|
360
|
+
}
|
|
361
|
+
return '<' + label + '>' + (chain ? ' (' + chain + ')' : '') + ': ' + where + ' changed';
|
|
362
|
+
}))(${JSON.stringify(value.regions)})`;
|
|
363
|
+
const hit = await connection.send(
|
|
364
|
+
"Runtime.evaluate",
|
|
365
|
+
{ expression: hitTest, returnByValue: true },
|
|
366
|
+
pageSession
|
|
367
|
+
);
|
|
368
|
+
if (Array.isArray(hit.result?.value)) sentences = hit.result.value.map(String);
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
return { pct: value.pct, sentences };
|
|
372
|
+
} catch {
|
|
373
|
+
return null;
|
|
374
|
+
} finally {
|
|
375
|
+
connection?.close();
|
|
376
|
+
child.kill("SIGKILL");
|
|
377
|
+
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
240
380
|
async function pixelDiffPct(chromePath, pngA, pngB) {
|
|
241
381
|
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
242
382
|
let connection = null;
|
|
@@ -297,6 +437,42 @@ var SLOP_AUDIT = `(() => {
|
|
|
297
437
|
break;
|
|
298
438
|
}
|
|
299
439
|
}
|
|
440
|
+
// APCA (the contrast model that graduated in DevTools 149): body text
|
|
441
|
+
// under |Lc| 60 reads as fog even when it squeaks past WCAG AA.
|
|
442
|
+
const chan = (v) => Math.pow(v / 255, 2.4);
|
|
443
|
+
const lum = (rgb) => {
|
|
444
|
+
const m = rgb.match(/rgba?\\(([\\d.]+)[,\\s]+([\\d.]+)[,\\s]+([\\d.]+)/);
|
|
445
|
+
if (!m) return null;
|
|
446
|
+
return 0.2126729 * chan(+m[1]) + 0.7151522 * chan(+m[2]) + 0.0721750 * chan(+m[3]);
|
|
447
|
+
};
|
|
448
|
+
const apca = (txt, bg) => {
|
|
449
|
+
let yt = lum(txt), yb = lum(bg);
|
|
450
|
+
if (yt === null || yb === null) return null;
|
|
451
|
+
const clamp = (y) => (y < 0.022 ? y + Math.pow(0.022 - y, 1.414) : y);
|
|
452
|
+
yt = clamp(yt); yb = clamp(yb);
|
|
453
|
+
const c = yb > yt ? (Math.pow(yb, 0.56) - Math.pow(yt, 0.57)) * 1.14 : (Math.pow(yb, 0.65) - Math.pow(yt, 0.62)) * 1.14;
|
|
454
|
+
return Math.abs(c) < 0.1 ? 0 : Math.round((Math.abs(c) - 0.027) * 100);
|
|
455
|
+
};
|
|
456
|
+
let apcaFlagged = 0;
|
|
457
|
+
for (const p of document.querySelectorAll('p, li, td, span')) {
|
|
458
|
+
if (apcaFlagged >= 2) break;
|
|
459
|
+
const text = (p.textContent || '').trim();
|
|
460
|
+
if (text.length < 40) continue;
|
|
461
|
+
const cs = getComputedStyle(p);
|
|
462
|
+
if (parseFloat(cs.fontSize) >= 24) continue;
|
|
463
|
+
let bgEl = p, bg = 'rgba(0, 0, 0, 0)';
|
|
464
|
+
while (bgEl) {
|
|
465
|
+
const b = getComputedStyle(bgEl).backgroundColor;
|
|
466
|
+
if (b && !b.includes('0, 0, 0, 0')) { bg = b; break; }
|
|
467
|
+
bgEl = bgEl.parentElement;
|
|
468
|
+
}
|
|
469
|
+
if (bg.includes('0, 0, 0, 0')) bg = 'rgb(255, 255, 255)';
|
|
470
|
+
const lc = apca(cs.color, bg);
|
|
471
|
+
if (lc !== null && lc > 0 && lc < 60) {
|
|
472
|
+
out.push('low APCA contrast: Lc ' + lc + ' on <' + p.tagName.toLowerCase() + '> body text ("' + text.slice(0, 40) + '\u2026") \u2014 fog, not elegance');
|
|
473
|
+
apcaFlagged++;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
300
476
|
const vw = innerWidth, vh = innerHeight;
|
|
301
477
|
for (const el of document.querySelectorAll('*')) {
|
|
302
478
|
const r = el.getBoundingClientRect();
|
|
@@ -457,6 +633,40 @@ var WEBMCP_SHIM = `(() => {
|
|
|
457
633
|
wrap(document.modelContext || (document.modelContext = {}));
|
|
458
634
|
wrap(navigator.modelContext || (navigator.modelContext = {}));
|
|
459
635
|
})()`;
|
|
636
|
+
var LOAF_SHIM = `(() => {
|
|
637
|
+
window.__squintLoaf = [];
|
|
638
|
+
try {
|
|
639
|
+
new PerformanceObserver((list) => {
|
|
640
|
+
for (const e of list.getEntries()) {
|
|
641
|
+
if (e.duration < 50) continue;
|
|
642
|
+
const s = (e.scripts && e.scripts[0]) || {};
|
|
643
|
+
window.__squintLoaf.push({
|
|
644
|
+
duration: Math.round(e.duration),
|
|
645
|
+
fn: s.sourceFunctionName || '',
|
|
646
|
+
url: (s.sourceURL || s.name || '').split('/').pop() || '',
|
|
647
|
+
invoker: s.invoker || '',
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}).observe({ type: 'long-animation-frame', buffered: true });
|
|
651
|
+
} catch {}
|
|
652
|
+
})()`;
|
|
653
|
+
var SCRIPTED_SCROLL = `(async () => {
|
|
654
|
+
const half = Math.max(0, (document.body.scrollHeight - innerHeight) / 2);
|
|
655
|
+
window.scrollTo({ top: half, behavior: 'smooth' });
|
|
656
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
657
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
658
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
659
|
+
const seen = new Map();
|
|
660
|
+
for (const e of window.__squintLoaf || []) {
|
|
661
|
+
const key = e.fn + '@' + e.url + '@' + e.invoker;
|
|
662
|
+
const prev = seen.get(key);
|
|
663
|
+
if (!prev || e.duration > prev.duration) seen.set(key, e);
|
|
664
|
+
}
|
|
665
|
+
return [...seen.values()]
|
|
666
|
+
.sort((a, b) => b.duration - a.duration)
|
|
667
|
+
.slice(0, 6)
|
|
668
|
+
.map((e) => e.duration + 'ms frame \u2014 ' + (e.fn || e.invoker || 'script') + (e.url ? ' @ ' + e.url : ''));
|
|
669
|
+
})()`;
|
|
460
670
|
async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, audit = false, checks = []) {
|
|
461
671
|
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
462
672
|
const report = { consoleErrors: [], pageErrors: [], failedRequests: [] };
|
|
@@ -470,6 +680,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
470
680
|
let components = [];
|
|
471
681
|
const checkFailures = [];
|
|
472
682
|
let webmcp = [];
|
|
683
|
+
let jank = [];
|
|
473
684
|
const requests = /* @__PURE__ */ new Map();
|
|
474
685
|
let connection = null;
|
|
475
686
|
try {
|
|
@@ -509,6 +720,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
509
720
|
await connection.send("Network.enable", {}, sessionId);
|
|
510
721
|
await connection.send("Page.enable", {}, sessionId);
|
|
511
722
|
await connection.send("Page.addScriptToEvaluateOnNewDocument", { source: WEBMCP_SHIM }, sessionId).catch(() => null);
|
|
723
|
+
await connection.send("Page.addScriptToEvaluateOnNewDocument", { source: LOAF_SHIM }, sessionId).catch(() => null);
|
|
512
724
|
await connection.send("Page.navigate", { url }, sessionId);
|
|
513
725
|
const deadline = Date.now() + 12e3;
|
|
514
726
|
while (!loaded && Date.now() < deadline) {
|
|
@@ -524,6 +736,15 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
524
736
|
if (result?.value && typeof result.value === "object") perf = result.value;
|
|
525
737
|
} catch {
|
|
526
738
|
}
|
|
739
|
+
try {
|
|
740
|
+
const { result } = await connection.send(
|
|
741
|
+
"Runtime.evaluate",
|
|
742
|
+
{ expression: SCRIPTED_SCROLL, returnByValue: true, awaitPromise: true },
|
|
743
|
+
sessionId
|
|
744
|
+
);
|
|
745
|
+
if (Array.isArray(result?.value)) jank = result.value.map(String);
|
|
746
|
+
} catch {
|
|
747
|
+
}
|
|
527
748
|
try {
|
|
528
749
|
const { result } = await connection.send(
|
|
529
750
|
"Runtime.evaluate",
|
|
@@ -643,7 +864,10 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
643
864
|
{
|
|
644
865
|
width: viewport.width,
|
|
645
866
|
height: viewport.height,
|
|
646
|
-
|
|
867
|
+
// 2x for review shots: sub-pixel border/kerning slop is
|
|
868
|
+
// invisible at 1x. The pulse stays 1x — its diff math and
|
|
869
|
+
// hit-testing assume CSS pixels.
|
|
870
|
+
deviceScaleFactor: viewport.name === "pulse" ? 1 : 2,
|
|
647
871
|
mobile: viewport.width < 500
|
|
648
872
|
},
|
|
649
873
|
sessionId
|
|
@@ -659,12 +883,13 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
659
883
|
child.kill("SIGKILL");
|
|
660
884
|
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
661
885
|
}
|
|
662
|
-
return { report, shots, a11y, slop, perf, narration, phantoms, viewTransitions, components, checkFailures, webmcp };
|
|
886
|
+
return { report, shots, a11y, slop, perf, narration, phantoms, viewTransitions, components, checkFailures, webmcp, jank };
|
|
663
887
|
}
|
|
664
888
|
|
|
665
889
|
export {
|
|
666
890
|
hasWebSocket,
|
|
667
891
|
runFlow,
|
|
892
|
+
pixelDiffAttributed,
|
|
668
893
|
pixelDiffPct,
|
|
669
894
|
cdpCapture
|
|
670
895
|
};
|
|
@@ -148,6 +148,10 @@ var claude = {
|
|
|
148
148
|
binary: "claude",
|
|
149
149
|
install: "npm install -g @anthropic-ai/claude-code",
|
|
150
150
|
supportsResume: true,
|
|
151
|
+
// 2.1.211+: subagent output joins stream-json. The env-var form is a
|
|
152
|
+
// no-op on older CLIs, unlike the flag, so the loops see spawned work
|
|
153
|
+
// wherever it's supported.
|
|
154
|
+
env: { CLAUDE_CODE_FORWARD_SUBAGENT_TEXT: "1" },
|
|
151
155
|
buildArgs(opts) {
|
|
152
156
|
const permissionMode = opts.mode === "plan" ? "plan" : opts.mode === "yolo" ? "bypassPermissions" : "acceptEdits";
|
|
153
157
|
const args = [
|
|
@@ -23,16 +23,16 @@ import {
|
|
|
23
23
|
buildFixPrompt,
|
|
24
24
|
detectDevCommand,
|
|
25
25
|
screenshotVariants
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-2I3YHT6P.js";
|
|
27
27
|
import {
|
|
28
28
|
applyVariant,
|
|
29
29
|
cleanVariants,
|
|
30
30
|
listVariants,
|
|
31
31
|
runVariants
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-J7ITMK6X.js";
|
|
33
33
|
import {
|
|
34
34
|
runAgent
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-6NPUVPQD.js";
|
|
36
36
|
import {
|
|
37
37
|
composePrompt
|
|
38
38
|
} from "./chunk-WAJXATCO.js";
|
|
@@ -46,10 +46,10 @@ import {
|
|
|
46
46
|
buildReviewPrompt,
|
|
47
47
|
buildRuntimeFixPrompt,
|
|
48
48
|
captureViewports,
|
|
49
|
-
|
|
49
|
+
comparePulseAttributed,
|
|
50
50
|
probeRuntime,
|
|
51
51
|
runtimeSummary
|
|
52
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-CRDRWTON.js";
|
|
53
53
|
import {
|
|
54
54
|
clearState,
|
|
55
55
|
loadState,
|
|
@@ -57,10 +57,10 @@ import {
|
|
|
57
57
|
} from "./chunk-7CAGWFAQ.js";
|
|
58
58
|
import {
|
|
59
59
|
findChrome
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-PYC53J7D.js";
|
|
61
61
|
import {
|
|
62
62
|
getEngine
|
|
63
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-GV2HAALP.js";
|
|
64
64
|
|
|
65
65
|
// src/session/engine.ts
|
|
66
66
|
import fs2 from "fs";
|
|
@@ -267,7 +267,7 @@ ${question}`,
|
|
|
267
267
|
return;
|
|
268
268
|
}
|
|
269
269
|
await this.runTurn(
|
|
270
|
-
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp),
|
|
270
|
+
buildReviewPrompt(result.shots, void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp, result.jank),
|
|
271
271
|
`\u{1F441} polish round ${round}/${rounds}`
|
|
272
272
|
);
|
|
273
273
|
}
|
|
@@ -509,6 +509,17 @@ ${driftSummary(drift)}`);
|
|
|
509
509
|
}
|
|
510
510
|
} catch {
|
|
511
511
|
}
|
|
512
|
+
try {
|
|
513
|
+
const { scanEvasion, sentinelSummary } = await import("./sentinel-42NZRTTS.js");
|
|
514
|
+
const evasions = scanEvasion(this.execCwd(), checkpoint.snapshot.stashHash ?? "HEAD");
|
|
515
|
+
if (evasions.length > 0) {
|
|
516
|
+
this.push("error", `\u26A0 sentinel: ${evasions.length} gate-evasion pattern(s) this turn
|
|
517
|
+
${sentinelSummary(evasions)}
|
|
518
|
+
Review before trusting green \u2014 /undo rolls the turn back.`);
|
|
519
|
+
runHook(this.opts.cwd, "on-sentinel", { count: String(evasions.length), summary: sentinelSummary(evasions) });
|
|
520
|
+
}
|
|
521
|
+
} catch {
|
|
522
|
+
}
|
|
512
523
|
try {
|
|
513
524
|
const { rulePackSummary, scanRulePacks } = await import("./rulepacks-JOA675OJ.js");
|
|
514
525
|
const findings = scanRulePacks(this.execCwd(), checkpoint.snapshot.stashHash ?? "HEAD");
|
|
@@ -647,7 +658,7 @@ ${probe.checkFailures.slice(0, 5).join("\n")}`);
|
|
|
647
658
|
const captureResult = await this.capture();
|
|
648
659
|
if (captureResult) {
|
|
649
660
|
await this.runTurn(
|
|
650
|
-
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration, captureResult.phantoms, captureResult.viewTransitions, captureResult.components, captureResult.webmcp),
|
|
661
|
+
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop, captureResult.narration, captureResult.phantoms, captureResult.viewTransitions, captureResult.components, captureResult.webmcp, captureResult.jank),
|
|
651
662
|
"\u{1F441} auto-review rendered UI"
|
|
652
663
|
);
|
|
653
664
|
}
|
|
@@ -744,11 +755,13 @@ ${probe.checkFailures.slice(0, 5).join("\n")}`);
|
|
|
744
755
|
this.push("image", pulsePath);
|
|
745
756
|
return null;
|
|
746
757
|
}
|
|
747
|
-
const
|
|
748
|
-
if (
|
|
758
|
+
const diff = await comparePulseAttributed(previous, current, this.state.devUrl ?? void 0);
|
|
759
|
+
if (diff === null) return null;
|
|
760
|
+
const pct = diff.pct;
|
|
749
761
|
this.push(
|
|
750
762
|
"status",
|
|
751
|
-
pct < 0.5 ? "visual pulse: stable vs last turn" : `visual pulse: ${pct.toFixed(1)}% of the page changed vs last turn`
|
|
763
|
+
pct < 0.5 ? "visual pulse: stable vs last turn" : `visual pulse: ${pct.toFixed(1)}% of the page changed vs last turn${diff.sentences.length > 0 ? `
|
|
764
|
+
${diff.sentences.map((s) => ` ${s}`).join("\n")}` : ""}`
|
|
752
765
|
);
|
|
753
766
|
runHook(this.opts.cwd, "on-pulse-diff", { pct: pct.toFixed(1) });
|
|
754
767
|
if (pct >= 0.5) this.push("image", pulsePath);
|
|
@@ -846,7 +859,7 @@ They are objective defects, not style preferences.`
|
|
|
846
859
|
const arg = rest.join(" ").trim();
|
|
847
860
|
switch (name) {
|
|
848
861
|
case "engines": {
|
|
849
|
-
void import("./registry-
|
|
862
|
+
void import("./registry-JBP7J3JB.js").then(({ detectEngines }) => {
|
|
850
863
|
const lines = detectEngines().map(({ engine, path: binaryPath }) => {
|
|
851
864
|
const mark = binaryPath ? "\u2713" : "\u2717";
|
|
852
865
|
const traits = [engine.createParser ? "stream" : "text", engine.supportsResume ? "resume" : null].filter(Boolean).join(" \xB7 ");
|
|
@@ -935,7 +948,7 @@ They are objective defects, not style preferences.`
|
|
|
935
948
|
break;
|
|
936
949
|
}
|
|
937
950
|
case "context": {
|
|
938
|
-
import("./contextDoctor-
|
|
951
|
+
import("./contextDoctor-3Y36YURE.js").then(({ contextReport, formatContextReport }) => {
|
|
939
952
|
this.push("status", formatContextReport(contextReport(this.execCwd())));
|
|
940
953
|
}).catch((error) => {
|
|
941
954
|
this.push("status", `context report failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -1059,7 +1072,7 @@ They are objective defects, not style preferences.`
|
|
|
1059
1072
|
break;
|
|
1060
1073
|
}
|
|
1061
1074
|
void (async () => {
|
|
1062
|
-
const { loadFlows } = await import("./flows-
|
|
1075
|
+
const { loadFlows } = await import("./flows-MXJL45HW.js");
|
|
1063
1076
|
const flows = loadFlows(this.opts.cwd);
|
|
1064
1077
|
if (flows.length === 0) {
|
|
1065
1078
|
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");
|
|
@@ -1070,8 +1083,8 @@ They are objective defects, not style preferences.`
|
|
|
1070
1083
|
this.push("error", "no Chrome/Chromium found for flows");
|
|
1071
1084
|
return;
|
|
1072
1085
|
}
|
|
1073
|
-
const { runFlow } = await import("./cdp-
|
|
1074
|
-
const { previewDir } = await import("./preview-
|
|
1086
|
+
const { runFlow } = await import("./cdp-WSWOSDEX.js");
|
|
1087
|
+
const { previewDir } = await import("./preview-CDEMPHWV.js");
|
|
1075
1088
|
this.push("status", `replaying ${flows.length} flow(s)\u2026`);
|
|
1076
1089
|
this.notify({ running: true, runStartedAt: Date.now() });
|
|
1077
1090
|
const failures = [];
|
|
@@ -1209,7 +1222,7 @@ They are objective defects, not style preferences.`
|
|
|
1209
1222
|
const result = await this.capture();
|
|
1210
1223
|
if (result) {
|
|
1211
1224
|
await this.runTurn(
|
|
1212
|
-
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp),
|
|
1225
|
+
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop, result.narration, result.phantoms, result.viewTransitions, result.components, result.webmcp, result.jank),
|
|
1213
1226
|
`\u{1F441} review rendered UI${arg ? ` \xB7 ${arg}` : ""}`
|
|
1214
1227
|
);
|
|
1215
1228
|
}
|
package/dist/cli.js
CHANGED
|
@@ -3,33 +3,33 @@ import {
|
|
|
3
3
|
connectDaemon,
|
|
4
4
|
socketPath,
|
|
5
5
|
startDaemon
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YK5DEB7T.js";
|
|
7
7
|
import {
|
|
8
8
|
App
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-EB4OSUB7.js";
|
|
10
10
|
import "./chunk-43NQNIJY.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-WASLLXEZ.js";
|
|
12
12
|
import "./chunk-ARDV4XH6.js";
|
|
13
13
|
import "./chunk-AUJJGMZG.js";
|
|
14
14
|
import "./chunk-YHRAOBI2.js";
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-2I3YHT6P.js";
|
|
16
|
+
import "./chunk-J7ITMK6X.js";
|
|
17
17
|
import {
|
|
18
18
|
runAgent
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-6NPUVPQD.js";
|
|
20
20
|
import "./chunk-K5QJMSJH.js";
|
|
21
21
|
import {
|
|
22
22
|
composePrompt
|
|
23
23
|
} from "./chunk-WAJXATCO.js";
|
|
24
24
|
import "./chunk-62JNF5M2.js";
|
|
25
|
-
import "./chunk-
|
|
25
|
+
import "./chunk-CRDRWTON.js";
|
|
26
26
|
import "./chunk-7CAGWFAQ.js";
|
|
27
|
-
import "./chunk-
|
|
28
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-DCFNX2CQ.js";
|
|
28
|
+
import "./chunk-PYC53J7D.js";
|
|
29
29
|
import {
|
|
30
30
|
detectEngines,
|
|
31
31
|
getEngine
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-GV2HAALP.js";
|
|
33
33
|
|
|
34
34
|
// src/cli.tsx
|
|
35
35
|
import { Command } from "commander";
|
|
@@ -185,15 +185,19 @@ function registerDaemon(program2) {
|
|
|
185
185
|
process.on("SIGINT", stop);
|
|
186
186
|
process.on("SIGTERM", stop);
|
|
187
187
|
});
|
|
188
|
+
program2.command("mcp").description("serve the gates as MCP tools over stdio (check, shot, flows, context)").action(async () => {
|
|
189
|
+
const { runMcpServer } = await import("./server-ENT2OFNL.js");
|
|
190
|
+
runMcpServer(process.cwd());
|
|
191
|
+
});
|
|
188
192
|
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) => {
|
|
189
193
|
const cwd = process.cwd();
|
|
190
194
|
if (!opts.plain) {
|
|
191
195
|
try {
|
|
192
|
-
const { RemoteSession } = await import("./remote-
|
|
196
|
+
const { RemoteSession } = await import("./remote-C4BACCKX.js");
|
|
193
197
|
const remote = await RemoteSession.connect(cwd);
|
|
194
198
|
const config = loadConfig(defaultPaths(cwd));
|
|
195
199
|
const { render: render2 } = await import("ink");
|
|
196
|
-
const { App: App2 } = await import("./App-
|
|
200
|
+
const { App: App2 } = await import("./App-MC2XEM7K.js");
|
|
197
201
|
const React = await import("react");
|
|
198
202
|
render2(
|
|
199
203
|
React.createElement(App2, {
|
|
@@ -287,7 +291,7 @@ function registerEnv(program2) {
|
|
|
287
291
|
console.log(`${status} ${engine.name}${binaryPath ? "" : pc3.dim(` \u2014 install: ${engine.install}`)}`);
|
|
288
292
|
}
|
|
289
293
|
if (options.probe) {
|
|
290
|
-
const { runAgent: runAgent2 } = await import("./run-
|
|
294
|
+
const { runAgent: runAgent2 } = await import("./run-GDDIYEBP.js");
|
|
291
295
|
console.log(pc3.dim("\nprobing engines with a one-word prompt (verifies auth end to end)\u2026"));
|
|
292
296
|
for (const { engine, path: binaryPath } of detected) {
|
|
293
297
|
if (!binaryPath) continue;
|
|
@@ -309,8 +313,8 @@ function registerEnv(program2) {
|
|
|
309
313
|
);
|
|
310
314
|
}
|
|
311
315
|
}
|
|
312
|
-
const { findChrome } = await import("./chrome-
|
|
313
|
-
const { hasWebSocket } = await import("./cdp-
|
|
316
|
+
const { findChrome } = await import("./chrome-EEXJCG6W.js");
|
|
317
|
+
const { hasWebSocket } = await import("./cdp-WSWOSDEX.js");
|
|
314
318
|
const chrome = findChrome();
|
|
315
319
|
console.log(
|
|
316
320
|
chrome ? `${pc3.green("\u2713")} Chrome ${pc3.dim(chrome)}` : `${pc3.yellow("\u25CB")} Chrome ${pc3.dim("\u2014 screenshots and runtime probing disabled")}`
|
|
@@ -451,7 +455,7 @@ function registerProject(program2) {
|
|
|
451
455
|
process.exitCode = 1;
|
|
452
456
|
return;
|
|
453
457
|
}
|
|
454
|
-
const { runVariants, cleanVariants } = await import("./variants-
|
|
458
|
+
const { runVariants, cleanVariants } = await import("./variants-VURXVIQO.js");
|
|
455
459
|
const config = loadConfig(defaultPaths(cwd));
|
|
456
460
|
const engineId = resolveEngineId(config, options.engine);
|
|
457
461
|
const engine = getEngine(engineId);
|
|
@@ -469,7 +473,7 @@ function registerProject(program2) {
|
|
|
469
473
|
);
|
|
470
474
|
const succeeded = runs.filter((r) => r.result.ok);
|
|
471
475
|
if (options.shots && succeeded.length > 0) {
|
|
472
|
-
const { screenshotVariants } = await import("./shots-
|
|
476
|
+
const { screenshotVariants } = await import("./shots-6DRTXFDI.js");
|
|
473
477
|
console.log(pc4.dim("capturing screenshots\u2026"));
|
|
474
478
|
const shots = await screenshotVariants(cwd, succeeded.map((r) => r.variant));
|
|
475
479
|
for (const shot of shots) {
|
|
@@ -484,7 +488,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
484
488
|
}
|
|
485
489
|
);
|
|
486
490
|
variantsCommand.command("list").description("List generated variants").action(async () => {
|
|
487
|
-
const { listVariants } = await import("./variants-
|
|
491
|
+
const { listVariants } = await import("./variants-VURXVIQO.js");
|
|
488
492
|
const ids = listVariants(process.cwd());
|
|
489
493
|
if (ids.length === 0) {
|
|
490
494
|
console.log(pc4.dim('no variants \u2014 squint variants gen <n> "<ask>"'));
|
|
@@ -493,7 +497,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
493
497
|
for (const id of ids) console.log(id);
|
|
494
498
|
});
|
|
495
499
|
variantsCommand.command("apply").description("Apply one variant\u2019s changes to the main tree and discard the rest").argument("<id>", "family id of the winning variant").action(async (id) => {
|
|
496
|
-
const { applyVariant, cleanVariants } = await import("./variants-
|
|
500
|
+
const { applyVariant, cleanVariants } = await import("./variants-VURXVIQO.js");
|
|
497
501
|
const cwd = process.cwd();
|
|
498
502
|
const result = applyVariant(cwd, id);
|
|
499
503
|
if (!result.ok) {
|
|
@@ -505,7 +509,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
505
509
|
console.log(pc4.green(`\u2713 applied ${id} to the working tree`) + pc4.dim(" \u2014 review with git diff"));
|
|
506
510
|
});
|
|
507
511
|
variantsCommand.command("clean").description("Discard all variants").action(async () => {
|
|
508
|
-
const { cleanVariants } = await import("./variants-
|
|
512
|
+
const { cleanVariants } = await import("./variants-VURXVIQO.js");
|
|
509
513
|
const count = cleanVariants(process.cwd());
|
|
510
514
|
console.log(pc4.dim(`removed ${count} variant(s)`));
|
|
511
515
|
});
|
|
@@ -532,8 +536,81 @@ function registerQuality(program2) {
|
|
|
532
536
|
});
|
|
533
537
|
if (results.some((r) => !r.ok)) process.exitCode = 1;
|
|
534
538
|
});
|
|
539
|
+
program2.command("ci").description("One-shot verification for pipelines: gates (+ audits and flows with --url), JSON report, non-zero exit on failure").option("--url <url>", "also audit a running app URL (runtime, a11y, phantoms, jank) and replay flows").option("--json <path>", "write the machine-readable report here").action(async (opts) => {
|
|
540
|
+
const cwd = process.cwd();
|
|
541
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
542
|
+
const report = { startedAt, cwd };
|
|
543
|
+
let failed = false;
|
|
544
|
+
const { detectGates, runGates } = await import("./gates-ZC67NMW3.js");
|
|
545
|
+
const gates = detectGates(cwd);
|
|
546
|
+
const gateResults = await runGates(cwd, gates, (result) => {
|
|
547
|
+
console.log(`${result.ok ? pc5.green("\u2713") : pc5.red("\u2717")} ${result.gate.id} ${pc5.dim(`${(result.durationMs / 1e3).toFixed(1)}s`)}`);
|
|
548
|
+
if (!result.ok) console.log(pc5.dim(result.outputTail.split("\n").slice(-12).join("\n")));
|
|
549
|
+
});
|
|
550
|
+
report.gates = gateResults.map((r) => ({ id: r.gate.id, ok: r.ok, durationMs: r.durationMs, outputTail: r.ok ? void 0 : r.outputTail }));
|
|
551
|
+
if (gateResults.some((r) => !r.ok)) failed = true;
|
|
552
|
+
if (opts.url) {
|
|
553
|
+
const { captureViewports, runtimeSummary, previewDir } = await import("./preview-CDEMPHWV.js");
|
|
554
|
+
const capture = await captureViewports(cwd, opts.url);
|
|
555
|
+
if (!capture) {
|
|
556
|
+
console.log(pc5.red("\u2717 audit: capture failed (Chrome missing or URL unreachable)"));
|
|
557
|
+
report.audit = { ok: false };
|
|
558
|
+
failed = true;
|
|
559
|
+
} else {
|
|
560
|
+
const runtime = capture.runtime ? runtimeSummary(capture.runtime) : null;
|
|
561
|
+
const hard = {
|
|
562
|
+
runtime,
|
|
563
|
+
phantoms: capture.phantoms ?? [],
|
|
564
|
+
viewTransitions: (capture.viewTransitions ?? []).filter((v) => v.startsWith("duplicate")),
|
|
565
|
+
checkFailures: capture.checkFailures ?? []
|
|
566
|
+
};
|
|
567
|
+
const advisories = {
|
|
568
|
+
a11y: capture.a11y ?? [],
|
|
569
|
+
slop: capture.slop ?? [],
|
|
570
|
+
jank: capture.jank ?? []
|
|
571
|
+
};
|
|
572
|
+
const hardCount = (runtime ? 1 : 0) + hard.phantoms.length + hard.viewTransitions.length + hard.checkFailures.length;
|
|
573
|
+
report.audit = { ok: hardCount === 0, hard, advisories, shots: capture.shots.map((s) => s.path) };
|
|
574
|
+
if (hardCount > 0) {
|
|
575
|
+
failed = true;
|
|
576
|
+
console.log(pc5.red(`\u2717 audit: ${hardCount} hard finding(s)`));
|
|
577
|
+
for (const [k, v] of Object.entries(hard)) {
|
|
578
|
+
if (Array.isArray(v) && v.length > 0) console.log(pc5.dim(` ${k}: ${v.join(" \xB7 ")}`));
|
|
579
|
+
else if (typeof v === "string" && v) console.log(pc5.dim(` ${k}: ${v}`));
|
|
580
|
+
}
|
|
581
|
+
} else {
|
|
582
|
+
console.log(`${pc5.green("\u2713")} audit ${pc5.dim(`(${advisories.a11y.length} a11y, ${advisories.slop.length} slop, ${advisories.jank.length} jank advisories)`)}`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const { loadFlows } = await import("./flows-MXJL45HW.js");
|
|
586
|
+
const flows = loadFlows(cwd);
|
|
587
|
+
if (flows.length > 0) {
|
|
588
|
+
const { findChrome } = await import("./chrome-EEXJCG6W.js");
|
|
589
|
+
const chrome = findChrome();
|
|
590
|
+
const flowReport = [];
|
|
591
|
+
if (chrome) {
|
|
592
|
+
const { runFlow } = await import("./cdp-WSWOSDEX.js");
|
|
593
|
+
for (const flow of flows) {
|
|
594
|
+
const result = await runFlow(chrome, opts.url, flow, previewDir(cwd));
|
|
595
|
+
flowReport.push({ name: flow.name, ok: result.ok, detail: result.ok ? void 0 : `step ${result.failedStep}: ${result.detail}` });
|
|
596
|
+
console.log(result.ok ? `${pc5.green("\u2713")} flow ${flow.name}` : pc5.red(`\u2717 flow ${flow.name} \u2014 step ${result.failedStep}: ${result.detail}`));
|
|
597
|
+
if (!result.ok) failed = true;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
report.flows = flowReport;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
report.ok = !failed;
|
|
604
|
+
report.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
605
|
+
if (opts.json) {
|
|
606
|
+
const fs2 = await import("fs");
|
|
607
|
+
fs2.writeFileSync(opts.json, JSON.stringify(report, null, 2));
|
|
608
|
+
console.log(pc5.dim(`report \u2192 ${opts.json}`));
|
|
609
|
+
}
|
|
610
|
+
if (failed) process.exitCode = 1;
|
|
611
|
+
});
|
|
535
612
|
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) => {
|
|
536
|
-
const { captureViewports } = await import("./preview-
|
|
613
|
+
const { captureViewports } = await import("./preview-CDEMPHWV.js");
|
|
537
614
|
const result = await captureViewports(process.cwd(), url);
|
|
538
615
|
if (!result) {
|
|
539
616
|
console.error(pc5.red("\u2717 no Chrome/Chromium found"));
|
|
@@ -720,7 +797,7 @@ function registerTui(program2) {
|
|
|
720
797
|
}
|
|
721
798
|
|
|
722
799
|
// src/cli.tsx
|
|
723
|
-
var VERSION = true ? "0.
|
|
800
|
+
var VERSION = true ? "0.5.0" : "0.0.0-dev";
|
|
724
801
|
var program = new Command();
|
|
725
802
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
726
803
|
registerRun(program);
|
|
@@ -5,22 +5,24 @@ import {
|
|
|
5
5
|
buildRuntimeFixPrompt,
|
|
6
6
|
captureViewports,
|
|
7
7
|
comparePulse,
|
|
8
|
+
comparePulseAttributed,
|
|
8
9
|
loadRoutes,
|
|
9
10
|
previewDir,
|
|
10
11
|
probeRuntime,
|
|
11
12
|
routeShotName,
|
|
12
13
|
runtimeSummary
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-CRDRWTON.js";
|
|
14
15
|
import "./chunk-7CAGWFAQ.js";
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-DCFNX2CQ.js";
|
|
17
|
+
import "./chunk-PYC53J7D.js";
|
|
18
|
+
import "./chunk-GV2HAALP.js";
|
|
18
19
|
export {
|
|
19
20
|
VIEWPORTS,
|
|
20
21
|
buildReviewPrompt,
|
|
21
22
|
buildRuntimeFixPrompt,
|
|
22
23
|
captureViewports,
|
|
23
24
|
comparePulse,
|
|
25
|
+
comparePulseAttributed,
|
|
24
26
|
loadRoutes,
|
|
25
27
|
previewDir,
|
|
26
28
|
probeRuntime,
|
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
import {
|
|
3
3
|
connectDaemon,
|
|
4
4
|
socketPath
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-YK5DEB7T.js";
|
|
6
|
+
import "./chunk-WASLLXEZ.js";
|
|
7
7
|
import "./chunk-ARDV4XH6.js";
|
|
8
8
|
import "./chunk-AUJJGMZG.js";
|
|
9
9
|
import "./chunk-YHRAOBI2.js";
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
10
|
+
import "./chunk-2I3YHT6P.js";
|
|
11
|
+
import "./chunk-J7ITMK6X.js";
|
|
12
|
+
import "./chunk-6NPUVPQD.js";
|
|
13
13
|
import "./chunk-K5QJMSJH.js";
|
|
14
14
|
import "./chunk-WAJXATCO.js";
|
|
15
15
|
import "./chunk-62JNF5M2.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-CRDRWTON.js";
|
|
17
17
|
import "./chunk-7CAGWFAQ.js";
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
20
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-DCFNX2CQ.js";
|
|
19
|
+
import "./chunk-PYC53J7D.js";
|
|
20
|
+
import "./chunk-GV2HAALP.js";
|
|
21
21
|
|
|
22
22
|
// src/daemon/remote.ts
|
|
23
23
|
var noteId = 0;
|
|
Binary file
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
contextReport,
|
|
4
|
+
formatContextReport
|
|
5
|
+
} from "./chunk-BKOGVFCO.js";
|
|
6
|
+
import "./chunk-ARDV4XH6.js";
|
|
7
|
+
import "./chunk-WAJXATCO.js";
|
|
8
|
+
import {
|
|
9
|
+
detectGates,
|
|
10
|
+
runGates
|
|
11
|
+
} from "./chunk-62JNF5M2.js";
|
|
12
|
+
import {
|
|
13
|
+
loadFlows
|
|
14
|
+
} from "./chunk-4XSHHGA6.js";
|
|
15
|
+
import {
|
|
16
|
+
captureViewports,
|
|
17
|
+
previewDir,
|
|
18
|
+
runtimeSummary
|
|
19
|
+
} from "./chunk-CRDRWTON.js";
|
|
20
|
+
import "./chunk-7CAGWFAQ.js";
|
|
21
|
+
import "./chunk-DCFNX2CQ.js";
|
|
22
|
+
import {
|
|
23
|
+
findChrome
|
|
24
|
+
} from "./chunk-PYC53J7D.js";
|
|
25
|
+
import "./chunk-GV2HAALP.js";
|
|
26
|
+
|
|
27
|
+
// src/mcp/server.ts
|
|
28
|
+
import readline from "readline";
|
|
29
|
+
var TOOLS = [
|
|
30
|
+
{
|
|
31
|
+
name: "squint_check",
|
|
32
|
+
description: "Run the repo quality gates (typecheck, lint, format, test, build as detected). Returns each gate with pass/fail and failure output.",
|
|
33
|
+
inputSchema: { type: "object", properties: {} },
|
|
34
|
+
async run(_args, cwd) {
|
|
35
|
+
const gates = detectGates(cwd);
|
|
36
|
+
if (gates.length === 0) return "no gates detected (no package.json scripts or known tools)";
|
|
37
|
+
const results = await runGates(cwd, gates);
|
|
38
|
+
return results.map((r) => `${r.ok ? "\u2713" : "\u2717"} ${r.gate.display}${r.ok ? "" : `
|
|
39
|
+
${r.outputTail.slice(-1500)}`}`).join("\n");
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "squint_shot",
|
|
44
|
+
description: "Screenshot and audit a running app URL headlessly: runtime errors, accessibility sweep, anti-slop tells, phantom classes, view-transition breakage, jank attribution, screen-reader narration. Returns findings; screenshots land in .squint/preview/.",
|
|
45
|
+
inputSchema: { type: "object", properties: { url: { type: "string" } }, required: ["url"] },
|
|
46
|
+
async run(args, cwd) {
|
|
47
|
+
const url = String(args.url ?? "");
|
|
48
|
+
const result = await captureViewports(cwd, url);
|
|
49
|
+
if (!result) return "capture failed \u2014 is Chrome installed and the URL reachable?";
|
|
50
|
+
const sections = [`shots: ${result.shots.map((s) => s.path).join(", ")}`];
|
|
51
|
+
const runtime = result.runtime ? runtimeSummary(result.runtime) : null;
|
|
52
|
+
if (runtime) sections.push(`runtime: ${runtime}`);
|
|
53
|
+
for (const [label, list] of [
|
|
54
|
+
["a11y", result.a11y],
|
|
55
|
+
["slop", result.slop],
|
|
56
|
+
["phantom classes", result.phantoms],
|
|
57
|
+
["view transitions", result.viewTransitions],
|
|
58
|
+
["jank", result.jank],
|
|
59
|
+
["components", result.components]
|
|
60
|
+
]) {
|
|
61
|
+
if (list && list.length > 0) sections.push(`${label}:
|
|
62
|
+
${list.join("\n")}`);
|
|
63
|
+
}
|
|
64
|
+
return sections.join("\n\n");
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "squint_flows",
|
|
69
|
+
description: "Replay the repo's declared user journeys (.squint/flows/*.flow) headlessly against a URL. Returns pass/fail per flow with the failing step.",
|
|
70
|
+
inputSchema: { type: "object", properties: { url: { type: "string" } }, required: ["url"] },
|
|
71
|
+
async run(args, cwd) {
|
|
72
|
+
const url = String(args.url ?? "");
|
|
73
|
+
const flows = loadFlows(cwd);
|
|
74
|
+
if (flows.length === 0) return "no flows declared \u2014 add .squint/flows/<name>.flow";
|
|
75
|
+
const chrome = findChrome();
|
|
76
|
+
if (!chrome) return "no Chrome/Chromium found";
|
|
77
|
+
const { runFlow } = await import("./cdp-WSWOSDEX.js");
|
|
78
|
+
const lines = [];
|
|
79
|
+
for (const flow of flows) {
|
|
80
|
+
const result = await runFlow(chrome, url, flow, previewDir(cwd));
|
|
81
|
+
lines.push(
|
|
82
|
+
result.ok ? `\u2713 ${flow.name} (${flow.steps.length} steps)` : `\u2717 ${flow.name} at step ${result.failedStep}: ${result.detail}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return lines.join("\n");
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "squint_context",
|
|
90
|
+
description: "Itemize what squint injects into engine asks \u2014 token cost per source with staleness warnings (stale locks, generic skill triggers, oversized always-on context).",
|
|
91
|
+
inputSchema: { type: "object", properties: {} },
|
|
92
|
+
async run(_args, cwd) {
|
|
93
|
+
return formatContextReport(contextReport(cwd));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
];
|
|
97
|
+
function runMcpServer(cwd, input = process.stdin, output = process.stdout) {
|
|
98
|
+
const write = (msg) => output.write(`${JSON.stringify(msg)}
|
|
99
|
+
`);
|
|
100
|
+
const rl = readline.createInterface({ input });
|
|
101
|
+
rl.on("line", (line) => {
|
|
102
|
+
if (!line.trim()) return;
|
|
103
|
+
let req;
|
|
104
|
+
try {
|
|
105
|
+
req = JSON.parse(line);
|
|
106
|
+
} catch {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
void handle(req, cwd, write);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async function handle(req, cwd, write) {
|
|
113
|
+
const reply = (result) => {
|
|
114
|
+
if (req.id !== void 0) write({ jsonrpc: "2.0", id: req.id, result });
|
|
115
|
+
};
|
|
116
|
+
const fail = (code, message) => {
|
|
117
|
+
if (req.id !== void 0) write({ jsonrpc: "2.0", id: req.id, error: { code, message } });
|
|
118
|
+
};
|
|
119
|
+
switch (req.method) {
|
|
120
|
+
case "initialize":
|
|
121
|
+
reply({
|
|
122
|
+
protocolVersion: "2025-06-18",
|
|
123
|
+
capabilities: { tools: {} },
|
|
124
|
+
serverInfo: { name: "squint", version: "0" }
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
127
|
+
case "notifications/initialized":
|
|
128
|
+
case "ping":
|
|
129
|
+
if (req.method === "ping") reply({});
|
|
130
|
+
return;
|
|
131
|
+
case "tools/list":
|
|
132
|
+
reply({ tools: TOOLS.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })) });
|
|
133
|
+
return;
|
|
134
|
+
case "tools/call": {
|
|
135
|
+
const name = String(req.params?.name ?? "");
|
|
136
|
+
const tool = TOOLS.find((t) => t.name === name);
|
|
137
|
+
if (!tool) return fail(-32602, `unknown tool: ${name}`);
|
|
138
|
+
try {
|
|
139
|
+
const text = await tool.run(req.params?.arguments ?? {}, cwd);
|
|
140
|
+
reply({ content: [{ type: "text", text }] });
|
|
141
|
+
} catch (error) {
|
|
142
|
+
reply({
|
|
143
|
+
content: [{ type: "text", text: `tool failed: ${error instanceof Error ? error.message : String(error)}` }],
|
|
144
|
+
isError: true
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
default:
|
|
150
|
+
fail(-32601, `method not found: ${req.method}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
runMcpServer
|
|
155
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
screenshotVariants
|
|
4
|
+
} from "./chunk-2I3YHT6P.js";
|
|
5
|
+
import "./chunk-J7ITMK6X.js";
|
|
6
|
+
import "./chunk-6NPUVPQD.js";
|
|
7
|
+
import "./chunk-K5QJMSJH.js";
|
|
8
|
+
import "./chunk-WAJXATCO.js";
|
|
9
|
+
import "./chunk-PYC53J7D.js";
|
|
10
|
+
import "./chunk-GV2HAALP.js";
|
|
11
|
+
export {
|
|
12
|
+
screenshotVariants
|
|
13
|
+
};
|
|
@@ -9,11 +9,11 @@ import {
|
|
|
9
9
|
runVariants,
|
|
10
10
|
variantPrompt,
|
|
11
11
|
variantsRoot
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-J7ITMK6X.js";
|
|
13
|
+
import "./chunk-6NPUVPQD.js";
|
|
14
14
|
import "./chunk-K5QJMSJH.js";
|
|
15
15
|
import "./chunk-WAJXATCO.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-GV2HAALP.js";
|
|
17
17
|
export {
|
|
18
18
|
MAX_VARIANTS,
|
|
19
19
|
applyVariant,
|
package/package.json
CHANGED
package/dist/shots-OKWOYF7F.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
screenshotVariants
|
|
4
|
-
} from "./chunk-QCHBDP46.js";
|
|
5
|
-
import "./chunk-YGSF2TSO.js";
|
|
6
|
-
import "./chunk-VH7OOFQP.js";
|
|
7
|
-
import "./chunk-K5QJMSJH.js";
|
|
8
|
-
import "./chunk-WAJXATCO.js";
|
|
9
|
-
import "./chunk-IMDRXXFU.js";
|
|
10
|
-
import "./chunk-KVYGPLWW.js";
|
|
11
|
-
export {
|
|
12
|
-
screenshotVariants
|
|
13
|
-
};
|