stashes 0.1.52 → 0.1.54
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/dist/cli.js +108 -80
- package/dist/mcp.js +108 -80
- package/dist/web/assets/index-C12Cs3ba.js +97 -0
- package/dist/web/assets/index-C4hi1WQy.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-DjPE9klT.js +0 -96
- package/dist/web/assets/index-mdSV-b1c.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -548,7 +548,6 @@ import { writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync a
|
|
|
548
548
|
import { join as join4 } from "path";
|
|
549
549
|
import { tmpdir } from "os";
|
|
550
550
|
var CLAUDE_BIN = "/opt/homebrew/bin/claude";
|
|
551
|
-
var DEFAULT_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
|
|
552
551
|
function getPlaywrightMcpConfigPath() {
|
|
553
552
|
const configDir = join4(tmpdir(), "stashes-mcp");
|
|
554
553
|
const configPath = join4(configDir, "playwright.json");
|
|
@@ -562,11 +561,29 @@ function getPlaywrightMcpConfigPath() {
|
|
|
562
561
|
}
|
|
563
562
|
return configPath;
|
|
564
563
|
}
|
|
564
|
+
var OVERHEAD_TOOLS = [
|
|
565
|
+
"Agent",
|
|
566
|
+
"TodoWrite",
|
|
567
|
+
"TaskCreate",
|
|
568
|
+
"TaskUpdate",
|
|
569
|
+
"TaskList",
|
|
570
|
+
"TaskGet",
|
|
571
|
+
"Skill",
|
|
572
|
+
"ToolSearch",
|
|
573
|
+
"EnterPlanMode",
|
|
574
|
+
"ExitPlanMode",
|
|
575
|
+
"WebSearch",
|
|
576
|
+
"WebFetch",
|
|
577
|
+
"NotebookEdit",
|
|
578
|
+
"mcp__UseAI__*",
|
|
579
|
+
"mcp__stashes__*",
|
|
580
|
+
"mcp__plugin_drills*",
|
|
581
|
+
"mcp__plugin_coverit*"
|
|
582
|
+
];
|
|
565
583
|
var processes = new Map;
|
|
566
584
|
function startAiProcess(idOrOpts, prompt, cwd, resumeSessionId, model) {
|
|
567
585
|
const opts = typeof idOrOpts === "string" ? { id: idOrOpts, prompt, cwd, resumeSessionId, model } : idOrOpts;
|
|
568
|
-
const
|
|
569
|
-
const tools = opts.tools ?? DEFAULT_TOOLS;
|
|
586
|
+
const restricted = opts.tools !== undefined;
|
|
570
587
|
killAiProcess(opts.id);
|
|
571
588
|
logger.info("claude", `spawning process: ${opts.id}`, {
|
|
572
589
|
cwd: opts.cwd,
|
|
@@ -574,15 +591,14 @@ function startAiProcess(idOrOpts, prompt, cwd, resumeSessionId, model) {
|
|
|
574
591
|
promptPreview: opts.prompt.substring(0, 100),
|
|
575
592
|
resumeSessionId: opts.resumeSessionId,
|
|
576
593
|
model: opts.model,
|
|
577
|
-
|
|
578
|
-
tools:
|
|
594
|
+
restricted,
|
|
595
|
+
tools: restricted ? opts.tools.join(",") || "none" : "all"
|
|
579
596
|
});
|
|
580
597
|
const cmd = [CLAUDE_BIN, "-p", opts.prompt, "--output-format=stream-json", "--verbose", "--dangerously-skip-permissions"];
|
|
581
|
-
if (
|
|
582
|
-
cmd.push("--
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
}
|
|
598
|
+
if (restricted) {
|
|
599
|
+
cmd.push("--tools", opts.tools.length > 0 ? opts.tools.join(",") : '""');
|
|
600
|
+
cmd.push("--disallowedTools", OVERHEAD_TOOLS.join(","));
|
|
601
|
+
cmd.push("--strict-mcp-config");
|
|
586
602
|
if (opts.mcpConfigPath) {
|
|
587
603
|
cmd.push("--mcp-config", opts.mcpConfigPath);
|
|
588
604
|
}
|
|
@@ -788,33 +804,49 @@ ${truncatedDiff}`;
|
|
|
788
804
|
}
|
|
789
805
|
}
|
|
790
806
|
function buildScreenshotPrompt(port, diff, screenshotDir, stashId) {
|
|
807
|
+
const outputPath = join6(screenshotDir, `${stashId}.png`);
|
|
791
808
|
return [
|
|
792
|
-
"You are a screenshot assistant. Take
|
|
809
|
+
"You are a screenshot assistant. Take a screenshot of a running web app.",
|
|
793
810
|
"Be fast \u2014 you have a strict time limit.",
|
|
794
811
|
"",
|
|
795
|
-
|
|
812
|
+
`The app is running at: http://localhost:${port}`,
|
|
796
813
|
"",
|
|
797
|
-
"## Git diff of changes:",
|
|
814
|
+
"## Git diff of changes (tells you what was modified):",
|
|
798
815
|
"```",
|
|
799
816
|
diff,
|
|
800
817
|
"```",
|
|
801
818
|
"",
|
|
802
|
-
"##
|
|
803
|
-
|
|
804
|
-
"
|
|
805
|
-
"
|
|
806
|
-
"
|
|
807
|
-
|
|
808
|
-
"
|
|
819
|
+
"## Instructions",
|
|
820
|
+
"Use mcp__playwright__browser_run_code to navigate AND screenshot in a single call.",
|
|
821
|
+
"From the diff, determine the route/section that shows the changes.",
|
|
822
|
+
"",
|
|
823
|
+
"**Call this tool:**",
|
|
824
|
+
"```",
|
|
825
|
+
"mcp__playwright__browser_run_code",
|
|
826
|
+
"```",
|
|
809
827
|
"",
|
|
810
|
-
|
|
811
|
-
|
|
828
|
+
"**With this code parameter** (adjust the URL path and scroll if needed):",
|
|
829
|
+
"```javascript",
|
|
830
|
+
`async (page) => {`,
|
|
831
|
+
` await page.setViewportSize({ width: 1280, height: 720 });`,
|
|
832
|
+
` await page.goto('http://localhost:${port}', { waitUntil: 'networkidle', timeout: 15000 });`,
|
|
833
|
+
` // If the changed component is further down, scroll to it:`,
|
|
834
|
+
` // await page.evaluate(() => window.scrollTo(0, 500));`,
|
|
835
|
+
` // await page.waitForTimeout(500);`,
|
|
836
|
+
` await page.waitForTimeout(2000);`,
|
|
837
|
+
` await page.screenshot({ path: '${outputPath.replace(/'/g, "\\'")}', type: 'png' });`,
|
|
838
|
+
` return 'saved';`,
|
|
839
|
+
`}`,
|
|
840
|
+
"```",
|
|
812
841
|
"",
|
|
813
|
-
"
|
|
842
|
+
"If the diff shows changes to a component that requires scrolling or navigation,",
|
|
843
|
+
"adjust the code: change the URL path, add scrollTo, or click a tab first.",
|
|
844
|
+
"",
|
|
845
|
+
`After the tool call succeeds, respond with ONLY this JSON:`,
|
|
814
846
|
"{",
|
|
815
847
|
' "screenshots": [',
|
|
816
848
|
" {",
|
|
817
|
-
` "path": "${
|
|
849
|
+
` "path": "${outputPath}",`,
|
|
818
850
|
' "label": "Short description of what is shown",',
|
|
819
851
|
' "route": "/the-url-path",',
|
|
820
852
|
' "isPrimary": true',
|
|
@@ -872,7 +904,6 @@ async function captureSmartScreenshots(opts) {
|
|
|
872
904
|
prompt,
|
|
873
905
|
cwd: worktreePath,
|
|
874
906
|
model: modelFlag,
|
|
875
|
-
bare: true,
|
|
876
907
|
tools: [],
|
|
877
908
|
mcpConfigPath: getPlaywrightMcpConfigPath()
|
|
878
909
|
});
|
|
@@ -1041,8 +1072,7 @@ async function generate(opts) {
|
|
|
1041
1072
|
const aiProcess = startAiProcess({
|
|
1042
1073
|
id: stashId,
|
|
1043
1074
|
prompt: stashPrompt,
|
|
1044
|
-
cwd: worktree.path
|
|
1045
|
-
bare: false
|
|
1075
|
+
cwd: worktree.path
|
|
1046
1076
|
});
|
|
1047
1077
|
try {
|
|
1048
1078
|
for await (const chunk of parseClaudeStream(aiProcess.process)) {
|
|
@@ -1110,8 +1140,53 @@ async function generate(opts) {
|
|
|
1110
1140
|
return;
|
|
1111
1141
|
}
|
|
1112
1142
|
const generatedStash = { ...stash, status: "screenshotting" };
|
|
1113
|
-
completedStashes.push(generatedStash);
|
|
1114
1143
|
persistence.saveStash(generatedStash);
|
|
1144
|
+
emit(onProgress, { type: "screenshotting", stashId });
|
|
1145
|
+
try {
|
|
1146
|
+
const port = await allocatePort();
|
|
1147
|
+
const screenshotWorktree = await worktreeManager.createForGeneration(`screenshot-${stashId}`);
|
|
1148
|
+
const screenshotGit = simpleGit3(screenshotWorktree.path);
|
|
1149
|
+
await screenshotGit.checkout(["--detach", stash.branch]);
|
|
1150
|
+
const devServer = spawn3({
|
|
1151
|
+
cmd: ["npm", "run", "dev"],
|
|
1152
|
+
cwd: screenshotWorktree.path,
|
|
1153
|
+
stdin: "ignore",
|
|
1154
|
+
stdout: "pipe",
|
|
1155
|
+
stderr: "pipe",
|
|
1156
|
+
env: { ...process.env, PORT: String(port), BROWSER: "none" }
|
|
1157
|
+
});
|
|
1158
|
+
try {
|
|
1159
|
+
await waitForPort(port, 60000);
|
|
1160
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
1161
|
+
const mainGit = simpleGit3(projectPath);
|
|
1162
|
+
const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
|
|
1163
|
+
const { primary, screenshots } = await captureSmartScreenshots({
|
|
1164
|
+
projectPath,
|
|
1165
|
+
stashId,
|
|
1166
|
+
stashBranch: stash.branch,
|
|
1167
|
+
parentBranch,
|
|
1168
|
+
worktreePath: screenshotWorktree.path,
|
|
1169
|
+
port,
|
|
1170
|
+
model: opts.screenshotModel,
|
|
1171
|
+
timeout: opts.screenshotTimeout
|
|
1172
|
+
});
|
|
1173
|
+
const readyStash = { ...generatedStash, status: "ready", screenshotUrl: primary, screenshots };
|
|
1174
|
+
completedStashes.push(readyStash);
|
|
1175
|
+
persistence.saveStash(readyStash);
|
|
1176
|
+
emit(onProgress, { type: "ready", stashId, screenshotPath: primary });
|
|
1177
|
+
} finally {
|
|
1178
|
+
devServer.kill();
|
|
1179
|
+
}
|
|
1180
|
+
await worktreeManager.removeGeneration(`screenshot-${stashId}`);
|
|
1181
|
+
} catch (err) {
|
|
1182
|
+
logger.error("generation", `screenshot failed for ${stashId}`, {
|
|
1183
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1184
|
+
});
|
|
1185
|
+
const readyStash = { ...generatedStash, status: "ready" };
|
|
1186
|
+
completedStashes.push(readyStash);
|
|
1187
|
+
persistence.saveStash(readyStash);
|
|
1188
|
+
emit(onProgress, { type: "ready", stashId, screenshotPath: "" });
|
|
1189
|
+
}
|
|
1115
1190
|
} catch (error) {
|
|
1116
1191
|
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
1117
1192
|
persistence.saveStash({ ...stash, status: "error", error: errorMsg });
|
|
@@ -1121,53 +1196,6 @@ async function generate(opts) {
|
|
|
1121
1196
|
}
|
|
1122
1197
|
});
|
|
1123
1198
|
await Promise.all(stashPromises);
|
|
1124
|
-
for (const stash of completedStashes) {
|
|
1125
|
-
emit(onProgress, { type: "screenshotting", stashId: stash.id });
|
|
1126
|
-
try {
|
|
1127
|
-
const port = await allocatePort();
|
|
1128
|
-
const worktree = await worktreeManager.createForGeneration(`screenshot-${stash.id}`);
|
|
1129
|
-
const screenshotGit = simpleGit3(worktree.path);
|
|
1130
|
-
await screenshotGit.checkout(["--detach", stash.branch]);
|
|
1131
|
-
const devServer = spawn3({
|
|
1132
|
-
cmd: ["npm", "run", "dev"],
|
|
1133
|
-
cwd: worktree.path,
|
|
1134
|
-
stdin: "ignore",
|
|
1135
|
-
stdout: "pipe",
|
|
1136
|
-
stderr: "pipe",
|
|
1137
|
-
env: { ...process.env, PORT: String(port), BROWSER: "none" }
|
|
1138
|
-
});
|
|
1139
|
-
try {
|
|
1140
|
-
await waitForPort(port, 60000);
|
|
1141
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
1142
|
-
const mainGit = simpleGit3(projectPath);
|
|
1143
|
-
const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
|
|
1144
|
-
const { primary, screenshots } = await captureSmartScreenshots({
|
|
1145
|
-
projectPath,
|
|
1146
|
-
stashId: stash.id,
|
|
1147
|
-
stashBranch: stash.branch,
|
|
1148
|
-
parentBranch,
|
|
1149
|
-
worktreePath: worktree.path,
|
|
1150
|
-
port,
|
|
1151
|
-
model: opts.screenshotModel,
|
|
1152
|
-
timeout: opts.screenshotTimeout
|
|
1153
|
-
});
|
|
1154
|
-
const updatedStash = { ...stash, status: "ready", screenshotUrl: primary, screenshots };
|
|
1155
|
-
persistence.saveStash(updatedStash);
|
|
1156
|
-
const idx = completedStashes.indexOf(stash);
|
|
1157
|
-
completedStashes[idx] = updatedStash;
|
|
1158
|
-
emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: primary });
|
|
1159
|
-
} finally {
|
|
1160
|
-
devServer.kill();
|
|
1161
|
-
}
|
|
1162
|
-
await worktreeManager.removeGeneration(`screenshot-${stash.id}`);
|
|
1163
|
-
} catch (err) {
|
|
1164
|
-
logger.error("generation", `screenshot failed for ${stash.id}`, {
|
|
1165
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1166
|
-
});
|
|
1167
|
-
persistence.saveStash({ ...stash, status: "ready" });
|
|
1168
|
-
emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: "" });
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
1199
|
return completedStashes;
|
|
1172
1200
|
}
|
|
1173
1201
|
// ../core/dist/vary.js
|
|
@@ -1242,8 +1270,7 @@ async function vary(opts) {
|
|
|
1242
1270
|
const aiProcess = startAiProcess({
|
|
1243
1271
|
id: stashId,
|
|
1244
1272
|
prompt: varyPrompt,
|
|
1245
|
-
cwd: worktree.path
|
|
1246
|
-
bare: false
|
|
1273
|
+
cwd: worktree.path
|
|
1247
1274
|
});
|
|
1248
1275
|
try {
|
|
1249
1276
|
for await (const chunk of parseClaudeStream(aiProcess.process)) {
|
|
@@ -1914,7 +1941,6 @@ class StashService {
|
|
|
1914
1941
|
prompt,
|
|
1915
1942
|
cwd: this.projectPath,
|
|
1916
1943
|
model: "claude-haiku-4-5-20251001",
|
|
1917
|
-
bare: true,
|
|
1918
1944
|
tools: ["Read", "Grep", "Glob", "Bash"]
|
|
1919
1945
|
});
|
|
1920
1946
|
let resolvedPath = "";
|
|
@@ -2030,8 +2056,7 @@ ${sourceCode.substring(0, 3000)}
|
|
|
2030
2056
|
id: "chat",
|
|
2031
2057
|
prompt: chatPrompt,
|
|
2032
2058
|
cwd: this.projectPath,
|
|
2033
|
-
resumeSessionId: existingSessionId
|
|
2034
|
-
bare: false
|
|
2059
|
+
resumeSessionId: existingSessionId
|
|
2035
2060
|
});
|
|
2036
2061
|
let thinkingBuf = "";
|
|
2037
2062
|
let textBuf = "";
|
|
@@ -2130,11 +2155,13 @@ ${sourceCode.substring(0, 3000)}
|
|
|
2130
2155
|
toolResult = parsed.result ?? chunk.content;
|
|
2131
2156
|
isError = !!parsed.is_error;
|
|
2132
2157
|
} catch {}
|
|
2158
|
+
const endToolName = chunk.toolName ?? "unknown";
|
|
2133
2159
|
save({
|
|
2134
2160
|
id: crypto.randomUUID(),
|
|
2135
2161
|
role: "assistant",
|
|
2136
2162
|
content: chunk.content,
|
|
2137
2163
|
type: "tool_end",
|
|
2164
|
+
toolName: endToolName,
|
|
2138
2165
|
toolStatus: isError ? "error" : "completed",
|
|
2139
2166
|
toolResult: toolResult.substring(0, 300),
|
|
2140
2167
|
stashActivity,
|
|
@@ -2145,6 +2172,7 @@ ${sourceCode.substring(0, 3000)}
|
|
|
2145
2172
|
content: chunk.content,
|
|
2146
2173
|
streamType: "tool_end",
|
|
2147
2174
|
source: "chat",
|
|
2175
|
+
toolName: endToolName,
|
|
2148
2176
|
toolStatus: isError ? "error" : "completed",
|
|
2149
2177
|
toolResult: toolResult.substring(0, 300)
|
|
2150
2178
|
});
|
package/dist/mcp.js
CHANGED
|
@@ -533,7 +533,6 @@ import { writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync a
|
|
|
533
533
|
import { join as join4 } from "path";
|
|
534
534
|
import { tmpdir } from "os";
|
|
535
535
|
var CLAUDE_BIN = "/opt/homebrew/bin/claude";
|
|
536
|
-
var DEFAULT_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
|
|
537
536
|
function getPlaywrightMcpConfigPath() {
|
|
538
537
|
const configDir = join4(tmpdir(), "stashes-mcp");
|
|
539
538
|
const configPath = join4(configDir, "playwright.json");
|
|
@@ -547,11 +546,29 @@ function getPlaywrightMcpConfigPath() {
|
|
|
547
546
|
}
|
|
548
547
|
return configPath;
|
|
549
548
|
}
|
|
549
|
+
var OVERHEAD_TOOLS = [
|
|
550
|
+
"Agent",
|
|
551
|
+
"TodoWrite",
|
|
552
|
+
"TaskCreate",
|
|
553
|
+
"TaskUpdate",
|
|
554
|
+
"TaskList",
|
|
555
|
+
"TaskGet",
|
|
556
|
+
"Skill",
|
|
557
|
+
"ToolSearch",
|
|
558
|
+
"EnterPlanMode",
|
|
559
|
+
"ExitPlanMode",
|
|
560
|
+
"WebSearch",
|
|
561
|
+
"WebFetch",
|
|
562
|
+
"NotebookEdit",
|
|
563
|
+
"mcp__UseAI__*",
|
|
564
|
+
"mcp__stashes__*",
|
|
565
|
+
"mcp__plugin_drills*",
|
|
566
|
+
"mcp__plugin_coverit*"
|
|
567
|
+
];
|
|
550
568
|
var processes = new Map;
|
|
551
569
|
function startAiProcess(idOrOpts, prompt, cwd, resumeSessionId, model) {
|
|
552
570
|
const opts = typeof idOrOpts === "string" ? { id: idOrOpts, prompt, cwd, resumeSessionId, model } : idOrOpts;
|
|
553
|
-
const
|
|
554
|
-
const tools = opts.tools ?? DEFAULT_TOOLS;
|
|
571
|
+
const restricted = opts.tools !== undefined;
|
|
555
572
|
killAiProcess(opts.id);
|
|
556
573
|
logger.info("claude", `spawning process: ${opts.id}`, {
|
|
557
574
|
cwd: opts.cwd,
|
|
@@ -559,15 +576,14 @@ function startAiProcess(idOrOpts, prompt, cwd, resumeSessionId, model) {
|
|
|
559
576
|
promptPreview: opts.prompt.substring(0, 100),
|
|
560
577
|
resumeSessionId: opts.resumeSessionId,
|
|
561
578
|
model: opts.model,
|
|
562
|
-
|
|
563
|
-
tools:
|
|
579
|
+
restricted,
|
|
580
|
+
tools: restricted ? opts.tools.join(",") || "none" : "all"
|
|
564
581
|
});
|
|
565
582
|
const cmd = [CLAUDE_BIN, "-p", opts.prompt, "--output-format=stream-json", "--verbose", "--dangerously-skip-permissions"];
|
|
566
|
-
if (
|
|
567
|
-
cmd.push("--
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
583
|
+
if (restricted) {
|
|
584
|
+
cmd.push("--tools", opts.tools.length > 0 ? opts.tools.join(",") : '""');
|
|
585
|
+
cmd.push("--disallowedTools", OVERHEAD_TOOLS.join(","));
|
|
586
|
+
cmd.push("--strict-mcp-config");
|
|
571
587
|
if (opts.mcpConfigPath) {
|
|
572
588
|
cmd.push("--mcp-config", opts.mcpConfigPath);
|
|
573
589
|
}
|
|
@@ -773,33 +789,49 @@ ${truncatedDiff}`;
|
|
|
773
789
|
}
|
|
774
790
|
}
|
|
775
791
|
function buildScreenshotPrompt(port, diff, screenshotDir, stashId) {
|
|
792
|
+
const outputPath = join6(screenshotDir, `${stashId}.png`);
|
|
776
793
|
return [
|
|
777
|
-
"You are a screenshot assistant. Take
|
|
794
|
+
"You are a screenshot assistant. Take a screenshot of a running web app.",
|
|
778
795
|
"Be fast \u2014 you have a strict time limit.",
|
|
779
796
|
"",
|
|
780
|
-
|
|
797
|
+
`The app is running at: http://localhost:${port}`,
|
|
781
798
|
"",
|
|
782
|
-
"## Git diff of changes:",
|
|
799
|
+
"## Git diff of changes (tells you what was modified):",
|
|
783
800
|
"```",
|
|
784
801
|
diff,
|
|
785
802
|
"```",
|
|
786
803
|
"",
|
|
787
|
-
"##
|
|
788
|
-
|
|
789
|
-
"
|
|
790
|
-
"
|
|
791
|
-
"
|
|
792
|
-
|
|
793
|
-
"
|
|
804
|
+
"## Instructions",
|
|
805
|
+
"Use mcp__playwright__browser_run_code to navigate AND screenshot in a single call.",
|
|
806
|
+
"From the diff, determine the route/section that shows the changes.",
|
|
807
|
+
"",
|
|
808
|
+
"**Call this tool:**",
|
|
809
|
+
"```",
|
|
810
|
+
"mcp__playwright__browser_run_code",
|
|
811
|
+
"```",
|
|
794
812
|
"",
|
|
795
|
-
|
|
796
|
-
|
|
813
|
+
"**With this code parameter** (adjust the URL path and scroll if needed):",
|
|
814
|
+
"```javascript",
|
|
815
|
+
`async (page) => {`,
|
|
816
|
+
` await page.setViewportSize({ width: 1280, height: 720 });`,
|
|
817
|
+
` await page.goto('http://localhost:${port}', { waitUntil: 'networkidle', timeout: 15000 });`,
|
|
818
|
+
` // If the changed component is further down, scroll to it:`,
|
|
819
|
+
` // await page.evaluate(() => window.scrollTo(0, 500));`,
|
|
820
|
+
` // await page.waitForTimeout(500);`,
|
|
821
|
+
` await page.waitForTimeout(2000);`,
|
|
822
|
+
` await page.screenshot({ path: '${outputPath.replace(/'/g, "\\'")}', type: 'png' });`,
|
|
823
|
+
` return 'saved';`,
|
|
824
|
+
`}`,
|
|
825
|
+
"```",
|
|
797
826
|
"",
|
|
798
|
-
"
|
|
827
|
+
"If the diff shows changes to a component that requires scrolling or navigation,",
|
|
828
|
+
"adjust the code: change the URL path, add scrollTo, or click a tab first.",
|
|
829
|
+
"",
|
|
830
|
+
`After the tool call succeeds, respond with ONLY this JSON:`,
|
|
799
831
|
"{",
|
|
800
832
|
' "screenshots": [',
|
|
801
833
|
" {",
|
|
802
|
-
` "path": "${
|
|
834
|
+
` "path": "${outputPath}",`,
|
|
803
835
|
' "label": "Short description of what is shown",',
|
|
804
836
|
' "route": "/the-url-path",',
|
|
805
837
|
' "isPrimary": true',
|
|
@@ -857,7 +889,6 @@ async function captureSmartScreenshots(opts) {
|
|
|
857
889
|
prompt,
|
|
858
890
|
cwd: worktreePath,
|
|
859
891
|
model: modelFlag,
|
|
860
|
-
bare: true,
|
|
861
892
|
tools: [],
|
|
862
893
|
mcpConfigPath: getPlaywrightMcpConfigPath()
|
|
863
894
|
});
|
|
@@ -1026,8 +1057,7 @@ async function generate(opts) {
|
|
|
1026
1057
|
const aiProcess = startAiProcess({
|
|
1027
1058
|
id: stashId,
|
|
1028
1059
|
prompt: stashPrompt,
|
|
1029
|
-
cwd: worktree.path
|
|
1030
|
-
bare: false
|
|
1060
|
+
cwd: worktree.path
|
|
1031
1061
|
});
|
|
1032
1062
|
try {
|
|
1033
1063
|
for await (const chunk of parseClaudeStream(aiProcess.process)) {
|
|
@@ -1095,8 +1125,53 @@ async function generate(opts) {
|
|
|
1095
1125
|
return;
|
|
1096
1126
|
}
|
|
1097
1127
|
const generatedStash = { ...stash, status: "screenshotting" };
|
|
1098
|
-
completedStashes.push(generatedStash);
|
|
1099
1128
|
persistence.saveStash(generatedStash);
|
|
1129
|
+
emit(onProgress, { type: "screenshotting", stashId });
|
|
1130
|
+
try {
|
|
1131
|
+
const port = await allocatePort();
|
|
1132
|
+
const screenshotWorktree = await worktreeManager.createForGeneration(`screenshot-${stashId}`);
|
|
1133
|
+
const screenshotGit = simpleGit3(screenshotWorktree.path);
|
|
1134
|
+
await screenshotGit.checkout(["--detach", stash.branch]);
|
|
1135
|
+
const devServer = spawn3({
|
|
1136
|
+
cmd: ["npm", "run", "dev"],
|
|
1137
|
+
cwd: screenshotWorktree.path,
|
|
1138
|
+
stdin: "ignore",
|
|
1139
|
+
stdout: "pipe",
|
|
1140
|
+
stderr: "pipe",
|
|
1141
|
+
env: { ...process.env, PORT: String(port), BROWSER: "none" }
|
|
1142
|
+
});
|
|
1143
|
+
try {
|
|
1144
|
+
await waitForPort(port, 60000);
|
|
1145
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
1146
|
+
const mainGit = simpleGit3(projectPath);
|
|
1147
|
+
const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
|
|
1148
|
+
const { primary, screenshots } = await captureSmartScreenshots({
|
|
1149
|
+
projectPath,
|
|
1150
|
+
stashId,
|
|
1151
|
+
stashBranch: stash.branch,
|
|
1152
|
+
parentBranch,
|
|
1153
|
+
worktreePath: screenshotWorktree.path,
|
|
1154
|
+
port,
|
|
1155
|
+
model: opts.screenshotModel,
|
|
1156
|
+
timeout: opts.screenshotTimeout
|
|
1157
|
+
});
|
|
1158
|
+
const readyStash = { ...generatedStash, status: "ready", screenshotUrl: primary, screenshots };
|
|
1159
|
+
completedStashes.push(readyStash);
|
|
1160
|
+
persistence.saveStash(readyStash);
|
|
1161
|
+
emit(onProgress, { type: "ready", stashId, screenshotPath: primary });
|
|
1162
|
+
} finally {
|
|
1163
|
+
devServer.kill();
|
|
1164
|
+
}
|
|
1165
|
+
await worktreeManager.removeGeneration(`screenshot-${stashId}`);
|
|
1166
|
+
} catch (err) {
|
|
1167
|
+
logger.error("generation", `screenshot failed for ${stashId}`, {
|
|
1168
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1169
|
+
});
|
|
1170
|
+
const readyStash = { ...generatedStash, status: "ready" };
|
|
1171
|
+
completedStashes.push(readyStash);
|
|
1172
|
+
persistence.saveStash(readyStash);
|
|
1173
|
+
emit(onProgress, { type: "ready", stashId, screenshotPath: "" });
|
|
1174
|
+
}
|
|
1100
1175
|
} catch (error) {
|
|
1101
1176
|
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
1102
1177
|
persistence.saveStash({ ...stash, status: "error", error: errorMsg });
|
|
@@ -1106,53 +1181,6 @@ async function generate(opts) {
|
|
|
1106
1181
|
}
|
|
1107
1182
|
});
|
|
1108
1183
|
await Promise.all(stashPromises);
|
|
1109
|
-
for (const stash of completedStashes) {
|
|
1110
|
-
emit(onProgress, { type: "screenshotting", stashId: stash.id });
|
|
1111
|
-
try {
|
|
1112
|
-
const port = await allocatePort();
|
|
1113
|
-
const worktree = await worktreeManager.createForGeneration(`screenshot-${stash.id}`);
|
|
1114
|
-
const screenshotGit = simpleGit3(worktree.path);
|
|
1115
|
-
await screenshotGit.checkout(["--detach", stash.branch]);
|
|
1116
|
-
const devServer = spawn3({
|
|
1117
|
-
cmd: ["npm", "run", "dev"],
|
|
1118
|
-
cwd: worktree.path,
|
|
1119
|
-
stdin: "ignore",
|
|
1120
|
-
stdout: "pipe",
|
|
1121
|
-
stderr: "pipe",
|
|
1122
|
-
env: { ...process.env, PORT: String(port), BROWSER: "none" }
|
|
1123
|
-
});
|
|
1124
|
-
try {
|
|
1125
|
-
await waitForPort(port, 60000);
|
|
1126
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
1127
|
-
const mainGit = simpleGit3(projectPath);
|
|
1128
|
-
const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
|
|
1129
|
-
const { primary, screenshots } = await captureSmartScreenshots({
|
|
1130
|
-
projectPath,
|
|
1131
|
-
stashId: stash.id,
|
|
1132
|
-
stashBranch: stash.branch,
|
|
1133
|
-
parentBranch,
|
|
1134
|
-
worktreePath: worktree.path,
|
|
1135
|
-
port,
|
|
1136
|
-
model: opts.screenshotModel,
|
|
1137
|
-
timeout: opts.screenshotTimeout
|
|
1138
|
-
});
|
|
1139
|
-
const updatedStash = { ...stash, status: "ready", screenshotUrl: primary, screenshots };
|
|
1140
|
-
persistence.saveStash(updatedStash);
|
|
1141
|
-
const idx = completedStashes.indexOf(stash);
|
|
1142
|
-
completedStashes[idx] = updatedStash;
|
|
1143
|
-
emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: primary });
|
|
1144
|
-
} finally {
|
|
1145
|
-
devServer.kill();
|
|
1146
|
-
}
|
|
1147
|
-
await worktreeManager.removeGeneration(`screenshot-${stash.id}`);
|
|
1148
|
-
} catch (err) {
|
|
1149
|
-
logger.error("generation", `screenshot failed for ${stash.id}`, {
|
|
1150
|
-
error: err instanceof Error ? err.message : String(err)
|
|
1151
|
-
});
|
|
1152
|
-
persistence.saveStash({ ...stash, status: "ready" });
|
|
1153
|
-
emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: "" });
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
1184
|
return completedStashes;
|
|
1157
1185
|
}
|
|
1158
1186
|
// ../core/dist/vary.js
|
|
@@ -1227,8 +1255,7 @@ async function vary(opts) {
|
|
|
1227
1255
|
const aiProcess = startAiProcess({
|
|
1228
1256
|
id: stashId,
|
|
1229
1257
|
prompt: varyPrompt,
|
|
1230
|
-
cwd: worktree.path
|
|
1231
|
-
bare: false
|
|
1258
|
+
cwd: worktree.path
|
|
1232
1259
|
});
|
|
1233
1260
|
try {
|
|
1234
1261
|
for await (const chunk of parseClaudeStream(aiProcess.process)) {
|
|
@@ -2138,7 +2165,6 @@ class StashService {
|
|
|
2138
2165
|
prompt,
|
|
2139
2166
|
cwd: this.projectPath,
|
|
2140
2167
|
model: "claude-haiku-4-5-20251001",
|
|
2141
|
-
bare: true,
|
|
2142
2168
|
tools: ["Read", "Grep", "Glob", "Bash"]
|
|
2143
2169
|
});
|
|
2144
2170
|
let resolvedPath = "";
|
|
@@ -2254,8 +2280,7 @@ ${sourceCode.substring(0, 3000)}
|
|
|
2254
2280
|
id: "chat",
|
|
2255
2281
|
prompt: chatPrompt,
|
|
2256
2282
|
cwd: this.projectPath,
|
|
2257
|
-
resumeSessionId: existingSessionId
|
|
2258
|
-
bare: false
|
|
2283
|
+
resumeSessionId: existingSessionId
|
|
2259
2284
|
});
|
|
2260
2285
|
let thinkingBuf = "";
|
|
2261
2286
|
let textBuf = "";
|
|
@@ -2354,11 +2379,13 @@ ${sourceCode.substring(0, 3000)}
|
|
|
2354
2379
|
toolResult = parsed.result ?? chunk.content;
|
|
2355
2380
|
isError = !!parsed.is_error;
|
|
2356
2381
|
} catch {}
|
|
2382
|
+
const endToolName = chunk.toolName ?? "unknown";
|
|
2357
2383
|
save({
|
|
2358
2384
|
id: crypto.randomUUID(),
|
|
2359
2385
|
role: "assistant",
|
|
2360
2386
|
content: chunk.content,
|
|
2361
2387
|
type: "tool_end",
|
|
2388
|
+
toolName: endToolName,
|
|
2362
2389
|
toolStatus: isError ? "error" : "completed",
|
|
2363
2390
|
toolResult: toolResult.substring(0, 300),
|
|
2364
2391
|
stashActivity,
|
|
@@ -2369,6 +2396,7 @@ ${sourceCode.substring(0, 3000)}
|
|
|
2369
2396
|
content: chunk.content,
|
|
2370
2397
|
streamType: "tool_end",
|
|
2371
2398
|
source: "chat",
|
|
2399
|
+
toolName: endToolName,
|
|
2372
2400
|
toolStatus: isError ? "error" : "completed",
|
|
2373
2401
|
toolResult: toolResult.substring(0, 300)
|
|
2374
2402
|
});
|