@aayambansal/squint 0.2.7 → 0.2.9
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 +5 -0
- package/dist/{chunk-CHZE3UHK.js → chunk-GOAFH2VN.js} +8 -53
- package/dist/chunk-O2S6PAJE.js +51 -0
- package/dist/chunk-RMNG2NTU.js +55 -0
- package/dist/chunk-XEQ6JXXL.js +97 -0
- package/dist/cli.js +143 -27
- package/dist/{commands-TYAKF276.js → commands-TERCCWFM.js} +1 -1
- package/dist/{preview-XGASKUXR.js → preview-VW5DAJNV.js} +2 -1
- package/dist/sandbox-J2NCPYHJ.js +19 -0
- package/dist/state-QOS7WCZO.js +13 -0
- package/package.json +1 -1
- package/dist/chunk-RZOQPFXC.js +0 -40
package/README.md
CHANGED
|
@@ -132,6 +132,7 @@ squint config set models.claude claude-sonnet-5
|
|
|
132
132
|
squint config set autoDev true # dev server starts with the TUI
|
|
133
133
|
squint config set autoFix true # errors auto-route back (max 2 tries)
|
|
134
134
|
squint config set autoCheck false # skip the per-turn typecheck+lint pass
|
|
135
|
+
squint config set autoReview true # big visual change → automatic self-critique
|
|
135
136
|
squint config set theme ocean # amber · ocean · moss · rose · mono
|
|
136
137
|
squint config set bell false # no bell on turn completion
|
|
137
138
|
squint doctor # engines + Chrome + WebSocket check
|
|
@@ -149,6 +150,9 @@ squint doctor --probe # run every engine end to end, verify auth act
|
|
|
149
150
|
- **Problems**: findings from gates, the dev server, the runtime probe, and a11y sweeps
|
|
150
151
|
collect into a list — `/problems` shows it, `/fix` sends everything as one turn,
|
|
151
152
|
`/fix <n>` targets one. The footer counts what's open.
|
|
153
|
+
- **Sandbox**: `/sandbox on` and asks accumulate in a shadow worktree — the dev server,
|
|
154
|
+
gates, and probes all run there; `/sandbox diff` shows what is staged, `apply` lands it
|
|
155
|
+
as one diff, `discard` walks away with the real tree untouched.
|
|
152
156
|
- **Variants without leaving**: `/variants 3 <ask>` runs parallel explorations with
|
|
153
157
|
streaming per-family status; `/variants apply <id>` keeps the winner.
|
|
154
158
|
- **Commands**: type `/` and matching commands appear with descriptions; tab completes.
|
|
@@ -227,6 +231,7 @@ All product behavior lives in the harness, so a new engine is ~80 lines.
|
|
|
227
231
|
|
|
228
232
|
- [Engine setup guide](./docs/engines.md) — install + auth for all eight, and how to choose
|
|
229
233
|
- [Configuration](./docs/configuration.md) — every key, every `.squint/` file
|
|
234
|
+
- [The loops](./docs/loops.md) — everything that runs automatically around each turn
|
|
230
235
|
- [Architecture](./docs/design/2026-07-25-architecture.md)
|
|
231
236
|
- [How Lovable works under the hood](./docs/research/lovable.md)
|
|
232
237
|
- [Making agents produce excellent frontend work](./docs/research/frontend-quality.md)
|
|
@@ -7,56 +7,14 @@ import {
|
|
|
7
7
|
cdpCapture,
|
|
8
8
|
hasWebSocket
|
|
9
9
|
} from "./chunk-OC6RU6XH.js";
|
|
10
|
+
import {
|
|
11
|
+
ensureSquintIgnore
|
|
12
|
+
} from "./chunk-O2S6PAJE.js";
|
|
10
13
|
|
|
11
14
|
// src/preview/preview.ts
|
|
12
|
-
import fs2 from "fs";
|
|
13
|
-
import os from "os";
|
|
14
|
-
import path2 from "path";
|
|
15
|
-
|
|
16
|
-
// src/state/state.ts
|
|
17
15
|
import fs from "fs";
|
|
16
|
+
import os from "os";
|
|
18
17
|
import path from "path";
|
|
19
|
-
var IGNORED = ["preview/", "state.json", "variants/", "transcripts/"];
|
|
20
|
-
function ensureSquintIgnore(cwd) {
|
|
21
|
-
const dir = path.join(cwd, ".squint");
|
|
22
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
23
|
-
const file = path.join(dir, ".gitignore");
|
|
24
|
-
let existing = "";
|
|
25
|
-
try {
|
|
26
|
-
existing = fs.readFileSync(file, "utf8");
|
|
27
|
-
} catch {
|
|
28
|
-
}
|
|
29
|
-
const lines = existing.split("\n").filter((l) => l.trim().length > 0);
|
|
30
|
-
const missing = IGNORED.filter((entry) => !lines.includes(entry));
|
|
31
|
-
if (missing.length > 0) {
|
|
32
|
-
fs.writeFileSync(file, [...lines, ...missing].join("\n") + "\n");
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
function stateFile(cwd) {
|
|
36
|
-
return path.join(cwd, ".squint", "state.json");
|
|
37
|
-
}
|
|
38
|
-
function loadState(cwd) {
|
|
39
|
-
try {
|
|
40
|
-
const raw = JSON.parse(fs.readFileSync(stateFile(cwd), "utf8"));
|
|
41
|
-
if (typeof raw?.engine === "string" && typeof raw?.sessionId === "string" && typeof raw?.at === "number") {
|
|
42
|
-
return raw;
|
|
43
|
-
}
|
|
44
|
-
} catch {
|
|
45
|
-
}
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
function saveState(cwd, state) {
|
|
49
|
-
ensureSquintIgnore(cwd);
|
|
50
|
-
fs.writeFileSync(stateFile(cwd), JSON.stringify(state, null, 2) + "\n");
|
|
51
|
-
}
|
|
52
|
-
function clearState(cwd) {
|
|
53
|
-
try {
|
|
54
|
-
fs.rmSync(stateFile(cwd));
|
|
55
|
-
} catch {
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/preview/preview.ts
|
|
60
18
|
var VIEWPORTS = [
|
|
61
19
|
{ name: "mobile", width: 390, height: 844 },
|
|
62
20
|
{ name: "tablet", width: 768, height: 1024 },
|
|
@@ -65,7 +23,7 @@ var VIEWPORTS = [
|
|
|
65
23
|
function loadRoutes(cwd) {
|
|
66
24
|
let lines = [];
|
|
67
25
|
try {
|
|
68
|
-
lines =
|
|
26
|
+
lines = fs.readFileSync(path.join(cwd, ".squint", "routes"), "utf8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
|
|
69
27
|
} catch {
|
|
70
28
|
}
|
|
71
29
|
const routes = ["/", ...lines.filter((l) => l !== "/")];
|
|
@@ -76,8 +34,8 @@ function routeShotName(route) {
|
|
|
76
34
|
return clean.length > 0 ? clean : "root";
|
|
77
35
|
}
|
|
78
36
|
function previewDir(cwd) {
|
|
79
|
-
const dir =
|
|
80
|
-
|
|
37
|
+
const dir = path.join(cwd, ".squint", "preview");
|
|
38
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
81
39
|
ensureSquintIgnore(cwd);
|
|
82
40
|
return dir;
|
|
83
41
|
}
|
|
@@ -111,7 +69,7 @@ async function captureViewports(cwd, url) {
|
|
|
111
69
|
const shots = [];
|
|
112
70
|
const errors = [];
|
|
113
71
|
for (const viewport of VIEWPORTS) {
|
|
114
|
-
const outPath =
|
|
72
|
+
const outPath = path.join(dir, `${viewport.name}.png`);
|
|
115
73
|
const result = await screenshot(chrome, url, outPath, {
|
|
116
74
|
width: viewport.width,
|
|
117
75
|
height: viewport.height
|
|
@@ -203,9 +161,6 @@ Read each screenshot and review the rendered UI against the design standards you
|
|
|
203
161
|
}
|
|
204
162
|
|
|
205
163
|
export {
|
|
206
|
-
loadState,
|
|
207
|
-
saveState,
|
|
208
|
-
clearState,
|
|
209
164
|
VIEWPORTS,
|
|
210
165
|
loadRoutes,
|
|
211
166
|
routeShotName,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/state/state.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
var IGNORED = ["preview/", "state.json", "variants/", "sandbox/", "sandbox.patch", "transcripts/"];
|
|
7
|
+
function ensureSquintIgnore(cwd) {
|
|
8
|
+
const dir = path.join(cwd, ".squint");
|
|
9
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
10
|
+
const file = path.join(dir, ".gitignore");
|
|
11
|
+
let existing = "";
|
|
12
|
+
try {
|
|
13
|
+
existing = fs.readFileSync(file, "utf8");
|
|
14
|
+
} catch {
|
|
15
|
+
}
|
|
16
|
+
const lines = existing.split("\n").filter((l) => l.trim().length > 0);
|
|
17
|
+
const missing = IGNORED.filter((entry) => !lines.includes(entry));
|
|
18
|
+
if (missing.length > 0) {
|
|
19
|
+
fs.writeFileSync(file, [...lines, ...missing].join("\n") + "\n");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function stateFile(cwd) {
|
|
23
|
+
return path.join(cwd, ".squint", "state.json");
|
|
24
|
+
}
|
|
25
|
+
function loadState(cwd) {
|
|
26
|
+
try {
|
|
27
|
+
const raw = JSON.parse(fs.readFileSync(stateFile(cwd), "utf8"));
|
|
28
|
+
if (typeof raw?.engine === "string" && typeof raw?.sessionId === "string" && typeof raw?.at === "number") {
|
|
29
|
+
return raw;
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function saveState(cwd, state) {
|
|
36
|
+
ensureSquintIgnore(cwd);
|
|
37
|
+
fs.writeFileSync(stateFile(cwd), JSON.stringify(state, null, 2) + "\n");
|
|
38
|
+
}
|
|
39
|
+
function clearState(cwd) {
|
|
40
|
+
try {
|
|
41
|
+
fs.rmSync(stateFile(cwd));
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
ensureSquintIgnore,
|
|
48
|
+
loadState,
|
|
49
|
+
saveState,
|
|
50
|
+
clearState
|
|
51
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/session/commands.ts
|
|
4
|
+
var COMMANDS = [
|
|
5
|
+
{ name: "dev", args: "[restart|logs]", group: "build", description: "start/stop the dev server; restart or show recent output" },
|
|
6
|
+
{ name: "mode", args: "plan|safe|yolo", group: "build", description: "how much the engine may do (shift+tab cycles)" },
|
|
7
|
+
{ name: "engine", args: "<id>", group: "build", description: "switch backend (new session)" },
|
|
8
|
+
{ name: "engines", group: "build", description: "list installed engines with streaming/resume support" },
|
|
9
|
+
{ name: "model", args: "[name]", group: "build", description: "model override for the engine" },
|
|
10
|
+
{ name: "queue", args: "clear", group: "build", description: "drop queued asks" },
|
|
11
|
+
{ name: "check", group: "verify", description: "run all quality gates (typecheck, lint, format, test, build)" },
|
|
12
|
+
{ name: "problems", group: "verify", description: "list open findings from gates, dev server, runtime, a11y" },
|
|
13
|
+
{ name: "fix", args: "[n]", group: "verify", description: "send all open problems to the engine, or just problem n" },
|
|
14
|
+
{ name: "shot", args: "[url]", group: "verify", description: "screenshot the app (or any url) at mobile/tablet/desktop" },
|
|
15
|
+
{ name: "review", args: "[focus]", group: "verify", description: "screenshots + the engine critiques its own rendered work" },
|
|
16
|
+
{ name: "variants", args: "<2-4> <ask>", group: "explore", description: "parallel design explorations; apply/list/clean" },
|
|
17
|
+
{ name: "sandbox", args: "[on|diff|apply|discard]", group: "explore", description: "asks accumulate in a shadow worktree until you apply" },
|
|
18
|
+
{ name: "undo", group: "explore", description: "revert the last ask (files only)" },
|
|
19
|
+
{ name: "checkpoints", group: "explore", description: "list per-ask checkpoints" },
|
|
20
|
+
{ name: "restore", args: "<n>", group: "explore", description: "rewind files to before ask n" },
|
|
21
|
+
{ name: "theme", args: "[name]", group: "session", description: "switch the TUI theme", viewLevel: true },
|
|
22
|
+
{ name: "copy", group: "session", description: "copy the last reply to the clipboard" },
|
|
23
|
+
{ name: "save", group: "session", description: "export the transcript to .squint/transcripts/" },
|
|
24
|
+
{ name: "resume", group: "session", description: "pick up the previous session for this repo" },
|
|
25
|
+
{ name: "clear", group: "session", description: "new session (transcript, totals, persisted state)" },
|
|
26
|
+
{ name: "help", group: "session", description: "list commands" },
|
|
27
|
+
{ name: "quit", group: "session", description: "exit with a session summary" }
|
|
28
|
+
];
|
|
29
|
+
function completeCommand(partial) {
|
|
30
|
+
const query = partial.toLowerCase();
|
|
31
|
+
return COMMANDS.filter((c) => c.name.startsWith(query));
|
|
32
|
+
}
|
|
33
|
+
var GROUP_TITLES = {
|
|
34
|
+
build: "build",
|
|
35
|
+
verify: "verify",
|
|
36
|
+
explore: "explore & rewind",
|
|
37
|
+
session: "session"
|
|
38
|
+
};
|
|
39
|
+
function commandHelp() {
|
|
40
|
+
const sections = [];
|
|
41
|
+
for (const group of ["build", "verify", "explore", "session"]) {
|
|
42
|
+
const rows = COMMANDS.filter((c) => c.group === group).map(
|
|
43
|
+
(c) => ` /${c.name}${c.args ? ` ${c.args}` : ""} \u2014 ${c.description}`
|
|
44
|
+
);
|
|
45
|
+
sections.push(`${GROUP_TITLES[group]}
|
|
46
|
+
${rows.join("\n")}`);
|
|
47
|
+
}
|
|
48
|
+
return sections.join("\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
COMMANDS,
|
|
53
|
+
completeCommand,
|
|
54
|
+
commandHelp
|
|
55
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/vcs/sandbox.ts
|
|
4
|
+
import { execFileSync } from "child_process";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
function git(cwd, args) {
|
|
8
|
+
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
9
|
+
}
|
|
10
|
+
function sandboxDir(cwd) {
|
|
11
|
+
return path.join(cwd, ".squint", "sandbox");
|
|
12
|
+
}
|
|
13
|
+
function sandboxExists(cwd) {
|
|
14
|
+
return fs.existsSync(path.join(sandboxDir(cwd), ".git"));
|
|
15
|
+
}
|
|
16
|
+
function openSandbox(cwd) {
|
|
17
|
+
const dir = sandboxDir(cwd);
|
|
18
|
+
void import("./state-QOS7WCZO.js").then(({ ensureSquintIgnore }) => ensureSquintIgnore(cwd)).catch(() => {
|
|
19
|
+
});
|
|
20
|
+
if (sandboxExists(cwd)) return { dir, reused: true };
|
|
21
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
22
|
+
fs.mkdirSync(path.dirname(dir), { recursive: true });
|
|
23
|
+
git(cwd, ["worktree", "add", "--force", "--detach", dir, "HEAD"]);
|
|
24
|
+
const mainModules = path.join(cwd, "node_modules");
|
|
25
|
+
const sandboxModules = path.join(dir, "node_modules");
|
|
26
|
+
if (fs.existsSync(mainModules) && !fs.existsSync(sandboxModules)) {
|
|
27
|
+
fs.symlinkSync(mainModules, sandboxModules, "dir");
|
|
28
|
+
}
|
|
29
|
+
return { dir, reused: false };
|
|
30
|
+
}
|
|
31
|
+
function sandboxDiffStat(cwd) {
|
|
32
|
+
if (!sandboxExists(cwd)) return null;
|
|
33
|
+
const dir = sandboxDir(cwd);
|
|
34
|
+
try {
|
|
35
|
+
git(dir, ["add", "-A"]);
|
|
36
|
+
const stat = git(dir, ["diff", "--cached", "--shortstat", "HEAD"]);
|
|
37
|
+
return stat.length > 0 ? stat : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function sandboxFiles(cwd) {
|
|
43
|
+
if (!sandboxExists(cwd)) return [];
|
|
44
|
+
const dir = sandboxDir(cwd);
|
|
45
|
+
try {
|
|
46
|
+
git(dir, ["add", "-A"]);
|
|
47
|
+
const out = git(dir, ["diff", "--cached", "--name-status", "HEAD"]);
|
|
48
|
+
return out.length > 0 ? out.split("\n") : [];
|
|
49
|
+
} catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function applySandbox(cwd) {
|
|
54
|
+
if (!sandboxExists(cwd)) return { ok: false, detail: "no sandbox open" };
|
|
55
|
+
const dir = sandboxDir(cwd);
|
|
56
|
+
try {
|
|
57
|
+
git(dir, ["add", "-A"]);
|
|
58
|
+
const patch = git(dir, ["diff", "--binary", "--cached", "HEAD"]);
|
|
59
|
+
if (patch.length === 0) return { ok: false, detail: "sandbox has no changes" };
|
|
60
|
+
const patchFile = path.join(cwd, ".squint", "sandbox.patch");
|
|
61
|
+
fs.writeFileSync(patchFile, patch + "\n");
|
|
62
|
+
git(cwd, ["apply", "--whitespace=nowarn", patchFile]);
|
|
63
|
+
fs.rmSync(patchFile, { force: true });
|
|
64
|
+
return { ok: true };
|
|
65
|
+
} catch (err) {
|
|
66
|
+
return { ok: false, detail: err instanceof Error ? err.message.split("\n")[0] : String(err) };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function discardSandbox(cwd) {
|
|
70
|
+
if (!sandboxExists(cwd)) return false;
|
|
71
|
+
const dir = sandboxDir(cwd);
|
|
72
|
+
try {
|
|
73
|
+
const link = path.join(dir, "node_modules");
|
|
74
|
+
if (fs.existsSync(link) && fs.lstatSync(link).isSymbolicLink()) fs.rmSync(link);
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
git(cwd, ["worktree", "remove", "--force", dir]);
|
|
79
|
+
} catch {
|
|
80
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
git(cwd, ["worktree", "prune"]);
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
sandboxDir,
|
|
91
|
+
sandboxExists,
|
|
92
|
+
openSandbox,
|
|
93
|
+
sandboxDiffStat,
|
|
94
|
+
sandboxFiles,
|
|
95
|
+
applySandbox,
|
|
96
|
+
discardSandbox
|
|
97
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
completeCommand
|
|
4
|
+
} from "./chunk-RMNG2NTU.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,
|
|
@@ -21,16 +39,10 @@ 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-RZOQPFXC.js";
|
|
44
|
+
runtimeSummary
|
|
45
|
+
} from "./chunk-GOAFH2VN.js";
|
|
34
46
|
import {
|
|
35
47
|
runAgent
|
|
36
48
|
} from "./chunk-C7WKNJG6.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";
|
|
@@ -317,6 +328,39 @@ function registerProject(program2) {
|
|
|
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) => {
|
|
@@ -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-VW5DAJNV.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"));
|
|
@@ -592,7 +636,8 @@ var Session = class {
|
|
|
592
636
|
totals: { costUsd: 0, turns: 0 },
|
|
593
637
|
queue: [],
|
|
594
638
|
mode: "safe",
|
|
595
|
-
problems: []
|
|
639
|
+
problems: [],
|
|
640
|
+
sandbox: false
|
|
596
641
|
};
|
|
597
642
|
if (opts.autoDev && detectDevCommand(opts.cwd)) {
|
|
598
643
|
this.devServer().start();
|
|
@@ -762,15 +807,33 @@ var Session = class {
|
|
|
762
807
|
this.push("assistant", text);
|
|
763
808
|
}
|
|
764
809
|
}
|
|
810
|
+
/** Where engines run and servers start: the sandbox worktree when on. */
|
|
811
|
+
execCwd() {
|
|
812
|
+
return this.state.sandbox ? sandboxDir(this.opts.cwd) : this.opts.cwd;
|
|
813
|
+
}
|
|
765
814
|
devServer() {
|
|
766
815
|
if (!this.dev) {
|
|
767
|
-
this.dev = new DevServer(this.
|
|
816
|
+
this.dev = new DevServer(this.execCwd(), {
|
|
768
817
|
onStateChange: (devState) => this.notify({ devState }),
|
|
769
818
|
onUrl: (url) => this.notify({ devUrl: url })
|
|
770
819
|
});
|
|
771
820
|
}
|
|
772
821
|
return this.dev;
|
|
773
822
|
}
|
|
823
|
+
/** Rebind the dev server after the execution tree changes. */
|
|
824
|
+
resetDevServer() {
|
|
825
|
+
const wasRunning = this.dev && this.dev.state !== "stopped";
|
|
826
|
+
this.dev?.stop();
|
|
827
|
+
this.dev = null;
|
|
828
|
+
this.notify({ devUrl: null, devState: "stopped" });
|
|
829
|
+
if (wasRunning) {
|
|
830
|
+
const command = detectDevCommand(this.execCwd());
|
|
831
|
+
if (command) {
|
|
832
|
+
this.devServer().start(command);
|
|
833
|
+
this.push("status", `dev server restarting in ${this.state.sandbox ? "the sandbox" : "the real tree"}`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
774
837
|
turnEdits = 0;
|
|
775
838
|
turnTools = 0;
|
|
776
839
|
toolStreak = 0;
|
|
@@ -849,7 +912,7 @@ var Session = class {
|
|
|
849
912
|
engine,
|
|
850
913
|
{
|
|
851
914
|
prompt,
|
|
852
|
-
cwd: this.
|
|
915
|
+
cwd: this.execCwd(),
|
|
853
916
|
model: this.state.model,
|
|
854
917
|
mode: this.state.mode,
|
|
855
918
|
sessionId: engine.supportsResume ? this.sessionId : void 0
|
|
@@ -892,9 +955,9 @@ var Session = class {
|
|
|
892
955
|
}
|
|
893
956
|
}
|
|
894
957
|
if (result.ok && this.opts.autoCheck !== false) {
|
|
895
|
-
const fastGates = detectFastGates(this.
|
|
958
|
+
const fastGates = detectFastGates(this.execCwd());
|
|
896
959
|
if (fastGates.length > 0) {
|
|
897
|
-
const gateResults = await runGates(this.
|
|
960
|
+
const gateResults = await runGates(this.execCwd(), fastGates);
|
|
898
961
|
const failures = gateResults.filter((r) => !r.ok);
|
|
899
962
|
if (failures.length > 0) {
|
|
900
963
|
this.addProblem(
|
|
@@ -969,7 +1032,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
969
1032
|
async submit(ask) {
|
|
970
1033
|
this.fixAttempts = 0;
|
|
971
1034
|
this.autoReviewedThisAsk = false;
|
|
972
|
-
const snapshot = takeSnapshot(this.opts.cwd);
|
|
1035
|
+
const snapshot = this.state.sandbox ? null : takeSnapshot(this.opts.cwd);
|
|
973
1036
|
if (snapshot) {
|
|
974
1037
|
this.checkpoints.push({
|
|
975
1038
|
snapshot,
|
|
@@ -1034,13 +1097,14 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1034
1097
|
return pct;
|
|
1035
1098
|
}
|
|
1036
1099
|
/** Screenshot the running app (and watch its runtime where CDP is available). */
|
|
1037
|
-
async capture() {
|
|
1038
|
-
|
|
1039
|
-
|
|
1100
|
+
async capture(urlOverride) {
|
|
1101
|
+
const url = urlOverride || this.state.devUrl;
|
|
1102
|
+
if (!url) {
|
|
1103
|
+
this.push("error", "dev server not running \u2014 /dev first, or /shot <url>");
|
|
1040
1104
|
return null;
|
|
1041
1105
|
}
|
|
1042
1106
|
this.push("status", "capturing screenshots\u2026");
|
|
1043
|
-
const result = await captureViewports(this.opts.cwd,
|
|
1107
|
+
const result = await captureViewports(this.opts.cwd, url);
|
|
1044
1108
|
if (!result) {
|
|
1045
1109
|
this.push("error", "no Chrome/Chromium found for screenshots");
|
|
1046
1110
|
return null;
|
|
@@ -1259,7 +1323,7 @@ They are objective defects, not style preferences.`
|
|
|
1259
1323
|
})();
|
|
1260
1324
|
break;
|
|
1261
1325
|
case "shot":
|
|
1262
|
-
void this.capture();
|
|
1326
|
+
void this.capture(arg || void 0);
|
|
1263
1327
|
break;
|
|
1264
1328
|
case "review":
|
|
1265
1329
|
void (async () => {
|
|
@@ -1320,6 +1384,57 @@ They are objective defects, not style preferences.`
|
|
|
1320
1384
|
this.push("status", `resumed ${saved.engine} session${saved.lastAsk ? ` \xB7 "${saved.lastAsk}"` : ""}`);
|
|
1321
1385
|
break;
|
|
1322
1386
|
}
|
|
1387
|
+
case "sandbox": {
|
|
1388
|
+
if (this.state.running) {
|
|
1389
|
+
this.push("status", "wait for the current turn before changing sandbox state");
|
|
1390
|
+
break;
|
|
1391
|
+
}
|
|
1392
|
+
if (arg === "diff") {
|
|
1393
|
+
const stat = sandboxDiffStat(this.opts.cwd);
|
|
1394
|
+
if (!stat) {
|
|
1395
|
+
this.push("status", sandboxExists(this.opts.cwd) ? "sandbox is clean" : "no sandbox open \u2014 /sandbox on");
|
|
1396
|
+
} else {
|
|
1397
|
+
this.push("status", `${stat}
|
|
1398
|
+
${sandboxFiles(this.opts.cwd).join("\n")}`);
|
|
1399
|
+
}
|
|
1400
|
+
break;
|
|
1401
|
+
}
|
|
1402
|
+
if (arg === "apply") {
|
|
1403
|
+
const applied = applySandbox(this.opts.cwd);
|
|
1404
|
+
if (applied.ok) {
|
|
1405
|
+
discardSandbox(this.opts.cwd);
|
|
1406
|
+
this.notify({ sandbox: false });
|
|
1407
|
+
this.resetDevServer();
|
|
1408
|
+
this.push("status", "sandbox applied to the real tree \u2014 review with git diff");
|
|
1409
|
+
} else {
|
|
1410
|
+
this.push("error", applied.detail ?? "apply failed");
|
|
1411
|
+
}
|
|
1412
|
+
break;
|
|
1413
|
+
}
|
|
1414
|
+
if (arg === "discard" || arg === "off") {
|
|
1415
|
+
const had = discardSandbox(this.opts.cwd);
|
|
1416
|
+
this.notify({ sandbox: false });
|
|
1417
|
+
this.resetDevServer();
|
|
1418
|
+
this.push("status", had ? "sandbox discarded \u2014 the real tree was never touched" : "no sandbox open");
|
|
1419
|
+
break;
|
|
1420
|
+
}
|
|
1421
|
+
if (arg === "on" || arg === "") {
|
|
1422
|
+
if (!isGitRepo(this.opts.cwd)) {
|
|
1423
|
+
this.push("error", "sandbox needs a git repo with at least one commit");
|
|
1424
|
+
break;
|
|
1425
|
+
}
|
|
1426
|
+
const { reused } = openSandbox(this.opts.cwd);
|
|
1427
|
+
this.notify({ sandbox: true });
|
|
1428
|
+
this.resetDevServer();
|
|
1429
|
+
this.push(
|
|
1430
|
+
"status",
|
|
1431
|
+
`${reused ? "rejoined the open sandbox" : "sandbox opened"} \u2014 asks now accumulate in a shadow worktree; /sandbox diff \xB7 apply \xB7 discard`
|
|
1432
|
+
);
|
|
1433
|
+
break;
|
|
1434
|
+
}
|
|
1435
|
+
this.push("status", "usage: /sandbox [on] \xB7 diff \xB7 apply \xB7 discard");
|
|
1436
|
+
break;
|
|
1437
|
+
}
|
|
1323
1438
|
case "variants": {
|
|
1324
1439
|
const [sub, ...subRest] = arg.split(/\s+/);
|
|
1325
1440
|
if (sub === "apply") {
|
|
@@ -1395,7 +1510,7 @@ They are objective defects, not style preferences.`
|
|
|
1395
1510
|
this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
|
|
1396
1511
|
break;
|
|
1397
1512
|
case "help": {
|
|
1398
|
-
void import("./commands-
|
|
1513
|
+
void import("./commands-TERCCWFM.js").then(({ commandHelp }) => this.push("status", commandHelp()));
|
|
1399
1514
|
break;
|
|
1400
1515
|
}
|
|
1401
1516
|
case "quit":
|
|
@@ -1925,6 +2040,7 @@ function App({
|
|
|
1925
2040
|
]
|
|
1926
2041
|
}
|
|
1927
2042
|
),
|
|
2043
|
+
state.sandbox && /* @__PURE__ */ jsx3(Text3, { color: theme2.accent, bold: true, children: " [sandbox]" }),
|
|
1928
2044
|
" ",
|
|
1929
2045
|
state.engineId,
|
|
1930
2046
|
state.model ? ` \xB7 ${state.model}` : "",
|
|
@@ -1979,7 +2095,7 @@ function registerTui(program2) {
|
|
|
1979
2095
|
}
|
|
1980
2096
|
|
|
1981
2097
|
// src/cli.tsx
|
|
1982
|
-
var VERSION = "0.2.
|
|
2098
|
+
var VERSION = true ? "0.2.9" : "0.0.0-dev";
|
|
1983
2099
|
var program = new Command();
|
|
1984
2100
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
1985
2101
|
registerRun(program);
|
|
@@ -10,10 +10,11 @@ import {
|
|
|
10
10
|
probeRuntime,
|
|
11
11
|
routeShotName,
|
|
12
12
|
runtimeSummary
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GOAFH2VN.js";
|
|
14
14
|
import "./chunk-B7LOERSP.js";
|
|
15
15
|
import "./chunk-BWZFACBT.js";
|
|
16
16
|
import "./chunk-OC6RU6XH.js";
|
|
17
|
+
import "./chunk-O2S6PAJE.js";
|
|
17
18
|
export {
|
|
18
19
|
VIEWPORTS,
|
|
19
20
|
buildReviewPrompt,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
applySandbox,
|
|
4
|
+
discardSandbox,
|
|
5
|
+
openSandbox,
|
|
6
|
+
sandboxDiffStat,
|
|
7
|
+
sandboxDir,
|
|
8
|
+
sandboxExists,
|
|
9
|
+
sandboxFiles
|
|
10
|
+
} from "./chunk-XEQ6JXXL.js";
|
|
11
|
+
export {
|
|
12
|
+
applySandbox,
|
|
13
|
+
discardSandbox,
|
|
14
|
+
openSandbox,
|
|
15
|
+
sandboxDiffStat,
|
|
16
|
+
sandboxDir,
|
|
17
|
+
sandboxExists,
|
|
18
|
+
sandboxFiles
|
|
19
|
+
};
|
package/package.json
CHANGED
package/dist/chunk-RZOQPFXC.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/session/commands.ts
|
|
4
|
-
var COMMANDS = [
|
|
5
|
-
{ name: "dev", args: "[restart|logs]", description: "start/stop the dev server; restart or show recent output" },
|
|
6
|
-
{ name: "check", description: "run all quality gates (typecheck, lint, test, build)" },
|
|
7
|
-
{ name: "problems", description: "list open findings from gates, dev server, runtime, a11y" },
|
|
8
|
-
{ name: "fix", args: "[n]", description: "send all open problems to the engine, or just problem n" },
|
|
9
|
-
{ name: "shot", description: "screenshot the app at mobile/tablet/desktop" },
|
|
10
|
-
{ name: "review", args: "[focus]", description: "screenshots + the engine critiques its own rendered work" },
|
|
11
|
-
{ name: "variants", args: "<2-4> <ask>", description: "parallel design explorations; apply/list/clean" },
|
|
12
|
-
{ name: "undo", description: "revert the last ask (files only)" },
|
|
13
|
-
{ name: "checkpoints", description: "list per-ask checkpoints" },
|
|
14
|
-
{ name: "restore", args: "<n>", description: "rewind files to before ask n" },
|
|
15
|
-
{ name: "mode", args: "plan|safe|yolo", description: "how much the engine may do (shift+tab cycles)" },
|
|
16
|
-
{ name: "engine", args: "<id>", description: "switch backend (new session)" },
|
|
17
|
-
{ name: "engines", description: "list installed engines with streaming/resume support" },
|
|
18
|
-
{ name: "model", args: "[name]", description: "model override for the engine" },
|
|
19
|
-
{ name: "theme", args: "[name]", description: "switch the TUI theme", viewLevel: true },
|
|
20
|
-
{ name: "copy", description: "copy the last reply to the clipboard" },
|
|
21
|
-
{ name: "save", description: "export the transcript to .squint/transcripts/" },
|
|
22
|
-
{ name: "queue", args: "clear", description: "drop queued asks" },
|
|
23
|
-
{ name: "resume", description: "pick up the previous session for this repo" },
|
|
24
|
-
{ name: "clear", description: "new session (transcript, totals, persisted state)" },
|
|
25
|
-
{ name: "help", description: "list commands" },
|
|
26
|
-
{ name: "quit", description: "exit with a session summary" }
|
|
27
|
-
];
|
|
28
|
-
function completeCommand(partial) {
|
|
29
|
-
const query = partial.toLowerCase();
|
|
30
|
-
return COMMANDS.filter((c) => c.name.startsWith(query));
|
|
31
|
-
}
|
|
32
|
-
function commandHelp() {
|
|
33
|
-
return COMMANDS.map((c) => `/${c.name}${c.args ? ` ${c.args}` : ""} \u2014 ${c.description}`).join("\n");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export {
|
|
37
|
-
COMMANDS,
|
|
38
|
-
completeCommand,
|
|
39
|
-
commandHelp
|
|
40
|
-
};
|