@aayambansal/squint 0.3.0 → 0.3.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.
@@ -3,7 +3,7 @@ import {
3
3
  cdpCapture,
4
4
  hasWebSocket,
5
5
  pixelDiffPct
6
- } from "./chunk-QEENJGCL.js";
6
+ } from "./chunk-DAETGO2M.js";
7
7
  export {
8
8
  cdpCapture,
9
9
  hasWebSocket,
@@ -5,6 +5,32 @@ import { spawn } from "child_process";
5
5
  import fs from "fs";
6
6
  import os from "os";
7
7
  import path from "path";
8
+ var PERF_PROBE = `(() => {
9
+ const out = {};
10
+ const buffered = (type) => {
11
+ try {
12
+ const po = new PerformanceObserver(() => {});
13
+ po.observe({ type, buffered: true });
14
+ const records = po.takeRecords();
15
+ po.disconnect();
16
+ return records;
17
+ } catch { return []; }
18
+ };
19
+ const lcp = buffered('largest-contentful-paint');
20
+ if (lcp.length > 0) out.lcpMs = Math.round(lcp[lcp.length - 1].startTime);
21
+ let cls = 0;
22
+ for (const e of buffered('layout-shift')) { if (!e.hadRecentInput) cls += e.value; }
23
+ out.cls = Math.round(cls * 1000) / 1000;
24
+ try {
25
+ const resources = performance.getEntriesByType('resource');
26
+ const nav = performance.getEntriesByType('navigation')[0];
27
+ let bytes = nav ? (nav.transferSize || 0) : 0;
28
+ for (const r of resources) bytes += r.transferSize || 0;
29
+ out.transferBytes = bytes;
30
+ out.requests = resources.length + (nav ? 1 : 0);
31
+ } catch {}
32
+ return out;
33
+ })()`;
8
34
  var A11Y_AUDIT = `(() => {
9
35
  const out = [];
10
36
  const name = (el) => (el.getAttribute('aria-label') || el.textContent || el.getAttribute('title') || '').trim();
@@ -251,6 +277,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
251
277
  const shots = [];
252
278
  let a11y = [];
253
279
  let slop = [];
280
+ let perf = {};
254
281
  const requests = /* @__PURE__ */ new Map();
255
282
  let connection = null;
256
283
  try {
@@ -295,6 +322,15 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
295
322
  await new Promise((resolve) => setTimeout(resolve, 100));
296
323
  }
297
324
  await new Promise((resolve) => setTimeout(resolve, settleMs));
325
+ try {
326
+ const { result } = await connection.send(
327
+ "Runtime.evaluate",
328
+ { expression: PERF_PROBE, returnByValue: true },
329
+ sessionId
330
+ );
331
+ if (result?.value && typeof result.value === "object") perf = result.value;
332
+ } catch {
333
+ }
298
334
  if (audit) {
299
335
  try {
300
336
  const { result } = await connection.send(
@@ -337,7 +373,7 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
337
373
  child.kill("SIGKILL");
338
374
  setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
339
375
  }
340
- return { report, shots, a11y, slop };
376
+ return { report, shots, a11y, slop, perf };
341
377
  }
342
378
 
343
379
  export {
@@ -19,6 +19,7 @@ var COMMANDS = [
19
19
  { name: "checkpoints", group: "explore", description: "list per-ask checkpoints" },
20
20
  { name: "restore", args: "<n>", group: "explore", description: "rewind files to before ask n" },
21
21
  { name: "theme", args: "[name]", group: "session", description: "switch the TUI theme", viewLevel: true },
22
+ { name: "btw", args: "<question>", group: "session", description: "read-only side question; the main thread is untouched" },
22
23
  { name: "copy", group: "session", description: "copy the last reply to the clipboard" },
23
24
  { name: "save", group: "session", description: "export the transcript to .squint/transcripts/" },
24
25
  { name: "resume", group: "session", description: "pick up the previous session for this repo" },
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  cdpCapture,
8
8
  hasWebSocket
9
- } from "./chunk-QEENJGCL.js";
9
+ } from "./chunk-DAETGO2M.js";
10
10
  import {
11
11
  ensureSquintIgnore
12
12
  } from "./chunk-O2S6PAJE.js";
@@ -118,14 +118,14 @@ async function probeRuntime(url, cwd) {
118
118
  if (!chrome || !hasWebSocket()) return null;
119
119
  try {
120
120
  const dir = cwd ? previewDir(cwd) : os.tmpdir();
121
- const { report, shots } = await cdpCapture(
121
+ const { report, shots, perf } = await cdpCapture(
122
122
  chrome,
123
123
  url,
124
124
  dir,
125
125
  cwd ? [{ name: "pulse", width: 1280, height: 800 }] : [],
126
126
  1500
127
127
  );
128
- return { report, pulsePath: shots[0]?.path };
128
+ return { report, pulsePath: shots[0]?.path, perf };
129
129
  } catch {
130
130
  return null;
131
131
  }
@@ -133,7 +133,7 @@ async function probeRuntime(url, cwd) {
133
133
  async function comparePulse(previous, current) {
134
134
  const chrome = findChrome();
135
135
  if (!chrome || !hasWebSocket()) return null;
136
- const { pixelDiffPct } = await import("./cdp-4IZHB3W5.js");
136
+ const { pixelDiffPct } = await import("./cdp-2XAU5MEA.js");
137
137
  return pixelDiffPct(chrome, previous, current);
138
138
  }
139
139
  function buildRuntimeFixPrompt(report) {
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  completeCommand
4
- } from "./chunk-CFWKEIJJ.js";
4
+ } from "./chunk-IH4L2KR6.js";
5
5
  import {
6
6
  applySandbox,
7
7
  discardSandbox,
@@ -42,7 +42,7 @@ import {
42
42
  comparePulse,
43
43
  probeRuntime,
44
44
  runtimeSummary
45
- } from "./chunk-RQFFAJFE.js";
45
+ } from "./chunk-OTCH66M7.js";
46
46
  import {
47
47
  runAgent
48
48
  } from "./chunk-VH7OOFQP.js";
@@ -53,7 +53,7 @@ import {
53
53
  detectEngines,
54
54
  getEngine
55
55
  } from "./chunk-KVYGPLWW.js";
56
- import "./chunk-QEENJGCL.js";
56
+ import "./chunk-DAETGO2M.js";
57
57
  import {
58
58
  enrich
59
59
  } from "./chunk-4LAU5TGK.js";
@@ -236,7 +236,7 @@ function registerEnv(program2) {
236
236
  }
237
237
  }
238
238
  const { findChrome: findChrome2 } = await import("./chrome-SBV3H77F.js");
239
- const { hasWebSocket } = await import("./cdp-4IZHB3W5.js");
239
+ const { hasWebSocket } = await import("./cdp-2XAU5MEA.js");
240
240
  const chrome = findChrome2();
241
241
  console.log(
242
242
  chrome ? `${pc2.green("\u2713")} Chrome ${pc2.dim(chrome)}` : `${pc2.yellow("\u25CB")} Chrome ${pc2.dim("\u2014 screenshots and runtime probing disabled")}`
@@ -459,7 +459,7 @@ function registerQuality(program2) {
459
459
  if (results.some((r) => !r.ok)) process.exitCode = 1;
460
460
  });
461
461
  program2.command("shot").description("Screenshot a running app at mobile/tablet/desktop viewports (+ .squint/routes)").argument("<url>", "URL of the running app (e.g. http://localhost:5173)").action(async (url) => {
462
- const { captureViewports: captureViewports2 } = await import("./preview-56UCV5A3.js");
462
+ const { captureViewports: captureViewports2 } = await import("./preview-PS7WJ2KO.js");
463
463
  const result = await captureViewports2(process.cwd(), url);
464
464
  if (!result) {
465
465
  console.error(pc4.red("\u2717 no Chrome/Chromium found"));
@@ -672,6 +672,7 @@ var Session = class {
672
672
  fixAttempts = 0;
673
673
  reviewTipShown = false;
674
674
  lastPulse = null;
675
+ lastPerf = null;
675
676
  autoReviewedThisAsk = false;
676
677
  startedAt = Date.now();
677
678
  subscribe(listener) {
@@ -733,6 +734,41 @@ var Session = class {
733
734
  this.dispatchFix([...this.state.problems]);
734
735
  return true;
735
736
  }
737
+ /**
738
+ * A side question: read-only, no session resume, so the main thread's
739
+ * context is untouched (Cursor's /btw). Costs count; loops don't run.
740
+ */
741
+ async btw(question) {
742
+ this.push("user", `\u{1F4AC} btw: ${question}`);
743
+ this.notify({ running: true, runStartedAt: Date.now() });
744
+ const engine = getEngine(this.state.engineId);
745
+ this.abort = new AbortController();
746
+ const result = await runAgent(
747
+ engine,
748
+ {
749
+ prompt: `Answer this question about the repository. Investigate as needed but make no changes:
750
+
751
+ ${question}`,
752
+ cwd: this.execCwd(),
753
+ model: this.state.model,
754
+ mode: "plan"
755
+ },
756
+ this.handleEvent,
757
+ this.abort.signal
758
+ );
759
+ this.abort = null;
760
+ this.commitLive();
761
+ this.flushToolCollapse();
762
+ if (result.ok) {
763
+ const cost = result.costUsd !== void 0 ? ` \xB7 $${result.costUsd.toFixed(2)}` : "";
764
+ this.push("status", `btw answered${cost}`);
765
+ this.notify({
766
+ totals: { costUsd: this.state.totals.costUsd + (result.costUsd ?? 0), turns: this.state.totals.turns }
767
+ });
768
+ }
769
+ this.notify({ running: false });
770
+ this.drainQueue();
771
+ }
736
772
  setMode(mode) {
737
773
  this.notify({ mode });
738
774
  const hint = mode === "plan" ? "read-only: the engine investigates and proposes, edits nothing" : mode === "yolo" ? "no approval friction \u2014 the engine can do anything" : "edits auto-approved inside the workspace";
@@ -943,6 +979,18 @@ var Session = class {
943
979
  const stat = checkpoint ? diffStatSince(this.opts.cwd, checkpoint.snapshot) : null;
944
980
  const work = stat ? ` \xB7 ${stat}` : this.turnEdits > 0 ? ` \xB7 ${this.turnEdits} edit${this.turnEdits === 1 ? "" : "s"}` : this.turnTools > 0 ? ` \xB7 ${this.turnTools} tool call${this.turnTools === 1 ? "" : "s"}` : "";
945
981
  this.push("status", `done${secs}${cost}${work}`);
982
+ if (checkpoint) {
983
+ try {
984
+ const { driftSummary, loadTokenIndex, scanDrift } = await import("./tokens-XYGRRAXC.js");
985
+ const index = loadTokenIndex(this.execCwd());
986
+ const drift = scanDrift(this.execCwd(), checkpoint.snapshot.stashHash ?? "HEAD", index);
987
+ if (drift.length > 0) {
988
+ this.push("status", `token drift: ${drift.length} hardcoded color(s)
989
+ ${driftSummary(drift)}`);
990
+ }
991
+ } catch {
992
+ }
993
+ }
946
994
  const before = this.state.totals.costUsd;
947
995
  this.notify({
948
996
  totals: {
@@ -1018,6 +1066,7 @@ ${errors.slice(-5).join("\n")}`);
1018
1066
  this.push("status", "/fix sends open problems to the engine \xB7 /problems lists them");
1019
1067
  } else if (probe) {
1020
1068
  this.clearProblems("runtime");
1069
+ this.perfPulse(probe.perf);
1021
1070
  const pct = await this.visualPulse(probe.pulsePath);
1022
1071
  if (this.opts.autoReview && pct !== null && pct >= 10 && !this.autoReviewedThisAsk) {
1023
1072
  this.autoReviewedThisAsk = true;
@@ -1083,6 +1132,25 @@ ${errors.slice(-5).join("\n")}`);
1083
1132
  this.push("error", `restore failed: ${result.detail ?? "unknown error"}`);
1084
1133
  }
1085
1134
  }
1135
+ /** Cross-turn load-performance deltas: the perf twin of the visual pulse. */
1136
+ perfPulse(perf) {
1137
+ if (!perf || perf.lcpMs === void 0 && perf.transferBytes === void 0) return;
1138
+ const previous = this.lastPerf;
1139
+ this.lastPerf = perf;
1140
+ const mb = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)}MB`;
1141
+ const parts = [];
1142
+ if (perf.lcpMs !== void 0) {
1143
+ const delta = previous?.lcpMs !== void 0 && Math.abs(perf.lcpMs - previous.lcpMs) >= 100 ? ` (${perf.lcpMs > previous.lcpMs ? "+" : "\u2212"}${Math.abs(perf.lcpMs - previous.lcpMs)}ms)` : "";
1144
+ parts.push(`LCP ${perf.lcpMs}ms${delta}`);
1145
+ }
1146
+ if (perf.cls !== void 0 && perf.cls > 0.05) parts.push(`CLS ${perf.cls}`);
1147
+ if (perf.transferBytes !== void 0) {
1148
+ const delta = previous?.transferBytes !== void 0 && Math.abs(perf.transferBytes - previous.transferBytes) > 100 * 1024 ? ` (${perf.transferBytes > previous.transferBytes ? "+" : "\u2212"}${mb(Math.abs(perf.transferBytes - previous.transferBytes))})` : "";
1149
+ parts.push(`${mb(perf.transferBytes)}${delta}`);
1150
+ }
1151
+ if (perf.requests !== void 0) parts.push(`${perf.requests} req`);
1152
+ if (parts.length > 0) this.push("status", `perf: ${parts.join(" \xB7 ")}`);
1153
+ }
1086
1154
  /**
1087
1155
  * Cross-turn visual drift check: compare this turn's pulse screenshot
1088
1156
  * with the previous one and report how much of the page changed.
@@ -1295,6 +1363,13 @@ They are objective defects, not style preferences.`
1295
1363
  })();
1296
1364
  break;
1297
1365
  }
1366
+ case "btw":
1367
+ if (!arg) {
1368
+ this.push("status", "usage: /btw <question> \u2014 read-only side question, main thread untouched");
1369
+ } else {
1370
+ void this.btw(arg);
1371
+ }
1372
+ break;
1298
1373
  case "copy": {
1299
1374
  const last = this.state.items.findLast((i) => i.role === "assistant");
1300
1375
  if (!last) {
@@ -1539,7 +1614,7 @@ ${sandboxFiles(this.opts.cwd).join("\n")}`);
1539
1614
  this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
1540
1615
  break;
1541
1616
  case "help": {
1542
- void import("./commands-QKDWUD4K.js").then(({ commandHelp }) => this.push("status", commandHelp()));
1617
+ void import("./commands-E7JIVJDP.js").then(({ commandHelp }) => this.push("status", commandHelp()));
1543
1618
  break;
1544
1619
  }
1545
1620
  case "quit":
@@ -2155,7 +2230,7 @@ function registerTui(program2) {
2155
2230
  }
2156
2231
 
2157
2232
  // src/cli.tsx
2158
- var VERSION = true ? "0.3.0" : "0.0.0-dev";
2233
+ var VERSION = true ? "0.3.1" : "0.0.0-dev";
2159
2234
  var program = new Command();
2160
2235
  program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
2161
2236
  registerRun(program);
@@ -3,7 +3,7 @@ import {
3
3
  COMMANDS,
4
4
  commandHelp,
5
5
  completeCommand
6
- } from "./chunk-CFWKEIJJ.js";
6
+ } from "./chunk-IH4L2KR6.js";
7
7
  export {
8
8
  COMMANDS,
9
9
  commandHelp,
@@ -10,10 +10,10 @@ import {
10
10
  probeRuntime,
11
11
  routeShotName,
12
12
  runtimeSummary
13
- } from "./chunk-RQFFAJFE.js";
13
+ } from "./chunk-OTCH66M7.js";
14
14
  import "./chunk-IMDRXXFU.js";
15
15
  import "./chunk-KVYGPLWW.js";
16
- import "./chunk-QEENJGCL.js";
16
+ import "./chunk-DAETGO2M.js";
17
17
  import "./chunk-O2S6PAJE.js";
18
18
  export {
19
19
  VIEWPORTS,
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/quality/tokens.ts
4
+ import { execFileSync } from "child_process";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ var HEX_RE = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b/g;
8
+ var VAR_DEF_RE = /(--[a-zA-Z0-9-_]+)\s*:\s*([^;]+);/g;
9
+ function parseHex(hex) {
10
+ const raw = hex.replace("#", "");
11
+ const full = raw.length === 3 ? raw.split("").map((c) => c + c).join("") : raw;
12
+ if (full.length !== 6) return null;
13
+ const n = Number.parseInt(full, 16);
14
+ if (Number.isNaN(n)) return null;
15
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
16
+ }
17
+ function loadTokenIndex(cwd) {
18
+ const colors = /* @__PURE__ */ new Map();
19
+ const cssFiles = [];
20
+ const walk = (dir, depth) => {
21
+ if (depth > 4 || cssFiles.length > 30) return;
22
+ let entries;
23
+ try {
24
+ entries = fs.readdirSync(dir, { withFileTypes: true });
25
+ } catch {
26
+ return;
27
+ }
28
+ for (const entry of entries) {
29
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
30
+ const full = path.join(dir, entry.name);
31
+ if (entry.isDirectory()) walk(full, depth + 1);
32
+ else if (entry.name.endsWith(".css")) cssFiles.push(full);
33
+ }
34
+ };
35
+ walk(path.join(cwd, "src"), 0);
36
+ walk(cwd, 1);
37
+ for (const file of cssFiles.slice(0, 30)) {
38
+ let text;
39
+ try {
40
+ text = fs.readFileSync(file, "utf8");
41
+ } catch {
42
+ continue;
43
+ }
44
+ for (const match of text.matchAll(VAR_DEF_RE)) {
45
+ const value = match[2].trim();
46
+ const hex = value.match(/#[0-9a-fA-F]{3,6}\b/)?.[0];
47
+ const rgbFn = /rgb\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/.exec(value);
48
+ let rgb = null;
49
+ if (hex) rgb = parseHex(hex);
50
+ else if (rgbFn) rgb = [Number(rgbFn[1]), Number(rgbFn[2]), Number(rgbFn[3])];
51
+ if (rgb) colors.set(match[1], { value, rgb });
52
+ }
53
+ }
54
+ return { colors };
55
+ }
56
+ var distance = (a, b) => Math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2);
57
+ function scanDrift(cwd, source, index) {
58
+ if (index.colors.size === 0) return [];
59
+ let diff;
60
+ try {
61
+ diff = execFileSync("git", ["diff", "-U0", source, "--", "*.css", "*.tsx", "*.jsx", "*.ts", "*.html"], {
62
+ cwd,
63
+ encoding: "utf8",
64
+ stdio: ["ignore", "pipe", "pipe"]
65
+ });
66
+ } catch {
67
+ return [];
68
+ }
69
+ const findings = [];
70
+ let currentFile = "";
71
+ for (const line of diff.split("\n")) {
72
+ if (line.startsWith("+++ b/")) {
73
+ currentFile = line.slice(6);
74
+ continue;
75
+ }
76
+ if (!line.startsWith("+") || line.startsWith("+++")) continue;
77
+ if (/--[a-zA-Z0-9-_]+\s*:/.test(line)) continue;
78
+ for (const match of line.matchAll(HEX_RE)) {
79
+ const rgb = parseHex(match[0]);
80
+ if (!rgb) continue;
81
+ let best = null;
82
+ for (const [token, entry] of index.colors) {
83
+ const d = distance(rgb, entry.rgb);
84
+ if (!best || d < best.d) best = { token, d };
85
+ }
86
+ if (best) {
87
+ findings.push({ file: currentFile, literal: match[0], token: best.token, distance: Math.round(best.d) });
88
+ }
89
+ if (findings.length >= 10) return findings;
90
+ }
91
+ }
92
+ return findings;
93
+ }
94
+ function driftSummary(findings) {
95
+ return findings.map(
96
+ (f) => `${f.file}: ${f.literal} \u2192 use var(${f.token})${f.distance === 0 ? " (exact match)" : ` (closest, \u0394${f.distance})`}`
97
+ ).join("\n");
98
+ }
99
+ export {
100
+ driftSummary,
101
+ loadTokenIndex,
102
+ parseHex,
103
+ scanDrift
104
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aayambansal/squint",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Lovable for your terminal — a frontend harness on top of Claude Code, Codex, and other coding agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",