@aayambansal/squint 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/{cdp-SFWNP7OA.js → cdp-4IZHB3W5.js} +1 -1
- package/dist/{chrome-NVU47PLK.js → chrome-SBV3H77F.js} +2 -2
- package/dist/{chunk-X6MDBKED.js → chunk-A64VVTPU.js} +1 -1
- package/dist/chunk-CFWKEIJJ.js +55 -0
- package/dist/{chunk-5VKUJPOL.js → chunk-F6JZSXEO.js} +3 -3
- package/dist/{chunk-B7LOERSP.js → chunk-IMDRXXFU.js} +1 -1
- package/dist/{chunk-BWZFACBT.js → chunk-KVYGPLWW.js} +12 -2
- package/dist/chunk-O2S6PAJE.js +51 -0
- package/dist/{chunk-OC6RU6XH.js → chunk-QEENJGCL.js} +55 -1
- package/dist/{chunk-CHZE3UHK.js → chunk-RQFFAJFE.js} +25 -60
- package/dist/{chunk-C7WKNJG6.js → chunk-VH7OOFQP.js} +1 -1
- package/dist/chunk-XEQ6JXXL.js +97 -0
- package/dist/cli.js +183 -164
- package/dist/{commands-N33DKZKC.js → commands-QKDWUD4K.js} +1 -1
- package/dist/{preview-XGASKUXR.js → preview-56UCV5A3.js} +5 -4
- package/dist/{registry-S3PCXJVF.js → registry-MIJ6LSAY.js} +1 -1
- package/dist/{run-S772BPMZ.js → run-NDSNTVYP.js} +2 -2
- package/dist/sandbox-J2NCPYHJ.js +19 -0
- package/dist/shots-E2AWBDCN.js +12 -0
- package/dist/state-QOS7WCZO.js +13 -0
- package/dist/{variants-UULPQ7Q5.js → variants-3ZSINJGX.js} +3 -3
- package/package.json +2 -1
- package/dist/chunk-YTO2YRF7.js +0 -41
- package/dist/shots-FVG4CDVK.js +0 -12
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
completeCommand
|
|
4
|
+
} from "./chunk-CFWKEIJJ.js";
|
|
5
|
+
import {
|
|
6
|
+
applySandbox,
|
|
7
|
+
discardSandbox,
|
|
8
|
+
openSandbox,
|
|
9
|
+
sandboxDiffStat,
|
|
10
|
+
sandboxDir,
|
|
11
|
+
sandboxExists,
|
|
12
|
+
sandboxFiles
|
|
13
|
+
} from "./chunk-XEQ6JXXL.js";
|
|
14
|
+
import {
|
|
15
|
+
diffStatSince,
|
|
16
|
+
isGitRepo,
|
|
17
|
+
restoreSnapshot,
|
|
18
|
+
takeSnapshot
|
|
19
|
+
} from "./chunk-YHRAOBI2.js";
|
|
2
20
|
import {
|
|
3
21
|
DevServer,
|
|
4
22
|
buildFixPrompt,
|
|
5
23
|
detectDevCommand,
|
|
6
24
|
screenshotVariants
|
|
7
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-F6JZSXEO.js";
|
|
8
26
|
import {
|
|
9
27
|
applyVariant,
|
|
10
28
|
cleanVariants,
|
|
11
29
|
listVariants,
|
|
12
30
|
runVariants
|
|
13
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-A64VVTPU.js";
|
|
14
32
|
import {
|
|
15
33
|
buildGatePrompt,
|
|
16
34
|
detectFastGates,
|
|
@@ -21,27 +39,21 @@ import {
|
|
|
21
39
|
buildReviewPrompt,
|
|
22
40
|
buildRuntimeFixPrompt,
|
|
23
41
|
captureViewports,
|
|
24
|
-
clearState,
|
|
25
42
|
comparePulse,
|
|
26
|
-
loadState,
|
|
27
43
|
probeRuntime,
|
|
28
|
-
runtimeSummary
|
|
29
|
-
|
|
30
|
-
} from "./chunk-CHZE3UHK.js";
|
|
31
|
-
import {
|
|
32
|
-
completeCommand
|
|
33
|
-
} from "./chunk-YTO2YRF7.js";
|
|
44
|
+
runtimeSummary
|
|
45
|
+
} from "./chunk-RQFFAJFE.js";
|
|
34
46
|
import {
|
|
35
47
|
runAgent
|
|
36
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-VH7OOFQP.js";
|
|
37
49
|
import {
|
|
38
50
|
findChrome
|
|
39
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-IMDRXXFU.js";
|
|
40
52
|
import {
|
|
41
53
|
detectEngines,
|
|
42
54
|
getEngine
|
|
43
|
-
} from "./chunk-
|
|
44
|
-
import "./chunk-
|
|
55
|
+
} from "./chunk-KVYGPLWW.js";
|
|
56
|
+
import "./chunk-QEENJGCL.js";
|
|
45
57
|
import {
|
|
46
58
|
enrich
|
|
47
59
|
} from "./chunk-4LAU5TGK.js";
|
|
@@ -49,11 +61,10 @@ import {
|
|
|
49
61
|
composePrompt
|
|
50
62
|
} from "./chunk-P3H4N2EN.js";
|
|
51
63
|
import {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
} from "./chunk-YHRAOBI2.js";
|
|
64
|
+
clearState,
|
|
65
|
+
loadState,
|
|
66
|
+
saveState
|
|
67
|
+
} from "./chunk-O2S6PAJE.js";
|
|
57
68
|
|
|
58
69
|
// src/cli.tsx
|
|
59
70
|
import { Command } from "commander";
|
|
@@ -202,7 +213,7 @@ function registerEnv(program2) {
|
|
|
202
213
|
console.log(`${status} ${engine.name}${binaryPath ? "" : pc2.dim(` \u2014 install: ${engine.install}`)}`);
|
|
203
214
|
}
|
|
204
215
|
if (options.probe) {
|
|
205
|
-
const { runAgent: runAgent2 } = await import("./run-
|
|
216
|
+
const { runAgent: runAgent2 } = await import("./run-NDSNTVYP.js");
|
|
206
217
|
console.log(pc2.dim("\nprobing engines with a one-word prompt (verifies auth end to end)\u2026"));
|
|
207
218
|
for (const { engine, path: binaryPath } of detected) {
|
|
208
219
|
if (!binaryPath) continue;
|
|
@@ -224,8 +235,8 @@ function registerEnv(program2) {
|
|
|
224
235
|
);
|
|
225
236
|
}
|
|
226
237
|
}
|
|
227
|
-
const { findChrome: findChrome2 } = await import("./chrome-
|
|
228
|
-
const { hasWebSocket } = await import("./cdp-
|
|
238
|
+
const { findChrome: findChrome2 } = await import("./chrome-SBV3H77F.js");
|
|
239
|
+
const { hasWebSocket } = await import("./cdp-4IZHB3W5.js");
|
|
229
240
|
const chrome = findChrome2();
|
|
230
241
|
console.log(
|
|
231
242
|
chrome ? `${pc2.green("\u2713")} Chrome ${pc2.dim(chrome)}` : `${pc2.yellow("\u25CB")} Chrome ${pc2.dim("\u2014 screenshots and runtime probing disabled")}`
|
|
@@ -265,22 +276,22 @@ function registerProject(program2) {
|
|
|
265
276
|
}
|
|
266
277
|
});
|
|
267
278
|
skillsCommand.command("init").description("Scaffold .squint/rules.md and an example skill").action(async () => {
|
|
268
|
-
const
|
|
279
|
+
const fs2 = await import("fs");
|
|
269
280
|
const nodePath = await import("path");
|
|
270
281
|
const cwd = process.cwd();
|
|
271
282
|
const skillsDir = nodePath.join(cwd, ".squint", "skills");
|
|
272
|
-
|
|
283
|
+
fs2.mkdirSync(skillsDir, { recursive: true });
|
|
273
284
|
const rules = nodePath.join(cwd, ".squint", "rules.md");
|
|
274
|
-
if (!
|
|
275
|
-
|
|
285
|
+
if (!fs2.existsSync(rules)) {
|
|
286
|
+
fs2.writeFileSync(
|
|
276
287
|
rules,
|
|
277
288
|
"# 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
289
|
);
|
|
279
290
|
console.log(pc3.green("\u2713 .squint/rules.md"));
|
|
280
291
|
}
|
|
281
292
|
const example = nodePath.join(skillsDir, "example.md");
|
|
282
|
-
if (!
|
|
283
|
-
|
|
293
|
+
if (!fs2.existsSync(example)) {
|
|
294
|
+
fs2.writeFileSync(
|
|
284
295
|
example,
|
|
285
296
|
"---\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
297
|
);
|
|
@@ -289,7 +300,7 @@ function registerProject(program2) {
|
|
|
289
300
|
console.log(pc3.dim("rules are always-on; skills inject when an ask mentions a trigger"));
|
|
290
301
|
});
|
|
291
302
|
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
|
|
303
|
+
const fs2 = await import("fs");
|
|
293
304
|
const nodePath = await import("path");
|
|
294
305
|
const { FAMILIES, getFamily, renderFamilyBrief } = await import("./families-2G5SLLY7.js");
|
|
295
306
|
if (!familyId) {
|
|
@@ -307,16 +318,49 @@ function registerProject(program2) {
|
|
|
307
318
|
return;
|
|
308
319
|
}
|
|
309
320
|
const target = nodePath.join(process.cwd(), ".squint", "brief.md");
|
|
310
|
-
if (
|
|
321
|
+
if (fs2.existsSync(target) && !options.force) {
|
|
311
322
|
console.error(pc3.red(`\u2717 ${target} exists \u2014 use --force to overwrite`));
|
|
312
323
|
process.exitCode = 1;
|
|
313
324
|
return;
|
|
314
325
|
}
|
|
315
|
-
|
|
316
|
-
|
|
326
|
+
fs2.mkdirSync(nodePath.dirname(target), { recursive: true });
|
|
327
|
+
fs2.writeFileSync(target, renderFamilyBrief(family) + "\n");
|
|
317
328
|
console.log(pc3.green(`\u2713 ${family.name} direction written to .squint/brief.md`));
|
|
318
329
|
console.log(pc3.dim("every squint ask in this repo now holds this direction \u2014 edit the file to remix"));
|
|
319
330
|
});
|
|
331
|
+
const sandboxCommand = program2.command("sandbox").description("Cumulative diff worktree \u2014 asks accumulate until you apply");
|
|
332
|
+
sandboxCommand.command("diff").description("Show accumulated sandbox changes").action(async () => {
|
|
333
|
+
const { sandboxDiffStat: sandboxDiffStat2, sandboxExists: sandboxExists2, sandboxFiles: sandboxFiles2 } = await import("./sandbox-J2NCPYHJ.js");
|
|
334
|
+
const cwd = process.cwd();
|
|
335
|
+
if (!sandboxExists2(cwd)) {
|
|
336
|
+
console.log(pc3.dim("no sandbox open \u2014 /sandbox on inside the TUI"));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const stat = sandboxDiffStat2(cwd);
|
|
340
|
+
if (!stat) {
|
|
341
|
+
console.log(pc3.dim("sandbox is clean"));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
console.log(stat);
|
|
345
|
+
for (const line of sandboxFiles2(cwd)) console.log(pc3.dim(line));
|
|
346
|
+
});
|
|
347
|
+
sandboxCommand.command("apply").description("Land the sandbox diff on the real tree and close it").action(async () => {
|
|
348
|
+
const { applySandbox: applySandbox2, discardSandbox: discardSandbox2 } = await import("./sandbox-J2NCPYHJ.js");
|
|
349
|
+
const cwd = process.cwd();
|
|
350
|
+
const result = applySandbox2(cwd);
|
|
351
|
+
if (!result.ok) {
|
|
352
|
+
console.error(pc3.red(`\u2717 ${result.detail}`));
|
|
353
|
+
process.exitCode = 1;
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
discardSandbox2(cwd);
|
|
357
|
+
console.log(pc3.green("\u2713 sandbox applied to the real tree") + pc3.dim(" \u2014 review with git diff"));
|
|
358
|
+
});
|
|
359
|
+
sandboxCommand.command("discard").description("Close the sandbox without touching the real tree").action(async () => {
|
|
360
|
+
const { discardSandbox: discardSandbox2 } = await import("./sandbox-J2NCPYHJ.js");
|
|
361
|
+
const had = discardSandbox2(process.cwd());
|
|
362
|
+
console.log(pc3.dim(had ? "sandbox discarded" : "no sandbox open"));
|
|
363
|
+
});
|
|
320
364
|
const variantsCommand = program2.command("variants").description("Parallel design explorations \u2014 one aesthetic family each, pick with your eyes");
|
|
321
365
|
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
366
|
async (nRaw, promptWords, options) => {
|
|
@@ -333,7 +377,7 @@ function registerProject(program2) {
|
|
|
333
377
|
process.exitCode = 1;
|
|
334
378
|
return;
|
|
335
379
|
}
|
|
336
|
-
const { runVariants: runVariants2, cleanVariants: cleanVariants2 } = await import("./variants-
|
|
380
|
+
const { runVariants: runVariants2, cleanVariants: cleanVariants2 } = await import("./variants-3ZSINJGX.js");
|
|
337
381
|
const config = loadConfig(defaultPaths(cwd));
|
|
338
382
|
const engineId = resolveEngineId(config, options.engine);
|
|
339
383
|
const engine = getEngine(engineId);
|
|
@@ -351,7 +395,7 @@ function registerProject(program2) {
|
|
|
351
395
|
);
|
|
352
396
|
const succeeded = runs.filter((r) => r.result.ok);
|
|
353
397
|
if (options.shots && succeeded.length > 0) {
|
|
354
|
-
const { screenshotVariants: screenshotVariants2 } = await import("./shots-
|
|
398
|
+
const { screenshotVariants: screenshotVariants2 } = await import("./shots-E2AWBDCN.js");
|
|
355
399
|
console.log(pc3.dim("capturing screenshots\u2026"));
|
|
356
400
|
const shots = await screenshotVariants2(cwd, succeeded.map((r) => r.variant));
|
|
357
401
|
for (const shot of shots) {
|
|
@@ -366,7 +410,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
366
410
|
}
|
|
367
411
|
);
|
|
368
412
|
variantsCommand.command("list").description("List generated variants").action(async () => {
|
|
369
|
-
const { listVariants: listVariants2 } = await import("./variants-
|
|
413
|
+
const { listVariants: listVariants2 } = await import("./variants-3ZSINJGX.js");
|
|
370
414
|
const ids = listVariants2(process.cwd());
|
|
371
415
|
if (ids.length === 0) {
|
|
372
416
|
console.log(pc3.dim('no variants \u2014 squint variants gen <n> "<ask>"'));
|
|
@@ -375,7 +419,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
375
419
|
for (const id of ids) console.log(id);
|
|
376
420
|
});
|
|
377
421
|
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-
|
|
422
|
+
const { applyVariant: applyVariant2, cleanVariants: cleanVariants2 } = await import("./variants-3ZSINJGX.js");
|
|
379
423
|
const cwd = process.cwd();
|
|
380
424
|
const result = applyVariant2(cwd, id);
|
|
381
425
|
if (!result.ok) {
|
|
@@ -387,7 +431,7 @@ ${succeeded.length}/${runs.length} variants ready in .squint/variants/ \u2014 `
|
|
|
387
431
|
console.log(pc3.green(`\u2713 applied ${id} to the working tree`) + pc3.dim(" \u2014 review with git diff"));
|
|
388
432
|
});
|
|
389
433
|
variantsCommand.command("clean").description("Discard all variants").action(async () => {
|
|
390
|
-
const { cleanVariants: cleanVariants2 } = await import("./variants-
|
|
434
|
+
const { cleanVariants: cleanVariants2 } = await import("./variants-3ZSINJGX.js");
|
|
391
435
|
const count = cleanVariants2(process.cwd());
|
|
392
436
|
console.log(pc3.dim(`removed ${count} variant(s)`));
|
|
393
437
|
});
|
|
@@ -415,7 +459,7 @@ function registerQuality(program2) {
|
|
|
415
459
|
if (results.some((r) => !r.ok)) process.exitCode = 1;
|
|
416
460
|
});
|
|
417
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) => {
|
|
418
|
-
const { captureViewports: captureViewports2 } = await import("./preview-
|
|
462
|
+
const { captureViewports: captureViewports2 } = await import("./preview-56UCV5A3.js");
|
|
419
463
|
const result = await captureViewports2(process.cwd(), url);
|
|
420
464
|
if (!result) {
|
|
421
465
|
console.error(pc4.red("\u2717 no Chrome/Chromium found"));
|
|
@@ -533,21 +577,21 @@ Next: ${pc6.bold(`${cd}${options.install ? "" : "npm install && "}squint`)}`);
|
|
|
533
577
|
console.log(pc6.dim("then describe what to build \u2014 /dev starts the preview server"));
|
|
534
578
|
});
|
|
535
579
|
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
|
|
580
|
+
const fs2 = await import("fs");
|
|
537
581
|
const nodePath = await import("path");
|
|
538
582
|
const { patchViteConfig, TAGGER_FILENAME, TAGGER_SOURCE } = await import("./source-ZZU245VN.js");
|
|
539
583
|
const cwd = process.cwd();
|
|
540
584
|
const taggerPath = nodePath.join(cwd, TAGGER_FILENAME);
|
|
541
|
-
|
|
585
|
+
fs2.writeFileSync(taggerPath, TAGGER_SOURCE);
|
|
542
586
|
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) =>
|
|
587
|
+
const configPath = ["vite.config.ts", "vite.config.js", "vite.config.mjs"].map((name) => nodePath.join(cwd, name)).find((candidate) => fs2.existsSync(candidate));
|
|
544
588
|
if (!configPath) {
|
|
545
589
|
console.log(pc6.yellow("\u25CB no vite config found \u2014 add the plugin manually:"));
|
|
546
590
|
console.log(pc6.dim(` import squintTagger from './${TAGGER_FILENAME}'
|
|
547
591
|
plugins: [squintTagger(), \u2026]`));
|
|
548
592
|
return;
|
|
549
593
|
}
|
|
550
|
-
const source =
|
|
594
|
+
const source = fs2.readFileSync(configPath, "utf8");
|
|
551
595
|
const patched = patchViteConfig(source);
|
|
552
596
|
if (patched === "already") {
|
|
553
597
|
console.log(pc6.dim("vite config already wired"));
|
|
@@ -556,7 +600,7 @@ Next: ${pc6.bold(`${cd}${options.install ? "" : "npm install && "}squint`)}`);
|
|
|
556
600
|
console.log(pc6.dim(` import squintTagger from './${TAGGER_FILENAME}'
|
|
557
601
|
plugins: [squintTagger(), \u2026]`));
|
|
558
602
|
} else {
|
|
559
|
-
|
|
603
|
+
fs2.writeFileSync(configPath, patched);
|
|
560
604
|
console.log(pc6.green(`\u2713 ${nodePath.basename(configPath)} wired`));
|
|
561
605
|
}
|
|
562
606
|
console.log(
|
|
@@ -569,98 +613,13 @@ Next: ${pc6.bold(`${cd}${options.install ? "" : "npm install && "}squint`)}`);
|
|
|
569
613
|
import { render } from "ink";
|
|
570
614
|
|
|
571
615
|
// src/tui/App.tsx
|
|
572
|
-
import { Box as
|
|
573
|
-
import
|
|
616
|
+
import { Box as Box3, Static, Text as Text3, useApp, useInput } from "ink";
|
|
617
|
+
import { InkPictureProvider } from "ink-picture";
|
|
618
|
+
import path3 from "path";
|
|
574
619
|
import { useMemo, useRef, useState as useState2, useSyncExternalStore } from "react";
|
|
575
620
|
|
|
576
621
|
// src/session/engine.ts
|
|
577
|
-
import path3 from "path";
|
|
578
|
-
|
|
579
|
-
// src/vcs/sandbox.ts
|
|
580
|
-
import { execFileSync } from "child_process";
|
|
581
|
-
import fs2 from "fs";
|
|
582
622
|
import path2 from "path";
|
|
583
|
-
function git(cwd, args) {
|
|
584
|
-
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
585
|
-
}
|
|
586
|
-
function sandboxDir(cwd) {
|
|
587
|
-
return path2.join(cwd, ".squint", "sandbox");
|
|
588
|
-
}
|
|
589
|
-
function sandboxExists(cwd) {
|
|
590
|
-
return fs2.existsSync(path2.join(sandboxDir(cwd), ".git"));
|
|
591
|
-
}
|
|
592
|
-
function openSandbox(cwd) {
|
|
593
|
-
const dir = sandboxDir(cwd);
|
|
594
|
-
if (sandboxExists(cwd)) return { dir, reused: true };
|
|
595
|
-
fs2.rmSync(dir, { recursive: true, force: true });
|
|
596
|
-
fs2.mkdirSync(path2.dirname(dir), { recursive: true });
|
|
597
|
-
git(cwd, ["worktree", "add", "--force", "--detach", dir, "HEAD"]);
|
|
598
|
-
const mainModules = path2.join(cwd, "node_modules");
|
|
599
|
-
const sandboxModules = path2.join(dir, "node_modules");
|
|
600
|
-
if (fs2.existsSync(mainModules) && !fs2.existsSync(sandboxModules)) {
|
|
601
|
-
fs2.symlinkSync(mainModules, sandboxModules, "dir");
|
|
602
|
-
}
|
|
603
|
-
return { dir, reused: false };
|
|
604
|
-
}
|
|
605
|
-
function sandboxDiffStat(cwd) {
|
|
606
|
-
if (!sandboxExists(cwd)) return null;
|
|
607
|
-
const dir = sandboxDir(cwd);
|
|
608
|
-
try {
|
|
609
|
-
git(dir, ["add", "-A"]);
|
|
610
|
-
const stat = git(dir, ["diff", "--cached", "--shortstat", "HEAD"]);
|
|
611
|
-
return stat.length > 0 ? stat : null;
|
|
612
|
-
} catch {
|
|
613
|
-
return null;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
function sandboxFiles(cwd) {
|
|
617
|
-
if (!sandboxExists(cwd)) return [];
|
|
618
|
-
const dir = sandboxDir(cwd);
|
|
619
|
-
try {
|
|
620
|
-
git(dir, ["add", "-A"]);
|
|
621
|
-
const out = git(dir, ["diff", "--cached", "--name-status", "HEAD"]);
|
|
622
|
-
return out.length > 0 ? out.split("\n") : [];
|
|
623
|
-
} catch {
|
|
624
|
-
return [];
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
function applySandbox(cwd) {
|
|
628
|
-
if (!sandboxExists(cwd)) return { ok: false, detail: "no sandbox open" };
|
|
629
|
-
const dir = sandboxDir(cwd);
|
|
630
|
-
try {
|
|
631
|
-
git(dir, ["add", "-A"]);
|
|
632
|
-
const patch = git(dir, ["diff", "--binary", "--cached", "HEAD"]);
|
|
633
|
-
if (patch.length === 0) return { ok: false, detail: "sandbox has no changes" };
|
|
634
|
-
const patchFile = path2.join(cwd, ".squint", "sandbox.patch");
|
|
635
|
-
fs2.writeFileSync(patchFile, patch + "\n");
|
|
636
|
-
git(cwd, ["apply", "--whitespace=nowarn", patchFile]);
|
|
637
|
-
fs2.rmSync(patchFile, { force: true });
|
|
638
|
-
return { ok: true };
|
|
639
|
-
} catch (err) {
|
|
640
|
-
return { ok: false, detail: err instanceof Error ? err.message.split("\n")[0] : String(err) };
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
function discardSandbox(cwd) {
|
|
644
|
-
if (!sandboxExists(cwd)) return false;
|
|
645
|
-
const dir = sandboxDir(cwd);
|
|
646
|
-
try {
|
|
647
|
-
const link = path2.join(dir, "node_modules");
|
|
648
|
-
if (fs2.existsSync(link) && fs2.lstatSync(link).isSymbolicLink()) fs2.rmSync(link);
|
|
649
|
-
} catch {
|
|
650
|
-
}
|
|
651
|
-
try {
|
|
652
|
-
git(cwd, ["worktree", "remove", "--force", dir]);
|
|
653
|
-
} catch {
|
|
654
|
-
fs2.rmSync(dir, { recursive: true, force: true });
|
|
655
|
-
}
|
|
656
|
-
try {
|
|
657
|
-
git(cwd, ["worktree", "prune"]);
|
|
658
|
-
} catch {
|
|
659
|
-
}
|
|
660
|
-
return true;
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// src/session/engine.ts
|
|
664
623
|
var MAX_AUTO_FIX_ATTEMPTS = 2;
|
|
665
624
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
666
625
|
var Session = class {
|
|
@@ -807,6 +766,18 @@ var Session = class {
|
|
|
807
766
|
this.push("status", "queue cleared");
|
|
808
767
|
return;
|
|
809
768
|
}
|
|
769
|
+
const drop = /^\/queue drop (\d+)$/.exec(value);
|
|
770
|
+
if (drop) {
|
|
771
|
+
const index = Number.parseInt(drop[1], 10) - 1;
|
|
772
|
+
if (index >= 0 && index < this.state.queue.length) {
|
|
773
|
+
const removed = this.state.queue[index];
|
|
774
|
+
this.notify({ queue: this.state.queue.filter((_, i) => i !== index) });
|
|
775
|
+
this.push("status", `dropped from queue: ${removed}`);
|
|
776
|
+
} else {
|
|
777
|
+
this.push("status", `queue has ${this.state.queue.length} item(s) \u2014 /queue drop <1-${Math.max(this.state.queue.length, 1)}>`);
|
|
778
|
+
}
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
810
781
|
this.notify({ queue: [...this.state.queue, value] });
|
|
811
782
|
return;
|
|
812
783
|
}
|
|
@@ -992,6 +963,7 @@ var Session = class {
|
|
|
992
963
|
sessionId: this.sessionId,
|
|
993
964
|
model: this.state.model,
|
|
994
965
|
lastAsk: display.length > 80 ? `${display.slice(0, 79)}\u2026` : display,
|
|
966
|
+
totals: this.state.totals,
|
|
995
967
|
at: Date.now()
|
|
996
968
|
});
|
|
997
969
|
}
|
|
@@ -1054,7 +1026,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1054
1026
|
const captureResult = await this.capture();
|
|
1055
1027
|
if (captureResult) {
|
|
1056
1028
|
await this.runTurn(
|
|
1057
|
-
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y),
|
|
1029
|
+
buildReviewPrompt(captureResult.shots, void 0, captureResult.runtime, captureResult.a11y, captureResult.slop),
|
|
1058
1030
|
"\u{1F441} auto-review rendered UI"
|
|
1059
1031
|
);
|
|
1060
1032
|
}
|
|
@@ -1128,6 +1100,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1128
1100
|
this.lastPulse = current;
|
|
1129
1101
|
if (!previous) {
|
|
1130
1102
|
this.push("status", "visual pulse: baseline captured");
|
|
1103
|
+
this.push("image", pulsePath);
|
|
1131
1104
|
return null;
|
|
1132
1105
|
}
|
|
1133
1106
|
const pct = await comparePulse(previous, current);
|
|
@@ -1136,16 +1109,18 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1136
1109
|
"status",
|
|
1137
1110
|
pct < 0.5 ? "visual pulse: stable vs last turn" : `visual pulse: ${pct.toFixed(1)}% of the page changed vs last turn`
|
|
1138
1111
|
);
|
|
1112
|
+
if (pct >= 0.5) this.push("image", pulsePath);
|
|
1139
1113
|
return pct;
|
|
1140
1114
|
}
|
|
1141
1115
|
/** Screenshot the running app (and watch its runtime where CDP is available). */
|
|
1142
|
-
async capture() {
|
|
1143
|
-
|
|
1144
|
-
|
|
1116
|
+
async capture(urlOverride) {
|
|
1117
|
+
const url = urlOverride || this.state.devUrl;
|
|
1118
|
+
if (!url) {
|
|
1119
|
+
this.push("error", "dev server not running \u2014 /dev first, or /shot <url>");
|
|
1145
1120
|
return null;
|
|
1146
1121
|
}
|
|
1147
1122
|
this.push("status", "capturing screenshots\u2026");
|
|
1148
|
-
const result = await captureViewports(this.opts.cwd,
|
|
1123
|
+
const result = await captureViewports(this.opts.cwd, url);
|
|
1149
1124
|
if (!result) {
|
|
1150
1125
|
this.push("error", "no Chrome/Chromium found for screenshots");
|
|
1151
1126
|
return null;
|
|
@@ -1154,8 +1129,10 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1154
1129
|
if (result.shots.length > 0) {
|
|
1155
1130
|
this.push(
|
|
1156
1131
|
"status",
|
|
1157
|
-
`captured ${result.shots.map((s) => s.name).join(", ")} \u2192 ${
|
|
1132
|
+
`captured ${result.shots.map((s) => s.name).join(", ")} \u2192 ${path2.dirname(result.shots[0].path)}`
|
|
1158
1133
|
);
|
|
1134
|
+
const desktop = result.shots.find((s) => s.name === "desktop") ?? result.shots[0];
|
|
1135
|
+
this.push("image", desktop.path);
|
|
1159
1136
|
}
|
|
1160
1137
|
if (result.runtime) {
|
|
1161
1138
|
const summary = runtimeSummary(result.runtime);
|
|
@@ -1168,6 +1145,10 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1168
1145
|
this.push("status", "runtime clean \u2014 no console errors, exceptions, or failed requests");
|
|
1169
1146
|
}
|
|
1170
1147
|
}
|
|
1148
|
+
if (result.slop && result.slop.length > 0) {
|
|
1149
|
+
this.push("status", `distinctiveness: ${result.slop.length} tell(s)
|
|
1150
|
+
${result.slop.join("\n")}`);
|
|
1151
|
+
}
|
|
1171
1152
|
if (result.a11y && result.a11y.length > 0) {
|
|
1172
1153
|
this.push("error", `a11y: ${result.a11y.length} finding(s)
|
|
1173
1154
|
${result.a11y.slice(0, 5).join("\n")}`);
|
|
@@ -1191,7 +1172,7 @@ They are objective defects, not style preferences.`
|
|
|
1191
1172
|
const arg = rest.join(" ").trim();
|
|
1192
1173
|
switch (name) {
|
|
1193
1174
|
case "engines": {
|
|
1194
|
-
void import("./registry-
|
|
1175
|
+
void import("./registry-MIJ6LSAY.js").then(({ detectEngines: detectEngines2 }) => {
|
|
1195
1176
|
const lines = detectEngines2().map(({ engine, path: binaryPath }) => {
|
|
1196
1177
|
const mark = binaryPath ? "\u2713" : "\u2717";
|
|
1197
1178
|
const traits = [engine.createParser ? "stream" : "text", engine.supportsResume ? "resume" : null].filter(Boolean).join(" \xB7 ");
|
|
@@ -1281,12 +1262,12 @@ They are objective defects, not style preferences.`
|
|
|
1281
1262
|
}
|
|
1282
1263
|
case "save": {
|
|
1283
1264
|
void (async () => {
|
|
1284
|
-
const
|
|
1285
|
-
const
|
|
1286
|
-
const dir =
|
|
1287
|
-
|
|
1265
|
+
const fs2 = await import("fs");
|
|
1266
|
+
const path4 = await import("path");
|
|
1267
|
+
const dir = path4.join(this.opts.cwd, ".squint", "transcripts");
|
|
1268
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1288
1269
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
1289
|
-
const file =
|
|
1270
|
+
const file = path4.join(dir, `${stamp}.md`);
|
|
1290
1271
|
const lines = [`# squint session \u2014 ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`, ""];
|
|
1291
1272
|
for (const item of this.state.items) {
|
|
1292
1273
|
switch (item.role) {
|
|
@@ -1299,6 +1280,9 @@ They are objective defects, not style preferences.`
|
|
|
1299
1280
|
case "tool":
|
|
1300
1281
|
lines.push(`- \u2699 ${item.text}`);
|
|
1301
1282
|
break;
|
|
1283
|
+
case "image":
|
|
1284
|
+
lines.push(``, "");
|
|
1285
|
+
break;
|
|
1302
1286
|
case "thinking":
|
|
1303
1287
|
break;
|
|
1304
1288
|
default:
|
|
@@ -1306,8 +1290,8 @@ They are objective defects, not style preferences.`
|
|
|
1306
1290
|
}
|
|
1307
1291
|
}
|
|
1308
1292
|
lines.push("", `> ${this.summary()}`);
|
|
1309
|
-
|
|
1310
|
-
this.push("status", `saved transcript \u2192 ${
|
|
1293
|
+
fs2.writeFileSync(file, lines.join("\n") + "\n");
|
|
1294
|
+
this.push("status", `saved transcript \u2192 ${path4.relative(this.opts.cwd, file)}`);
|
|
1311
1295
|
})();
|
|
1312
1296
|
break;
|
|
1313
1297
|
}
|
|
@@ -1364,14 +1348,14 @@ They are objective defects, not style preferences.`
|
|
|
1364
1348
|
})();
|
|
1365
1349
|
break;
|
|
1366
1350
|
case "shot":
|
|
1367
|
-
void this.capture();
|
|
1351
|
+
void this.capture(arg || void 0);
|
|
1368
1352
|
break;
|
|
1369
1353
|
case "review":
|
|
1370
1354
|
void (async () => {
|
|
1371
1355
|
const result = await this.capture();
|
|
1372
1356
|
if (result) {
|
|
1373
1357
|
await this.runTurn(
|
|
1374
|
-
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y),
|
|
1358
|
+
buildReviewPrompt(result.shots, arg || void 0, result.runtime, result.a11y, result.slop),
|
|
1375
1359
|
`\u{1F441} review rendered UI${arg ? ` \xB7 ${arg}` : ""}`
|
|
1376
1360
|
);
|
|
1377
1361
|
}
|
|
@@ -1421,7 +1405,11 @@ They are objective defects, not style preferences.`
|
|
|
1421
1405
|
break;
|
|
1422
1406
|
}
|
|
1423
1407
|
this.sessionId = saved.sessionId;
|
|
1424
|
-
this.notify({
|
|
1408
|
+
this.notify({
|
|
1409
|
+
engineId: saved.engine,
|
|
1410
|
+
model: saved.model ?? this.state.model,
|
|
1411
|
+
totals: saved.totals ?? this.state.totals
|
|
1412
|
+
});
|
|
1425
1413
|
this.push("status", `resumed ${saved.engine} session${saved.lastAsk ? ` \xB7 "${saved.lastAsk}"` : ""}`);
|
|
1426
1414
|
break;
|
|
1427
1415
|
}
|
|
@@ -1551,7 +1539,7 @@ ${sandboxFiles(this.opts.cwd).join("\n")}`);
|
|
|
1551
1539
|
this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
|
|
1552
1540
|
break;
|
|
1553
1541
|
case "help": {
|
|
1554
|
-
void import("./commands-
|
|
1542
|
+
void import("./commands-QKDWUD4K.js").then(({ commandHelp }) => this.push("status", commandHelp()));
|
|
1555
1543
|
break;
|
|
1556
1544
|
}
|
|
1557
1545
|
case "quit":
|
|
@@ -1813,9 +1801,23 @@ function Markdown({ text }) {
|
|
|
1813
1801
|
}
|
|
1814
1802
|
|
|
1815
1803
|
// src/tui/messages.tsx
|
|
1816
|
-
import { Text as Text2 } from "ink";
|
|
1804
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
1805
|
+
import Image from "ink-picture";
|
|
1817
1806
|
import { useEffect, useState } from "react";
|
|
1807
|
+
|
|
1808
|
+
// src/tui/termImage.ts
|
|
1809
|
+
function supportsInlineImages(env = process.env) {
|
|
1810
|
+
if (env.TMUX) return false;
|
|
1811
|
+
if (env.TERM === "xterm-kitty" || env.KITTY_WINDOW_ID) return true;
|
|
1812
|
+
if (env.TERM === "xterm-ghostty" || env.GHOSTTY_RESOURCES_DIR) return true;
|
|
1813
|
+
if (env.TERM_PROGRAM === "WezTerm") return true;
|
|
1814
|
+
if (env.TERM_PROGRAM === "iTerm.app") return true;
|
|
1815
|
+
return false;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// src/tui/messages.tsx
|
|
1818
1819
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1820
|
+
var INLINE_IMAGES = supportsInlineImages();
|
|
1819
1821
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1820
1822
|
var PHRASES = ["working", "thinking", "squinting", "crafting", "still at it"];
|
|
1821
1823
|
function WorkingLine({ startedAt }) {
|
|
@@ -1880,6 +1882,20 @@ function MessageLine({ message }) {
|
|
|
1880
1882
|
"\u2717 ",
|
|
1881
1883
|
message.text
|
|
1882
1884
|
] });
|
|
1885
|
+
case "image":
|
|
1886
|
+
if (!INLINE_IMAGES) {
|
|
1887
|
+
return /* @__PURE__ */ jsxs2(Text2, { color: theme2.dim, wrap: "wrap", children: [
|
|
1888
|
+
"\u25A3 ",
|
|
1889
|
+
message.text
|
|
1890
|
+
] });
|
|
1891
|
+
}
|
|
1892
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
1893
|
+
/* @__PURE__ */ jsx2(Image, { src: message.text, width: 48, height: 14, alt: "screenshot" }),
|
|
1894
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme2.dim, children: [
|
|
1895
|
+
"\u25A3 ",
|
|
1896
|
+
message.text
|
|
1897
|
+
] })
|
|
1898
|
+
] });
|
|
1883
1899
|
}
|
|
1884
1900
|
}
|
|
1885
1901
|
|
|
@@ -2039,20 +2055,23 @@ function App({
|
|
|
2039
2055
|
});
|
|
2040
2056
|
const devBadge = state.devState === "running" ? ` \xB7 ${state.devUrl ?? "dev running"}` : state.devState === "starting" ? " \xB7 dev starting\u2026" : state.devState === "crashed" ? " \xB7 dev crashed" : "";
|
|
2041
2057
|
const totalsBadge = state.totals.turns > 0 ? ` \xB7 ${state.totals.turns} turn${state.totals.turns === 1 ? "" : "s"}${state.totals.costUsd > 0 ? ` \xB7 $${state.totals.costUsd.toFixed(2)}` : ""}` : "";
|
|
2042
|
-
return /* @__PURE__ */ jsx3(ThemeProvider, { value: theme2, children: /* @__PURE__ */ jsxs3(
|
|
2043
|
-
/* @__PURE__ */ jsx3(Static, { items: state.items, children: (item) => /* @__PURE__ */ jsx3(
|
|
2044
|
-
state.liveText.length > 0 && /* @__PURE__ */ jsx3(
|
|
2045
|
-
state.items.length === 0 && state.liveText.length === 0 && !state.running && /* @__PURE__ */ jsxs3(
|
|
2058
|
+
return /* @__PURE__ */ jsx3(ThemeProvider, { value: theme2, children: /* @__PURE__ */ jsx3(InkPictureProvider, { children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, children: [
|
|
2059
|
+
/* @__PURE__ */ jsx3(Static, { items: state.items, children: (item) => /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(MessageLine, { message: item }) }, item.id) }),
|
|
2060
|
+
state.liveText.length > 0 && /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Markdown, { text: state.liveText }) }),
|
|
2061
|
+
state.items.length === 0 && state.liveText.length === 0 && !state.running && /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
|
|
2046
2062
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: "describe what to build \u2014 or try:" }),
|
|
2047
2063
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: " /dev start the preview server \xB7 /review after a change \xB7 /variants 3 explore wide" }),
|
|
2048
2064
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: " shift+tab cycles plan/safe/yolo \xB7 type while the agent works to queue asks" })
|
|
2049
2065
|
] }),
|
|
2050
|
-
state.running && /* @__PURE__ */ jsx3(
|
|
2051
|
-
state.queue.map((queued, index) => /* @__PURE__ */ jsx3(
|
|
2052
|
-
"\u22EF
|
|
2053
|
-
|
|
2066
|
+
state.running && /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(WorkingLine, { startedAt: state.runStartedAt }) }),
|
|
2067
|
+
state.queue.map((queued, index) => /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: theme2.dim, children: [
|
|
2068
|
+
"\u22EF ",
|
|
2069
|
+
index + 1,
|
|
2070
|
+
". ",
|
|
2071
|
+
queued,
|
|
2072
|
+
index === state.queue.length - 1 ? " (/queue drop <n> removes)" : ""
|
|
2054
2073
|
] }) }, index)),
|
|
2055
|
-
line.text.startsWith("/") && !line.text.includes(" ") && /* @__PURE__ */ jsx3(
|
|
2074
|
+
line.text.startsWith("/") && !line.text.includes(" ") && /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: completeCommand(line.text.slice(1)).slice(0, 5).map((command, index) => /* @__PURE__ */ jsxs3(Text3, { color: index === 0 ? theme2.accent : theme2.dim, children: [
|
|
2056
2075
|
"/",
|
|
2057
2076
|
command.name,
|
|
2058
2077
|
command.args ? ` ${command.args}` : "",
|
|
@@ -2062,13 +2081,13 @@ function App({
|
|
|
2062
2081
|
command.description
|
|
2063
2082
|
] })
|
|
2064
2083
|
] }, command.name)) }),
|
|
2065
|
-
/* @__PURE__ */ jsx3(
|
|
2084
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: state.running ? 0 : 1, children: /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
2066
2085
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.accent, children: "\u276F " }),
|
|
2067
2086
|
line.text.slice(0, line.cursor),
|
|
2068
2087
|
/* @__PURE__ */ jsx3(Text3, { inverse: true, children: line.text[line.cursor] ?? " " }),
|
|
2069
2088
|
line.text.slice(line.cursor + 1)
|
|
2070
2089
|
] }) }),
|
|
2071
|
-
/* @__PURE__ */ jsx3(
|
|
2090
|
+
/* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: theme2.dim, children: [
|
|
2072
2091
|
/* @__PURE__ */ jsxs3(
|
|
2073
2092
|
Text3,
|
|
2074
2093
|
{
|
|
@@ -2086,7 +2105,7 @@ function App({
|
|
|
2086
2105
|
state.engineId,
|
|
2087
2106
|
state.model ? ` \xB7 ${state.model}` : "",
|
|
2088
2107
|
" \xB7 ",
|
|
2089
|
-
|
|
2108
|
+
path3.basename(cwd),
|
|
2090
2109
|
devBadge,
|
|
2091
2110
|
totalsBadge,
|
|
2092
2111
|
state.problems.length > 0 && /* @__PURE__ */ jsxs3(Text3, { color: theme2.error, children: [
|
|
@@ -2098,7 +2117,7 @@ function App({
|
|
|
2098
2117
|
" ",
|
|
2099
2118
|
"\xB7 shift+tab mode \xB7 /help"
|
|
2100
2119
|
] }) })
|
|
2101
|
-
] }) });
|
|
2120
|
+
] }) }) });
|
|
2102
2121
|
}
|
|
2103
2122
|
|
|
2104
2123
|
// src/cli/tui.tsx
|
|
@@ -2136,7 +2155,7 @@ function registerTui(program2) {
|
|
|
2136
2155
|
}
|
|
2137
2156
|
|
|
2138
2157
|
// src/cli.tsx
|
|
2139
|
-
var VERSION = "0.
|
|
2158
|
+
var VERSION = true ? "0.3.0" : "0.0.0-dev";
|
|
2140
2159
|
var program = new Command();
|
|
2141
2160
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
2142
2161
|
registerRun(program);
|