@aayambansal/squint 0.2.1 → 0.2.3
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 +21 -5
- package/dist/cdp-SFWNP7OA.js +11 -0
- package/dist/{chunk-XZKQZKEE.js → chunk-4LAU5TGK.js} +18 -0
- package/dist/chunk-EECGXFRX.js +39 -0
- package/dist/{chunk-VJGE7HSP.js → chunk-OC6RU6XH.js} +50 -0
- package/dist/{chunk-REDPR3KI.js → chunk-SU6YRRMF.js} +19 -5
- package/dist/{chunk-WTA6YYBY.js → chunk-YHRAOBI2.js} +17 -0
- package/dist/cli.js +242 -74
- package/dist/commands-6XDXUVO6.js +11 -0
- package/dist/{preview-XQ7EOHV4.js → preview-UX4VS4SX.js} +4 -2
- package/dist/{skills-UGHU22BS.js → skills-DNBHZAAU.js} +3 -1
- package/dist/{snapshot-KKUY5RR6.js → snapshot-H6VYFHLN.js} +3 -1
- package/package.json +1 -1
- package/dist/cdp-RTWPFIX5.js +0 -9
package/README.md
CHANGED
|
@@ -123,9 +123,10 @@ squint check # gates: typecheck -> lint -> test -> build
|
|
|
123
123
|
squint shot http://localhost:5173 # screenshots at 390/768/1440
|
|
124
124
|
squint brief # list design directions
|
|
125
125
|
squint brief cinematic-dark # commit one for this repo
|
|
126
|
-
squint tag #
|
|
126
|
+
squint tag # Alt+S element picker: pin elements + notes, alt+enter copies all
|
|
127
127
|
squint variants gen 3 "<ask>" # 3 parallel design explorations
|
|
128
128
|
squint variants apply terminal # keep the winner
|
|
129
|
+
squint skills init # scaffold .squint/rules.md + a trigger-matched skill
|
|
129
130
|
squint config set engine claude
|
|
130
131
|
squint config set models.claude claude-sonnet-5
|
|
131
132
|
squint config set autoDev true # dev server starts with the TUI
|
|
@@ -134,6 +135,7 @@ squint config set autoCheck false # skip the per-turn typecheck+lint pass
|
|
|
134
135
|
squint config set theme ocean # amber · ocean · moss · rose · mono
|
|
135
136
|
squint config set bell false # no bell on turn completion
|
|
136
137
|
squint doctor # engines + Chrome + WebSocket check
|
|
138
|
+
squint doctor --probe # run every engine end to end, verify auth actually works
|
|
137
139
|
```
|
|
138
140
|
|
|
139
141
|
**Inside the TUI:**
|
|
@@ -144,10 +146,24 @@ squint doctor # engines + Chrome + WebSocket check
|
|
|
144
146
|
order; `/queue clear` drops them. `Esc` interrupts the current turn.
|
|
145
147
|
- **Editing**: a real line editor — arrows move, `alt+←/→` jump words, `ctrl+a/e/k/u/w`,
|
|
146
148
|
`↑/↓` history. `ctrl+c` twice exits with a session summary.
|
|
147
|
-
- **
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
- **Problems**: findings from gates, the dev server, the runtime probe, and a11y sweeps
|
|
150
|
+
collect into a list — `/problems` shows it, `/fix` sends everything as one turn,
|
|
151
|
+
`/fix <n>` targets one. The footer counts what's open.
|
|
152
|
+
- **Variants without leaving**: `/variants 3 <ask>` runs parallel explorations with
|
|
153
|
+
streaming per-family status; `/variants apply <id>` keeps the winner.
|
|
154
|
+
- **Commands**: type `/` and matching commands appear with descriptions; tab completes.
|
|
155
|
+
`/dev` `/check` `/problems` `/fix [n]` `/shot` `/review [focus]` `/variants` `/undo`
|
|
156
|
+
`/checkpoints` `/restore <n>` `/mode` `/theme` `/copy` `/save` `/resume` `/clear`.
|
|
157
|
+
- **Visual pulse**: every clean turn is screenshotted and pixel-compared with the last —
|
|
158
|
+
drift shows up as a number, not a surprise. `.squint/locks` lists paths the engine must
|
|
159
|
+
never touch; `/save` exports the transcript as markdown.
|
|
160
|
+
- Assistant output renders as markdown; the done line measures real work via git
|
|
161
|
+
(`3 files +42 −7`); the footer tracks session turns and cost; a bell rings when a
|
|
162
|
+
turn finishes.
|
|
163
|
+
|
|
164
|
+
**Project knowledge** rides along automatically: `.squint/rules.md` on every ask, and
|
|
165
|
+
`.squint/skills/*.md` (frontmatter `triggers: auth, login`) only when an ask mentions a
|
|
166
|
+
trigger — deterministic context routing, no embeddings.
|
|
151
167
|
|
|
152
168
|
## Design directions
|
|
153
169
|
|
|
@@ -51,12 +51,29 @@ function matchSkills(skills, ask) {
|
|
|
51
51
|
const haystack = ask.toLowerCase();
|
|
52
52
|
return skills.filter((skill) => skill.triggers.some((t) => haystack.includes(t)));
|
|
53
53
|
}
|
|
54
|
+
function loadLocks(cwd) {
|
|
55
|
+
try {
|
|
56
|
+
return fs.readFileSync(path.join(cwd, ".squint", "locks"), "utf8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
57
|
+
} catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
54
61
|
function enrich(cwd, ask) {
|
|
55
62
|
const parts = [];
|
|
56
63
|
const rules = loadRules(cwd);
|
|
57
64
|
if (rules) parts.push(`## Project rules (always apply)
|
|
58
65
|
|
|
59
66
|
${rules}`);
|
|
67
|
+
const locks = loadLocks(cwd);
|
|
68
|
+
if (locks.length > 0) {
|
|
69
|
+
parts.push(
|
|
70
|
+
`## Locked files (hard constraint)
|
|
71
|
+
|
|
72
|
+
Never modify these paths, no matter what the task seems to need:
|
|
73
|
+
${locks.map((l) => `- ${l}`).join("\n")}
|
|
74
|
+
If the task appears to require changing them, stop and explain instead.`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
60
77
|
const matched = matchSkills(loadSkills(cwd), ask);
|
|
61
78
|
for (const skill of matched) {
|
|
62
79
|
parts.push(`## Project notes: ${skill.name}
|
|
@@ -76,5 +93,6 @@ export {
|
|
|
76
93
|
loadSkills,
|
|
77
94
|
loadRules,
|
|
78
95
|
matchSkills,
|
|
96
|
+
loadLocks,
|
|
79
97
|
enrich
|
|
80
98
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/session/commands.ts
|
|
4
|
+
var COMMANDS = [
|
|
5
|
+
{ name: "dev", description: "start/stop the project dev server" },
|
|
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: "model", args: "[name]", description: "model override for the engine" },
|
|
18
|
+
{ name: "theme", args: "[name]", description: "switch the TUI theme", viewLevel: true },
|
|
19
|
+
{ name: "copy", description: "copy the last reply to the clipboard" },
|
|
20
|
+
{ name: "save", description: "export the transcript to .squint/transcripts/" },
|
|
21
|
+
{ name: "queue", args: "clear", description: "drop queued asks" },
|
|
22
|
+
{ name: "resume", description: "pick up the previous session for this repo" },
|
|
23
|
+
{ name: "clear", description: "new session (transcript, totals, persisted state)" },
|
|
24
|
+
{ name: "help", description: "list commands" },
|
|
25
|
+
{ name: "quit", description: "exit with a session summary" }
|
|
26
|
+
];
|
|
27
|
+
function completeCommand(partial) {
|
|
28
|
+
const query = partial.toLowerCase();
|
|
29
|
+
return COMMANDS.filter((c) => c.name.startsWith(query));
|
|
30
|
+
}
|
|
31
|
+
function commandHelp() {
|
|
32
|
+
return COMMANDS.map((c) => `/${c.name}${c.args ? ` ${c.args}` : ""} \u2014 ${c.description}`).join("\n");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
COMMANDS,
|
|
37
|
+
completeCommand,
|
|
38
|
+
commandHelp
|
|
39
|
+
};
|
|
@@ -152,6 +152,55 @@ var describe = (value) => {
|
|
|
152
152
|
}
|
|
153
153
|
return String(value);
|
|
154
154
|
};
|
|
155
|
+
async function pixelDiffPct(chromePath, pngA, pngB) {
|
|
156
|
+
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
157
|
+
let connection = null;
|
|
158
|
+
try {
|
|
159
|
+
connection = await CdpConnection.connect(wsUrl, 1e4);
|
|
160
|
+
const { targetId } = await connection.send("Target.createTarget", { url: "about:blank" });
|
|
161
|
+
const { sessionId } = await connection.send("Target.attachToTarget", { targetId, flatten: true });
|
|
162
|
+
await connection.send("Runtime.enable", {}, sessionId);
|
|
163
|
+
const expression = `(async () => {
|
|
164
|
+
const load = (src) => new Promise((resolve, reject) => {
|
|
165
|
+
const img = new Image();
|
|
166
|
+
img.onload = () => resolve(img);
|
|
167
|
+
img.onerror = () => reject(new Error('decode'));
|
|
168
|
+
img.src = src;
|
|
169
|
+
});
|
|
170
|
+
const a = await load('data:image/png;base64,${pngA.toString("base64")}');
|
|
171
|
+
const b = await load('data:image/png;base64,${pngB.toString("base64")}');
|
|
172
|
+
const w = Math.min(a.width, b.width), h = Math.min(a.height, b.height);
|
|
173
|
+
if (w === 0 || h === 0) return null;
|
|
174
|
+
const draw = (img) => {
|
|
175
|
+
const c = document.createElement('canvas');
|
|
176
|
+
c.width = w; c.height = h;
|
|
177
|
+
const ctx = c.getContext('2d');
|
|
178
|
+
ctx.drawImage(img, 0, 0);
|
|
179
|
+
return ctx.getImageData(0, 0, w, h).data;
|
|
180
|
+
};
|
|
181
|
+
const da = draw(a), db = draw(b);
|
|
182
|
+
let differ = 0, total = 0;
|
|
183
|
+
for (let i = 0; i < da.length; i += 8) {
|
|
184
|
+
total++;
|
|
185
|
+
if (Math.abs(da[i] - db[i]) > 8 || Math.abs(da[i + 1] - db[i + 1]) > 8 || Math.abs(da[i + 2] - db[i + 2]) > 8) differ++;
|
|
186
|
+
}
|
|
187
|
+
const sizePenalty = (a.width !== b.width || a.height !== b.height) ? 1 : 0;
|
|
188
|
+
return Math.min(100, (differ / total) * 100 + sizePenalty);
|
|
189
|
+
})()`;
|
|
190
|
+
const { result } = await connection.send(
|
|
191
|
+
"Runtime.evaluate",
|
|
192
|
+
{ expression, awaitPromise: true, returnByValue: true },
|
|
193
|
+
sessionId
|
|
194
|
+
);
|
|
195
|
+
return typeof result?.value === "number" ? result.value : null;
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
} finally {
|
|
199
|
+
connection?.close();
|
|
200
|
+
child.kill("SIGKILL");
|
|
201
|
+
setTimeout(() => fs.rmSync(profileDir, { recursive: true, force: true }), 500).unref?.();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
155
204
|
async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, audit = false) {
|
|
156
205
|
const { child, wsUrl, profileDir } = await launchChrome(chromePath);
|
|
157
206
|
const report = { consoleErrors: [], pageErrors: [], failedRequests: [] };
|
|
@@ -239,5 +288,6 @@ async function cdpCapture(chromePath, url, outDir, viewports, settleMs = 2500, a
|
|
|
239
288
|
|
|
240
289
|
export {
|
|
241
290
|
hasWebSocket,
|
|
291
|
+
pixelDiffPct,
|
|
242
292
|
cdpCapture
|
|
243
293
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
cdpCapture,
|
|
4
4
|
hasWebSocket
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-OC6RU6XH.js";
|
|
6
6
|
import {
|
|
7
7
|
findChrome,
|
|
8
8
|
screenshot
|
|
@@ -16,7 +16,7 @@ import path2 from "path";
|
|
|
16
16
|
// src/state/state.ts
|
|
17
17
|
import fs from "fs";
|
|
18
18
|
import path from "path";
|
|
19
|
-
var IGNORED = ["preview/", "state.json", "variants/"];
|
|
19
|
+
var IGNORED = ["preview/", "state.json", "variants/", "transcripts/"];
|
|
20
20
|
function ensureSquintIgnore(cwd) {
|
|
21
21
|
const dir = path.join(cwd, ".squint");
|
|
22
22
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -126,16 +126,29 @@ ${blocks.join("\n\n")}
|
|
|
126
126
|
|
|
127
127
|
Fix these first \u2014 a page that errors is broken regardless of how it looks.`;
|
|
128
128
|
}
|
|
129
|
-
async function probeRuntime(url) {
|
|
129
|
+
async function probeRuntime(url, cwd) {
|
|
130
130
|
const chrome = findChrome();
|
|
131
131
|
if (!chrome || !hasWebSocket()) return null;
|
|
132
132
|
try {
|
|
133
|
-
const
|
|
134
|
-
|
|
133
|
+
const dir = cwd ? previewDir(cwd) : os.tmpdir();
|
|
134
|
+
const { report, shots } = await cdpCapture(
|
|
135
|
+
chrome,
|
|
136
|
+
url,
|
|
137
|
+
dir,
|
|
138
|
+
cwd ? [{ name: "pulse", width: 1280, height: 800 }] : [],
|
|
139
|
+
1500
|
|
140
|
+
);
|
|
141
|
+
return { report, pulsePath: shots[0]?.path };
|
|
135
142
|
} catch {
|
|
136
143
|
return null;
|
|
137
144
|
}
|
|
138
145
|
}
|
|
146
|
+
async function comparePulse(previous, current) {
|
|
147
|
+
const chrome = findChrome();
|
|
148
|
+
if (!chrome || !hasWebSocket()) return null;
|
|
149
|
+
const { pixelDiffPct } = await import("./cdp-SFWNP7OA.js");
|
|
150
|
+
return pixelDiffPct(chrome, previous, current);
|
|
151
|
+
}
|
|
139
152
|
function buildRuntimeFixPrompt(report) {
|
|
140
153
|
return `The running app has runtime problems.${runtimeSection(report)}
|
|
141
154
|
|
|
@@ -169,6 +182,7 @@ export {
|
|
|
169
182
|
captureViewports,
|
|
170
183
|
runtimeSummary,
|
|
171
184
|
probeRuntime,
|
|
185
|
+
comparePulse,
|
|
172
186
|
buildRuntimeFixPrompt,
|
|
173
187
|
buildReviewPrompt
|
|
174
188
|
};
|
|
@@ -28,6 +28,22 @@ function takeSnapshot(cwd) {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
function diffStatSince(cwd, snapshot) {
|
|
32
|
+
try {
|
|
33
|
+
const source = snapshot.stashHash ?? "HEAD";
|
|
34
|
+
const stat = git(cwd, ["diff", "--shortstat", source]);
|
|
35
|
+
const files = /(\d+) files? changed/.exec(stat);
|
|
36
|
+
if (!files) return null;
|
|
37
|
+
const insertions = /(\d+) insertions?/.exec(stat);
|
|
38
|
+
const deletions = /(\d+) deletions?/.exec(stat);
|
|
39
|
+
let out = `${files[1]} file${files[1] === "1" ? "" : "s"}`;
|
|
40
|
+
if (insertions) out += ` +${insertions[1]}`;
|
|
41
|
+
if (deletions) out += ` \u2212${deletions[1]}`;
|
|
42
|
+
return out;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
31
47
|
function restoreSnapshot(cwd, snapshot) {
|
|
32
48
|
try {
|
|
33
49
|
const before = new Set(snapshot.untracked);
|
|
@@ -50,5 +66,6 @@ function restoreSnapshot(cwd, snapshot) {
|
|
|
50
66
|
export {
|
|
51
67
|
isGitRepo,
|
|
52
68
|
takeSnapshot,
|
|
69
|
+
diffStatSince,
|
|
53
70
|
restoreSnapshot
|
|
54
71
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
diffStatSince,
|
|
4
|
+
isGitRepo,
|
|
5
|
+
restoreSnapshot,
|
|
6
|
+
takeSnapshot
|
|
7
|
+
} from "./chunk-YHRAOBI2.js";
|
|
2
8
|
import {
|
|
3
9
|
DevServer,
|
|
4
10
|
buildFixPrompt,
|
|
@@ -17,6 +23,9 @@ import {
|
|
|
17
23
|
import {
|
|
18
24
|
runAgent
|
|
19
25
|
} from "./chunk-OTG4ZB4R.js";
|
|
26
|
+
import {
|
|
27
|
+
completeCommand
|
|
28
|
+
} from "./chunk-EECGXFRX.js";
|
|
20
29
|
import {
|
|
21
30
|
buildGatePrompt,
|
|
22
31
|
detectFastGates,
|
|
@@ -28,12 +37,13 @@ import {
|
|
|
28
37
|
buildRuntimeFixPrompt,
|
|
29
38
|
captureViewports,
|
|
30
39
|
clearState,
|
|
40
|
+
comparePulse,
|
|
31
41
|
loadState,
|
|
32
42
|
probeRuntime,
|
|
33
43
|
runtimeSummary,
|
|
34
44
|
saveState
|
|
35
|
-
} from "./chunk-
|
|
36
|
-
import "./chunk-
|
|
45
|
+
} from "./chunk-SU6YRRMF.js";
|
|
46
|
+
import "./chunk-OC6RU6XH.js";
|
|
37
47
|
import {
|
|
38
48
|
findChrome
|
|
39
49
|
} from "./chunk-P3V5CWH5.js";
|
|
@@ -43,12 +53,7 @@ import {
|
|
|
43
53
|
} from "./chunk-2LRIKWBU.js";
|
|
44
54
|
import {
|
|
45
55
|
enrich
|
|
46
|
-
} from "./chunk-
|
|
47
|
-
import {
|
|
48
|
-
isGitRepo,
|
|
49
|
-
restoreSnapshot,
|
|
50
|
-
takeSnapshot
|
|
51
|
-
} from "./chunk-WTA6YYBY.js";
|
|
56
|
+
} from "./chunk-4LAU5TGK.js";
|
|
52
57
|
|
|
53
58
|
// src/cli.tsx
|
|
54
59
|
import { Command } from "commander";
|
|
@@ -161,7 +166,8 @@ var Session = class {
|
|
|
161
166
|
devUrl: null,
|
|
162
167
|
totals: { costUsd: 0, turns: 0 },
|
|
163
168
|
queue: [],
|
|
164
|
-
mode: "safe"
|
|
169
|
+
mode: "safe",
|
|
170
|
+
problems: []
|
|
165
171
|
};
|
|
166
172
|
if (opts.autoDev && detectDevCommand(opts.cwd)) {
|
|
167
173
|
this.devServer().start();
|
|
@@ -188,10 +194,13 @@ var Session = class {
|
|
|
188
194
|
sessionId;
|
|
189
195
|
dev = null;
|
|
190
196
|
abort = null;
|
|
191
|
-
|
|
197
|
+
/** Full problem records; state carries the summaries. */
|
|
198
|
+
problemPrompts = /* @__PURE__ */ new Map();
|
|
199
|
+
nextProblemId = 0;
|
|
192
200
|
checkpoints = [];
|
|
193
201
|
fixAttempts = 0;
|
|
194
202
|
reviewTipShown = false;
|
|
203
|
+
lastPulse = null;
|
|
195
204
|
startedAt = Date.now();
|
|
196
205
|
subscribe(listener) {
|
|
197
206
|
this.listeners.add(listener);
|
|
@@ -211,6 +220,47 @@ var Session = class {
|
|
|
211
220
|
note(text) {
|
|
212
221
|
this.push("status", text);
|
|
213
222
|
}
|
|
223
|
+
/** Register a finding; a fresh finding from a source supersedes its old one. */
|
|
224
|
+
addProblem(source, summary, prompt) {
|
|
225
|
+
const kept = this.state.problems.filter((p) => p.source !== source);
|
|
226
|
+
for (const gone of this.state.problems) {
|
|
227
|
+
if (gone.source === source) this.problemPrompts.delete(gone.id);
|
|
228
|
+
}
|
|
229
|
+
this.nextProblemId += 1;
|
|
230
|
+
this.problemPrompts.set(this.nextProblemId, prompt);
|
|
231
|
+
this.notify({ problems: [...kept, { id: this.nextProblemId, source, summary }] });
|
|
232
|
+
}
|
|
233
|
+
clearProblems(source) {
|
|
234
|
+
const removed = this.state.problems.filter((p) => p.source === source);
|
|
235
|
+
if (removed.length === 0) return;
|
|
236
|
+
for (const problem of removed) this.problemPrompts.delete(problem.id);
|
|
237
|
+
this.notify({ problems: this.state.problems.filter((p) => p.source !== source) });
|
|
238
|
+
}
|
|
239
|
+
/** One prompt covering the given problems, oldest first. */
|
|
240
|
+
combinedFixPrompt(problems) {
|
|
241
|
+
const sections = problems.map(
|
|
242
|
+
(p) => this.problemPrompts.get(p.id) ?? `Fix this reported problem: ${p.summary}`
|
|
243
|
+
);
|
|
244
|
+
return sections.join("\n\n---\n\n");
|
|
245
|
+
}
|
|
246
|
+
dispatchFix(problems) {
|
|
247
|
+
if (problems.length === 0) return;
|
|
248
|
+
const prompt = this.combinedFixPrompt(problems);
|
|
249
|
+
const display = `\u26D1 fix: ${problems.map((p) => p.source).join(" + ")}`;
|
|
250
|
+
for (const problem of problems) this.problemPrompts.delete(problem.id);
|
|
251
|
+
this.notify({ problems: this.state.problems.filter((p) => !problems.includes(p)) });
|
|
252
|
+
void this.runTurn(prompt, display);
|
|
253
|
+
}
|
|
254
|
+
/** Launch a capped auto-fix turn over all open problems. Returns true if launched. */
|
|
255
|
+
maybeAutoFix() {
|
|
256
|
+
if (!this.opts.autoFix || this.fixAttempts >= MAX_AUTO_FIX_ATTEMPTS) return false;
|
|
257
|
+
if (this.state.problems.length === 0) return false;
|
|
258
|
+
this.fixAttempts += 1;
|
|
259
|
+
this.push("status", `auto-fix attempt ${this.fixAttempts}/${MAX_AUTO_FIX_ATTEMPTS}`);
|
|
260
|
+
this.notify({ running: false });
|
|
261
|
+
this.dispatchFix([...this.state.problems]);
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
214
264
|
setMode(mode) {
|
|
215
265
|
this.notify({ mode });
|
|
216
266
|
const hint = mode === "plan" ? "read-only: the engine investigates and proposes, edits nothing" : mode === "yolo" ? "no approval friction \u2014 the engine can do anything" : "edits auto-approved inside the workspace";
|
|
@@ -363,7 +413,9 @@ var Session = class {
|
|
|
363
413
|
if (result.ok) {
|
|
364
414
|
const cost = result.costUsd !== void 0 ? ` \xB7 $${result.costUsd.toFixed(2)}` : "";
|
|
365
415
|
const secs = result.durationMs !== void 0 ? ` \xB7 ${(result.durationMs / 1e3).toFixed(0)}s` : "";
|
|
366
|
-
const
|
|
416
|
+
const checkpoint = this.checkpoints.at(-1);
|
|
417
|
+
const stat = checkpoint ? diffStatSince(this.opts.cwd, checkpoint.snapshot) : null;
|
|
418
|
+
const work = stat ? ` \xB7 ${stat}` : this.turnEdits > 0 ? ` \xB7 ${this.turnEdits} edit${this.turnEdits === 1 ? "" : "s"}` : this.turnTools > 0 ? ` \xB7 ${this.turnTools} tool call${this.turnTools === 1 ? "" : "s"}` : "";
|
|
367
419
|
this.push("status", `done${secs}${cost}${work}`);
|
|
368
420
|
this.notify({
|
|
369
421
|
totals: {
|
|
@@ -387,26 +439,22 @@ var Session = class {
|
|
|
387
439
|
const gateResults = await runGates(this.opts.cwd, fastGates);
|
|
388
440
|
const failures = gateResults.filter((r) => !r.ok);
|
|
389
441
|
if (failures.length > 0) {
|
|
390
|
-
this.
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
442
|
+
this.addProblem(
|
|
443
|
+
"gates",
|
|
444
|
+
failures.map((f) => f.gate.id).join(" + "),
|
|
445
|
+
buildGatePrompt(failures)
|
|
446
|
+
);
|
|
394
447
|
this.push(
|
|
395
448
|
"error",
|
|
396
449
|
failures.map((f) => `\u2717 ${f.gate.id} \xB7 ${f.outputTail.split("\n").slice(-3).join("\n")}`).join("\n")
|
|
397
450
|
);
|
|
398
|
-
if (this.
|
|
399
|
-
|
|
400
|
-
this.push("status", `auto-fix attempt ${this.fixAttempts}/${MAX_AUTO_FIX_ATTEMPTS}`);
|
|
401
|
-
this.notify({ running: false });
|
|
402
|
-
await this.runTurn(this.pendingFix.prompt, this.pendingFix.display);
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
this.push("status", "type /fix to send them to the engine");
|
|
451
|
+
if (this.maybeAutoFix()) return;
|
|
452
|
+
this.push("status", "/fix sends open problems to the engine \xB7 /problems lists them");
|
|
406
453
|
this.notify({ running: false });
|
|
407
454
|
this.drainQueue();
|
|
408
455
|
return;
|
|
409
456
|
}
|
|
457
|
+
this.clearProblems("gates");
|
|
410
458
|
}
|
|
411
459
|
}
|
|
412
460
|
const dev = this.dev;
|
|
@@ -414,39 +462,28 @@ var Session = class {
|
|
|
414
462
|
await delay(1500);
|
|
415
463
|
const errors = dev.errorsSince(runStart);
|
|
416
464
|
if (errors.length > 0) {
|
|
417
|
-
this.
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
465
|
+
this.addProblem(
|
|
466
|
+
"dev",
|
|
467
|
+
`${errors.length} dev server error line(s)`,
|
|
468
|
+
buildFixPrompt(errors, dev.tail(30))
|
|
469
|
+
);
|
|
421
470
|
this.push("error", `dev server: ${errors.length} error line(s)
|
|
422
471
|
${errors.slice(-5).join("\n")}`);
|
|
423
|
-
if (this.
|
|
424
|
-
|
|
425
|
-
this.push("status", `auto-fix attempt ${this.fixAttempts}/${MAX_AUTO_FIX_ATTEMPTS}`);
|
|
426
|
-
this.notify({ running: false });
|
|
427
|
-
await this.runTurn(this.pendingFix.prompt, this.pendingFix.display);
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
this.push("status", "type /fix to send them to the engine");
|
|
472
|
+
if (this.maybeAutoFix()) return;
|
|
473
|
+
this.push("status", "/fix sends open problems to the engine \xB7 /problems lists them");
|
|
431
474
|
} else {
|
|
432
|
-
this.
|
|
475
|
+
this.clearProblems("dev");
|
|
433
476
|
if (this.opts.autoProbe !== false && this.state.devUrl) {
|
|
434
|
-
const
|
|
435
|
-
const summary =
|
|
436
|
-
if (
|
|
437
|
-
this.
|
|
438
|
-
prompt: buildRuntimeFixPrompt(report),
|
|
439
|
-
display: "\u26D1 fix runtime errors"
|
|
440
|
-
};
|
|
477
|
+
const probe = await probeRuntime(this.state.devUrl, this.opts.cwd);
|
|
478
|
+
const summary = probe ? runtimeSummary(probe.report) : null;
|
|
479
|
+
if (probe && summary) {
|
|
480
|
+
this.addProblem("runtime", summary, buildRuntimeFixPrompt(probe.report));
|
|
441
481
|
this.push("error", `runtime: ${summary}`);
|
|
442
|
-
if (this.
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
|
-
this.push("status", "type /fix to send them to the engine");
|
|
482
|
+
if (this.maybeAutoFix()) return;
|
|
483
|
+
this.push("status", "/fix sends open problems to the engine \xB7 /problems lists them");
|
|
484
|
+
} else if (probe) {
|
|
485
|
+
this.clearProblems("runtime");
|
|
486
|
+
await this.visualPulse(probe.pulsePath);
|
|
450
487
|
}
|
|
451
488
|
}
|
|
452
489
|
if (!this.reviewTipShown && this.state.devUrl) {
|
|
@@ -497,6 +534,32 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
497
534
|
this.push("error", `restore failed: ${result.detail ?? "unknown error"}`);
|
|
498
535
|
}
|
|
499
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Cross-turn visual drift check: compare this turn's pulse screenshot
|
|
539
|
+
* with the previous one and report how much of the page changed.
|
|
540
|
+
* Informational — changes are usually intended; surprises shouldn't be.
|
|
541
|
+
*/
|
|
542
|
+
async visualPulse(pulsePath) {
|
|
543
|
+
if (!pulsePath) return;
|
|
544
|
+
let current;
|
|
545
|
+
try {
|
|
546
|
+
current = (await import("fs")).readFileSync(pulsePath);
|
|
547
|
+
} catch {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const previous = this.lastPulse;
|
|
551
|
+
this.lastPulse = current;
|
|
552
|
+
if (!previous) {
|
|
553
|
+
this.push("status", "visual pulse: baseline captured");
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const pct = await comparePulse(previous, current);
|
|
557
|
+
if (pct === null) return;
|
|
558
|
+
this.push(
|
|
559
|
+
"status",
|
|
560
|
+
pct < 0.5 ? "visual pulse: stable vs last turn" : `visual pulse: ${pct.toFixed(1)}% of the page changed vs last turn`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
500
563
|
/** Screenshot the running app (and watch its runtime where CDP is available). */
|
|
501
564
|
async capture() {
|
|
502
565
|
if (!this.state.devUrl) {
|
|
@@ -520,16 +583,28 @@ ${errors.slice(-5).join("\n")}`);
|
|
|
520
583
|
const summary = runtimeSummary(result.runtime);
|
|
521
584
|
if (summary) {
|
|
522
585
|
this.push("error", `runtime: ${summary}`);
|
|
523
|
-
this.
|
|
524
|
-
this.push("status", "
|
|
586
|
+
this.addProblem("runtime", summary, buildRuntimeFixPrompt(result.runtime));
|
|
587
|
+
this.push("status", "/fix sends open problems to the engine");
|
|
525
588
|
} else {
|
|
589
|
+
this.clearProblems("runtime");
|
|
526
590
|
this.push("status", "runtime clean \u2014 no console errors, exceptions, or failed requests");
|
|
527
591
|
}
|
|
528
592
|
}
|
|
529
593
|
if (result.a11y && result.a11y.length > 0) {
|
|
530
594
|
this.push("error", `a11y: ${result.a11y.length} finding(s)
|
|
531
595
|
${result.a11y.slice(0, 5).join("\n")}`);
|
|
532
|
-
this.
|
|
596
|
+
this.addProblem(
|
|
597
|
+
"a11y",
|
|
598
|
+
`${result.a11y.length} accessibility finding(s)`,
|
|
599
|
+
`Fix these accessibility defects found by an automated sweep of the running app:
|
|
600
|
+
|
|
601
|
+
${result.a11y.join("\n")}
|
|
602
|
+
|
|
603
|
+
They are objective defects, not style preferences.`
|
|
604
|
+
);
|
|
605
|
+
this.push("status", "/review folds these into the fix pass \xB7 /fix sends them directly");
|
|
606
|
+
} else if (result.runtime) {
|
|
607
|
+
this.clearProblems("a11y");
|
|
533
608
|
}
|
|
534
609
|
return result.shots.length > 0 ? result : null;
|
|
535
610
|
}
|
|
@@ -579,11 +654,79 @@ ${result.a11y.slice(0, 5).join("\n")}`);
|
|
|
579
654
|
}
|
|
580
655
|
break;
|
|
581
656
|
}
|
|
582
|
-
case "fix":
|
|
583
|
-
if (
|
|
584
|
-
this.push("status", "nothing to fix \u2014 no
|
|
657
|
+
case "fix": {
|
|
658
|
+
if (this.state.problems.length === 0) {
|
|
659
|
+
this.push("status", "nothing to fix \u2014 no open problems");
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
const index = Number.parseInt(arg, 10);
|
|
663
|
+
if (arg && Number.isInteger(index)) {
|
|
664
|
+
const target = this.state.problems[index - 1];
|
|
665
|
+
if (!target) {
|
|
666
|
+
this.push("status", `usage: /fix [1\u2013${this.state.problems.length}] \u2014 see /problems`);
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
this.dispatchFix([target]);
|
|
670
|
+
} else {
|
|
671
|
+
this.dispatchFix([...this.state.problems]);
|
|
672
|
+
}
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
case "save": {
|
|
676
|
+
void (async () => {
|
|
677
|
+
const fs2 = await import("fs");
|
|
678
|
+
const path4 = await import("path");
|
|
679
|
+
const dir = path4.join(this.opts.cwd, ".squint", "transcripts");
|
|
680
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
681
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
682
|
+
const file = path4.join(dir, `${stamp}.md`);
|
|
683
|
+
const lines = [`# squint session \u2014 ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)}`, ""];
|
|
684
|
+
for (const item of this.state.items) {
|
|
685
|
+
switch (item.role) {
|
|
686
|
+
case "user":
|
|
687
|
+
lines.push(`## \u276F ${item.text}`, "");
|
|
688
|
+
break;
|
|
689
|
+
case "assistant":
|
|
690
|
+
lines.push(item.text, "");
|
|
691
|
+
break;
|
|
692
|
+
case "tool":
|
|
693
|
+
lines.push(`- \u2699 ${item.text}`);
|
|
694
|
+
break;
|
|
695
|
+
case "thinking":
|
|
696
|
+
break;
|
|
697
|
+
default:
|
|
698
|
+
lines.push(`> ${item.role === "error" ? "\u2717 " : ""}${item.text.split("\n").join("\n> ")}`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
lines.push("", `> ${this.summary()}`);
|
|
702
|
+
fs2.writeFileSync(file, lines.join("\n") + "\n");
|
|
703
|
+
this.push("status", `saved transcript \u2192 ${path4.relative(this.opts.cwd, file)}`);
|
|
704
|
+
})();
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
case "copy": {
|
|
708
|
+
const last = this.state.items.findLast((i) => i.role === "assistant");
|
|
709
|
+
if (!last) {
|
|
710
|
+
this.push("status", "nothing to copy yet");
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
void import("child_process").then(({ spawn }) => {
|
|
714
|
+
const cmd = process.platform === "darwin" ? spawn("pbcopy") : process.platform === "win32" ? spawn("clip") : spawn("xclip", ["-selection", "clipboard"]);
|
|
715
|
+
cmd.on("error", () => this.push("error", "no clipboard tool found"));
|
|
716
|
+
cmd.on("close", (code) => {
|
|
717
|
+
if (code === 0) this.push("status", `copied last reply (${last.text.length} chars)`);
|
|
718
|
+
});
|
|
719
|
+
cmd.stdin?.end(last.text);
|
|
720
|
+
});
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
case "problems":
|
|
724
|
+
if (this.state.problems.length === 0) {
|
|
725
|
+
this.push("status", "no open problems");
|
|
585
726
|
} else {
|
|
586
|
-
|
|
727
|
+
const lines = this.state.problems.map((p, i) => `${i + 1}. [${p.source}] ${p.summary}`);
|
|
728
|
+
this.push("status", `${lines.join("\n")}
|
|
729
|
+
/fix sends all \xB7 /fix <n> targets one`);
|
|
587
730
|
}
|
|
588
731
|
break;
|
|
589
732
|
case "check":
|
|
@@ -605,12 +748,10 @@ ${result.a11y.slice(0, 5).join("\n")}`);
|
|
|
605
748
|
this.drainQueue();
|
|
606
749
|
const failures = results.filter((r) => !r.ok);
|
|
607
750
|
if (failures.length > 0) {
|
|
608
|
-
this.
|
|
609
|
-
|
|
610
|
-
display: `\u26D1 fix failing gates: ${failures.map((f) => f.gate.id).join(", ")}`
|
|
611
|
-
};
|
|
612
|
-
this.push("status", "type /fix to send failures to the engine");
|
|
751
|
+
this.addProblem("gates", failures.map((f) => f.gate.id).join(" + "), buildGatePrompt(failures));
|
|
752
|
+
this.push("status", "/fix sends open problems to the engine \xB7 /problems lists them");
|
|
613
753
|
} else {
|
|
754
|
+
this.clearProblems("gates");
|
|
614
755
|
this.push("status", "all gates passed");
|
|
615
756
|
}
|
|
616
757
|
})();
|
|
@@ -751,12 +892,10 @@ ${result.a11y.slice(0, 5).join("\n")}`);
|
|
|
751
892
|
clearState(this.opts.cwd);
|
|
752
893
|
this.notify({ items: [], totals: { costUsd: 0, turns: 0 } });
|
|
753
894
|
break;
|
|
754
|
-
case "help":
|
|
755
|
-
this.push(
|
|
756
|
-
"status",
|
|
757
|
-
"/engine <id> \xB7 /model <name> \xB7 /mode plan|safe|yolo \xB7 /dev \xB7 /check (gates) \xB7 /fix \xB7 /shot \xB7 /review [focus] \xB7 /variants <2-4> <ask> \xB7 /undo \xB7 /checkpoints \xB7 /restore <n> \xB7 /resume \xB7 /clear \xB7 /quit"
|
|
758
|
-
);
|
|
895
|
+
case "help": {
|
|
896
|
+
void import("./commands-6XDXUVO6.js").then(({ commandHelp }) => this.push("status", commandHelp()));
|
|
759
897
|
break;
|
|
898
|
+
}
|
|
760
899
|
case "quit":
|
|
761
900
|
case "exit":
|
|
762
901
|
this.push("status", this.summary());
|
|
@@ -1142,6 +1281,13 @@ function App({
|
|
|
1142
1281
|
session.cycleMode();
|
|
1143
1282
|
return;
|
|
1144
1283
|
}
|
|
1284
|
+
if (key.tab && line.text.startsWith("/") && !line.text.includes(" ")) {
|
|
1285
|
+
const matches = completeCommand(line.text.slice(1));
|
|
1286
|
+
if (matches.length > 0) {
|
|
1287
|
+
setLine(fromText(`/${matches[0].name}${matches[0].args ? " " : ""}`));
|
|
1288
|
+
}
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1145
1291
|
if (key.return) {
|
|
1146
1292
|
const value = line.text.trim();
|
|
1147
1293
|
setLine(emptyLine);
|
|
@@ -1225,11 +1371,26 @@ function App({
|
|
|
1225
1371
|
return /* @__PURE__ */ jsx3(ThemeProvider, { value: theme2, children: /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
1226
1372
|
/* @__PURE__ */ jsx3(Static, { items: state.items, children: (item) => /* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsx3(MessageLine, { message: item }) }, item.id) }),
|
|
1227
1373
|
state.liveText.length > 0 && /* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsx3(Markdown, { text: state.liveText }) }),
|
|
1374
|
+
state.items.length === 0 && state.liveText.length === 0 && !state.running && /* @__PURE__ */ jsxs3(Box2, { flexDirection: "column", marginTop: 1, children: [
|
|
1375
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: "describe what to build \u2014 or try:" }),
|
|
1376
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: " /dev start the preview server \xB7 /review after a change \xB7 /variants 3 explore wide" }),
|
|
1377
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme2.dim, children: " shift+tab cycles plan/safe/yolo \xB7 type while the agent works to queue asks" })
|
|
1378
|
+
] }),
|
|
1228
1379
|
state.running && /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(WorkingLine, { startedAt: state.runStartedAt }) }),
|
|
1229
1380
|
state.queue.map((queued, index) => /* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsxs3(Text3, { color: theme2.dim, children: [
|
|
1230
1381
|
"\u22EF queued: ",
|
|
1231
1382
|
queued
|
|
1232
1383
|
] }) }, index)),
|
|
1384
|
+
line.text.startsWith("/") && !line.text.includes(" ") && /* @__PURE__ */ jsx3(Box2, { 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: [
|
|
1385
|
+
"/",
|
|
1386
|
+
command.name,
|
|
1387
|
+
command.args ? ` ${command.args}` : "",
|
|
1388
|
+
" ",
|
|
1389
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme2.dim, children: [
|
|
1390
|
+
"\u2014 ",
|
|
1391
|
+
command.description
|
|
1392
|
+
] })
|
|
1393
|
+
] }, command.name)) }),
|
|
1233
1394
|
/* @__PURE__ */ jsx3(Box2, { marginTop: state.running ? 0 : 1, children: /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1234
1395
|
/* @__PURE__ */ jsx3(Text3, { color: theme2.accent, children: "\u276F " }),
|
|
1235
1396
|
line.text.slice(0, line.cursor),
|
|
@@ -1256,14 +1417,21 @@ function App({
|
|
|
1256
1417
|
path3.basename(cwd),
|
|
1257
1418
|
devBadge,
|
|
1258
1419
|
totalsBadge,
|
|
1259
|
-
|
|
1420
|
+
state.problems.length > 0 && /* @__PURE__ */ jsxs3(Text3, { color: theme2.error, children: [
|
|
1421
|
+
" \xB7 ",
|
|
1422
|
+
state.problems.length,
|
|
1423
|
+
" problem",
|
|
1424
|
+
state.problems.length === 1 ? "" : "s"
|
|
1425
|
+
] }),
|
|
1426
|
+
" ",
|
|
1427
|
+
"\xB7 shift+tab mode \xB7 /help"
|
|
1260
1428
|
] }) })
|
|
1261
1429
|
] }) });
|
|
1262
1430
|
}
|
|
1263
1431
|
|
|
1264
1432
|
// src/cli.tsx
|
|
1265
1433
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1266
|
-
var VERSION = "0.2.
|
|
1434
|
+
var VERSION = "0.2.3";
|
|
1267
1435
|
var program = new Command();
|
|
1268
1436
|
program.name("squint").description("Lovable for your terminal \u2014 a frontend harness on top of Claude Code, Codex, and friends.").version(VERSION);
|
|
1269
1437
|
program.command("run").description("Run one prompt headlessly and stream the result").argument("<prompt...>", "what to build or change").option("-e, --engine <id>", "engine to use (claude, codex, gemini, opencode)").option("-m, --model <name>", "model override for the engine").option("--no-brief", "send the prompt without the squint design brief").option("--json", "emit normalized agent events as ndjson").action(
|
|
@@ -1330,7 +1498,7 @@ program.command("doctor").description("Check squint prerequisites and engine ava
|
|
|
1330
1498
|
}
|
|
1331
1499
|
}
|
|
1332
1500
|
const { findChrome: findChrome2 } = await import("./chrome-RD7XQ767.js");
|
|
1333
|
-
const { hasWebSocket } = await import("./cdp-
|
|
1501
|
+
const { hasWebSocket } = await import("./cdp-SFWNP7OA.js");
|
|
1334
1502
|
const chrome = findChrome2();
|
|
1335
1503
|
console.log(
|
|
1336
1504
|
chrome ? `${pc.green("\u2713")} Chrome ${pc.dim(chrome)}` : `${pc.yellow("\u25CB")} Chrome ${pc.dim("\u2014 screenshots and runtime probing disabled")}`
|
|
@@ -1374,7 +1542,7 @@ Next: ${pc.bold(`${cd}${options.install ? "" : "npm install && "}squint`)}`);
|
|
|
1374
1542
|
});
|
|
1375
1543
|
var skillsCommand = program.command("skills").description("Project knowledge injected into asks (.squint/rules.md + .squint/skills/)");
|
|
1376
1544
|
skillsCommand.command("list").description("Show always-on rules and trigger-matched skills").action(async () => {
|
|
1377
|
-
const { loadRules, loadSkills } = await import("./skills-
|
|
1545
|
+
const { loadRules, loadSkills } = await import("./skills-DNBHZAAU.js");
|
|
1378
1546
|
const cwd = process.cwd();
|
|
1379
1547
|
const rules = loadRules(cwd);
|
|
1380
1548
|
console.log(rules ? `${pc.green("\u2713")} rules.md ${pc.dim(`(${rules.split("\n").length} lines, always on)`)}` : pc.dim("\u25CB no .squint/rules.md"));
|
|
@@ -1450,7 +1618,7 @@ variantsCommand.command("gen").description("Generate n variants of one ask in pa
|
|
|
1450
1618
|
process.exitCode = 1;
|
|
1451
1619
|
return;
|
|
1452
1620
|
}
|
|
1453
|
-
const { isGitRepo: isGitRepo2 } = await import("./snapshot-
|
|
1621
|
+
const { isGitRepo: isGitRepo2 } = await import("./snapshot-H6VYFHLN.js");
|
|
1454
1622
|
if (!isGitRepo2(cwd)) {
|
|
1455
1623
|
console.error(pc.red("\u2717 variants need a git repo with at least one commit"));
|
|
1456
1624
|
process.exitCode = 1;
|
|
@@ -1560,7 +1728,7 @@ program.command("check").description("Run this project\u2019s quality gates (typ
|
|
|
1560
1728
|
if (results.some((r) => !r.ok)) process.exitCode = 1;
|
|
1561
1729
|
});
|
|
1562
1730
|
program.command("shot").description("Screenshot a running app at mobile/tablet/desktop viewports").argument("<url>", "URL of the running app (e.g. http://localhost:5173)").action(async (url) => {
|
|
1563
|
-
const { captureViewports: captureViewports2 } = await import("./preview-
|
|
1731
|
+
const { captureViewports: captureViewports2 } = await import("./preview-UX4VS4SX.js");
|
|
1564
1732
|
const result = await captureViewports2(process.cwd(), url);
|
|
1565
1733
|
if (!result) {
|
|
1566
1734
|
console.error(pc.red("\u2717 no Chrome/Chromium found"));
|
|
@@ -4,11 +4,12 @@ import {
|
|
|
4
4
|
buildReviewPrompt,
|
|
5
5
|
buildRuntimeFixPrompt,
|
|
6
6
|
captureViewports,
|
|
7
|
+
comparePulse,
|
|
7
8
|
previewDir,
|
|
8
9
|
probeRuntime,
|
|
9
10
|
runtimeSummary
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-SU6YRRMF.js";
|
|
12
|
+
import "./chunk-OC6RU6XH.js";
|
|
12
13
|
import "./chunk-P3V5CWH5.js";
|
|
13
14
|
import "./chunk-2LRIKWBU.js";
|
|
14
15
|
export {
|
|
@@ -16,6 +17,7 @@ export {
|
|
|
16
17
|
buildReviewPrompt,
|
|
17
18
|
buildRuntimeFixPrompt,
|
|
18
19
|
captureViewports,
|
|
20
|
+
comparePulse,
|
|
19
21
|
previewDir,
|
|
20
22
|
probeRuntime,
|
|
21
23
|
runtimeSummary
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
enrich,
|
|
4
|
+
loadLocks,
|
|
4
5
|
loadRules,
|
|
5
6
|
loadSkills,
|
|
6
7
|
matchSkills,
|
|
7
8
|
parseSkill
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-4LAU5TGK.js";
|
|
9
10
|
export {
|
|
10
11
|
enrich,
|
|
12
|
+
loadLocks,
|
|
11
13
|
loadRules,
|
|
12
14
|
loadSkills,
|
|
13
15
|
matchSkills,
|
package/package.json
CHANGED