@aayambansal/squint 0.2.6 → 0.2.7

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  variantsRoot
4
- } from "./chunk-LVA2OQSR.js";
4
+ } from "./chunk-X6MDBKED.js";
5
5
  import {
6
6
  findChrome,
7
7
  screenshot
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/session/commands.ts
4
4
  var COMMANDS = [
5
- { name: "dev", description: "start/stop the project dev server" },
5
+ { name: "dev", args: "[restart|logs]", description: "start/stop the dev server; restart or show recent output" },
6
6
  { name: "check", description: "run all quality gates (typecheck, lint, test, build)" },
7
7
  { name: "problems", description: "list open findings from gates, dev server, runtime, a11y" },
8
8
  { name: "fix", args: "[n]", description: "send all open problems to the engine, or just problem n" },
package/dist/cli.js CHANGED
@@ -1,34 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- enrich
4
- } from "./chunk-4LAU5TGK.js";
5
- import {
6
- diffStatSince,
7
- isGitRepo,
8
- restoreSnapshot,
9
- takeSnapshot
10
- } from "./chunk-YHRAOBI2.js";
11
2
  import {
12
3
  DevServer,
13
4
  buildFixPrompt,
14
5
  detectDevCommand,
15
6
  screenshotVariants
16
- } from "./chunk-43L4QXCM.js";
7
+ } from "./chunk-5VKUJPOL.js";
17
8
  import {
18
9
  applyVariant,
19
10
  cleanVariants,
20
11
  listVariants,
21
12
  runVariants
22
- } from "./chunk-LVA2OQSR.js";
23
- import {
24
- composePrompt
25
- } from "./chunk-P3H4N2EN.js";
26
- import {
27
- runAgent
28
- } from "./chunk-C7WKNJG6.js";
29
- import {
30
- completeCommand
31
- } from "./chunk-V3M4WQLH.js";
13
+ } from "./chunk-X6MDBKED.js";
32
14
  import {
33
15
  buildGatePrompt,
34
16
  detectFastGates,
@@ -45,8 +27,13 @@ import {
45
27
  probeRuntime,
46
28
  runtimeSummary,
47
29
  saveState
48
- } from "./chunk-YZEFBPBJ.js";
49
- import "./chunk-OC6RU6XH.js";
30
+ } from "./chunk-CHZE3UHK.js";
31
+ import {
32
+ completeCommand
33
+ } from "./chunk-RZOQPFXC.js";
34
+ import {
35
+ runAgent
36
+ } from "./chunk-C7WKNJG6.js";
50
37
  import {
51
38
  findChrome
52
39
  } from "./chunk-B7LOERSP.js";
@@ -54,10 +41,24 @@ import {
54
41
  detectEngines,
55
42
  getEngine
56
43
  } from "./chunk-BWZFACBT.js";
44
+ import "./chunk-OC6RU6XH.js";
45
+ import {
46
+ enrich
47
+ } from "./chunk-4LAU5TGK.js";
48
+ import {
49
+ composePrompt
50
+ } from "./chunk-P3H4N2EN.js";
51
+ import {
52
+ diffStatSince,
53
+ isGitRepo,
54
+ restoreSnapshot,
55
+ takeSnapshot
56
+ } from "./chunk-YHRAOBI2.js";
57
57
 
58
58
  // src/cli.tsx
59
59
  import { Command } from "commander";
60
- import { render } from "ink";
60
+
61
+ // src/cli/configCmd.ts
61
62
  import pc from "picocolors";
62
63
 
63
64
  // src/config/config.ts
@@ -82,6 +83,8 @@ var ConfigSchema = z.object({
82
83
  bell: z.boolean().optional(),
83
84
  /** Session budget in USD; crossing it warns (never blocks). */
84
85
  budgetUsd: z.number().positive().optional(),
86
+ /** Auto-run /review when the visual pulse shows a big change (default off). */
87
+ autoReview: z.boolean().optional(),
85
88
  /** TUI theme name (amber, ocean, moss, rose, mono). */
86
89
  theme: z.string().optional()
87
90
  });
@@ -126,7 +129,7 @@ function setConfigValue(file, key, value) {
126
129
  let next;
127
130
  if (key === "engine" || key === "theme") {
128
131
  next = { ...current, [key]: value };
129
- } else if (key === "autoDev" || key === "autoFix" || key === "autoProbe" || key === "autoCheck" || key === "bell") {
132
+ } else if (key === "autoDev" || key === "autoFix" || key === "autoProbe" || key === "autoCheck" || key === "autoReview" || key === "bell") {
130
133
  if (value !== "true" && value !== "false") {
131
134
  throw new Error(`"${key}" must be true or false`);
132
135
  }
@@ -143,7 +146,7 @@ function setConfigValue(file, key, value) {
143
146
  next = { ...current, models: { ...current.models, [engineId]: value } };
144
147
  } else {
145
148
  throw new Error(
146
- `Unknown config key "${key}". Supported: engine, theme, autoDev, autoFix, autoProbe, autoCheck, bell, budgetUsd, models.<engineId>`
149
+ `Unknown config key "${key}". Supported: engine, theme, autoDev, autoFix, autoProbe, autoCheck, autoReview, bell, budgetUsd, models.<engineId>`
147
150
  );
148
151
  }
149
152
  fs.mkdirSync(path.dirname(file), { recursive: true });
@@ -151,6 +154,420 @@ function setConfigValue(file, key, value) {
151
154
  return next;
152
155
  }
153
156
 
157
+ // src/cli/configCmd.ts
158
+ function registerConfig(program2) {
159
+ const configCommand = program2.command("config").description("Read or change squint configuration");
160
+ configCommand.command("get").description("Print the resolved configuration").action(() => {
161
+ const paths = defaultPaths(process.cwd());
162
+ console.log(JSON.stringify(loadConfig(paths), null, 2));
163
+ });
164
+ configCommand.command("set").description("Set a value (see docs/configuration.md for every key)").argument("<key>").argument("<value>").option("--project", "write to this project (.squint/config.json) instead of global").action((key, value, options) => {
165
+ const paths = defaultPaths(process.cwd());
166
+ const file = options.project ? paths.projectFile : paths.globalFile;
167
+ setConfigValue(file, key, value);
168
+ console.log(pc.green(`\u2713 ${key} = ${value}`) + pc.dim(` (${file})`));
169
+ });
170
+ configCommand.command("path").description("Show config file locations").action(() => {
171
+ const paths = defaultPaths(process.cwd());
172
+ console.log(`global ${paths.globalFile}`);
173
+ console.log(`project ${paths.projectFile}`);
174
+ });
175
+ }
176
+
177
+ // src/cli/env.ts
178
+ import pc2 from "picocolors";
179
+ function registerEnv(program2) {
180
+ program2.command("engines").description("List engines: installed, streaming, session resume").action(() => {
181
+ console.log(pc2.dim(" id name stream resume where"));
182
+ for (const { engine, path: binaryPath } of detectEngines()) {
183
+ const status = binaryPath ? pc2.green("\u2713") : pc2.red("\u2717");
184
+ const stream = engine.createParser ? pc2.green("yes") : pc2.dim("text");
185
+ const resume = engine.supportsResume ? pc2.green("yes") : pc2.dim("no");
186
+ const location = binaryPath ?? pc2.dim(`not found \u2014 ${engine.install}`);
187
+ console.log(
188
+ `${status} ${engine.id.padEnd(10)} ${engine.name.padEnd(14)} ${stream.padEnd(15)} ${resume.padEnd(14)} ${location}`
189
+ );
190
+ }
191
+ console.log(pc2.dim("\nplan/safe/yolo modes map onto every engine \xB7 squint doctor --probe verifies auth"));
192
+ });
193
+ program2.command("doctor").description("Check squint prerequisites and engine availability").option("--probe", "run each detected engine with a one-word prompt to verify auth works").action(async (options) => {
194
+ const [major] = process.versions.node.split(".");
195
+ const nodeOk = Number(major) >= 22;
196
+ console.log(
197
+ `${nodeOk ? pc2.green("\u2713") : pc2.red("\u2717")} node ${process.versions.node}${nodeOk ? "" : " (need >= 22)"}`
198
+ );
199
+ const detected = detectEngines();
200
+ for (const { engine, path: binaryPath } of detected) {
201
+ const status = binaryPath ? pc2.green("\u2713") : pc2.yellow("\u25CB");
202
+ console.log(`${status} ${engine.name}${binaryPath ? "" : pc2.dim(` \u2014 install: ${engine.install}`)}`);
203
+ }
204
+ if (options.probe) {
205
+ const { runAgent: runAgent2 } = await import("./run-S772BPMZ.js");
206
+ console.log(pc2.dim("\nprobing engines with a one-word prompt (verifies auth end to end)\u2026"));
207
+ for (const { engine, path: binaryPath } of detected) {
208
+ if (!binaryPath) continue;
209
+ const startedAt = Date.now();
210
+ const abort = new AbortController();
211
+ const timer = setTimeout(() => abort.abort(), 9e4);
212
+ const result = await runAgent2(
213
+ engine,
214
+ { prompt: "Reply with exactly: ok", cwd: process.cwd() },
215
+ () => {
216
+ },
217
+ abort.signal
218
+ );
219
+ clearTimeout(timer);
220
+ const secs = ((Date.now() - startedAt) / 1e3).toFixed(1);
221
+ const detail = (result.error ?? "failed").split("\n").filter((l) => l.trim()).at(-1) ?? "failed";
222
+ console.log(
223
+ result.ok ? `${pc2.green("\u2713")} ${engine.id.padEnd(10)} responded in ${secs}s` : `${pc2.red("\u2717")} ${engine.id.padEnd(10)} ${pc2.dim(detail.slice(0, 110))}`
224
+ );
225
+ }
226
+ }
227
+ const { findChrome: findChrome2 } = await import("./chrome-NVU47PLK.js");
228
+ const { hasWebSocket } = await import("./cdp-SFWNP7OA.js");
229
+ const chrome = findChrome2();
230
+ console.log(
231
+ chrome ? `${pc2.green("\u2713")} Chrome ${pc2.dim(chrome)}` : `${pc2.yellow("\u25CB")} Chrome ${pc2.dim("\u2014 screenshots and runtime probing disabled")}`
232
+ );
233
+ console.log(
234
+ hasWebSocket() ? `${pc2.green("\u2713")} WebSocket ${pc2.dim("runtime console/network capture available")}` : `${pc2.yellow("\u25CB")} WebSocket ${pc2.dim("\u2014 node 22+ enables runtime capture; screenshots still work")}`
235
+ );
236
+ const available = detected.filter((d) => d.path !== null);
237
+ if (available.length === 0) {
238
+ console.log(pc2.red("\nNo engines found. Install at least one to use squint."));
239
+ process.exitCode = 1;
240
+ } else {
241
+ console.log(pc2.dim(`
242
+ ${available.length} engine(s) ready.`));
243
+ }
244
+ });
245
+ }
246
+
247
+ // src/cli/project.ts
248
+ import pc3 from "picocolors";
249
+ function registerProject(program2) {
250
+ const skillsCommand = program2.command("skills").description("Project knowledge injected into asks (.squint/rules.md + .squint/skills/)");
251
+ skillsCommand.command("list").description("Show always-on rules and trigger-matched skills").action(async () => {
252
+ const { loadRules, loadSkills } = await import("./skills-DNBHZAAU.js");
253
+ const cwd = process.cwd();
254
+ const rules = loadRules(cwd);
255
+ console.log(
256
+ rules ? `${pc3.green("\u2713")} rules.md ${pc3.dim(`(${rules.split("\n").length} lines, always on)`)}` : pc3.dim("\u25CB no .squint/rules.md")
257
+ );
258
+ const skills = loadSkills(cwd);
259
+ if (skills.length === 0) {
260
+ console.log(pc3.dim("\u25CB no skills \u2014 squint skills init writes an example"));
261
+ return;
262
+ }
263
+ for (const skill of skills) {
264
+ console.log(`${pc3.green("\u2713")} ${skill.name.padEnd(20)} ${pc3.dim(`triggers: ${skill.triggers.join(", ")}`)}`);
265
+ }
266
+ });
267
+ skillsCommand.command("init").description("Scaffold .squint/rules.md and an example skill").action(async () => {
268
+ const fs2 = await import("fs");
269
+ const nodePath = await import("path");
270
+ const cwd = process.cwd();
271
+ const skillsDir = nodePath.join(cwd, ".squint", "skills");
272
+ fs2.mkdirSync(skillsDir, { recursive: true });
273
+ const rules = nodePath.join(cwd, ".squint", "rules.md");
274
+ if (!fs2.existsSync(rules)) {
275
+ fs2.writeFileSync(
276
+ rules,
277
+ "# Project rules\n\nThese ride along on every squint ask. Keep them short \u2014 cut anything that would not cause a mistake if removed.\n"
278
+ );
279
+ console.log(pc3.green("\u2713 .squint/rules.md"));
280
+ }
281
+ const example = nodePath.join(skillsDir, "example.md");
282
+ if (!fs2.existsSync(example)) {
283
+ fs2.writeFileSync(
284
+ example,
285
+ "---\ntriggers: example, sample\n---\n\nThis note is injected only when an ask mentions one of the triggers above.\nDocument the parts of this repo an agent would otherwise rediscover every time:\nwhere state lives, which helpers to reuse, what not to touch.\n"
286
+ );
287
+ console.log(pc3.green("\u2713 .squint/skills/example.md"));
288
+ }
289
+ console.log(pc3.dim("rules are always-on; skills inject when an ask mentions a trigger"));
290
+ });
291
+ program2.command("brief").description("Set a committed design direction for this project (.squint/brief.md)").argument("[family]", "aesthetic family id (omit to list)").option("--force", "overwrite an existing project brief").action(async (familyId, options) => {
292
+ const fs2 = await import("fs");
293
+ const nodePath = await import("path");
294
+ const { FAMILIES, getFamily, renderFamilyBrief } = await import("./families-2G5SLLY7.js");
295
+ if (!familyId) {
296
+ console.log(pc3.bold("Aesthetic families") + pc3.dim(" \u2014 squint brief <id>\n"));
297
+ for (const family2 of FAMILIES) {
298
+ console.log(`${pc3.green(family2.id.padEnd(18))} ${family2.name.padEnd(22)} ${pc3.dim(family2.summary)}`);
299
+ }
300
+ console.log(pc3.dim("\nThe brief wraps every ask; edit .squint/brief.md to remix."));
301
+ return;
302
+ }
303
+ const family = getFamily(familyId);
304
+ if (!family) {
305
+ console.error(pc3.red(`\u2717 unknown family "${familyId}" \u2014 run squint brief to list`));
306
+ process.exitCode = 1;
307
+ return;
308
+ }
309
+ const target = nodePath.join(process.cwd(), ".squint", "brief.md");
310
+ if (fs2.existsSync(target) && !options.force) {
311
+ console.error(pc3.red(`\u2717 ${target} exists \u2014 use --force to overwrite`));
312
+ process.exitCode = 1;
313
+ return;
314
+ }
315
+ fs2.mkdirSync(nodePath.dirname(target), { recursive: true });
316
+ fs2.writeFileSync(target, renderFamilyBrief(family) + "\n");
317
+ console.log(pc3.green(`\u2713 ${family.name} direction written to .squint/brief.md`));
318
+ console.log(pc3.dim("every squint ask in this repo now holds this direction \u2014 edit the file to remix"));
319
+ });
320
+ const variantsCommand = program2.command("variants").description("Parallel design explorations \u2014 one aesthetic family each, pick with your eyes");
321
+ variantsCommand.command("gen").description("Generate n variants of one ask in parallel (n engine runs \u2014 n\xD7 cost)").argument("<n>", "how many variants (max 4)").argument("<prompt...>", "what to build").option("-e, --engine <id>", "engine to use").option("-m, --model <name>", "model override").option("--no-shots", "skip the screenshot pass").action(
322
+ async (nRaw, promptWords, options) => {
323
+ const cwd = process.cwd();
324
+ const n = Number.parseInt(nRaw, 10);
325
+ if (!Number.isInteger(n) || n < 2 || n > 4) {
326
+ console.error(pc3.red("\u2717 n must be 2\u20134"));
327
+ process.exitCode = 1;
328
+ return;
329
+ }
330
+ const { isGitRepo: isGitRepo2 } = await import("./snapshot-H6VYFHLN.js");
331
+ if (!isGitRepo2(cwd)) {
332
+ console.error(pc3.red("\u2717 variants need a git repo with at least one commit"));
333
+ process.exitCode = 1;
334
+ return;
335
+ }
336
+ const { runVariants: runVariants2, cleanVariants: cleanVariants2 } = await import("./variants-UULPQ7Q5.js");
337
+ const config = loadConfig(defaultPaths(cwd));
338
+ const engineId = resolveEngineId(config, options.engine);
339
+ const engine = getEngine(engineId);
340
+ const model = resolveModel(config, engineId, options.model);
341
+ const ask = promptWords.join(" ");
342
+ cleanVariants2(cwd);
343
+ console.log(pc3.dim(`generating ${n} directions in parallel via ${engine.id} \u2014 this runs ${n} engine sessions`));
344
+ const runs = await runVariants2(
345
+ cwd,
346
+ ask,
347
+ n,
348
+ engine,
349
+ model,
350
+ (familyId, text) => console.log(`${pc3.cyan(familyId.padEnd(18))} ${pc3.dim(text)}`)
351
+ );
352
+ const succeeded = runs.filter((r) => r.result.ok);
353
+ if (options.shots && succeeded.length > 0) {
354
+ const { screenshotVariants: screenshotVariants2 } = await import("./shots-FVG4CDVK.js");
355
+ console.log(pc3.dim("capturing screenshots\u2026"));
356
+ const shots = await screenshotVariants2(cwd, succeeded.map((r) => r.variant));
357
+ for (const shot of shots) {
358
+ console.log(`${pc3.green("\u2713")} ${shot.familyId.padEnd(18)} ${shot.path ?? pc3.dim(shot.error ?? "")}`);
359
+ }
360
+ }
361
+ console.log(
362
+ `
363
+ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 ` + pc3.bold("squint variants apply <id>") + pc3.dim(" applies the winner, squint variants clean discards all")
364
+ );
365
+ if (succeeded.length === 0) process.exitCode = 1;
366
+ }
367
+ );
368
+ variantsCommand.command("list").description("List generated variants").action(async () => {
369
+ const { listVariants: listVariants2 } = await import("./variants-UULPQ7Q5.js");
370
+ const ids = listVariants2(process.cwd());
371
+ if (ids.length === 0) {
372
+ console.log(pc3.dim('no variants \u2014 squint variants gen <n> "<ask>"'));
373
+ return;
374
+ }
375
+ for (const id of ids) console.log(id);
376
+ });
377
+ 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) => {
378
+ const { applyVariant: applyVariant2, cleanVariants: cleanVariants2 } = await import("./variants-UULPQ7Q5.js");
379
+ const cwd = process.cwd();
380
+ const result = applyVariant2(cwd, id);
381
+ if (!result.ok) {
382
+ console.error(pc3.red(`\u2717 ${result.detail}`));
383
+ process.exitCode = 1;
384
+ return;
385
+ }
386
+ cleanVariants2(cwd);
387
+ console.log(pc3.green(`\u2713 applied ${id} to the working tree`) + pc3.dim(" \u2014 review with git diff"));
388
+ });
389
+ variantsCommand.command("clean").description("Discard all variants").action(async () => {
390
+ const { cleanVariants: cleanVariants2 } = await import("./variants-UULPQ7Q5.js");
391
+ const count = cleanVariants2(process.cwd());
392
+ console.log(pc3.dim(`removed ${count} variant(s)`));
393
+ });
394
+ }
395
+
396
+ // src/cli/quality.ts
397
+ import pc4 from "picocolors";
398
+ function registerQuality(program2) {
399
+ program2.command("check").description("Run this project\u2019s quality gates (typecheck, lint, format, test, build)").action(async () => {
400
+ const { detectGates: detectGates2, runGates: runGates2 } = await import("./gates-ZC67NMW3.js");
401
+ const cwd = process.cwd();
402
+ const gates = detectGates2(cwd);
403
+ if (gates.length === 0) {
404
+ console.log(pc4.dim("no gates detected (no package.json scripts, tsconfig, or eslint config)"));
405
+ return;
406
+ }
407
+ console.log(pc4.dim(`running ${gates.map((g) => g.id).join(" \u2192 ")}`));
408
+ const results = await runGates2(cwd, gates, (result) => {
409
+ const mark = result.ok ? pc4.green("\u2713") : pc4.red("\u2717");
410
+ console.log(
411
+ `${mark} ${result.gate.id.padEnd(10)} ${pc4.dim(`${(result.durationMs / 1e3).toFixed(1)}s \xB7 ${result.gate.display}`)}`
412
+ );
413
+ if (!result.ok) console.log(pc4.dim(result.outputTail.split("\n").slice(-12).join("\n")));
414
+ });
415
+ if (results.some((r) => !r.ok)) process.exitCode = 1;
416
+ });
417
+ 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) => {
418
+ const { captureViewports: captureViewports2 } = await import("./preview-XGASKUXR.js");
419
+ const result = await captureViewports2(process.cwd(), url);
420
+ if (!result) {
421
+ console.error(pc4.red("\u2717 no Chrome/Chromium found"));
422
+ process.exitCode = 1;
423
+ return;
424
+ }
425
+ for (const shot of result.shots) console.log(`${pc4.green("\u2713")} ${shot.name.padEnd(8)} ${shot.path}`);
426
+ for (const error of result.errors) console.error(pc4.red(`\u2717 ${error}`));
427
+ if (result.shots.length === 0) process.exitCode = 1;
428
+ });
429
+ }
430
+
431
+ // src/cli/run.ts
432
+ import pc5 from "picocolors";
433
+ function createPrinter() {
434
+ let streaming = false;
435
+ return (event) => {
436
+ switch (event.type) {
437
+ case "status":
438
+ console.log(pc5.dim(`\xB7 ${event.text}`));
439
+ break;
440
+ case "delta":
441
+ streaming = true;
442
+ process.stdout.write(event.text);
443
+ break;
444
+ case "text":
445
+ if (event.streamed && streaming) {
446
+ process.stdout.write("\n");
447
+ } else {
448
+ console.log(event.text);
449
+ }
450
+ streaming = false;
451
+ break;
452
+ case "thinking":
453
+ console.log(pc5.dim(pc5.italic(event.text)));
454
+ break;
455
+ case "tool":
456
+ if (streaming) {
457
+ process.stdout.write("\n");
458
+ streaming = false;
459
+ }
460
+ console.log(pc5.cyan(`\u2699 ${event.name}${event.detail ? ` \xB7 ${event.detail}` : ""}`));
461
+ break;
462
+ case "error":
463
+ console.error(pc5.red(`\u2717 ${event.text}`));
464
+ break;
465
+ case "result":
466
+ case "raw":
467
+ break;
468
+ }
469
+ };
470
+ }
471
+ function registerRun(program2) {
472
+ program2.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(
473
+ async (promptWords, options) => {
474
+ const cwd = process.cwd();
475
+ if (options.mode && !["plan", "safe", "yolo"].includes(options.mode)) {
476
+ console.error(pc5.red("\u2717 --mode must be plan, safe, or yolo"));
477
+ process.exitCode = 1;
478
+ return;
479
+ }
480
+ const mode = options.mode;
481
+ const config = loadConfig(defaultPaths(cwd));
482
+ const engineId = resolveEngineId(config, options.engine);
483
+ const engine = getEngine(engineId);
484
+ const model = resolveModel(config, engineId, options.model);
485
+ const ask = promptWords.join(" ");
486
+ const prompt = composePrompt(ask, { cwd, noBrief: !options.brief });
487
+ const onEvent = options.json ? (event) => {
488
+ if (event.type !== "delta") console.log(JSON.stringify(event));
489
+ } : createPrinter();
490
+ if (!options.json)
491
+ console.log(
492
+ pc5.dim(`squint \xB7 ${engine.id}${model ? ` \xB7 ${model}` : ""}${mode && mode !== "safe" ? ` \xB7 ${mode}` : ""}`)
493
+ );
494
+ const result = await runAgent(engine, { prompt, cwd, model, mode }, onEvent);
495
+ if (options.json) {
496
+ console.log(JSON.stringify({ type: "summary", ...result }));
497
+ } else if (result.ok) {
498
+ const cost = result.costUsd !== void 0 ? ` \xB7 $${result.costUsd.toFixed(2)}` : "";
499
+ const secs = result.durationMs !== void 0 ? ` \xB7 ${(result.durationMs / 1e3).toFixed(0)}s` : "";
500
+ console.log(pc5.green(`\u2713 done${secs}${cost}`));
501
+ }
502
+ if (!result.ok) process.exitCode = 1;
503
+ }
504
+ );
505
+ }
506
+
507
+ // src/cli/scaffold.ts
508
+ import pc6 from "picocolors";
509
+ function registerScaffold(program2) {
510
+ program2.command("init").description("Scaffold a new Vite + React + TS + Tailwind app with token-first CSS").argument("[dir]", "target directory", ".").option("--force", "write into a non-empty directory").option("--no-install", "skip npm install").action(async (dir, options) => {
511
+ const { installDependencies, writeTemplate } = await import("./init-LSYB32NS.js");
512
+ let result;
513
+ try {
514
+ result = writeTemplate(dir, { force: options.force });
515
+ } catch (err) {
516
+ console.error(pc6.red(`\u2717 ${err instanceof Error ? err.message : String(err)}`));
517
+ process.exitCode = 1;
518
+ return;
519
+ }
520
+ console.log(pc6.green(`\u2713 scaffolded ${result.files.length} files in ${result.dir}`));
521
+ if (options.install) {
522
+ console.log(pc6.dim("installing dependencies\u2026"));
523
+ const ok = await installDependencies(result.dir);
524
+ if (!ok) {
525
+ console.error(pc6.red("\u2717 npm install failed \u2014 run it manually"));
526
+ process.exitCode = 1;
527
+ return;
528
+ }
529
+ }
530
+ const cd = result.dir === "." ? "" : `cd ${result.dir} && `;
531
+ console.log(`
532
+ Next: ${pc6.bold(`${cd}${options.install ? "" : "npm install && "}squint`)}`);
533
+ console.log(pc6.dim("then describe what to build \u2014 /dev starts the preview server"));
534
+ });
535
+ program2.command("tag").description("Add the element picker to this Vite app (Alt+S in the browser \u2192 click \u2192 file:line:col)").action(async () => {
536
+ const fs2 = await import("fs");
537
+ const nodePath = await import("path");
538
+ const { patchViteConfig, TAGGER_FILENAME, TAGGER_SOURCE } = await import("./source-ZZU245VN.js");
539
+ const cwd = process.cwd();
540
+ const taggerPath = nodePath.join(cwd, TAGGER_FILENAME);
541
+ fs2.writeFileSync(taggerPath, TAGGER_SOURCE);
542
+ console.log(pc6.green(`\u2713 ${TAGGER_FILENAME} written`));
543
+ const configPath = ["vite.config.ts", "vite.config.js", "vite.config.mjs"].map((name) => nodePath.join(cwd, name)).find((candidate) => fs2.existsSync(candidate));
544
+ if (!configPath) {
545
+ console.log(pc6.yellow("\u25CB no vite config found \u2014 add the plugin manually:"));
546
+ console.log(pc6.dim(` import squintTagger from './${TAGGER_FILENAME}'
547
+ plugins: [squintTagger(), \u2026]`));
548
+ return;
549
+ }
550
+ const source = fs2.readFileSync(configPath, "utf8");
551
+ const patched = patchViteConfig(source);
552
+ if (patched === "already") {
553
+ console.log(pc6.dim("vite config already wired"));
554
+ } else if (patched === null) {
555
+ console.log(pc6.yellow(`\u25CB could not patch ${nodePath.basename(configPath)} automatically \u2014 add:`));
556
+ console.log(pc6.dim(` import squintTagger from './${TAGGER_FILENAME}'
557
+ plugins: [squintTagger(), \u2026]`));
558
+ } else {
559
+ fs2.writeFileSync(configPath, patched);
560
+ console.log(pc6.green(`\u2713 ${nodePath.basename(configPath)} wired`));
561
+ }
562
+ console.log(
563
+ pc6.dim("\nin the running app: Alt+S \u2192 click elements to pin, alt+enter copies all \u2014 paste into squint")
564
+ );
565
+ });
566
+ }
567
+
568
+ // src/cli/tui.tsx
569
+ import { render } from "ink";
570
+
154
571
  // src/tui/App.tsx
155
572
  import { Box as Box2, Static, Text as Text3, useApp, useInput } from "ink";
156
573
  import path3 from "path";
@@ -209,6 +626,7 @@ var Session = class {
209
626
  fixAttempts = 0;
210
627
  reviewTipShown = false;
211
628
  lastPulse = null;
629
+ autoReviewedThisAsk = false;
212
630
  startedAt = Date.now();
213
631
  subscribe(listener) {
214
632
  this.listeners.add(listener);
@@ -523,7 +941,20 @@ ${errors.slice(-5).join("\n")}`);
523
941
  this.push("status", "/fix sends open problems to the engine \xB7 /problems lists them");
524
942
  } else if (probe) {
525
943
  this.clearProblems("runtime");
526
- await this.visualPulse(probe.pulsePath);
944
+ const pct = await this.visualPulse(probe.pulsePath);
945
+ if (this.opts.autoReview && pct !== null && pct >= 10 && !this.autoReviewedThisAsk) {
946
+ this.autoReviewedThisAsk = true;
947
+ this.push("status", `auto-review: the page changed ${pct.toFixed(0)}% \u2014 capturing for self-critique`);
948
+ this.notify({ running: false });
949
+ const captureResult = await this.capture();
950
+ if (captureResult) {
951
+ await this.runTurn(
952
+ buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y),
953
+ "\u{1F441} auto-review rendered UI"
954
+ );
955
+ }
956
+ return;
957
+ }
527
958
  }
528
959
  }
529
960
  if (!this.reviewTipShown && this.state.devUrl) {
@@ -537,6 +968,7 @@ ${errors.slice(-5).join("\n")}`);
537
968
  }
538
969
  async submit(ask) {
539
970
  this.fixAttempts = 0;
971
+ this.autoReviewedThisAsk = false;
540
972
  const snapshot = takeSnapshot(this.opts.cwd);
541
973
  if (snapshot) {
542
974
  this.checkpoints.push({
@@ -580,25 +1012,26 @@ ${errors.slice(-5).join("\n")}`);
580
1012
  * Informational — changes are usually intended; surprises shouldn't be.
581
1013
  */
582
1014
  async visualPulse(pulsePath) {
583
- if (!pulsePath) return;
1015
+ if (!pulsePath) return null;
584
1016
  let current;
585
1017
  try {
586
1018
  current = (await import("fs")).readFileSync(pulsePath);
587
1019
  } catch {
588
- return;
1020
+ return null;
589
1021
  }
590
1022
  const previous = this.lastPulse;
591
1023
  this.lastPulse = current;
592
1024
  if (!previous) {
593
1025
  this.push("status", "visual pulse: baseline captured");
594
- return;
1026
+ return null;
595
1027
  }
596
1028
  const pct = await comparePulse(previous, current);
597
- if (pct === null) return;
1029
+ if (pct === null) return null;
598
1030
  this.push(
599
1031
  "status",
600
1032
  pct < 0.5 ? "visual pulse: stable vs last turn" : `visual pulse: ${pct.toFixed(1)}% of the page changed vs last turn`
601
1033
  );
1034
+ return pct;
602
1035
  }
603
1036
  /** Screenshot the running app (and watch its runtime where CDP is available). */
604
1037
  async capture() {
@@ -691,6 +1124,23 @@ They are objective defects, not style preferences.`
691
1124
  break;
692
1125
  case "dev": {
693
1126
  const dev = this.devServer();
1127
+ if (arg === "logs") {
1128
+ const tail = dev.tail(15);
1129
+ this.push("status", tail.length > 0 ? tail.join("\n") : "no dev server output captured yet");
1130
+ break;
1131
+ }
1132
+ if (arg === "restart") {
1133
+ if (dev.state !== "stopped") dev.stop();
1134
+ this.notify({ devUrl: null });
1135
+ const devCommand = detectDevCommand(this.opts.cwd);
1136
+ if (!devCommand) {
1137
+ this.push("error", "no dev/start script found in package.json");
1138
+ } else {
1139
+ dev.start(devCommand);
1140
+ this.push("status", `dev server restarting \xB7 ${devCommand.display}`);
1141
+ }
1142
+ break;
1143
+ }
694
1144
  if (dev.state === "stopped" || dev.state === "crashed") {
695
1145
  const devCommand = detectDevCommand(this.opts.cwd);
696
1146
  if (!devCommand) {
@@ -945,7 +1395,7 @@ They are objective defects, not style preferences.`
945
1395
  this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
946
1396
  break;
947
1397
  case "help": {
948
- void import("./commands-7DTZB4JE.js").then(({ commandHelp }) => this.push("status", commandHelp()));
1398
+ void import("./commands-TYAKF276.js").then(({ commandHelp }) => this.push("status", commandHelp()));
949
1399
  break;
950
1400
  }
951
1401
  case "quit":
@@ -1287,6 +1737,7 @@ function App({
1287
1737
  autoFix,
1288
1738
  autoProbe,
1289
1739
  autoCheck,
1740
+ autoReview,
1290
1741
  bell,
1291
1742
  budgetUsd,
1292
1743
  initialTheme
@@ -1304,6 +1755,7 @@ function App({
1304
1755
  autoFix,
1305
1756
  autoProbe,
1306
1757
  autoCheck,
1758
+ autoReview,
1307
1759
  budgetUsd,
1308
1760
  // Delay lets the goodbye summary land in the Static scrollback.
1309
1761
  onQuit: () => setTimeout(() => exit(), 60)
@@ -1492,409 +1944,49 @@ function App({
1492
1944
  ] }) });
1493
1945
  }
1494
1946
 
1495
- // src/cli.tsx
1947
+ // src/cli/tui.tsx
1496
1948
  import { jsx as jsx4 } from "react/jsx-runtime";
1497
- var VERSION = "0.2.6";
1498
- var program = new Command();
1499
- program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
1500
- 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(
1501
- async (promptWords, options) => {
1949
+ function registerTui(program2) {
1950
+ program2.action(async () => {
1502
1951
  const cwd = process.cwd();
1503
- if (options.mode && !["plan", "safe", "yolo"].includes(options.mode)) {
1504
- console.error(pc.red("\u2717 --mode must be plan, safe, or yolo"));
1505
- process.exitCode = 1;
1506
- return;
1507
- }
1508
- const mode = options.mode;
1509
1952
  const config = loadConfig(defaultPaths(cwd));
1510
- const engineId = resolveEngineId(config, options.engine);
1511
- const engine = getEngine(engineId);
1512
- const model = resolveModel(config, engineId, options.model);
1513
- const ask = promptWords.join(" ");
1514
- const prompt = composePrompt(ask, { cwd, noBrief: !options.brief });
1515
- const onEvent = options.json ? (event) => {
1516
- if (event.type !== "delta") console.log(JSON.stringify(event));
1517
- } : createPrinter();
1518
- if (!options.json)
1519
- console.log(pc.dim(`squint \xB7 ${engine.id}${model ? ` \xB7 ${model}` : ""}${mode && mode !== "safe" ? ` \xB7 ${mode}` : ""}`));
1520
- const result = await runAgent(engine, { prompt, cwd, model, mode }, onEvent);
1521
- if (options.json) {
1522
- console.log(JSON.stringify({ type: "summary", ...result }));
1523
- } else if (result.ok) {
1524
- const cost = result.costUsd !== void 0 ? ` \xB7 $${result.costUsd.toFixed(2)}` : "";
1525
- const secs = result.durationMs !== void 0 ? ` \xB7 ${(result.durationMs / 1e3).toFixed(0)}s` : "";
1526
- console.log(pc.green(`\u2713 done${secs}${cost}`));
1527
- }
1528
- if (!result.ok) process.exitCode = 1;
1529
- }
1530
- );
1531
- program.command("engines").description("List engines: installed, streaming, session resume").action(() => {
1532
- console.log(pc.dim(" id name stream resume where"));
1533
- for (const { engine, path: binaryPath } of detectEngines()) {
1534
- const status = binaryPath ? pc.green("\u2713") : pc.red("\u2717");
1535
- const stream = engine.createParser ? pc.green("yes") : pc.dim("text");
1536
- const resume = engine.supportsResume ? pc.green("yes") : pc.dim("no");
1537
- const location = binaryPath ?? pc.dim(`not found \u2014 ${engine.install}`);
1538
- console.log(
1539
- `${status} ${engine.id.padEnd(10)} ${engine.name.padEnd(14)} ${stream.padEnd(15)} ${resume.padEnd(14)} ${location}`
1540
- );
1541
- }
1542
- console.log(pc.dim("\nplan/safe/yolo modes map onto every engine \xB7 squint doctor --probe verifies auth"));
1543
- });
1544
- program.command("doctor").description("Check squint prerequisites and engine availability").option("--probe", "run each detected engine with a one-word prompt to verify auth works").action(async (options) => {
1545
- const [major] = process.versions.node.split(".");
1546
- const nodeOk = Number(major) >= 22;
1547
- console.log(`${nodeOk ? pc.green("\u2713") : pc.red("\u2717")} node ${process.versions.node}${nodeOk ? "" : " (need >= 22)"}`);
1548
- const detected = detectEngines();
1549
- for (const { engine, path: binaryPath } of detected) {
1550
- const status = binaryPath ? pc.green("\u2713") : pc.yellow("\u25CB");
1551
- console.log(`${status} ${engine.name}${binaryPath ? "" : pc.dim(` \u2014 install: ${engine.install}`)}`);
1552
- }
1553
- if (options.probe) {
1554
- const { runAgent: runAgent2 } = await import("./run-S772BPMZ.js");
1555
- console.log(pc.dim("\nprobing engines with a one-word prompt (verifies auth end to end)\u2026"));
1556
- for (const { engine, path: binaryPath } of detected) {
1557
- if (!binaryPath) continue;
1558
- const startedAt = Date.now();
1559
- const abort = new AbortController();
1560
- const timer = setTimeout(() => abort.abort(), 9e4);
1561
- const result = await runAgent2(
1562
- engine,
1563
- { prompt: "Reply with exactly: ok", cwd: process.cwd() },
1564
- () => {
1565
- },
1566
- abort.signal
1567
- );
1568
- clearTimeout(timer);
1569
- const secs = ((Date.now() - startedAt) / 1e3).toFixed(1);
1570
- const detail = (result.error ?? "failed").split("\n").filter((l) => l.trim()).at(-1) ?? "failed";
1571
- console.log(
1572
- result.ok ? `${pc.green("\u2713")} ${engine.id.padEnd(10)} responded in ${secs}s` : `${pc.red("\u2717")} ${engine.id.padEnd(10)} ${pc.dim(detail.slice(0, 110))}`
1573
- );
1574
- }
1575
- }
1576
- const { findChrome: findChrome2 } = await import("./chrome-NVU47PLK.js");
1577
- const { hasWebSocket } = await import("./cdp-SFWNP7OA.js");
1578
- const chrome = findChrome2();
1579
- console.log(
1580
- chrome ? `${pc.green("\u2713")} Chrome ${pc.dim(chrome)}` : `${pc.yellow("\u25CB")} Chrome ${pc.dim("\u2014 screenshots and runtime probing disabled")}`
1581
- );
1582
- console.log(
1583
- hasWebSocket() ? `${pc.green("\u2713")} WebSocket ${pc.dim("runtime console/network capture available")}` : `${pc.yellow("\u25CB")} WebSocket ${pc.dim("\u2014 node 22+ enables runtime capture; screenshots still work")}`
1584
- );
1585
- const available = detected.filter((d) => d.path !== null);
1586
- if (available.length === 0) {
1587
- console.log(pc.red("\nNo engines found. Install at least one to use squint."));
1588
- process.exitCode = 1;
1589
- } else {
1590
- console.log(pc.dim(`
1591
- ${available.length} engine(s) ready.`));
1592
- }
1593
- });
1594
- program.command("init").description("Scaffold a new Vite + React + TS + Tailwind app with token-first CSS").argument("[dir]", "target directory", ".").option("--force", "write into a non-empty directory").option("--no-install", "skip npm install").action(async (dir, options) => {
1595
- const { installDependencies, writeTemplate } = await import("./init-LSYB32NS.js");
1596
- let result;
1597
- try {
1598
- result = writeTemplate(dir, { force: options.force });
1599
- } catch (err) {
1600
- console.error(pc.red(`\u2717 ${err instanceof Error ? err.message : String(err)}`));
1601
- process.exitCode = 1;
1602
- return;
1603
- }
1604
- console.log(pc.green(`\u2713 scaffolded ${result.files.length} files in ${result.dir}`));
1605
- if (options.install) {
1606
- console.log(pc.dim("installing dependencies\u2026"));
1607
- const ok = await installDependencies(result.dir);
1608
- if (!ok) {
1609
- console.error(pc.red("\u2717 npm install failed \u2014 run it manually"));
1610
- process.exitCode = 1;
1611
- return;
1953
+ const engineId = resolveEngineId(config);
1954
+ const model = resolveModel(config, engineId);
1955
+ let theme2 = config.theme;
1956
+ if (!theme2 && !process.env.NO_COLOR) {
1957
+ const { detectBackground } = await import("./background-J7OQTYEC.js");
1958
+ if (await detectBackground() === "light") theme2 = "light";
1612
1959
  }
1613
- }
1614
- const cd = result.dir === "." ? "" : `cd ${result.dir} && `;
1615
- console.log(`
1616
- Next: ${pc.bold(`${cd}${options.install ? "" : "npm install && "}squint`)}`);
1617
- console.log(pc.dim("then describe what to build \u2014 /dev starts the preview server"));
1618
- });
1619
- var skillsCommand = program.command("skills").description("Project knowledge injected into asks (.squint/rules.md + .squint/skills/)");
1620
- skillsCommand.command("list").description("Show always-on rules and trigger-matched skills").action(async () => {
1621
- const { loadRules, loadSkills } = await import("./skills-DNBHZAAU.js");
1622
- const cwd = process.cwd();
1623
- const rules = loadRules(cwd);
1624
- console.log(rules ? `${pc.green("\u2713")} rules.md ${pc.dim(`(${rules.split("\n").length} lines, always on)`)}` : pc.dim("\u25CB no .squint/rules.md"));
1625
- const skills = loadSkills(cwd);
1626
- if (skills.length === 0) {
1627
- console.log(pc.dim("\u25CB no skills \u2014 squint skills init writes an example"));
1628
- return;
1629
- }
1630
- for (const skill of skills) {
1631
- console.log(`${pc.green("\u2713")} ${skill.name.padEnd(20)} ${pc.dim(`triggers: ${skill.triggers.join(", ")}`)}`);
1632
- }
1633
- });
1634
- skillsCommand.command("init").description("Scaffold .squint/rules.md and an example skill").action(async () => {
1635
- const fs2 = await import("fs");
1636
- const nodePath = await import("path");
1637
- const cwd = process.cwd();
1638
- const skillsDir = nodePath.join(cwd, ".squint", "skills");
1639
- fs2.mkdirSync(skillsDir, { recursive: true });
1640
- const rules = nodePath.join(cwd, ".squint", "rules.md");
1641
- if (!fs2.existsSync(rules)) {
1642
- fs2.writeFileSync(
1643
- rules,
1644
- "# Project rules\n\nThese ride along on every squint ask. Keep them short \u2014 cut anything that would not cause a mistake if removed.\n"
1645
- );
1646
- console.log(pc.green("\u2713 .squint/rules.md"));
1647
- }
1648
- const example = nodePath.join(skillsDir, "example.md");
1649
- if (!fs2.existsSync(example)) {
1650
- fs2.writeFileSync(
1651
- example,
1652
- "---\ntriggers: example, sample\n---\n\nThis note is injected only when an ask mentions one of the triggers above.\nDocument the parts of this repo an agent would otherwise rediscover every time:\nwhere state lives, which helpers to reuse, what not to touch.\n"
1653
- );
1654
- console.log(pc.green("\u2713 .squint/skills/example.md"));
1655
- }
1656
- console.log(pc.dim("rules are always-on; skills inject when an ask mentions a trigger"));
1657
- });
1658
- program.command("tag").description("Add the element picker to this Vite app (Alt+S in the browser \u2192 click \u2192 file:line:col)").action(async () => {
1659
- const fs2 = await import("fs");
1660
- const nodePath = await import("path");
1661
- const { patchViteConfig, TAGGER_FILENAME, TAGGER_SOURCE } = await import("./source-ZZU245VN.js");
1662
- const cwd = process.cwd();
1663
- const taggerPath = nodePath.join(cwd, TAGGER_FILENAME);
1664
- fs2.writeFileSync(taggerPath, TAGGER_SOURCE);
1665
- console.log(pc.green(`\u2713 ${TAGGER_FILENAME} written`));
1666
- const configPath = ["vite.config.ts", "vite.config.js", "vite.config.mjs"].map((name) => nodePath.join(cwd, name)).find((candidate) => fs2.existsSync(candidate));
1667
- if (!configPath) {
1668
- console.log(pc.yellow("\u25CB no vite config found \u2014 add the plugin manually:"));
1669
- console.log(pc.dim(` import squintTagger from './${TAGGER_FILENAME}'
1670
- plugins: [squintTagger(), \u2026]`));
1671
- return;
1672
- }
1673
- const source = fs2.readFileSync(configPath, "utf8");
1674
- const patched = patchViteConfig(source);
1675
- if (patched === "already") {
1676
- console.log(pc.dim("vite config already wired"));
1677
- } else if (patched === null) {
1678
- console.log(pc.yellow(`\u25CB could not patch ${nodePath.basename(configPath)} automatically \u2014 add:`));
1679
- console.log(pc.dim(` import squintTagger from './${TAGGER_FILENAME}'
1680
- plugins: [squintTagger(), \u2026]`));
1681
- } else {
1682
- fs2.writeFileSync(configPath, patched);
1683
- console.log(pc.green(`\u2713 ${nodePath.basename(configPath)} wired`));
1684
- }
1685
- console.log(pc.dim("\nin the running app: Alt+S \u2192 click an element \u2192 paste the copied file:line:col into squint"));
1686
- });
1687
- var variantsCommand = program.command("variants").description("Parallel design explorations \u2014 one aesthetic family each, pick with your eyes");
1688
- variantsCommand.command("gen").description("Generate n variants of one ask in parallel (n engine runs \u2014 n\xD7 cost)").argument("<n>", "how many variants (max 4)").argument("<prompt...>", "what to build").option("-e, --engine <id>", "engine to use").option("-m, --model <name>", "model override").option("--no-shots", "skip the screenshot pass").action(
1689
- async (nRaw, promptWords, options) => {
1690
- const cwd = process.cwd();
1691
- const n = Number.parseInt(nRaw, 10);
1692
- if (!Number.isInteger(n) || n < 2 || n > 4) {
1693
- console.error(pc.red("\u2717 n must be 2\u20134"));
1694
- process.exitCode = 1;
1695
- return;
1696
- }
1697
- const { isGitRepo: isGitRepo2 } = await import("./snapshot-H6VYFHLN.js");
1698
- if (!isGitRepo2(cwd)) {
1699
- console.error(pc.red("\u2717 variants need a git repo with at least one commit"));
1700
- process.exitCode = 1;
1701
- return;
1702
- }
1703
- const { runVariants: runVariants2, cleanVariants: cleanVariants2 } = await import("./variants-2IM274AL.js");
1704
- const config = loadConfig(defaultPaths(cwd));
1705
- const engineId = resolveEngineId(config, options.engine);
1706
- const engine = getEngine(engineId);
1707
- const model = resolveModel(config, engineId, options.model);
1708
- const ask = promptWords.join(" ");
1709
- cleanVariants2(cwd);
1710
- console.log(pc.dim(`generating ${n} directions in parallel via ${engine.id} \u2014 this runs ${n} engine sessions`));
1711
- const runs = await runVariants2(
1712
- cwd,
1713
- ask,
1714
- n,
1715
- engine,
1716
- model,
1717
- (familyId, text) => console.log(`${pc.cyan(familyId.padEnd(18))} ${pc.dim(text)}`)
1718
- );
1719
- const succeeded = runs.filter((r) => r.result.ok);
1720
- if (options.shots && succeeded.length > 0) {
1721
- const { screenshotVariants: screenshotVariants2 } = await import("./shots-J7JG2FC4.js");
1722
- console.log(pc.dim("capturing screenshots\u2026"));
1723
- const shots = await screenshotVariants2(cwd, succeeded.map((r) => r.variant));
1724
- for (const shot of shots) {
1725
- console.log(`${pc.green("\u2713")} ${shot.familyId.padEnd(18)} ${shot.path ?? pc.dim(shot.error ?? "")}`);
1726
- }
1727
- }
1728
- console.log(
1729
- `
1730
- ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 ` + pc.bold("squint variants apply <id>") + pc.dim(" applies the winner, squint variants clean discards all")
1960
+ render(
1961
+ /* @__PURE__ */ jsx4(
1962
+ App,
1963
+ {
1964
+ cwd,
1965
+ initialEngine: engineId,
1966
+ initialModel: model,
1967
+ autoDev: config.autoDev,
1968
+ autoFix: config.autoFix,
1969
+ autoProbe: config.autoProbe,
1970
+ autoCheck: config.autoCheck,
1971
+ autoReview: config.autoReview,
1972
+ bell: config.bell,
1973
+ budgetUsd: config.budgetUsd,
1974
+ initialTheme: theme2
1975
+ }
1976
+ )
1731
1977
  );
1732
- if (succeeded.length === 0) process.exitCode = 1;
1733
- }
1734
- );
1735
- variantsCommand.command("list").description("List generated variants").action(async () => {
1736
- const { listVariants: listVariants2 } = await import("./variants-2IM274AL.js");
1737
- const ids = listVariants2(process.cwd());
1738
- if (ids.length === 0) {
1739
- console.log(pc.dim('no variants \u2014 squint variants gen <n> "<ask>"'));
1740
- return;
1741
- }
1742
- for (const id of ids) console.log(id);
1743
- });
1744
- 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) => {
1745
- const { applyVariant: applyVariant2, cleanVariants: cleanVariants2 } = await import("./variants-2IM274AL.js");
1746
- const cwd = process.cwd();
1747
- const result = applyVariant2(cwd, id);
1748
- if (!result.ok) {
1749
- console.error(pc.red(`\u2717 ${result.detail}`));
1750
- process.exitCode = 1;
1751
- return;
1752
- }
1753
- cleanVariants2(cwd);
1754
- console.log(pc.green(`\u2713 applied ${id} to the working tree`) + pc.dim(" \u2014 review with git diff"));
1755
- });
1756
- variantsCommand.command("clean").description("Discard all variants").action(async () => {
1757
- const { cleanVariants: cleanVariants2 } = await import("./variants-2IM274AL.js");
1758
- const count = cleanVariants2(process.cwd());
1759
- console.log(pc.dim(`removed ${count} variant(s)`));
1760
- });
1761
- program.command("brief").description("Set a committed design direction for this project (.squint/brief.md)").argument("[family]", "aesthetic family id (omit to list)").option("--force", "overwrite an existing project brief").action(async (familyId, options) => {
1762
- const fs2 = await import("fs");
1763
- const nodePath = await import("path");
1764
- const { FAMILIES, getFamily, renderFamilyBrief } = await import("./families-2G5SLLY7.js");
1765
- if (!familyId) {
1766
- console.log(pc.bold("Aesthetic families") + pc.dim(" \u2014 squint brief <id>\n"));
1767
- for (const family2 of FAMILIES) {
1768
- console.log(`${pc.green(family2.id.padEnd(18))} ${family2.name.padEnd(22)} ${pc.dim(family2.summary)}`);
1769
- }
1770
- console.log(pc.dim("\nThe brief wraps every ask; edit .squint/brief.md to remix."));
1771
- return;
1772
- }
1773
- const family = getFamily(familyId);
1774
- if (!family) {
1775
- console.error(pc.red(`\u2717 unknown family "${familyId}" \u2014 run squint brief to list`));
1776
- process.exitCode = 1;
1777
- return;
1778
- }
1779
- const target = nodePath.join(process.cwd(), ".squint", "brief.md");
1780
- if (fs2.existsSync(target) && !options.force) {
1781
- console.error(pc.red(`\u2717 ${target} exists \u2014 use --force to overwrite`));
1782
- process.exitCode = 1;
1783
- return;
1784
- }
1785
- fs2.mkdirSync(nodePath.dirname(target), { recursive: true });
1786
- fs2.writeFileSync(target, renderFamilyBrief(family) + "\n");
1787
- console.log(pc.green(`\u2713 ${family.name} direction written to .squint/brief.md`));
1788
- console.log(pc.dim("every squint ask in this repo now holds this direction \u2014 edit the file to remix"));
1789
- });
1790
- program.command("check").description("Run this project\u2019s quality gates (typecheck, lint, test, build)").action(async () => {
1791
- const { detectGates: detectGates2, runGates: runGates2 } = await import("./gates-ZC67NMW3.js");
1792
- const cwd = process.cwd();
1793
- const gates = detectGates2(cwd);
1794
- if (gates.length === 0) {
1795
- console.log(pc.dim("no gates detected (no package.json scripts, tsconfig, or eslint config)"));
1796
- return;
1797
- }
1798
- console.log(pc.dim(`running ${gates.map((g) => g.id).join(" \u2192 ")}`));
1799
- const results = await runGates2(cwd, gates, (result) => {
1800
- const mark = result.ok ? pc.green("\u2713") : pc.red("\u2717");
1801
- console.log(`${mark} ${result.gate.id.padEnd(10)} ${pc.dim(`${(result.durationMs / 1e3).toFixed(1)}s \xB7 ${result.gate.display}`)}`);
1802
- if (!result.ok) console.log(pc.dim(result.outputTail.split("\n").slice(-12).join("\n")));
1803
1978
  });
1804
- if (results.some((r) => !r.ok)) process.exitCode = 1;
1805
- });
1806
- 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) => {
1807
- const { captureViewports: captureViewports2 } = await import("./preview-UCH7H5R5.js");
1808
- const result = await captureViewports2(process.cwd(), url);
1809
- if (!result) {
1810
- console.error(pc.red("\u2717 no Chrome/Chromium found"));
1811
- process.exitCode = 1;
1812
- return;
1813
- }
1814
- for (const shot of result.shots) console.log(`${pc.green("\u2713")} ${shot.name.padEnd(8)} ${shot.path}`);
1815
- for (const error of result.errors) console.error(pc.red(`\u2717 ${error}`));
1816
- if (result.shots.length === 0) process.exitCode = 1;
1817
- });
1818
- var configCommand = program.command("config").description("Read or change squint configuration");
1819
- configCommand.command("get").description("Print the resolved configuration").action(() => {
1820
- const paths = defaultPaths(process.cwd());
1821
- console.log(JSON.stringify(loadConfig(paths), null, 2));
1822
- });
1823
- configCommand.command("set").description("Set a value (keys: engine, models.<engineId>)").argument("<key>").argument("<value>").option("--project", "write to this project (.squint/config.json) instead of global").action((key, value, options) => {
1824
- const paths = defaultPaths(process.cwd());
1825
- const file = options.project ? paths.projectFile : paths.globalFile;
1826
- setConfigValue(file, key, value);
1827
- console.log(pc.green(`\u2713 ${key} = ${value}`) + pc.dim(` (${file})`));
1828
- });
1829
- configCommand.command("path").description("Show config file locations").action(() => {
1830
- const paths = defaultPaths(process.cwd());
1831
- console.log(`global ${paths.globalFile}`);
1832
- console.log(`project ${paths.projectFile}`);
1833
- });
1834
- program.action(async () => {
1835
- const cwd = process.cwd();
1836
- const config = loadConfig(defaultPaths(cwd));
1837
- const engineId = resolveEngineId(config);
1838
- const model = resolveModel(config, engineId);
1839
- let theme2 = config.theme;
1840
- if (!theme2 && !process.env.NO_COLOR) {
1841
- const { detectBackground } = await import("./background-J7OQTYEC.js");
1842
- if (await detectBackground() === "light") theme2 = "light";
1843
- }
1844
- render(
1845
- /* @__PURE__ */ jsx4(
1846
- App,
1847
- {
1848
- cwd,
1849
- initialEngine: engineId,
1850
- initialModel: model,
1851
- autoDev: config.autoDev,
1852
- autoFix: config.autoFix,
1853
- autoProbe: config.autoProbe,
1854
- autoCheck: config.autoCheck,
1855
- bell: config.bell,
1856
- budgetUsd: config.budgetUsd,
1857
- initialTheme: theme2
1858
- }
1859
- )
1860
- );
1861
- });
1862
- function createPrinter() {
1863
- let streaming = false;
1864
- return (event) => {
1865
- switch (event.type) {
1866
- case "status":
1867
- console.log(pc.dim(`\xB7 ${event.text}`));
1868
- break;
1869
- case "delta":
1870
- streaming = true;
1871
- process.stdout.write(event.text);
1872
- break;
1873
- case "text":
1874
- if (event.streamed && streaming) {
1875
- process.stdout.write("\n");
1876
- } else {
1877
- console.log(event.text);
1878
- }
1879
- streaming = false;
1880
- break;
1881
- case "thinking":
1882
- console.log(pc.dim(pc.italic(event.text)));
1883
- break;
1884
- case "tool":
1885
- if (streaming) {
1886
- process.stdout.write("\n");
1887
- streaming = false;
1888
- }
1889
- console.log(pc.cyan(`\u2699 ${event.name}${event.detail ? ` \xB7 ${event.detail}` : ""}`));
1890
- break;
1891
- case "error":
1892
- console.error(pc.red(`\u2717 ${event.text}`));
1893
- break;
1894
- case "result":
1895
- case "raw":
1896
- break;
1897
- }
1898
- };
1899
1979
  }
1980
+
1981
+ // src/cli.tsx
1982
+ var VERSION = "0.2.7";
1983
+ var program = new Command();
1984
+ program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
1985
+ registerRun(program);
1986
+ registerEnv(program);
1987
+ registerScaffold(program);
1988
+ registerProject(program);
1989
+ registerQuality(program);
1990
+ registerConfig(program);
1991
+ registerTui(program);
1900
1992
  program.parseAsync(process.argv);
@@ -3,7 +3,7 @@ import {
3
3
  COMMANDS,
4
4
  commandHelp,
5
5
  completeCommand
6
- } from "./chunk-V3M4WQLH.js";
6
+ } from "./chunk-RZOQPFXC.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-YZEFBPBJ.js";
14
- import "./chunk-OC6RU6XH.js";
13
+ } from "./chunk-CHZE3UHK.js";
15
14
  import "./chunk-B7LOERSP.js";
16
15
  import "./chunk-BWZFACBT.js";
16
+ import "./chunk-OC6RU6XH.js";
17
17
  export {
18
18
  VIEWPORTS,
19
19
  buildReviewPrompt,
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  screenshotVariants
4
- } from "./chunk-43L4QXCM.js";
5
- import "./chunk-LVA2OQSR.js";
6
- import "./chunk-P3H4N2EN.js";
4
+ } from "./chunk-5VKUJPOL.js";
5
+ import "./chunk-X6MDBKED.js";
7
6
  import "./chunk-C7WKNJG6.js";
8
7
  import "./chunk-B7LOERSP.js";
9
8
  import "./chunk-BWZFACBT.js";
9
+ import "./chunk-P3H4N2EN.js";
10
10
  export {
11
11
  screenshotVariants
12
12
  };
@@ -9,10 +9,10 @@ import {
9
9
  runVariants,
10
10
  variantPrompt,
11
11
  variantsRoot
12
- } from "./chunk-LVA2OQSR.js";
13
- import "./chunk-P3H4N2EN.js";
12
+ } from "./chunk-X6MDBKED.js";
14
13
  import "./chunk-C7WKNJG6.js";
15
14
  import "./chunk-BWZFACBT.js";
15
+ import "./chunk-P3H4N2EN.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.6",
3
+ "version": "0.2.7",
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 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- cdpCapture,
4
- hasWebSocket
5
- } from "./chunk-OC6RU6XH.js";
6
2
  import {
7
3
  findChrome,
8
4
  screenshot
9
5
  } from "./chunk-B7LOERSP.js";
6
+ import {
7
+ cdpCapture,
8
+ hasWebSocket
9
+ } from "./chunk-OC6RU6XH.js";
10
10
 
11
11
  // src/preview/preview.ts
12
12
  import fs2 from "fs";
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ runAgent
4
+ } from "./chunk-C7WKNJG6.js";
2
5
  import {
3
6
  FAMILIES,
4
7
  FIRST_TURN_ADDENDUM,
5
8
  renderFamilyBrief
6
9
  } from "./chunk-P3H4N2EN.js";
7
- import {
8
- runAgent
9
- } from "./chunk-C7WKNJG6.js";
10
10
 
11
11
  // src/variants/variants.ts
12
12
  import { execFileSync } from "child_process";