@aayambansal/squint 0.2.8 → 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 +4 -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 +91 -132
- package/dist/{commands-N33DKZKC.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-YTO2YRF7.js +0 -41
package/README.md
CHANGED
|
@@ -150,6 +150,9 @@ squint doctor --probe # run every engine end to end, verify auth act
|
|
|
150
150
|
- **Problems**: findings from gates, the dev server, the runtime probe, and a11y sweeps
|
|
151
151
|
collect into a list — `/problems` shows it, `/fix` sends everything as one turn,
|
|
152
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.
|
|
153
156
|
- **Variants without leaving**: `/variants 3 <ask>` runs parallel explorations with
|
|
154
157
|
streaming per-family status; `/variants apply <id>` keeps the winner.
|
|
155
158
|
- **Commands**: type `/` and matching commands appear with descriptions; tab completes.
|
|
@@ -228,6 +231,7 @@ All product behavior lives in the harness, so a new engine is ~80 lines.
|
|
|
228
231
|
|
|
229
232
|
- [Engine setup guide](./docs/engines.md) — install + auth for all eight, and how to choose
|
|
230
233
|
- [Configuration](./docs/configuration.md) — every key, every `.squint/` file
|
|
234
|
+
- [The loops](./docs/loops.md) — everything that runs automatically around each turn
|
|
231
235
|
- [Architecture](./docs/design/2026-07-25-architecture.md)
|
|
232
236
|
- [How Lovable works under the hood](./docs/research/lovable.md)
|
|
233
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-YTO2YRF7.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";
|
|
@@ -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) => {
|
|
@@ -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"));
|
|
@@ -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(
|
|
@@ -570,97 +614,11 @@ import { render } from "ink";
|
|
|
570
614
|
|
|
571
615
|
// src/tui/App.tsx
|
|
572
616
|
import { Box as Box2, Static, Text as Text3, useApp, useInput } from "ink";
|
|
573
|
-
import
|
|
617
|
+
import path3 from "path";
|
|
574
618
|
import { useMemo, useRef, useState as useState2, useSyncExternalStore } from "react";
|
|
575
619
|
|
|
576
620
|
// 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
621
|
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
622
|
var MAX_AUTO_FIX_ATTEMPTS = 2;
|
|
665
623
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
666
624
|
var Session = class {
|
|
@@ -1139,13 +1097,14 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1139
1097
|
return pct;
|
|
1140
1098
|
}
|
|
1141
1099
|
/** Screenshot the running app (and watch its runtime where CDP is available). */
|
|
1142
|
-
async capture() {
|
|
1143
|
-
|
|
1144
|
-
|
|
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>");
|
|
1145
1104
|
return null;
|
|
1146
1105
|
}
|
|
1147
1106
|
this.push("status", "capturing screenshots\u2026");
|
|
1148
|
-
const result = await captureViewports(this.opts.cwd,
|
|
1107
|
+
const result = await captureViewports(this.opts.cwd, url);
|
|
1149
1108
|
if (!result) {
|
|
1150
1109
|
this.push("error", "no Chrome/Chromium found for screenshots");
|
|
1151
1110
|
return null;
|
|
@@ -1154,7 +1113,7 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
1154
1113
|
if (result.shots.length > 0) {
|
|
1155
1114
|
this.push(
|
|
1156
1115
|
"status",
|
|
1157
|
-
`captured ${result.shots.map((s) => s.name).join(", ")} \u2192 ${
|
|
1116
|
+
`captured ${result.shots.map((s) => s.name).join(", ")} \u2192 ${path2.dirname(result.shots[0].path)}`
|
|
1158
1117
|
);
|
|
1159
1118
|
}
|
|
1160
1119
|
if (result.runtime) {
|
|
@@ -1281,12 +1240,12 @@ They are objective defects, not style preferences.`
|
|
|
1281
1240
|
}
|
|
1282
1241
|
case "save": {
|
|
1283
1242
|
void (async () => {
|
|
1284
|
-
const
|
|
1285
|
-
const
|
|
1286
|
-
const dir =
|
|
1287
|
-
|
|
1243
|
+
const fs2 = await import("fs");
|
|
1244
|
+
const path4 = await import("path");
|
|
1245
|
+
const dir = path4.join(this.opts.cwd, ".squint", "transcripts");
|
|
1246
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1288
1247
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
1289
|
-
const file =
|
|
1248
|
+
const file = path4.join(dir, `${stamp}.md`);
|
|
1290
1249
|
const lines = [`# squint session \u2014 ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`, ""];
|
|
1291
1250
|
for (const item of this.state.items) {
|
|
1292
1251
|
switch (item.role) {
|
|
@@ -1306,8 +1265,8 @@ They are objective defects, not style preferences.`
|
|
|
1306
1265
|
}
|
|
1307
1266
|
}
|
|
1308
1267
|
lines.push("", `> ${this.summary()}`);
|
|
1309
|
-
|
|
1310
|
-
this.push("status", `saved transcript \u2192 ${
|
|
1268
|
+
fs2.writeFileSync(file, lines.join("\n") + "\n");
|
|
1269
|
+
this.push("status", `saved transcript \u2192 ${path4.relative(this.opts.cwd, file)}`);
|
|
1311
1270
|
})();
|
|
1312
1271
|
break;
|
|
1313
1272
|
}
|
|
@@ -1364,7 +1323,7 @@ They are objective defects, not style preferences.`
|
|
|
1364
1323
|
})();
|
|
1365
1324
|
break;
|
|
1366
1325
|
case "shot":
|
|
1367
|
-
void this.capture();
|
|
1326
|
+
void this.capture(arg || void 0);
|
|
1368
1327
|
break;
|
|
1369
1328
|
case "review":
|
|
1370
1329
|
void (async () => {
|
|
@@ -1551,7 +1510,7 @@ ${sandboxFiles(this.opts.cwd).join("\n")}`);
|
|
|
1551
1510
|
this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
|
|
1552
1511
|
break;
|
|
1553
1512
|
case "help": {
|
|
1554
|
-
void import("./commands-
|
|
1513
|
+
void import("./commands-TERCCWFM.js").then(({ commandHelp }) => this.push("status", commandHelp()));
|
|
1555
1514
|
break;
|
|
1556
1515
|
}
|
|
1557
1516
|
case "quit":
|
|
@@ -2086,7 +2045,7 @@ function App({
|
|
|
2086
2045
|
state.engineId,
|
|
2087
2046
|
state.model ? ` \xB7 ${state.model}` : "",
|
|
2088
2047
|
" \xB7 ",
|
|
2089
|
-
|
|
2048
|
+
path3.basename(cwd),
|
|
2090
2049
|
devBadge,
|
|
2091
2050
|
totalsBadge,
|
|
2092
2051
|
state.problems.length > 0 && /* @__PURE__ */ jsxs3(Text3, { color: theme2.error, children: [
|
|
@@ -2136,7 +2095,7 @@ function registerTui(program2) {
|
|
|
2136
2095
|
}
|
|
2137
2096
|
|
|
2138
2097
|
// src/cli.tsx
|
|
2139
|
-
var VERSION = "0.2.
|
|
2098
|
+
var VERSION = true ? "0.2.9" : "0.0.0-dev";
|
|
2140
2099
|
var program = new Command();
|
|
2141
2100
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
2142
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-YTO2YRF7.js
DELETED
|
@@ -1,41 +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: "sandbox", args: "[on|diff|apply|discard]", description: "asks accumulate in a shadow worktree until you apply" },
|
|
13
|
-
{ name: "undo", description: "revert the last ask (files only)" },
|
|
14
|
-
{ name: "checkpoints", description: "list per-ask checkpoints" },
|
|
15
|
-
{ name: "restore", args: "<n>", description: "rewind files to before ask n" },
|
|
16
|
-
{ name: "mode", args: "plan|safe|yolo", description: "how much the engine may do (shift+tab cycles)" },
|
|
17
|
-
{ name: "engine", args: "<id>", description: "switch backend (new session)" },
|
|
18
|
-
{ name: "engines", description: "list installed engines with streaming/resume support" },
|
|
19
|
-
{ name: "model", args: "[name]", description: "model override for the engine" },
|
|
20
|
-
{ name: "theme", args: "[name]", description: "switch the TUI theme", viewLevel: true },
|
|
21
|
-
{ name: "copy", description: "copy the last reply to the clipboard" },
|
|
22
|
-
{ name: "save", description: "export the transcript to .squint/transcripts/" },
|
|
23
|
-
{ name: "queue", args: "clear", description: "drop queued asks" },
|
|
24
|
-
{ name: "resume", description: "pick up the previous session for this repo" },
|
|
25
|
-
{ name: "clear", description: "new session (transcript, totals, persisted state)" },
|
|
26
|
-
{ name: "help", description: "list commands" },
|
|
27
|
-
{ name: "quit", 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
|
-
function commandHelp() {
|
|
34
|
-
return COMMANDS.map((c) => `/${c.name}${c.args ? ` ${c.args}` : ""} \u2014 ${c.description}`).join("\n");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export {
|
|
38
|
-
COMMANDS,
|
|
39
|
-
completeCommand,
|
|
40
|
-
commandHelp
|
|
41
|
-
};
|