@aayambansal/squint 0.2.3 → 0.2.5

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 CHANGED
@@ -225,6 +225,8 @@ All product behavior lives in the harness, so a new engine is ~80 lines.
225
225
 
226
226
  ## Docs
227
227
 
228
+ - [Engine setup guide](./docs/engines.md) — install + auth for all eight, and how to choose
229
+ - [Configuration](./docs/configuration.md) — every key, every `.squint/` file
228
230
  - [Architecture](./docs/design/2026-07-25-architecture.md)
229
231
  - [How Lovable works under the hood](./docs/research/lovable.md)
230
232
  - [Making agents produce excellent frontend work](./docs/research/frontend-quality.md)
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/tui/background.ts
4
+ function parseOsc11(reply) {
5
+ const match = /rgb:([0-9a-fA-F]{2,4})\/([0-9a-fA-F]{2,4})\/([0-9a-fA-F]{2,4})/.exec(reply);
6
+ if (!match) return "unknown";
7
+ const channel = (hex) => Number.parseInt(hex.slice(0, 2), 16);
8
+ const [r, g, b] = [channel(match[1]), channel(match[2]), channel(match[3])];
9
+ const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
10
+ return luminance > 140 ? "light" : "dark";
11
+ }
12
+ function detectBackground(timeoutMs = 300) {
13
+ return new Promise((resolve) => {
14
+ const { stdin, stdout } = process;
15
+ if (!stdin.isTTY || !stdout.isTTY) return resolve("unknown");
16
+ let buffer = "";
17
+ const finish = (result) => {
18
+ clearTimeout(timer);
19
+ stdin.off("data", onData);
20
+ resolve(result);
21
+ };
22
+ const onData = (chunk) => {
23
+ buffer += chunk.toString("utf8");
24
+ if (buffer.includes("rgb:")) {
25
+ const parsed = parseOsc11(buffer);
26
+ if (parsed !== "unknown") finish(parsed);
27
+ }
28
+ };
29
+ const timer = setTimeout(() => finish("unknown"), timeoutMs);
30
+ stdin.on("data", onData);
31
+ try {
32
+ stdout.write("\x1B]11;?\x07");
33
+ } catch {
34
+ finish("unknown");
35
+ }
36
+ });
37
+ }
38
+ export {
39
+ detectBackground,
40
+ parseOsc11
41
+ };
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  findChrome,
4
4
  screenshot
5
- } from "./chunk-P3V5CWH5.js";
6
- import "./chunk-2LRIKWBU.js";
5
+ } from "./chunk-CJXYSQNB.js";
6
+ import "./chunk-CH4GNJCZ.js";
7
7
  export {
8
8
  findChrome,
9
9
  screenshot
@@ -39,6 +39,20 @@ function detectGates(cwd) {
39
39
  } else if (hasEslintConfig) {
40
40
  gates.push({ id: "lint", command: "npx", args: ["eslint", ".", "--max-warnings", "0"], display: "eslint ." });
41
41
  }
42
+ const hasPrettier = [
43
+ ".prettierrc",
44
+ ".prettierrc.json",
45
+ ".prettierrc.js",
46
+ ".prettierrc.yaml",
47
+ ".prettierrc.yml",
48
+ "prettier.config.js",
49
+ "prettier.config.mjs"
50
+ ].some((file) => fs.existsSync(path.join(cwd, file)));
51
+ if (scripts.format && /--check|-c\b/.test(scripts.format)) {
52
+ gates.push({ id: "format", ...npmRun("format") });
53
+ } else if (hasPrettier) {
54
+ gates.push({ id: "format", command: "npx", args: ["prettier", "--check", "."], display: "prettier --check ." });
55
+ }
42
56
  const testScript = scripts.test;
43
57
  if (testScript && !/no test specified/i.test(testScript)) {
44
58
  gates.push({ id: "test", ...npmRun("test") });
@@ -37,6 +37,7 @@ var aider = {
37
37
  supportsResume: false,
38
38
  buildArgs(opts) {
39
39
  const args = ["--message", opts.prompt, "--yes-always", "--no-auto-commits"];
40
+ if (opts.mode === "plan") args.push("--dry-run");
40
41
  if (opts.model) args.push("--model", opts.model);
41
42
  return args;
42
43
  }
@@ -123,10 +124,9 @@ var amp = {
123
124
  install: "npm install -g @sourcegraph/amp",
124
125
  supportsResume: true,
125
126
  buildArgs(opts) {
126
- if (opts.sessionId) {
127
- return ["threads", "continue", "--execute", opts.prompt, "--stream-json"];
128
- }
129
- return ["-x", opts.prompt, "--stream-json"];
127
+ const args = opts.sessionId ? ["threads", "continue", "--execute", opts.prompt, "--stream-json"] : ["-x", opts.prompt, "--stream-json"];
128
+ if (opts.mode === "yolo") args.push("--dangerously-allow-all");
129
+ return args;
130
130
  },
131
131
  createParser: () => createClaudeStreamParser("amp")
132
132
  };
@@ -265,7 +265,8 @@ var copilot = {
265
265
  install: "npm install -g @github/copilot",
266
266
  supportsResume: false,
267
267
  buildArgs(opts) {
268
- const args = ["-p", opts.prompt, "-s", "--allow-all-tools"];
268
+ const args = ["-p", opts.prompt, "-s"];
269
+ if (opts.mode !== "plan") args.push("--allow-all-tools");
269
270
  if (opts.model) args.push("--model", opts.model);
270
271
  return args;
271
272
  }
@@ -314,6 +315,8 @@ var opencode = {
314
315
  supportsResume: true,
315
316
  buildArgs(opts) {
316
317
  const args = ["run", "--format", "json"];
318
+ if (opts.mode === "plan") args.push("--agent", "plan");
319
+ if (opts.mode === "yolo") args.push("--auto");
317
320
  if (opts.model) args.push("--model", opts.model);
318
321
  if (opts.sessionId) args.push("--session", opts.sessionId);
319
322
  args.push(opts.prompt);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  findBinary
4
- } from "./chunk-2LRIKWBU.js";
4
+ } from "./chunk-CH4GNJCZ.js";
5
5
 
6
6
  // src/preview/chrome.ts
7
7
  import { spawn } from "child_process";
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  findChrome,
8
8
  screenshot
9
- } from "./chunk-P3V5CWH5.js";
9
+ } from "./chunk-CJXYSQNB.js";
10
10
 
11
11
  // src/preview/preview.ts
12
12
  import fs2 from "fs";
@@ -62,6 +62,19 @@ var VIEWPORTS = [
62
62
  { name: "tablet", width: 768, height: 1024 },
63
63
  { name: "desktop", width: 1440, height: 900 }
64
64
  ];
65
+ function loadRoutes(cwd) {
66
+ let lines = [];
67
+ try {
68
+ lines = fs2.readFileSync(path2.join(cwd, ".squint", "routes"), "utf8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
69
+ } catch {
70
+ }
71
+ const routes = ["/", ...lines.filter((l) => l !== "/")];
72
+ return routes.slice(0, 6).map((r) => r.startsWith("/") ? r : `/${r}`);
73
+ }
74
+ function routeShotName(route) {
75
+ const clean = route.replace(/^\/+|\/+$/g, "").replace(/[^a-zA-Z0-9]+/g, "-");
76
+ return clean.length > 0 ? clean : "root";
77
+ }
65
78
  function previewDir(cwd) {
66
79
  const dir = path2.join(cwd, ".squint", "preview");
67
80
  fs2.mkdirSync(dir, { recursive: true });
@@ -72,10 +85,26 @@ async function captureViewports(cwd, url) {
72
85
  const chrome = findChrome();
73
86
  if (!chrome) return null;
74
87
  const dir = previewDir(cwd);
88
+ const routes = loadRoutes(cwd);
89
+ const base = url.replace(/\/+$/, "");
75
90
  if (hasWebSocket()) {
76
91
  try {
77
92
  const { report, shots: shots2, a11y } = await cdpCapture(chrome, url, dir, VIEWPORTS, 2500, true);
78
- return { shots: shots2, errors: [], runtime: report, a11y };
93
+ const errors2 = [];
94
+ for (const route of routes.slice(1)) {
95
+ try {
96
+ const routeCapture = await cdpCapture(chrome, `${base}${route}`, dir, [
97
+ { name: routeShotName(route), width: 1440, height: 900 }
98
+ ]);
99
+ shots2.push(...routeCapture.shots);
100
+ for (const err of routeCapture.report.pageErrors.slice(0, 3)) {
101
+ errors2.push(`${route}: ${err.split("\n")[0]}`);
102
+ }
103
+ } catch {
104
+ errors2.push(`${route}: capture failed`);
105
+ }
106
+ }
107
+ return { shots: shots2, errors: errors2, runtime: report, a11y };
79
108
  } catch {
80
109
  }
81
110
  }
@@ -178,6 +207,8 @@ export {
178
207
  saveState,
179
208
  clearState,
180
209
  VIEWPORTS,
210
+ loadRoutes,
211
+ routeShotName,
181
212
  previewDir,
182
213
  captureViewports,
183
214
  runtimeSummary,
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  variantsRoot
4
- } from "./chunk-LCS47EAG.js";
4
+ } from "./chunk-XLNTTZX4.js";
5
5
  import {
6
6
  findChrome,
7
7
  screenshot
8
- } from "./chunk-P3V5CWH5.js";
8
+ } from "./chunk-CJXYSQNB.js";
9
9
  import {
10
10
  lineSplitter
11
- } from "./chunk-2LRIKWBU.js";
11
+ } from "./chunk-CH4GNJCZ.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-2LRIKWBU.js";
6
+ } from "./chunk-CH4GNJCZ.js";
7
7
 
8
8
  // src/runner/run.ts
9
9
  import { spawn } from "child_process";
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-P3H4N2EN.js";
7
7
  import {
8
8
  runAgent
9
- } from "./chunk-OTG4ZB4R.js";
9
+ } from "./chunk-TI2R7QRL.js";
10
10
 
11
11
  // src/variants/variants.ts
12
12
  import { execFileSync } from "child_process";
package/dist/cli.js CHANGED
@@ -10,19 +10,19 @@ import {
10
10
  buildFixPrompt,
11
11
  detectDevCommand,
12
12
  screenshotVariants
13
- } from "./chunk-2IW2MMCY.js";
13
+ } from "./chunk-QNNT6FP4.js";
14
14
  import {
15
15
  applyVariant,
16
16
  cleanVariants,
17
17
  listVariants,
18
18
  runVariants
19
- } from "./chunk-LCS47EAG.js";
19
+ } from "./chunk-XLNTTZX4.js";
20
20
  import {
21
21
  composePrompt
22
22
  } from "./chunk-P3H4N2EN.js";
23
23
  import {
24
24
  runAgent
25
- } from "./chunk-OTG4ZB4R.js";
25
+ } from "./chunk-TI2R7QRL.js";
26
26
  import {
27
27
  completeCommand
28
28
  } from "./chunk-EECGXFRX.js";
@@ -31,7 +31,7 @@ import {
31
31
  detectFastGates,
32
32
  detectGates,
33
33
  runGates
34
- } from "./chunk-DLCRYRWQ.js";
34
+ } from "./chunk-62JNF5M2.js";
35
35
  import {
36
36
  buildReviewPrompt,
37
37
  buildRuntimeFixPrompt,
@@ -42,15 +42,15 @@ import {
42
42
  probeRuntime,
43
43
  runtimeSummary,
44
44
  saveState
45
- } from "./chunk-SU6YRRMF.js";
45
+ } from "./chunk-DKQ5KJD7.js";
46
46
  import "./chunk-OC6RU6XH.js";
47
47
  import {
48
48
  findChrome
49
- } from "./chunk-P3V5CWH5.js";
49
+ } from "./chunk-CJXYSQNB.js";
50
50
  import {
51
51
  detectEngines,
52
52
  getEngine
53
- } from "./chunk-2LRIKWBU.js";
53
+ } from "./chunk-CH4GNJCZ.js";
54
54
  import {
55
55
  enrich
56
56
  } from "./chunk-4LAU5TGK.js";
@@ -80,6 +80,8 @@ var ConfigSchema = z.object({
80
80
  autoCheck: z.boolean().optional(),
81
81
  /** Terminal bell when a turn finishes (default on). */
82
82
  bell: z.boolean().optional(),
83
+ /** Session budget in USD; crossing it warns (never blocks). */
84
+ budgetUsd: z.number().positive().optional(),
83
85
  /** TUI theme name (amber, ocean, moss, rose, mono). */
84
86
  theme: z.string().optional()
85
87
  });
@@ -129,13 +131,19 @@ function setConfigValue(file, key, value) {
129
131
  throw new Error(`"${key}" must be true or false`);
130
132
  }
131
133
  next = { ...current, [key]: value === "true" };
134
+ } else if (key === "budgetUsd") {
135
+ const budget = Number.parseFloat(value);
136
+ if (!Number.isFinite(budget) || budget <= 0) {
137
+ throw new Error('"budgetUsd" must be a positive number');
138
+ }
139
+ next = { ...current, budgetUsd: budget };
132
140
  } else if (key.startsWith("models.")) {
133
141
  const engineId = key.slice("models.".length);
134
142
  if (!engineId) throw new Error("Usage: squint config set models.<engineId> <model>");
135
143
  next = { ...current, models: { ...current.models, [engineId]: value } };
136
144
  } else {
137
145
  throw new Error(
138
- `Unknown config key "${key}". Supported: engine, theme, autoDev, autoFix, autoProbe, autoCheck, bell, models.<engineId>`
146
+ `Unknown config key "${key}". Supported: engine, theme, autoDev, autoFix, autoProbe, autoCheck, bell, budgetUsd, models.<engineId>`
139
147
  );
140
148
  }
141
149
  fs.mkdirSync(path.dirname(file), { recursive: true });
@@ -347,16 +355,32 @@ var Session = class {
347
355
  }
348
356
  turnEdits = 0;
349
357
  turnTools = 0;
358
+ toolStreak = 0;
359
+ collapsedTools = 0;
360
+ /**
361
+ * Long tool cascades collapse: the first three of a consecutive burst
362
+ * render, the rest fold into one "+N more" line pushed when the burst
363
+ * ends — append-only, so the Static transcript stays valid.
364
+ */
365
+ flushToolCollapse() {
366
+ if (this.collapsedTools > 0) {
367
+ this.push("tool", `+${this.collapsedTools} more tool call${this.collapsedTools === 1 ? "" : "s"}`);
368
+ this.collapsedTools = 0;
369
+ }
370
+ this.toolStreak = 0;
371
+ }
350
372
  handleEvent = (event) => {
351
373
  switch (event.type) {
352
374
  case "status":
353
375
  this.commitLive();
376
+ this.flushToolCollapse();
354
377
  this.push("status", event.text);
355
378
  break;
356
379
  case "delta":
357
380
  this.setLive(this.live + event.text);
358
381
  break;
359
382
  case "text":
383
+ this.flushToolCollapse();
360
384
  if (event.streamed) {
361
385
  this.live = "";
362
386
  this.state = { ...this.state, liveText: "" };
@@ -367,17 +391,24 @@ var Session = class {
367
391
  break;
368
392
  case "thinking":
369
393
  this.commitLive();
394
+ this.flushToolCollapse();
370
395
  this.push("thinking", event.text);
371
396
  break;
372
397
  case "tool": {
373
398
  this.commitLive();
374
399
  this.turnTools += 1;
400
+ this.toolStreak += 1;
375
401
  if (/edit|write|patch|apply/i.test(event.name)) this.turnEdits += 1;
376
- this.push("tool", event.detail ? `${event.name} \xB7 ${event.detail}` : event.name);
402
+ if (this.toolStreak <= 3) {
403
+ this.push("tool", event.detail ? `${event.name} \xB7 ${event.detail}` : event.name);
404
+ } else {
405
+ this.collapsedTools += 1;
406
+ }
377
407
  break;
378
408
  }
379
409
  case "error":
380
410
  this.commitLive();
411
+ this.flushToolCollapse();
381
412
  this.push("error", event.text);
382
413
  break;
383
414
  case "result":
@@ -410,6 +441,7 @@ var Session = class {
410
441
  );
411
442
  this.abort = null;
412
443
  this.commitLive();
444
+ this.flushToolCollapse();
413
445
  if (result.ok) {
414
446
  const cost = result.costUsd !== void 0 ? ` \xB7 $${result.costUsd.toFixed(2)}` : "";
415
447
  const secs = result.durationMs !== void 0 ? ` \xB7 ${(result.durationMs / 1e3).toFixed(0)}s` : "";
@@ -417,12 +449,20 @@ var Session = class {
417
449
  const stat = checkpoint ? diffStatSince(this.opts.cwd, checkpoint.snapshot) : null;
418
450
  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"}` : "";
419
451
  this.push("status", `done${secs}${cost}${work}`);
452
+ const before = this.state.totals.costUsd;
420
453
  this.notify({
421
454
  totals: {
422
- costUsd: this.state.totals.costUsd + (result.costUsd ?? 0),
455
+ costUsd: before + (result.costUsd ?? 0),
423
456
  turns: this.state.totals.turns + 1
424
457
  }
425
458
  });
459
+ const budget = this.opts.budgetUsd;
460
+ if (budget && before < budget && this.state.totals.costUsd >= budget) {
461
+ this.push(
462
+ "error",
463
+ `session cost $${this.state.totals.costUsd.toFixed(2)} crossed your $${budget.toFixed(2)} budget \u2014 squint keeps working, this is just the flag you asked for`
464
+ );
465
+ }
426
466
  if (this.sessionId) {
427
467
  saveState(this.opts.cwd, {
428
468
  engine: this.state.engineId,
@@ -1011,6 +1051,15 @@ var THEMES = {
1011
1051
  success: "#31748f",
1012
1052
  tool: "#c4a7e7"
1013
1053
  },
1054
+ light: {
1055
+ name: "light",
1056
+ accent: "#9a6b1f",
1057
+ dim: "#6b6f76",
1058
+ user: "#2a5db0",
1059
+ error: "#c4322e",
1060
+ success: "#3d7a37",
1061
+ tool: "#0f7b8a"
1062
+ },
1014
1063
  mono: {
1015
1064
  name: "mono",
1016
1065
  accent: "white",
@@ -1227,6 +1276,7 @@ function App({
1227
1276
  autoProbe,
1228
1277
  autoCheck,
1229
1278
  bell,
1279
+ budgetUsd,
1230
1280
  initialTheme
1231
1281
  }) {
1232
1282
  const { exit } = useApp();
@@ -1242,6 +1292,7 @@ function App({
1242
1292
  autoFix,
1243
1293
  autoProbe,
1244
1294
  autoCheck,
1295
+ budgetUsd,
1245
1296
  // Delay lets the goodbye summary land in the Static scrollback.
1246
1297
  onQuit: () => setTimeout(() => exit(), 60)
1247
1298
  });
@@ -1431,12 +1482,18 @@ function App({
1431
1482
 
1432
1483
  // src/cli.tsx
1433
1484
  import { jsx as jsx4 } from "react/jsx-runtime";
1434
- var VERSION = "0.2.3";
1485
+ var VERSION = "0.2.5";
1435
1486
  var program = new Command();
1436
1487
  program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
1437
- program.command("run").description("Run one prompt headlessly and stream the result").argument("<prompt...>", "what to build or change").option("-e, --engine <id>", "engine to use (claude, codex, gemini, opencode)").option("-m, --model <name>", "model override for the engine").option("--no-brief", "send the prompt without the squint design brief").option("--json", "emit normalized agent events as ndjson").action(
1488
+ program.command("run").description("Run one prompt headlessly and stream the result").argument("<prompt...>", "what to build or change").option("-e, --engine <id>", "engine to use (see squint engines)").option("-m, --model <name>", "model override for the engine").option("--mode <mode>", "plan (read-only) \xB7 safe (default) \xB7 yolo (no friction)").option("--no-brief", "send the prompt without the squint design brief").option("--json", "emit normalized agent events as ndjson").action(
1438
1489
  async (promptWords, options) => {
1439
1490
  const cwd = process.cwd();
1491
+ if (options.mode && !["plan", "safe", "yolo"].includes(options.mode)) {
1492
+ console.error(pc.red("\u2717 --mode must be plan, safe, or yolo"));
1493
+ process.exitCode = 1;
1494
+ return;
1495
+ }
1496
+ const mode = options.mode;
1440
1497
  const config = loadConfig(defaultPaths(cwd));
1441
1498
  const engineId = resolveEngineId(config, options.engine);
1442
1499
  const engine = getEngine(engineId);
@@ -1446,8 +1503,9 @@ program.command("run").description("Run one prompt headlessly and stream the res
1446
1503
  const onEvent = options.json ? (event) => {
1447
1504
  if (event.type !== "delta") console.log(JSON.stringify(event));
1448
1505
  } : createPrinter();
1449
- if (!options.json) console.log(pc.dim(`squint \xB7 ${engine.id}${model ? ` \xB7 ${model}` : ""}`));
1450
- const result = await runAgent(engine, { prompt, cwd, model }, onEvent);
1506
+ if (!options.json)
1507
+ console.log(pc.dim(`squint \xB7 ${engine.id}${model ? ` \xB7 ${model}` : ""}${mode && mode !== "safe" ? ` \xB7 ${mode}` : ""}`));
1508
+ const result = await runAgent(engine, { prompt, cwd, model, mode }, onEvent);
1451
1509
  if (options.json) {
1452
1510
  console.log(JSON.stringify({ type: "summary", ...result }));
1453
1511
  } else if (result.ok) {
@@ -1475,7 +1533,7 @@ program.command("doctor").description("Check squint prerequisites and engine ava
1475
1533
  console.log(`${status} ${engine.name}${binaryPath ? "" : pc.dim(` \u2014 install: ${engine.install}`)}`);
1476
1534
  }
1477
1535
  if (options.probe) {
1478
- const { runAgent: runAgent2 } = await import("./run-XHLCTF6V.js");
1536
+ const { runAgent: runAgent2 } = await import("./run-TIGEZHMQ.js");
1479
1537
  console.log(pc.dim("\nprobing engines with a one-word prompt (verifies auth end to end)\u2026"));
1480
1538
  for (const { engine, path: binaryPath } of detected) {
1481
1539
  if (!binaryPath) continue;
@@ -1497,7 +1555,7 @@ program.command("doctor").description("Check squint prerequisites and engine ava
1497
1555
  );
1498
1556
  }
1499
1557
  }
1500
- const { findChrome: findChrome2 } = await import("./chrome-RD7XQ767.js");
1558
+ const { findChrome: findChrome2 } = await import("./chrome-RV25VKUA.js");
1501
1559
  const { hasWebSocket } = await import("./cdp-SFWNP7OA.js");
1502
1560
  const chrome = findChrome2();
1503
1561
  console.log(
@@ -1624,7 +1682,7 @@ variantsCommand.command("gen").description("Generate n variants of one ask in pa
1624
1682
  process.exitCode = 1;
1625
1683
  return;
1626
1684
  }
1627
- const { runVariants: runVariants2, cleanVariants: cleanVariants2 } = await import("./variants-7A7723L7.js");
1685
+ const { runVariants: runVariants2, cleanVariants: cleanVariants2 } = await import("./variants-SM6NRX4U.js");
1628
1686
  const config = loadConfig(defaultPaths(cwd));
1629
1687
  const engineId = resolveEngineId(config, options.engine);
1630
1688
  const engine = getEngine(engineId);
@@ -1642,7 +1700,7 @@ variantsCommand.command("gen").description("Generate n variants of one ask in pa
1642
1700
  );
1643
1701
  const succeeded = runs.filter((r) => r.result.ok);
1644
1702
  if (options.shots && succeeded.length > 0) {
1645
- const { screenshotVariants: screenshotVariants2 } = await import("./shots-LRYYMTPK.js");
1703
+ const { screenshotVariants: screenshotVariants2 } = await import("./shots-X3I6MRLX.js");
1646
1704
  console.log(pc.dim("capturing screenshots\u2026"));
1647
1705
  const shots = await screenshotVariants2(cwd, succeeded.map((r) => r.variant));
1648
1706
  for (const shot of shots) {
@@ -1657,7 +1715,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
1657
1715
  }
1658
1716
  );
1659
1717
  variantsCommand.command("list").description("List generated variants").action(async () => {
1660
- const { listVariants: listVariants2 } = await import("./variants-7A7723L7.js");
1718
+ const { listVariants: listVariants2 } = await import("./variants-SM6NRX4U.js");
1661
1719
  const ids = listVariants2(process.cwd());
1662
1720
  if (ids.length === 0) {
1663
1721
  console.log(pc.dim('no variants \u2014 squint variants gen <n> "<ask>"'));
@@ -1666,7 +1724,7 @@ variantsCommand.command("list").description("List generated variants").action(as
1666
1724
  for (const id of ids) console.log(id);
1667
1725
  });
1668
1726
  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) => {
1669
- const { applyVariant: applyVariant2, cleanVariants: cleanVariants2 } = await import("./variants-7A7723L7.js");
1727
+ const { applyVariant: applyVariant2, cleanVariants: cleanVariants2 } = await import("./variants-SM6NRX4U.js");
1670
1728
  const cwd = process.cwd();
1671
1729
  const result = applyVariant2(cwd, id);
1672
1730
  if (!result.ok) {
@@ -1678,7 +1736,7 @@ variantsCommand.command("apply").description("Apply one variant\u2019s changes t
1678
1736
  console.log(pc.green(`\u2713 applied ${id} to the working tree`) + pc.dim(" \u2014 review with git diff"));
1679
1737
  });
1680
1738
  variantsCommand.command("clean").description("Discard all variants").action(async () => {
1681
- const { cleanVariants: cleanVariants2 } = await import("./variants-7A7723L7.js");
1739
+ const { cleanVariants: cleanVariants2 } = await import("./variants-SM6NRX4U.js");
1682
1740
  const count = cleanVariants2(process.cwd());
1683
1741
  console.log(pc.dim(`removed ${count} variant(s)`));
1684
1742
  });
@@ -1712,7 +1770,7 @@ program.command("brief").description("Set a committed design direction for this
1712
1770
  console.log(pc.dim("every squint ask in this repo now holds this direction \u2014 edit the file to remix"));
1713
1771
  });
1714
1772
  program.command("check").description("Run this project\u2019s quality gates (typecheck, lint, test, build)").action(async () => {
1715
- const { detectGates: detectGates2, runGates: runGates2 } = await import("./gates-I2GGQGMA.js");
1773
+ const { detectGates: detectGates2, runGates: runGates2 } = await import("./gates-ZC67NMW3.js");
1716
1774
  const cwd = process.cwd();
1717
1775
  const gates = detectGates2(cwd);
1718
1776
  if (gates.length === 0) {
@@ -1728,7 +1786,7 @@ program.command("check").description("Run this project\u2019s quality gates (typ
1728
1786
  if (results.some((r) => !r.ok)) process.exitCode = 1;
1729
1787
  });
1730
1788
  program.command("shot").description("Screenshot a running app at mobile/tablet/desktop viewports").argument("<url>", "URL of the running app (e.g. http://localhost:5173)").action(async (url) => {
1731
- const { captureViewports: captureViewports2 } = await import("./preview-UX4VS4SX.js");
1789
+ const { captureViewports: captureViewports2 } = await import("./preview-ZHUQT7TJ.js");
1732
1790
  const result = await captureViewports2(process.cwd(), url);
1733
1791
  if (!result) {
1734
1792
  console.error(pc.red("\u2717 no Chrome/Chromium found"));
@@ -1755,11 +1813,16 @@ configCommand.command("path").description("Show config file locations").action((
1755
1813
  console.log(`global ${paths.globalFile}`);
1756
1814
  console.log(`project ${paths.projectFile}`);
1757
1815
  });
1758
- program.action(() => {
1816
+ program.action(async () => {
1759
1817
  const cwd = process.cwd();
1760
1818
  const config = loadConfig(defaultPaths(cwd));
1761
1819
  const engineId = resolveEngineId(config);
1762
1820
  const model = resolveModel(config, engineId);
1821
+ let theme2 = config.theme;
1822
+ if (!theme2 && !process.env.NO_COLOR) {
1823
+ const { detectBackground } = await import("./background-J7OQTYEC.js");
1824
+ if (await detectBackground() === "light") theme2 = "light";
1825
+ }
1763
1826
  render(
1764
1827
  /* @__PURE__ */ jsx4(
1765
1828
  App,
@@ -1772,7 +1835,8 @@ program.action(() => {
1772
1835
  autoProbe: config.autoProbe,
1773
1836
  autoCheck: config.autoCheck,
1774
1837
  bell: config.bell,
1775
- initialTheme: config.theme
1838
+ budgetUsd: config.budgetUsd,
1839
+ initialTheme: theme2
1776
1840
  }
1777
1841
  )
1778
1842
  );
@@ -5,7 +5,7 @@ import {
5
5
  detectGates,
6
6
  runGate,
7
7
  runGates
8
- } from "./chunk-DLCRYRWQ.js";
8
+ } from "./chunk-62JNF5M2.js";
9
9
  export {
10
10
  buildGatePrompt,
11
11
  detectFastGates,
@@ -5,20 +5,24 @@ import {
5
5
  buildRuntimeFixPrompt,
6
6
  captureViewports,
7
7
  comparePulse,
8
+ loadRoutes,
8
9
  previewDir,
9
10
  probeRuntime,
11
+ routeShotName,
10
12
  runtimeSummary
11
- } from "./chunk-SU6YRRMF.js";
13
+ } from "./chunk-DKQ5KJD7.js";
12
14
  import "./chunk-OC6RU6XH.js";
13
- import "./chunk-P3V5CWH5.js";
14
- import "./chunk-2LRIKWBU.js";
15
+ import "./chunk-CJXYSQNB.js";
16
+ import "./chunk-CH4GNJCZ.js";
15
17
  export {
16
18
  VIEWPORTS,
17
19
  buildReviewPrompt,
18
20
  buildRuntimeFixPrompt,
19
21
  captureViewports,
20
22
  comparePulse,
23
+ loadRoutes,
21
24
  previewDir,
22
25
  probeRuntime,
26
+ routeShotName,
23
27
  runtimeSummary
24
28
  };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runAgent
4
- } from "./chunk-OTG4ZB4R.js";
5
- import "./chunk-2LRIKWBU.js";
4
+ } from "./chunk-TI2R7QRL.js";
5
+ import "./chunk-CH4GNJCZ.js";
6
6
  export {
7
7
  runAgent
8
8
  };
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ screenshotVariants
4
+ } from "./chunk-QNNT6FP4.js";
5
+ import "./chunk-XLNTTZX4.js";
6
+ import "./chunk-P3H4N2EN.js";
7
+ import "./chunk-TI2R7QRL.js";
8
+ import "./chunk-CJXYSQNB.js";
9
+ import "./chunk-CH4GNJCZ.js";
10
+ export {
11
+ screenshotVariants
12
+ };
@@ -9,10 +9,10 @@ import {
9
9
  runVariants,
10
10
  variantPrompt,
11
11
  variantsRoot
12
- } from "./chunk-LCS47EAG.js";
12
+ } from "./chunk-XLNTTZX4.js";
13
13
  import "./chunk-P3H4N2EN.js";
14
- import "./chunk-OTG4ZB4R.js";
15
- import "./chunk-2LRIKWBU.js";
14
+ import "./chunk-TI2R7QRL.js";
15
+ import "./chunk-CH4GNJCZ.js";
16
16
  export {
17
17
  MAX_VARIANTS,
18
18
  applyVariant,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aayambansal/squint",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
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",
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- screenshotVariants
4
- } from "./chunk-2IW2MMCY.js";
5
- import "./chunk-LCS47EAG.js";
6
- import "./chunk-P3H4N2EN.js";
7
- import "./chunk-OTG4ZB4R.js";
8
- import "./chunk-P3V5CWH5.js";
9
- import "./chunk-2LRIKWBU.js";
10
- export {
11
- screenshotVariants
12
- };