stashes 0.1.26 → 0.1.28
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 +412 -215
- package/dist/mcp.js +395 -198
- package/dist/web/assets/{index-Db9gbHCa.js → index-DFTLJjq2.js} +19 -19
- package/dist/web/assets/index-DUXYFG67.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-Yo4820v-.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ var __require = import.meta.require;
|
|
|
20
20
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
import { readFileSync as readFileSync9 } from "fs";
|
|
23
|
-
import { join as
|
|
23
|
+
import { join as join13, dirname as dirname5 } from "path";
|
|
24
24
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
25
25
|
import { Command } from "commander";
|
|
26
26
|
|
|
@@ -31,9 +31,9 @@ import open from "open";
|
|
|
31
31
|
// ../server/dist/index.js
|
|
32
32
|
import { Hono as Hono2 } from "hono";
|
|
33
33
|
import { cors } from "hono/cors";
|
|
34
|
-
import { join as
|
|
34
|
+
import { join as join9, dirname as dirname2 } from "path";
|
|
35
35
|
import { fileURLToPath } from "url";
|
|
36
|
-
import { existsSync as
|
|
36
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
|
|
37
37
|
// ../shared/dist/constants/index.js
|
|
38
38
|
var STASHES_PORT = 4000;
|
|
39
39
|
var DEFAULT_STASH_COUNT = 3;
|
|
@@ -159,10 +159,10 @@ function ensureProject(persistence) {
|
|
|
159
159
|
var apiRoutes = app;
|
|
160
160
|
|
|
161
161
|
// ../core/dist/generation.js
|
|
162
|
-
import { readFileSync as readFileSync3, existsSync as
|
|
163
|
-
import { join as
|
|
162
|
+
import { readFileSync as readFileSync3, existsSync as existsSync7 } from "fs";
|
|
163
|
+
import { join as join7 } from "path";
|
|
164
164
|
var {spawn: spawn3 } = globalThis.Bun;
|
|
165
|
-
import
|
|
165
|
+
import simpleGit3 from "simple-git";
|
|
166
166
|
|
|
167
167
|
// ../core/dist/worktree.js
|
|
168
168
|
import simpleGit from "simple-git";
|
|
@@ -542,6 +542,14 @@ class PersistenceService {
|
|
|
542
542
|
const filePath = join4(this.basePath, "projects", projectId, "stashes.json");
|
|
543
543
|
writeJson(filePath, stashes);
|
|
544
544
|
}
|
|
545
|
+
getProjectSettings(projectId) {
|
|
546
|
+
const filePath = join4(this.basePath, "projects", projectId, "settings.json");
|
|
547
|
+
return readJson(filePath, {});
|
|
548
|
+
}
|
|
549
|
+
saveProjectSettings(projectId, settings) {
|
|
550
|
+
const filePath = join4(this.basePath, "projects", projectId, "settings.json");
|
|
551
|
+
writeJson(filePath, settings);
|
|
552
|
+
}
|
|
545
553
|
listChats(projectId) {
|
|
546
554
|
const dir = join4(this.basePath, "projects", projectId, "chats");
|
|
547
555
|
if (!existsSync4(dir))
|
|
@@ -622,18 +630,22 @@ class PersistenceService {
|
|
|
622
630
|
var {spawn } = globalThis.Bun;
|
|
623
631
|
var CLAUDE_BIN = "/opt/homebrew/bin/claude";
|
|
624
632
|
var processes = new Map;
|
|
625
|
-
function startAiProcess(id, prompt, cwd, resumeSessionId) {
|
|
633
|
+
function startAiProcess(id, prompt, cwd, resumeSessionId, model) {
|
|
626
634
|
killAiProcess(id);
|
|
627
635
|
logger.info("claude", `spawning process: ${id}`, {
|
|
628
636
|
cwd,
|
|
629
637
|
promptLength: prompt.length,
|
|
630
638
|
promptPreview: prompt.substring(0, 100),
|
|
631
|
-
resumeSessionId
|
|
639
|
+
resumeSessionId,
|
|
640
|
+
model
|
|
632
641
|
});
|
|
633
642
|
const cmd = [CLAUDE_BIN, "-p", prompt, "--output-format=stream-json", "--verbose", "--dangerously-skip-permissions"];
|
|
634
643
|
if (resumeSessionId) {
|
|
635
644
|
cmd.push("--resume", resumeSessionId);
|
|
636
645
|
}
|
|
646
|
+
if (model) {
|
|
647
|
+
cmd.push("--model", model);
|
|
648
|
+
}
|
|
637
649
|
const proc = spawn({
|
|
638
650
|
cmd,
|
|
639
651
|
stdin: "ignore",
|
|
@@ -757,6 +769,11 @@ async function* parseClaudeStream(proc) {
|
|
|
757
769
|
}
|
|
758
770
|
}
|
|
759
771
|
|
|
772
|
+
// ../core/dist/smart-screenshot.js
|
|
773
|
+
import { join as join6 } from "path";
|
|
774
|
+
import { mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
|
|
775
|
+
import simpleGit2 from "simple-git";
|
|
776
|
+
|
|
760
777
|
// ../core/dist/screenshot.js
|
|
761
778
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
762
779
|
import { join as join5 } from "path";
|
|
@@ -799,6 +816,171 @@ async function captureScreenshot(port, projectPath, stashId) {
|
|
|
799
816
|
return `/api/screenshots/${filename}`;
|
|
800
817
|
}
|
|
801
818
|
|
|
819
|
+
// ../core/dist/smart-screenshot.js
|
|
820
|
+
var SCREENSHOTS_DIR2 = ".stashes/screenshots";
|
|
821
|
+
var DEFAULT_TIMEOUT = 120000;
|
|
822
|
+
var DIFF_MAX_CHARS = 1e4;
|
|
823
|
+
async function getStashDiff(worktreePath, parentBranch) {
|
|
824
|
+
const git = simpleGit2(worktreePath);
|
|
825
|
+
try {
|
|
826
|
+
const diff = await git.diff([parentBranch, "HEAD"]);
|
|
827
|
+
if (diff.length <= DIFF_MAX_CHARS)
|
|
828
|
+
return diff;
|
|
829
|
+
const diffStat = await git.diff([parentBranch, "HEAD", "--stat"]);
|
|
830
|
+
const truncatedDiff = diff.substring(0, DIFF_MAX_CHARS);
|
|
831
|
+
return `## Changed files:
|
|
832
|
+
${diffStat}
|
|
833
|
+
|
|
834
|
+
## Partial diff (truncated):
|
|
835
|
+
${truncatedDiff}`;
|
|
836
|
+
} catch (err) {
|
|
837
|
+
logger.warn("smart-screenshot", `git diff failed, falling back`, {
|
|
838
|
+
error: err instanceof Error ? err.message : String(err)
|
|
839
|
+
});
|
|
840
|
+
return "";
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
function buildScreenshotPrompt(port, diff, screenshotDir, stashId) {
|
|
844
|
+
return [
|
|
845
|
+
"You are a screenshot assistant. A developer made UI changes to a web app.",
|
|
846
|
+
"Your job: navigate the running app, find the pages affected by the changes, and take screenshots.",
|
|
847
|
+
"",
|
|
848
|
+
`## The app is running at: http://localhost:${port}`,
|
|
849
|
+
"",
|
|
850
|
+
"## Git diff of changes:",
|
|
851
|
+
"```",
|
|
852
|
+
diff,
|
|
853
|
+
"```",
|
|
854
|
+
"",
|
|
855
|
+
"## Instructions:",
|
|
856
|
+
"1. Analyze the diff to identify which components/pages changed",
|
|
857
|
+
"2. Determine the URL routes where these changes are visible",
|
|
858
|
+
"3. For each route:",
|
|
859
|
+
" - Navigate to it using browser_navigate",
|
|
860
|
+
" - If the change is behind an interaction (tab, modal, accordion), perform that interaction",
|
|
861
|
+
" - Wait for the page to settle",
|
|
862
|
+
" - Take a screenshot using browser_take_screenshot",
|
|
863
|
+
"4. Decide which screenshot shows the MOST visually significant change \u2014 mark it as primary",
|
|
864
|
+
"",
|
|
865
|
+
`## Screenshot save paths:`,
|
|
866
|
+
`- Primary: ${join6(screenshotDir, `${stashId}.png`)}`,
|
|
867
|
+
`- Additional: ${join6(screenshotDir, `${stashId}-1.png`)}, ${join6(screenshotDir, `${stashId}-2.png`)}, etc.`,
|
|
868
|
+
"",
|
|
869
|
+
"## Output format (after all screenshots are taken):",
|
|
870
|
+
"Respond with ONLY this JSON (no markdown fences):",
|
|
871
|
+
"{",
|
|
872
|
+
' "screenshots": [',
|
|
873
|
+
" {",
|
|
874
|
+
` "path": "${join6(screenshotDir, `${stashId}.png`)}",`,
|
|
875
|
+
' "label": "Short description of what is shown",',
|
|
876
|
+
' "route": "/the-url-path",',
|
|
877
|
+
' "isPrimary": true',
|
|
878
|
+
" }",
|
|
879
|
+
" ]",
|
|
880
|
+
"}"
|
|
881
|
+
].join(`
|
|
882
|
+
`);
|
|
883
|
+
}
|
|
884
|
+
function parseAiResult(text) {
|
|
885
|
+
try {
|
|
886
|
+
return JSON.parse(text);
|
|
887
|
+
} catch {
|
|
888
|
+
const jsonMatch = text.match(/\{[\s\S]*"screenshots"[\s\S]*\}/);
|
|
889
|
+
if (jsonMatch) {
|
|
890
|
+
try {
|
|
891
|
+
return JSON.parse(jsonMatch[0]);
|
|
892
|
+
} catch {
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
async function captureSmartScreenshots(opts) {
|
|
900
|
+
const { projectPath, stashId, stashBranch, parentBranch, worktreePath, port, model = "haiku", timeout = DEFAULT_TIMEOUT } = opts;
|
|
901
|
+
const screenshotDir = join6(projectPath, SCREENSHOTS_DIR2);
|
|
902
|
+
if (!existsSync6(screenshotDir)) {
|
|
903
|
+
mkdirSync4(screenshotDir, { recursive: true });
|
|
904
|
+
}
|
|
905
|
+
const diff = await getStashDiff(worktreePath, parentBranch);
|
|
906
|
+
if (!diff) {
|
|
907
|
+
logger.info("smart-screenshot", `No diff found for ${stashId}, using simple screenshot`);
|
|
908
|
+
const url = await captureScreenshot(port, projectPath, stashId);
|
|
909
|
+
return {
|
|
910
|
+
primary: url,
|
|
911
|
+
screenshots: [{ url, label: "Homepage", route: "/", isPrimary: true }]
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
const processId = `screenshot-ai-${stashId}`;
|
|
915
|
+
const prompt = buildScreenshotPrompt(port, diff, screenshotDir, stashId);
|
|
916
|
+
const modelFlag = model === "sonnet" ? "sonnet" : "haiku";
|
|
917
|
+
const aiProcess = startAiProcess(processId, prompt, worktreePath, undefined, modelFlag);
|
|
918
|
+
let textOutput = "";
|
|
919
|
+
let timedOut = false;
|
|
920
|
+
const timeoutId = setTimeout(() => {
|
|
921
|
+
timedOut = true;
|
|
922
|
+
killAiProcess(processId);
|
|
923
|
+
}, timeout);
|
|
924
|
+
try {
|
|
925
|
+
for await (const chunk of parseClaudeStream(aiProcess.process)) {
|
|
926
|
+
if (chunk.type === "text") {
|
|
927
|
+
textOutput += chunk.content;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
await aiProcess.process.exited;
|
|
931
|
+
} catch (err) {
|
|
932
|
+
logger.warn("smart-screenshot", `AI process error for ${stashId}`, {
|
|
933
|
+
error: err instanceof Error ? err.message : String(err),
|
|
934
|
+
timedOut
|
|
935
|
+
});
|
|
936
|
+
} finally {
|
|
937
|
+
clearTimeout(timeoutId);
|
|
938
|
+
killAiProcess(processId);
|
|
939
|
+
}
|
|
940
|
+
const result = parseAiResult(textOutput);
|
|
941
|
+
if (!result || !result.screenshots || result.screenshots.length === 0) {
|
|
942
|
+
logger.info("smart-screenshot", `AI returned no screenshots for ${stashId}, falling back`);
|
|
943
|
+
const url = await captureScreenshot(port, projectPath, stashId);
|
|
944
|
+
return {
|
|
945
|
+
primary: url,
|
|
946
|
+
screenshots: [{ url, label: "Homepage", route: "/", isPrimary: true }]
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
const screenshots = [];
|
|
950
|
+
let primaryUrl = "";
|
|
951
|
+
for (const shot of result.screenshots) {
|
|
952
|
+
const filename = shot.path.split("/").pop() || "";
|
|
953
|
+
if (!existsSync6(shot.path)) {
|
|
954
|
+
logger.warn("smart-screenshot", `Screenshot file not found: ${shot.path}`);
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
const url = `/api/screenshots/${filename}`;
|
|
958
|
+
const screenshot = {
|
|
959
|
+
url,
|
|
960
|
+
label: shot.label,
|
|
961
|
+
route: shot.route,
|
|
962
|
+
isPrimary: shot.isPrimary
|
|
963
|
+
};
|
|
964
|
+
screenshots.push(screenshot);
|
|
965
|
+
if (shot.isPrimary)
|
|
966
|
+
primaryUrl = url;
|
|
967
|
+
}
|
|
968
|
+
if (screenshots.length === 0) {
|
|
969
|
+
logger.info("smart-screenshot", `No valid screenshots for ${stashId}, falling back`);
|
|
970
|
+
const url = await captureScreenshot(port, projectPath, stashId);
|
|
971
|
+
return {
|
|
972
|
+
primary: url,
|
|
973
|
+
screenshots: [{ url, label: "Homepage", route: "/", isPrimary: true }]
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
if (!primaryUrl) {
|
|
977
|
+
primaryUrl = screenshots[0].url;
|
|
978
|
+
screenshots[0] = { ...screenshots[0], isPrimary: true };
|
|
979
|
+
}
|
|
980
|
+
logger.info("smart-screenshot", `Captured ${screenshots.length} smart screenshots for ${stashId}`);
|
|
981
|
+
return { primary: primaryUrl, screenshots };
|
|
982
|
+
}
|
|
983
|
+
|
|
802
984
|
// ../core/dist/prompt.js
|
|
803
985
|
function buildStashPrompt(component, sourceCode, userPrompt, directive) {
|
|
804
986
|
const parts = [
|
|
@@ -849,23 +1031,6 @@ async function waitForPort(port, timeout) {
|
|
|
849
1031
|
}
|
|
850
1032
|
throw new Error(`Port ${port} not ready within ${timeout}ms`);
|
|
851
1033
|
}
|
|
852
|
-
async function captureEphemeralScreenshot(worktreePath, projectPath, stashId, port) {
|
|
853
|
-
const devServer = spawn3({
|
|
854
|
-
cmd: ["npm", "run", "dev"],
|
|
855
|
-
cwd: worktreePath,
|
|
856
|
-
stdin: "ignore",
|
|
857
|
-
stdout: "pipe",
|
|
858
|
-
stderr: "pipe",
|
|
859
|
-
env: { ...process.env, PORT: String(port), BROWSER: "none" }
|
|
860
|
-
});
|
|
861
|
-
try {
|
|
862
|
-
await waitForPort(port, 60000);
|
|
863
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
864
|
-
return await captureScreenshot(port, projectPath, stashId);
|
|
865
|
-
} finally {
|
|
866
|
-
devServer.kill();
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
1034
|
async function allocatePort() {
|
|
870
1035
|
for (let port = 4010;port <= 4030; port++) {
|
|
871
1036
|
try {
|
|
@@ -883,8 +1048,8 @@ async function generate(opts) {
|
|
|
883
1048
|
const selectedDirectives = directives.slice(0, count);
|
|
884
1049
|
let sourceCode = "";
|
|
885
1050
|
if (component?.filePath) {
|
|
886
|
-
const sourceFile =
|
|
887
|
-
if (
|
|
1051
|
+
const sourceFile = join7(projectPath, component.filePath);
|
|
1052
|
+
if (existsSync7(sourceFile)) {
|
|
888
1053
|
sourceCode = readFileSync3(sourceFile, "utf-8");
|
|
889
1054
|
}
|
|
890
1055
|
}
|
|
@@ -906,6 +1071,7 @@ async function generate(opts) {
|
|
|
906
1071
|
worktreePath: worktree.path,
|
|
907
1072
|
port: null,
|
|
908
1073
|
screenshotUrl: null,
|
|
1074
|
+
screenshots: [],
|
|
909
1075
|
status: "generating",
|
|
910
1076
|
error: null,
|
|
911
1077
|
relatedTo: [],
|
|
@@ -930,7 +1096,7 @@ async function generate(opts) {
|
|
|
930
1096
|
});
|
|
931
1097
|
}
|
|
932
1098
|
await aiProcess.process.exited;
|
|
933
|
-
const wtGit =
|
|
1099
|
+
const wtGit = simpleGit3(worktree.path);
|
|
934
1100
|
try {
|
|
935
1101
|
await wtGit.add("-A");
|
|
936
1102
|
await wtGit.commit(`stashes: stash ${stashId}`);
|
|
@@ -953,15 +1119,40 @@ async function generate(opts) {
|
|
|
953
1119
|
try {
|
|
954
1120
|
const port = await allocatePort();
|
|
955
1121
|
const worktree = await worktreeManager.createForGeneration(`screenshot-${stash.id}`);
|
|
956
|
-
const screenshotGit =
|
|
1122
|
+
const screenshotGit = simpleGit3(worktree.path);
|
|
957
1123
|
await screenshotGit.checkout(["-f", stash.branch]);
|
|
958
|
-
const
|
|
1124
|
+
const devServer = spawn3({
|
|
1125
|
+
cmd: ["npm", "run", "dev"],
|
|
1126
|
+
cwd: worktree.path,
|
|
1127
|
+
stdin: "ignore",
|
|
1128
|
+
stdout: "pipe",
|
|
1129
|
+
stderr: "pipe",
|
|
1130
|
+
env: { ...process.env, PORT: String(port), BROWSER: "none" }
|
|
1131
|
+
});
|
|
1132
|
+
try {
|
|
1133
|
+
await waitForPort(port, 60000);
|
|
1134
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
1135
|
+
const mainGit = simpleGit3(projectPath);
|
|
1136
|
+
const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
|
|
1137
|
+
const { primary, screenshots } = await captureSmartScreenshots({
|
|
1138
|
+
projectPath,
|
|
1139
|
+
stashId: stash.id,
|
|
1140
|
+
stashBranch: stash.branch,
|
|
1141
|
+
parentBranch,
|
|
1142
|
+
worktreePath: worktree.path,
|
|
1143
|
+
port,
|
|
1144
|
+
model: opts.screenshotModel,
|
|
1145
|
+
timeout: opts.screenshotTimeout
|
|
1146
|
+
});
|
|
1147
|
+
const updatedStash = { ...stash, screenshotUrl: primary, screenshots };
|
|
1148
|
+
persistence.saveStash(updatedStash);
|
|
1149
|
+
const idx = completedStashes.indexOf(stash);
|
|
1150
|
+
completedStashes[idx] = updatedStash;
|
|
1151
|
+
emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: primary });
|
|
1152
|
+
} finally {
|
|
1153
|
+
devServer.kill();
|
|
1154
|
+
}
|
|
959
1155
|
await worktreeManager.removeGeneration(`screenshot-${stash.id}`);
|
|
960
|
-
const updatedStash = { ...stash, screenshotUrl: screenshotPath };
|
|
961
|
-
persistence.saveStash(updatedStash);
|
|
962
|
-
const idx = completedStashes.indexOf(stash);
|
|
963
|
-
completedStashes[idx] = updatedStash;
|
|
964
|
-
emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath });
|
|
965
1156
|
} catch (err) {
|
|
966
1157
|
logger.error("generation", `screenshot failed for ${stash.id}`, {
|
|
967
1158
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -973,7 +1164,7 @@ async function generate(opts) {
|
|
|
973
1164
|
}
|
|
974
1165
|
// ../core/dist/vary.js
|
|
975
1166
|
var {spawn: spawn4 } = globalThis.Bun;
|
|
976
|
-
import
|
|
1167
|
+
import simpleGit4 from "simple-git";
|
|
977
1168
|
function emit2(onProgress, event) {
|
|
978
1169
|
if (onProgress)
|
|
979
1170
|
onProgress(event);
|
|
@@ -1031,6 +1222,7 @@ async function vary(opts) {
|
|
|
1031
1222
|
worktreePath: worktree.path,
|
|
1032
1223
|
port: null,
|
|
1033
1224
|
screenshotUrl: null,
|
|
1225
|
+
screenshots: [],
|
|
1034
1226
|
status: "generating",
|
|
1035
1227
|
error: null,
|
|
1036
1228
|
relatedTo: [sourceStashId],
|
|
@@ -1050,7 +1242,7 @@ async function vary(opts) {
|
|
|
1050
1242
|
});
|
|
1051
1243
|
}
|
|
1052
1244
|
await aiProcess.process.exited;
|
|
1053
|
-
const wtGit =
|
|
1245
|
+
const wtGit = simpleGit4(worktree.path);
|
|
1054
1246
|
try {
|
|
1055
1247
|
await wtGit.add("-A");
|
|
1056
1248
|
await wtGit.commit(`stashes: vary ${stashId} from ${sourceStashId}`);
|
|
@@ -1058,10 +1250,11 @@ async function vary(opts) {
|
|
|
1058
1250
|
await worktreeManager.removeGeneration(stashId);
|
|
1059
1251
|
emit2(onProgress, { type: "screenshotting", stashId });
|
|
1060
1252
|
let screenshotPath = "";
|
|
1253
|
+
let screenshots = [];
|
|
1061
1254
|
try {
|
|
1062
1255
|
const port = await allocatePort2();
|
|
1063
1256
|
const screenshotWorktree = await worktreeManager.createForGeneration(`screenshot-${stashId}`);
|
|
1064
|
-
const screenshotGit =
|
|
1257
|
+
const screenshotGit = simpleGit4(screenshotWorktree.path);
|
|
1065
1258
|
await screenshotGit.checkout(["-f", stash.branch]);
|
|
1066
1259
|
const devServer = spawn4({
|
|
1067
1260
|
cmd: ["npm", "run", "dev"],
|
|
@@ -1074,7 +1267,18 @@ async function vary(opts) {
|
|
|
1074
1267
|
try {
|
|
1075
1268
|
await waitForPort2(port, 60000);
|
|
1076
1269
|
await new Promise((r) => setTimeout(r, 2000));
|
|
1077
|
-
|
|
1270
|
+
const result = await captureSmartScreenshots({
|
|
1271
|
+
projectPath,
|
|
1272
|
+
stashId,
|
|
1273
|
+
stashBranch: stash.branch,
|
|
1274
|
+
parentBranch: sourceStash.branch,
|
|
1275
|
+
worktreePath: screenshotWorktree.path,
|
|
1276
|
+
port,
|
|
1277
|
+
model: opts.screenshotModel,
|
|
1278
|
+
timeout: opts.screenshotTimeout
|
|
1279
|
+
});
|
|
1280
|
+
screenshotPath = result.primary;
|
|
1281
|
+
screenshots = [...result.screenshots];
|
|
1078
1282
|
} finally {
|
|
1079
1283
|
devServer.kill();
|
|
1080
1284
|
}
|
|
@@ -1084,7 +1288,7 @@ async function vary(opts) {
|
|
|
1084
1288
|
error: err instanceof Error ? err.message : String(err)
|
|
1085
1289
|
});
|
|
1086
1290
|
}
|
|
1087
|
-
const readyStash = { ...stash, status: "ready", screenshotUrl: screenshotPath || null };
|
|
1291
|
+
const readyStash = { ...stash, status: "ready", screenshotUrl: screenshotPath || null, screenshots };
|
|
1088
1292
|
persistence.saveStash(readyStash);
|
|
1089
1293
|
emit2(onProgress, { type: "ready", stashId, screenshotPath });
|
|
1090
1294
|
return readyStash;
|
|
@@ -1108,7 +1312,7 @@ async function apply(opts) {
|
|
|
1108
1312
|
logger.info("apply", `stash ${stashId} applied and worktrees cleaned up`);
|
|
1109
1313
|
}
|
|
1110
1314
|
// ../core/dist/manage.js
|
|
1111
|
-
import
|
|
1315
|
+
import simpleGit5 from "simple-git";
|
|
1112
1316
|
async function list(projectPath) {
|
|
1113
1317
|
const persistence = new PersistenceService(projectPath);
|
|
1114
1318
|
const projects = persistence.listProjects();
|
|
@@ -1129,7 +1333,7 @@ async function remove(projectPath, stashId) {
|
|
|
1129
1333
|
}
|
|
1130
1334
|
}
|
|
1131
1335
|
try {
|
|
1132
|
-
const git =
|
|
1336
|
+
const git = simpleGit5(projectPath);
|
|
1133
1337
|
await git.raw(["branch", "-D", `stashes/${stashId}`]);
|
|
1134
1338
|
} catch {}
|
|
1135
1339
|
logger.info("manage", `removed stash: ${stashId}`);
|
|
@@ -1140,8 +1344,8 @@ async function cleanup(projectPath) {
|
|
|
1140
1344
|
logger.info("manage", "cleanup complete");
|
|
1141
1345
|
}
|
|
1142
1346
|
// ../server/dist/services/stash-service.js
|
|
1143
|
-
import { readFileSync as readFileSync4, existsSync as
|
|
1144
|
-
import { join as
|
|
1347
|
+
import { readFileSync as readFileSync4, existsSync as existsSync8 } from "fs";
|
|
1348
|
+
import { join as join8 } from "path";
|
|
1145
1349
|
|
|
1146
1350
|
// ../server/dist/services/preview-pool.js
|
|
1147
1351
|
class PreviewPool {
|
|
@@ -1404,8 +1608,8 @@ class StashService {
|
|
|
1404
1608
|
let sourceCode = "";
|
|
1405
1609
|
const filePath = component?.filePath || "";
|
|
1406
1610
|
if (filePath && filePath !== "auto-detect") {
|
|
1407
|
-
const sourceFile =
|
|
1408
|
-
if (
|
|
1611
|
+
const sourceFile = join8(this.projectPath, filePath);
|
|
1612
|
+
if (existsSync8(sourceFile)) {
|
|
1409
1613
|
sourceCode = readFileSync4(sourceFile, "utf-8");
|
|
1410
1614
|
}
|
|
1411
1615
|
}
|
|
@@ -1555,6 +1759,7 @@ ${sourceCode.substring(0, 3000)}
|
|
|
1555
1759
|
for (const msg of pendingMessages) {
|
|
1556
1760
|
this.persistence.saveChatMessage(projectId, chatId, msg);
|
|
1557
1761
|
}
|
|
1762
|
+
this.syncStashesFromDisk(projectId);
|
|
1558
1763
|
} catch (err) {
|
|
1559
1764
|
this.broadcast({
|
|
1560
1765
|
type: "ai_stream",
|
|
@@ -1566,6 +1771,25 @@ ${sourceCode.substring(0, 3000)}
|
|
|
1566
1771
|
killAiProcess("chat");
|
|
1567
1772
|
}
|
|
1568
1773
|
}
|
|
1774
|
+
syncStashesFromDisk(projectId) {
|
|
1775
|
+
const diskStashes = this.persistence.listStashes(projectId);
|
|
1776
|
+
for (const stash of diskStashes) {
|
|
1777
|
+
this.broadcast({
|
|
1778
|
+
type: "stash:status",
|
|
1779
|
+
stashId: stash.id,
|
|
1780
|
+
status: stash.status,
|
|
1781
|
+
number: stash.number
|
|
1782
|
+
});
|
|
1783
|
+
if (stash.screenshotUrl) {
|
|
1784
|
+
this.broadcast({
|
|
1785
|
+
type: "stash:screenshot",
|
|
1786
|
+
stashId: stash.id,
|
|
1787
|
+
url: stash.screenshotUrl,
|
|
1788
|
+
screenshots: stash.screenshots?.length ? [...stash.screenshots] : undefined
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1569
1793
|
progressToBroadcast(event) {
|
|
1570
1794
|
switch (event.type) {
|
|
1571
1795
|
case "generating":
|
|
@@ -1578,7 +1802,12 @@ ${sourceCode.substring(0, 3000)}
|
|
|
1578
1802
|
..."number" in event ? { number: event.number } : {}
|
|
1579
1803
|
});
|
|
1580
1804
|
if (event.type === "ready" && "screenshotPath" in event && event.screenshotPath) {
|
|
1581
|
-
this.broadcast({
|
|
1805
|
+
this.broadcast({
|
|
1806
|
+
type: "stash:screenshot",
|
|
1807
|
+
stashId: event.stashId,
|
|
1808
|
+
url: event.screenshotPath,
|
|
1809
|
+
screenshots: "screenshots" in event ? event.screenshots : undefined
|
|
1810
|
+
});
|
|
1582
1811
|
}
|
|
1583
1812
|
break;
|
|
1584
1813
|
case "error":
|
|
@@ -1592,6 +1821,7 @@ ${sourceCode.substring(0, 3000)}
|
|
|
1592
1821
|
}
|
|
1593
1822
|
}
|
|
1594
1823
|
async generate(projectId, prompt, stashCount = DEFAULT_STASH_COUNT, referenceStashIds) {
|
|
1824
|
+
const settings = this.persistence.getProjectSettings(projectId);
|
|
1595
1825
|
let enrichedPrompt = prompt;
|
|
1596
1826
|
if (referenceStashIds?.length) {
|
|
1597
1827
|
const refDescriptions = referenceStashIds.map((id) => this.persistence.getStash(projectId, id)).filter(Boolean).map((s) => `- "${s.prompt}"${s.componentPath ? ` (${s.componentPath})` : ""}`);
|
|
@@ -1609,14 +1839,27 @@ ${refDescriptions.join(`
|
|
|
1609
1839
|
prompt: enrichedPrompt,
|
|
1610
1840
|
component: this.selectedComponent ? { filePath: this.selectedComponent.filePath, exportName: this.selectedComponent.name } : undefined,
|
|
1611
1841
|
count: stashCount,
|
|
1842
|
+
screenshotModel: settings.screenshotModel,
|
|
1843
|
+
screenshotTimeout: settings.screenshotTimeout,
|
|
1612
1844
|
onProgress: (event) => this.progressToBroadcast(event)
|
|
1613
1845
|
});
|
|
1614
1846
|
}
|
|
1615
1847
|
async vary(sourceStashId, prompt) {
|
|
1848
|
+
let projectId = "";
|
|
1849
|
+
for (const project of this.persistence.listProjects()) {
|
|
1850
|
+
const stashes = this.persistence.listStashes(project.id);
|
|
1851
|
+
if (stashes.find((s) => s.id === sourceStashId)) {
|
|
1852
|
+
projectId = project.id;
|
|
1853
|
+
break;
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
const settings = projectId ? this.persistence.getProjectSettings(projectId) : {};
|
|
1616
1857
|
await vary({
|
|
1617
1858
|
projectPath: this.projectPath,
|
|
1618
1859
|
sourceStashId,
|
|
1619
1860
|
prompt,
|
|
1861
|
+
screenshotModel: settings.screenshotModel,
|
|
1862
|
+
screenshotTimeout: settings.screenshotTimeout,
|
|
1620
1863
|
onProgress: (event) => this.progressToBroadcast(event)
|
|
1621
1864
|
});
|
|
1622
1865
|
}
|
|
@@ -1730,115 +1973,93 @@ function createWebSocketHandler(projectPath, userDevPort, appProxyPort) {
|
|
|
1730
1973
|
}
|
|
1731
1974
|
|
|
1732
1975
|
// ../server/dist/services/app-proxy.js
|
|
1976
|
+
import { spawn as spawn5 } from "child_process";
|
|
1733
1977
|
function startAppProxy(userDevPort, proxyPort, injectOverlay) {
|
|
1734
|
-
const
|
|
1735
|
-
const
|
|
1736
|
-
const
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
const contentType = response.headers.get("content-type") || "";
|
|
1768
|
-
const respHeaders = new Headers(response.headers);
|
|
1769
|
-
if (response.status >= 300 && response.status < 400) {
|
|
1770
|
-
const location = respHeaders.get("location");
|
|
1771
|
-
if (location?.startsWith(upstreamOrigin)) {
|
|
1772
|
-
respHeaders.set("location", proxyOrigin + location.substring(upstreamOrigin.length));
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
if (contentType.includes("text/html")) {
|
|
1776
|
-
const html = await response.text();
|
|
1777
|
-
const injectedHtml = injectOverlay(html, userDevPort, proxyPort);
|
|
1778
|
-
respHeaders.delete("content-encoding");
|
|
1779
|
-
respHeaders.delete("content-length");
|
|
1780
|
-
return new Response(injectedHtml, {
|
|
1781
|
-
status: response.status,
|
|
1782
|
-
headers: respHeaders
|
|
1783
|
-
});
|
|
1784
|
-
}
|
|
1785
|
-
respHeaders.delete("content-encoding");
|
|
1786
|
-
return new Response(response.body, {
|
|
1787
|
-
status: response.status,
|
|
1788
|
-
headers: respHeaders
|
|
1789
|
-
});
|
|
1790
|
-
} catch (err) {
|
|
1791
|
-
return new Response(JSON.stringify({ error: "Proxy failed", detail: String(err) }), {
|
|
1792
|
-
status: 502,
|
|
1793
|
-
headers: { "content-type": "application/json" }
|
|
1794
|
-
});
|
|
1795
|
-
}
|
|
1796
|
-
},
|
|
1797
|
-
websocket: {
|
|
1798
|
-
open(ws) {
|
|
1799
|
-
const { data } = ws;
|
|
1800
|
-
const upstream = new WebSocket(`ws://localhost:${userDevPort}${data.path}`);
|
|
1801
|
-
upstream.addEventListener("open", () => {
|
|
1802
|
-
data.upstream = upstream;
|
|
1803
|
-
data.ready = true;
|
|
1804
|
-
for (const msg of data.buffer) {
|
|
1805
|
-
upstream.send(msg);
|
|
1806
|
-
}
|
|
1807
|
-
data.buffer = [];
|
|
1808
|
-
});
|
|
1809
|
-
upstream.addEventListener("message", (event) => {
|
|
1810
|
-
try {
|
|
1811
|
-
ws.sendText(typeof event.data === "string" ? event.data : String(event.data));
|
|
1812
|
-
} catch {}
|
|
1813
|
-
});
|
|
1814
|
-
upstream.addEventListener("close", () => {
|
|
1815
|
-
try {
|
|
1816
|
-
ws.close();
|
|
1817
|
-
} catch {}
|
|
1818
|
-
});
|
|
1819
|
-
upstream.addEventListener("error", () => {
|
|
1820
|
-
try {
|
|
1821
|
-
ws.close();
|
|
1822
|
-
} catch {}
|
|
1823
|
-
});
|
|
1824
|
-
},
|
|
1825
|
-
message(ws, msg) {
|
|
1826
|
-
const { data } = ws;
|
|
1827
|
-
if (data.ready && data.upstream) {
|
|
1828
|
-
data.upstream.send(typeof msg === "string" ? msg : new Uint8Array(msg));
|
|
1829
|
-
} else {
|
|
1830
|
-
data.buffer.push(typeof msg === "string" ? msg : new Uint8Array(msg).buffer);
|
|
1978
|
+
const overlayScript = injectOverlay("", userDevPort, proxyPort);
|
|
1979
|
+
const overlayEscaped = JSON.stringify(overlayScript);
|
|
1980
|
+
const proxyScript = `
|
|
1981
|
+
const http = require('http');
|
|
1982
|
+
const net = require('net');
|
|
1983
|
+
const zlib = require('zlib');
|
|
1984
|
+
const UPSTREAM = ${userDevPort};
|
|
1985
|
+
const OVERLAY = ${overlayEscaped};
|
|
1986
|
+
|
|
1987
|
+
const server = http.createServer((clientReq, clientRes) => {
|
|
1988
|
+
const opts = {
|
|
1989
|
+
hostname: 'localhost',
|
|
1990
|
+
port: UPSTREAM,
|
|
1991
|
+
path: clientReq.url,
|
|
1992
|
+
method: clientReq.method,
|
|
1993
|
+
headers: clientReq.headers,
|
|
1994
|
+
};
|
|
1995
|
+
const proxyReq = http.request(opts, (proxyRes) => {
|
|
1996
|
+
const ct = proxyRes.headers['content-type'] || '';
|
|
1997
|
+
if (ct.includes('text/html')) {
|
|
1998
|
+
// Buffer HTML to inject overlay
|
|
1999
|
+
const chunks = [];
|
|
2000
|
+
proxyRes.on('data', c => chunks.push(c));
|
|
2001
|
+
proxyRes.on('end', () => {
|
|
2002
|
+
let html = Buffer.concat(chunks);
|
|
2003
|
+
const enc = proxyRes.headers['content-encoding'];
|
|
2004
|
+
// Decompress if needed
|
|
2005
|
+
if (enc === 'gzip') {
|
|
2006
|
+
try { html = zlib.gunzipSync(html); } catch {}
|
|
2007
|
+
} else if (enc === 'br') {
|
|
2008
|
+
try { html = zlib.brotliDecompressSync(html); } catch {}
|
|
2009
|
+
} else if (enc === 'deflate') {
|
|
2010
|
+
try { html = zlib.inflateSync(html); } catch {}
|
|
1831
2011
|
}
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
2012
|
+
const hdrs = { ...proxyRes.headers };
|
|
2013
|
+
delete hdrs['content-length'];
|
|
2014
|
+
delete hdrs['content-encoding'];
|
|
2015
|
+
delete hdrs['transfer-encoding'];
|
|
2016
|
+
clientRes.writeHead(proxyRes.statusCode, hdrs);
|
|
2017
|
+
clientRes.write(html);
|
|
2018
|
+
clientRes.end(OVERLAY);
|
|
2019
|
+
});
|
|
2020
|
+
} else {
|
|
2021
|
+
// Non-HTML: stream through unchanged
|
|
2022
|
+
clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);
|
|
2023
|
+
proxyRes.pipe(clientRes);
|
|
1838
2024
|
}
|
|
2025
|
+
proxyRes.on('error', () => clientRes.end());
|
|
1839
2026
|
});
|
|
1840
|
-
|
|
1841
|
-
|
|
2027
|
+
proxyReq.on('error', () => { try { clientRes.writeHead(502); clientRes.end(); } catch {} });
|
|
2028
|
+
clientReq.pipe(proxyReq);
|
|
2029
|
+
});
|
|
2030
|
+
|
|
2031
|
+
// WebSocket upgrades: raw TCP pipe
|
|
2032
|
+
server.on('upgrade', (req, socket, head) => {
|
|
2033
|
+
const upstream = net.createConnection(UPSTREAM, 'localhost', () => {
|
|
2034
|
+
const lines = [req.method + ' ' + req.url + ' HTTP/1.1'];
|
|
2035
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
2036
|
+
lines.push(k + ': ' + (Array.isArray(v) ? v.join(', ') : v));
|
|
2037
|
+
}
|
|
2038
|
+
upstream.write(lines.join('\\r\\n') + '\\r\\n\\r\\n');
|
|
2039
|
+
if (head.length) upstream.write(head);
|
|
2040
|
+
socket.pipe(upstream);
|
|
2041
|
+
upstream.pipe(socket);
|
|
2042
|
+
});
|
|
2043
|
+
upstream.on('error', () => socket.destroy());
|
|
2044
|
+
socket.on('error', () => upstream.destroy());
|
|
2045
|
+
});
|
|
2046
|
+
|
|
2047
|
+
server.listen(${proxyPort}, () => {
|
|
2048
|
+
if (process.send) process.send('ready');
|
|
2049
|
+
});
|
|
2050
|
+
`;
|
|
2051
|
+
const child = spawn5("node", ["-e", proxyScript], {
|
|
2052
|
+
stdio: ["ignore", "inherit", "inherit", "ipc"]
|
|
2053
|
+
});
|
|
2054
|
+
child.on("error", (err) => {
|
|
2055
|
+
logger.error("proxy", `Failed to start proxy: ${err.message}`);
|
|
2056
|
+
});
|
|
2057
|
+
child.on("message", (msg) => {
|
|
2058
|
+
if (msg === "ready") {
|
|
2059
|
+
logger.info("proxy", `App proxy at http://localhost:${proxyPort} \u2192 http://localhost:${userDevPort}`);
|
|
2060
|
+
}
|
|
2061
|
+
});
|
|
2062
|
+
return child;
|
|
1842
2063
|
}
|
|
1843
2064
|
|
|
1844
2065
|
// ../server/dist/index.js
|
|
@@ -1855,12 +2076,12 @@ app2.route("/api", apiRoutes);
|
|
|
1855
2076
|
app2.get("/*", async (c) => {
|
|
1856
2077
|
const path = c.req.path;
|
|
1857
2078
|
const selfDir = dirname2(fileURLToPath(import.meta.url));
|
|
1858
|
-
const bundledWebDir =
|
|
1859
|
-
const monorepoWebDir =
|
|
1860
|
-
const webDistDir =
|
|
2079
|
+
const bundledWebDir = join9(selfDir, "web");
|
|
2080
|
+
const monorepoWebDir = join9(selfDir, "../../web/dist");
|
|
2081
|
+
const webDistDir = existsSync9(join9(bundledWebDir, "index.html")) ? bundledWebDir : monorepoWebDir;
|
|
1861
2082
|
const requestPath = path === "/" ? "/index.html" : path;
|
|
1862
|
-
const filePath =
|
|
1863
|
-
if (
|
|
2083
|
+
const filePath = join9(webDistDir, requestPath);
|
|
2084
|
+
if (existsSync9(filePath) && !filePath.includes("..")) {
|
|
1864
2085
|
const content = readFileSync5(filePath);
|
|
1865
2086
|
const ext = filePath.split(".").pop() || "";
|
|
1866
2087
|
const contentTypes = {
|
|
@@ -1879,8 +2100,8 @@ app2.get("/*", async (c) => {
|
|
|
1879
2100
|
headers: { "content-type": contentTypes[ext] || "application/octet-stream" }
|
|
1880
2101
|
});
|
|
1881
2102
|
}
|
|
1882
|
-
const indexPath =
|
|
1883
|
-
if (
|
|
2103
|
+
const indexPath = join9(webDistDir, "index.html");
|
|
2104
|
+
if (existsSync9(indexPath)) {
|
|
1884
2105
|
const html = readFileSync5(indexPath, "utf-8");
|
|
1885
2106
|
return new Response(html, {
|
|
1886
2107
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
@@ -1916,7 +2137,7 @@ function startServer(projectPath, userDevPort, port = STASHES_PORT) {
|
|
|
1916
2137
|
logger.info("server", `Project: ${projectPath}`);
|
|
1917
2138
|
return server;
|
|
1918
2139
|
}
|
|
1919
|
-
function injectOverlayScript(html,
|
|
2140
|
+
function injectOverlayScript(html, _upstreamPort, _proxyPort) {
|
|
1920
2141
|
const overlayScript = `
|
|
1921
2142
|
<script data-stashes-overlay>
|
|
1922
2143
|
(function() {
|
|
@@ -1924,35 +2145,6 @@ function injectOverlayScript(html, upstreamPort, proxyPort) {
|
|
|
1924
2145
|
var pickerEnabled = false;
|
|
1925
2146
|
var precisionMode = false;
|
|
1926
2147
|
|
|
1927
|
-
// Rewrite cross-origin requests to the upstream dev server through the proxy
|
|
1928
|
-
var upstreamOrigin = 'http://localhost:${upstreamPort}';
|
|
1929
|
-
var proxyOrigin = window.location.origin;
|
|
1930
|
-
if (proxyOrigin !== upstreamOrigin) {
|
|
1931
|
-
function rewriteUrl(url) {
|
|
1932
|
-
if (typeof url === 'string' && (url.startsWith(upstreamOrigin + '/') || url === upstreamOrigin)) {
|
|
1933
|
-
return proxyOrigin + url.substring(upstreamOrigin.length);
|
|
1934
|
-
}
|
|
1935
|
-
return url;
|
|
1936
|
-
}
|
|
1937
|
-
var origFetch = window.fetch;
|
|
1938
|
-
window.fetch = function(input, init) {
|
|
1939
|
-
if (typeof input === 'string') {
|
|
1940
|
-
input = rewriteUrl(input);
|
|
1941
|
-
} else if (input instanceof Request) {
|
|
1942
|
-
var rewritten = rewriteUrl(input.url);
|
|
1943
|
-
if (rewritten !== input.url) { input = new Request(rewritten, input); }
|
|
1944
|
-
}
|
|
1945
|
-
return origFetch.call(window, input, init);
|
|
1946
|
-
};
|
|
1947
|
-
var origXhrOpen = XMLHttpRequest.prototype.open;
|
|
1948
|
-
XMLHttpRequest.prototype.open = function() {
|
|
1949
|
-
if (arguments.length >= 2 && typeof arguments[1] === 'string') {
|
|
1950
|
-
arguments[1] = rewriteUrl(arguments[1]);
|
|
1951
|
-
}
|
|
1952
|
-
return origXhrOpen.apply(this, arguments);
|
|
1953
|
-
};
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
2148
|
function createOverlay() {
|
|
1957
2149
|
var overlay = document.createElement('div');
|
|
1958
2150
|
overlay.id = 'stashes-highlight';
|
|
@@ -1966,13 +2158,18 @@ function injectOverlayScript(html, upstreamPort, proxyPort) {
|
|
|
1966
2158
|
}
|
|
1967
2159
|
|
|
1968
2160
|
var SEMANTIC_TAGS = ['header','nav','main','section','article','aside','footer','form','dialog'];
|
|
2161
|
+
var LEAF_TAGS = ['h1','h2','h3','h4','h5','h6','p','button','a','input','textarea','select','img','svg','video','label','li','td','th','figcaption','blockquote','pre','code','span'];
|
|
1969
2162
|
|
|
1970
2163
|
function findTarget(el, precise) {
|
|
1971
2164
|
if (precise) return el;
|
|
2165
|
+
// If the element itself is a meaningful leaf, select it directly
|
|
2166
|
+
var elTag = el.tagName ? el.tagName.toLowerCase() : '';
|
|
2167
|
+
if (LEAF_TAGS.indexOf(elTag) !== -1) return el;
|
|
1972
2168
|
var current = el;
|
|
1973
2169
|
var best = el;
|
|
1974
2170
|
while (current && current !== document.body) {
|
|
1975
2171
|
var tag = current.tagName.toLowerCase();
|
|
2172
|
+
if (LEAF_TAGS.indexOf(tag) !== -1) { best = current; break; }
|
|
1976
2173
|
if (SEMANTIC_TAGS.indexOf(tag) !== -1) { best = current; break; }
|
|
1977
2174
|
if (current.id) { best = current; break; }
|
|
1978
2175
|
if (current.getAttribute('role')) { best = current; break; }
|
|
@@ -2123,11 +2320,11 @@ function injectOverlayScript(html, upstreamPort, proxyPort) {
|
|
|
2123
2320
|
}
|
|
2124
2321
|
|
|
2125
2322
|
// ../server/dist/services/detector.js
|
|
2126
|
-
import { existsSync as
|
|
2127
|
-
import { join as
|
|
2323
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
2324
|
+
import { join as join10 } from "path";
|
|
2128
2325
|
function detectFramework(projectPath) {
|
|
2129
|
-
const packageJsonPath =
|
|
2130
|
-
if (!
|
|
2326
|
+
const packageJsonPath = join10(projectPath, "package.json");
|
|
2327
|
+
if (!existsSync10(packageJsonPath)) {
|
|
2131
2328
|
return {
|
|
2132
2329
|
framework: "unknown",
|
|
2133
2330
|
devCommand: "npm run dev",
|
|
@@ -2189,7 +2386,7 @@ function getDevCommand(packageJson, fallback) {
|
|
|
2189
2386
|
}
|
|
2190
2387
|
function findConfig(projectPath, candidates) {
|
|
2191
2388
|
for (const candidate of candidates) {
|
|
2192
|
-
if (
|
|
2389
|
+
if (existsSync10(join10(projectPath, candidate))) {
|
|
2193
2390
|
return candidate;
|
|
2194
2391
|
}
|
|
2195
2392
|
}
|
|
@@ -2378,8 +2575,8 @@ Cleaning up all stashes and worktrees...`);
|
|
|
2378
2575
|
}
|
|
2379
2576
|
|
|
2380
2577
|
// src/commands/setup.ts
|
|
2381
|
-
import { existsSync as
|
|
2382
|
-
import { dirname as dirname3, join as
|
|
2578
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync2, mkdirSync as mkdirSync5 } from "fs";
|
|
2579
|
+
import { dirname as dirname3, join as join11 } from "path";
|
|
2383
2580
|
import { homedir } from "os";
|
|
2384
2581
|
import * as p from "@clack/prompts";
|
|
2385
2582
|
import pc from "picocolors";
|
|
@@ -2398,60 +2595,60 @@ var MCP_ENTRY_ZED = {
|
|
|
2398
2595
|
};
|
|
2399
2596
|
function buildToolDefinitions() {
|
|
2400
2597
|
const home = homedir();
|
|
2401
|
-
const appSupport =
|
|
2598
|
+
const appSupport = join11(home, "Library", "Application Support");
|
|
2402
2599
|
return [
|
|
2403
2600
|
{
|
|
2404
2601
|
id: "claude-code",
|
|
2405
2602
|
name: "Claude Code",
|
|
2406
|
-
configPath:
|
|
2603
|
+
configPath: join11(home, ".claude.json"),
|
|
2407
2604
|
serversKey: "mcpServers",
|
|
2408
2605
|
format: "standard",
|
|
2409
|
-
detect: () =>
|
|
2606
|
+
detect: () => existsSync11(join11(home, ".claude.json")) || existsSync11(join11(home, ".claude"))
|
|
2410
2607
|
},
|
|
2411
2608
|
{
|
|
2412
2609
|
id: "claude-desktop",
|
|
2413
2610
|
name: "Claude Desktop",
|
|
2414
|
-
configPath:
|
|
2611
|
+
configPath: join11(appSupport, "Claude", "claude_desktop_config.json"),
|
|
2415
2612
|
serversKey: "mcpServers",
|
|
2416
2613
|
format: "standard",
|
|
2417
|
-
detect: () =>
|
|
2614
|
+
detect: () => existsSync11(join11(appSupport, "Claude")) || existsSync11("/Applications/Claude.app")
|
|
2418
2615
|
},
|
|
2419
2616
|
{
|
|
2420
2617
|
id: "vscode",
|
|
2421
2618
|
name: "VS Code",
|
|
2422
|
-
configPath:
|
|
2619
|
+
configPath: join11(appSupport, "Code", "User", "mcp.json"),
|
|
2423
2620
|
serversKey: "servers",
|
|
2424
2621
|
format: "standard",
|
|
2425
|
-
detect: () =>
|
|
2622
|
+
detect: () => existsSync11(join11(appSupport, "Code", "User"))
|
|
2426
2623
|
},
|
|
2427
2624
|
{
|
|
2428
2625
|
id: "cursor",
|
|
2429
2626
|
name: "Cursor",
|
|
2430
|
-
configPath:
|
|
2627
|
+
configPath: join11(home, ".cursor", "mcp.json"),
|
|
2431
2628
|
serversKey: "mcpServers",
|
|
2432
2629
|
format: "standard",
|
|
2433
|
-
detect: () =>
|
|
2630
|
+
detect: () => existsSync11(join11(home, ".cursor"))
|
|
2434
2631
|
},
|
|
2435
2632
|
{
|
|
2436
2633
|
id: "windsurf",
|
|
2437
2634
|
name: "Windsurf",
|
|
2438
|
-
configPath:
|
|
2635
|
+
configPath: join11(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
2439
2636
|
serversKey: "mcpServers",
|
|
2440
2637
|
format: "standard",
|
|
2441
|
-
detect: () =>
|
|
2638
|
+
detect: () => existsSync11(join11(home, ".codeium", "windsurf"))
|
|
2442
2639
|
},
|
|
2443
2640
|
{
|
|
2444
2641
|
id: "zed",
|
|
2445
2642
|
name: "Zed",
|
|
2446
|
-
configPath:
|
|
2643
|
+
configPath: join11(appSupport, "Zed", "settings.json"),
|
|
2447
2644
|
serversKey: "context_servers",
|
|
2448
2645
|
format: "zed",
|
|
2449
|
-
detect: () =>
|
|
2646
|
+
detect: () => existsSync11(join11(appSupport, "Zed"))
|
|
2450
2647
|
}
|
|
2451
2648
|
];
|
|
2452
2649
|
}
|
|
2453
2650
|
function readJsonFile(path) {
|
|
2454
|
-
if (!
|
|
2651
|
+
if (!existsSync11(path))
|
|
2455
2652
|
return {};
|
|
2456
2653
|
try {
|
|
2457
2654
|
const raw = readFileSync7(path, "utf-8").trim();
|
|
@@ -2463,7 +2660,7 @@ function readJsonFile(path) {
|
|
|
2463
2660
|
}
|
|
2464
2661
|
}
|
|
2465
2662
|
function writeJsonFile(path, data) {
|
|
2466
|
-
|
|
2663
|
+
mkdirSync5(dirname3(path), { recursive: true });
|
|
2467
2664
|
writeFileSync2(path, JSON.stringify(data, null, 2) + `
|
|
2468
2665
|
`);
|
|
2469
2666
|
}
|
|
@@ -2604,13 +2801,13 @@ async function setupCommand(options) {
|
|
|
2604
2801
|
import { execFileSync, execSync } from "child_process";
|
|
2605
2802
|
import { writeFileSync as writeFileSync3, unlinkSync, chmodSync, readFileSync as readFileSync8 } from "fs";
|
|
2606
2803
|
import { tmpdir } from "os";
|
|
2607
|
-
import { join as
|
|
2804
|
+
import { join as join12, dirname as dirname4 } from "path";
|
|
2608
2805
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
2609
2806
|
import * as p2 from "@clack/prompts";
|
|
2610
2807
|
import pc2 from "picocolors";
|
|
2611
2808
|
function getCurrentVersion() {
|
|
2612
2809
|
const selfDir = dirname4(fileURLToPath2(import.meta.url));
|
|
2613
|
-
const pkgPath =
|
|
2810
|
+
const pkgPath = join12(selfDir, "..", "package.json");
|
|
2614
2811
|
return JSON.parse(readFileSync8(pkgPath, "utf-8")).version;
|
|
2615
2812
|
}
|
|
2616
2813
|
function fetchLatestVersion() {
|
|
@@ -2687,7 +2884,7 @@ async function updateCommand() {
|
|
|
2687
2884
|
}
|
|
2688
2885
|
s.stop(`Removed from ${configuredTools.length} tool${configuredTools.length === 1 ? "" : "s"}`);
|
|
2689
2886
|
}
|
|
2690
|
-
const scriptPath =
|
|
2887
|
+
const scriptPath = join12(tmpdir(), `stashes-update-${Date.now()}.sh`);
|
|
2691
2888
|
writeFileSync3(scriptPath, buildUpdateScript(), "utf-8");
|
|
2692
2889
|
chmodSync(scriptPath, 493);
|
|
2693
2890
|
try {
|
|
@@ -2706,7 +2903,7 @@ Update failed. Try manually:`);
|
|
|
2706
2903
|
|
|
2707
2904
|
// src/index.ts
|
|
2708
2905
|
var selfDir = dirname5(fileURLToPath3(import.meta.url));
|
|
2709
|
-
var pkgPath =
|
|
2906
|
+
var pkgPath = join13(selfDir, "..", "package.json");
|
|
2710
2907
|
var version = JSON.parse(readFileSync9(pkgPath, "utf-8")).version;
|
|
2711
2908
|
var program = new Command;
|
|
2712
2909
|
program.name("stashes").description("Generate AI-powered UI design explorations in your project").version(version, "-v, --version");
|