stashes 0.1.56 → 0.1.57
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 +125 -174
- package/dist/mcp.js +99 -148
- package/package.json +1 -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 readFileSync10 } from "fs";
|
|
23
|
-
import { join as
|
|
23
|
+
import { join as join14, dirname as dirname6 } 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 join10, dirname as dirname3 } from "path";
|
|
35
35
|
import { fileURLToPath } from "url";
|
|
36
|
-
import { existsSync as
|
|
36
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
37
37
|
// ../shared/dist/constants/index.js
|
|
38
38
|
var STASHES_PORT = 4000;
|
|
39
39
|
var DEFAULT_STASH_COUNT = 3;
|
|
@@ -56,12 +56,12 @@ var DEFAULT_DIRECTIVES = [
|
|
|
56
56
|
];
|
|
57
57
|
// ../server/dist/routes/api.js
|
|
58
58
|
import { Hono } from "hono";
|
|
59
|
-
import { join as
|
|
60
|
-
import { existsSync as
|
|
59
|
+
import { join as join9, basename } from "path";
|
|
60
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
|
|
61
61
|
|
|
62
62
|
// ../core/dist/generation.js
|
|
63
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
64
|
-
import { join as
|
|
63
|
+
import { readFileSync as readFileSync2, existsSync as existsSync6 } from "fs";
|
|
64
|
+
import { join as join6 } from "path";
|
|
65
65
|
var {spawn: spawn3 } = globalThis.Bun;
|
|
66
66
|
import simpleGit3 from "simple-git";
|
|
67
67
|
|
|
@@ -544,65 +544,19 @@ class PersistenceService {
|
|
|
544
544
|
|
|
545
545
|
// ../core/dist/ai-process.js
|
|
546
546
|
var {spawn } = globalThis.Bun;
|
|
547
|
-
import { writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
548
|
-
import { join as join4 } from "path";
|
|
549
|
-
import { tmpdir } from "os";
|
|
550
547
|
var CLAUDE_BIN = "/opt/homebrew/bin/claude";
|
|
551
|
-
function getPlaywrightMcpConfigPath() {
|
|
552
|
-
const configDir = join4(tmpdir(), "stashes-mcp");
|
|
553
|
-
const configPath = join4(configDir, "playwright.json");
|
|
554
|
-
if (!existsSync4(configPath)) {
|
|
555
|
-
mkdirSync3(configDir, { recursive: true });
|
|
556
|
-
writeFileSync2(configPath, JSON.stringify({
|
|
557
|
-
mcpServers: {
|
|
558
|
-
playwright: { command: "npx", args: ["@playwright/mcp@latest"] }
|
|
559
|
-
}
|
|
560
|
-
}), "utf-8");
|
|
561
|
-
}
|
|
562
|
-
return configPath;
|
|
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
|
-
];
|
|
583
548
|
var processes = new Map;
|
|
584
549
|
function startAiProcess(idOrOpts, prompt, cwd, resumeSessionId, model) {
|
|
585
550
|
const opts = typeof idOrOpts === "string" ? { id: idOrOpts, prompt, cwd, resumeSessionId, model } : idOrOpts;
|
|
586
|
-
const restricted = opts.tools !== undefined;
|
|
587
551
|
killAiProcess(opts.id);
|
|
588
552
|
logger.info("claude", `spawning process: ${opts.id}`, {
|
|
589
553
|
cwd: opts.cwd,
|
|
590
554
|
promptLength: opts.prompt.length,
|
|
591
555
|
promptPreview: opts.prompt.substring(0, 100),
|
|
592
556
|
resumeSessionId: opts.resumeSessionId,
|
|
593
|
-
model: opts.model
|
|
594
|
-
restricted,
|
|
595
|
-
tools: restricted ? opts.tools.join(",") || "none" : "all"
|
|
557
|
+
model: opts.model
|
|
596
558
|
});
|
|
597
559
|
const cmd = [CLAUDE_BIN, "-p", opts.prompt, "--output-format=stream-json", "--verbose", "--dangerously-skip-permissions"];
|
|
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");
|
|
602
|
-
if (opts.mcpConfigPath) {
|
|
603
|
-
cmd.push("--mcp-config", opts.mcpConfigPath);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
560
|
if (opts.resumeSessionId) {
|
|
607
561
|
cmd.push("--resume", opts.resumeSessionId);
|
|
608
562
|
}
|
|
@@ -733,35 +687,47 @@ async function* parseClaudeStream(proc) {
|
|
|
733
687
|
}
|
|
734
688
|
|
|
735
689
|
// ../core/dist/smart-screenshot.js
|
|
736
|
-
import { join as
|
|
737
|
-
import { mkdirSync as
|
|
690
|
+
import { join as join5 } from "path";
|
|
691
|
+
import { mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
|
|
738
692
|
import simpleGit2 from "simple-git";
|
|
739
693
|
|
|
740
694
|
// ../core/dist/screenshot.js
|
|
741
695
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
742
|
-
import { join as
|
|
743
|
-
import { mkdirSync as
|
|
696
|
+
import { join as join4 } from "path";
|
|
697
|
+
import { mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
744
698
|
var SCREENSHOTS_DIR = ".stashes/screenshots";
|
|
745
|
-
async function captureScreenshot(
|
|
746
|
-
const
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
const
|
|
699
|
+
async function captureScreenshot(portOrOpts, projectPath, stashId) {
|
|
700
|
+
const opts = typeof portOrOpts === "number" ? { port: portOrOpts, projectPath, stashId } : portOrOpts;
|
|
701
|
+
const screenshotsDir = join4(opts.projectPath, SCREENSHOTS_DIR);
|
|
702
|
+
if (!existsSync4(screenshotsDir)) {
|
|
703
|
+
mkdirSync3(screenshotsDir, { recursive: true });
|
|
704
|
+
}
|
|
705
|
+
const filename = `${opts.stashId}.png`;
|
|
706
|
+
const outputPath = join4(screenshotsDir, filename);
|
|
707
|
+
const url = `http://localhost:${opts.port}${opts.route || "/"}`;
|
|
708
|
+
const scrollCode = opts.scrollToSelector ? `
|
|
709
|
+
try {
|
|
710
|
+
const el = await page.waitForSelector('${opts.scrollToSelector.replace(/'/g, "\\'")}', { timeout: 5000 });
|
|
711
|
+
if (el) {
|
|
712
|
+
await el.scrollIntoViewIfNeeded();
|
|
713
|
+
await page.waitForTimeout(500);
|
|
714
|
+
}
|
|
715
|
+
} catch(e) { /* selector not found, capture viewport */ }
|
|
716
|
+
` : "";
|
|
752
717
|
const playwrightScript = `
|
|
753
718
|
const { chromium } = require('playwright');
|
|
754
719
|
(async () => {
|
|
755
720
|
const browser = await chromium.launch({ headless: true });
|
|
756
721
|
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
|
|
757
722
|
try {
|
|
758
|
-
await page.goto('
|
|
723
|
+
await page.goto('${url}', { waitUntil: 'networkidle', timeout: 20000 });
|
|
759
724
|
await page.waitForTimeout(2000);
|
|
760
725
|
} catch(e) {
|
|
761
|
-
await page.goto('
|
|
726
|
+
await page.goto('${url}', { waitUntil: 'domcontentloaded', timeout: 10000 });
|
|
762
727
|
await page.waitForTimeout(3000);
|
|
763
728
|
}
|
|
764
|
-
|
|
729
|
+
${scrollCode}
|
|
730
|
+
await page.screenshot({ path: '${outputPath.replace(/'/g, "\\'")}', type: 'png'${opts.fullPage ? ", fullPage: true" : ""} });
|
|
765
731
|
await browser.close();
|
|
766
732
|
})();
|
|
767
733
|
`;
|
|
@@ -797,62 +763,56 @@ ${diffStat}
|
|
|
797
763
|
## Partial diff (truncated):
|
|
798
764
|
${truncatedDiff}`;
|
|
799
765
|
} catch (err) {
|
|
800
|
-
logger.warn("smart-screenshot", `git diff failed
|
|
766
|
+
logger.warn("smart-screenshot", `git diff failed`, {
|
|
801
767
|
error: err instanceof Error ? err.message : String(err)
|
|
802
768
|
});
|
|
803
769
|
return "";
|
|
804
770
|
}
|
|
805
771
|
}
|
|
806
772
|
function buildScreenshotPrompt(port, diff, screenshotDir, stashId) {
|
|
807
|
-
const outputPath =
|
|
773
|
+
const outputPath = join5(screenshotDir, `${stashId}.png`);
|
|
808
774
|
return [
|
|
809
|
-
"You are
|
|
810
|
-
"Be fast \u2014 you have a strict time limit.",
|
|
775
|
+
"## CRITICAL: You are an automated screenshot subprocess. Follow these rules exactly.",
|
|
811
776
|
"",
|
|
812
|
-
|
|
777
|
+
"FORBIDDEN \u2014 do NOT call any of these tools:",
|
|
778
|
+
"- ToolSearch, Skill, Agent, TodoWrite, TaskCreate, TaskUpdate, TaskList",
|
|
779
|
+
"- mcp__UseAI__useai_start, mcp__UseAI__useai_end, mcp__UseAI__useai_heartbeat",
|
|
780
|
+
"- mcp__stashes__*, mcp__plugin_drills__*, mcp__plugin_coverit__*",
|
|
781
|
+
"- EnterPlanMode, ExitPlanMode, WebSearch, WebFetch, LSP",
|
|
782
|
+
"- Read, Write, Edit, Glob, Grep (you do NOT need to read code)",
|
|
813
783
|
"",
|
|
814
|
-
"
|
|
815
|
-
"```",
|
|
816
|
-
diff,
|
|
817
|
-
"```",
|
|
784
|
+
"YOUR ONLY JOB: navigate to the app and take a screenshot using Playwright.",
|
|
818
785
|
"",
|
|
819
|
-
|
|
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.",
|
|
786
|
+
`## App URL: http://localhost:${port}`,
|
|
822
787
|
"",
|
|
823
|
-
"
|
|
824
|
-
"```",
|
|
825
|
-
"mcp__playwright__browser_run_code",
|
|
788
|
+
"## Git diff (shows what changed \u2014 use this to decide where to navigate):",
|
|
826
789
|
"```",
|
|
827
|
-
|
|
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
|
-
`}`,
|
|
790
|
+
diff,
|
|
840
791
|
"```",
|
|
841
792
|
"",
|
|
842
|
-
"
|
|
843
|
-
|
|
793
|
+
"## Steps (do these in order, nothing else):",
|
|
794
|
+
`1. Navigate: call mcp__plugin_playwright_playwright__browser_navigate with url "http://localhost:${port}"`,
|
|
795
|
+
"2. Wait 3 seconds for the page to render",
|
|
796
|
+
"3. If the diff shows changes below the fold, scroll down using mcp__plugin_playwright_playwright__browser_evaluate",
|
|
797
|
+
`4. Take screenshot: call mcp__plugin_playwright_playwright__browser_take_screenshot with type "png"`,
|
|
798
|
+
` The screenshot will be saved automatically.`,
|
|
799
|
+
"5. Output ONLY this JSON (replace the screenshot path with the actual path returned):",
|
|
844
800
|
"",
|
|
845
|
-
|
|
801
|
+
"```json",
|
|
846
802
|
"{",
|
|
847
803
|
' "screenshots": [',
|
|
848
804
|
" {",
|
|
849
805
|
` "path": "${outputPath}",`,
|
|
850
|
-
' "label": "
|
|
851
|
-
' "route": "/
|
|
806
|
+
' "label": "Description of what is shown",',
|
|
807
|
+
' "route": "/",',
|
|
852
808
|
' "isPrimary": true',
|
|
853
809
|
" }",
|
|
854
810
|
" ]",
|
|
855
|
-
"}"
|
|
811
|
+
"}",
|
|
812
|
+
"```",
|
|
813
|
+
"",
|
|
814
|
+
"REMEMBER: Do NOT call ToolSearch, Skill, UseAI, Read, or any tool not listed above.",
|
|
815
|
+
"You have a strict time limit. Just navigate, screenshot, output JSON, done."
|
|
856
816
|
].join(`
|
|
857
817
|
`);
|
|
858
818
|
}
|
|
@@ -887,9 +847,9 @@ async function fallbackScreenshot(port, projectPath, stashId) {
|
|
|
887
847
|
}
|
|
888
848
|
async function captureSmartScreenshots(opts) {
|
|
889
849
|
const { projectPath, stashId, stashBranch, parentBranch, worktreePath, port, model = "sonnet", timeout = DEFAULT_TIMEOUT } = opts;
|
|
890
|
-
const screenshotDir =
|
|
891
|
-
if (!
|
|
892
|
-
|
|
850
|
+
const screenshotDir = join5(projectPath, SCREENSHOTS_DIR2);
|
|
851
|
+
if (!existsSync5(screenshotDir)) {
|
|
852
|
+
mkdirSync4(screenshotDir, { recursive: true });
|
|
893
853
|
}
|
|
894
854
|
const diff = await getStashDiff(worktreePath, parentBranch);
|
|
895
855
|
if (!diff) {
|
|
@@ -903,9 +863,7 @@ async function captureSmartScreenshots(opts) {
|
|
|
903
863
|
id: processId,
|
|
904
864
|
prompt,
|
|
905
865
|
cwd: worktreePath,
|
|
906
|
-
model: modelFlag
|
|
907
|
-
tools: [],
|
|
908
|
-
mcpConfigPath: getPlaywrightMcpConfigPath()
|
|
866
|
+
model: modelFlag
|
|
909
867
|
});
|
|
910
868
|
let textOutput = "";
|
|
911
869
|
let timedOut = false;
|
|
@@ -931,9 +889,9 @@ async function captureSmartScreenshots(opts) {
|
|
|
931
889
|
}
|
|
932
890
|
const result = parseAiResult(textOutput);
|
|
933
891
|
if (!result || !result.screenshots || result.screenshots.length === 0) {
|
|
934
|
-
const expectedPath =
|
|
935
|
-
if (
|
|
936
|
-
logger.info("smart-screenshot", `AI saved screenshot for ${stashId}
|
|
892
|
+
const expectedPath = join5(screenshotDir, `${stashId}.png`);
|
|
893
|
+
if (existsSync5(expectedPath)) {
|
|
894
|
+
logger.info("smart-screenshot", `AI saved screenshot for ${stashId} (no JSON response, but file exists)`);
|
|
937
895
|
const url = `/api/screenshots/${stashId}.png`;
|
|
938
896
|
return {
|
|
939
897
|
primary: url,
|
|
@@ -947,18 +905,12 @@ async function captureSmartScreenshots(opts) {
|
|
|
947
905
|
let primaryUrl = "";
|
|
948
906
|
for (const shot of result.screenshots) {
|
|
949
907
|
const filename = shot.path.split("/").pop() || "";
|
|
950
|
-
if (!
|
|
908
|
+
if (!existsSync5(shot.path)) {
|
|
951
909
|
logger.warn("smart-screenshot", `Screenshot file not found: ${shot.path}`);
|
|
952
910
|
continue;
|
|
953
911
|
}
|
|
954
912
|
const url = `/api/screenshots/${filename}`;
|
|
955
|
-
|
|
956
|
-
url,
|
|
957
|
-
label: shot.label,
|
|
958
|
-
route: shot.route,
|
|
959
|
-
isPrimary: shot.isPrimary
|
|
960
|
-
};
|
|
961
|
-
screenshots.push(screenshot);
|
|
913
|
+
screenshots.push({ url, label: shot.label, route: shot.route, isPrimary: shot.isPrimary });
|
|
962
914
|
if (shot.isPrimary)
|
|
963
915
|
primaryUrl = url;
|
|
964
916
|
}
|
|
@@ -1041,8 +993,8 @@ async function generate(opts) {
|
|
|
1041
993
|
const selectedDirectives = directives.slice(0, count);
|
|
1042
994
|
let sourceCode = "";
|
|
1043
995
|
if (component?.filePath) {
|
|
1044
|
-
const sourceFile =
|
|
1045
|
-
if (
|
|
996
|
+
const sourceFile = join6(projectPath, component.filePath);
|
|
997
|
+
if (existsSync6(sourceFile)) {
|
|
1046
998
|
sourceCode = readFileSync2(sourceFile, "utf-8");
|
|
1047
999
|
}
|
|
1048
1000
|
}
|
|
@@ -1412,8 +1364,8 @@ async function cleanup(projectPath) {
|
|
|
1412
1364
|
logger.info("manage", "cleanup complete");
|
|
1413
1365
|
}
|
|
1414
1366
|
// ../server/dist/services/stash-service.js
|
|
1415
|
-
import { readFileSync as readFileSync3, existsSync as
|
|
1416
|
-
import { join as
|
|
1367
|
+
import { readFileSync as readFileSync3, existsSync as existsSync7 } from "fs";
|
|
1368
|
+
import { join as join7 } from "path";
|
|
1417
1369
|
|
|
1418
1370
|
// ../server/dist/services/app-proxy.js
|
|
1419
1371
|
import { spawn as spawn5 } from "child_process";
|
|
@@ -1949,8 +1901,7 @@ class StashService {
|
|
|
1949
1901
|
id: "resolve-component",
|
|
1950
1902
|
prompt,
|
|
1951
1903
|
cwd: this.projectPath,
|
|
1952
|
-
model: "claude-haiku-4-5-20251001"
|
|
1953
|
-
tools: ["Read", "Grep", "Glob", "Bash"]
|
|
1904
|
+
model: "claude-haiku-4-5-20251001"
|
|
1954
1905
|
});
|
|
1955
1906
|
let resolvedPath = "";
|
|
1956
1907
|
try {
|
|
@@ -2012,8 +1963,8 @@ class StashService {
|
|
|
2012
1963
|
let sourceCode = "";
|
|
2013
1964
|
const filePath = component?.filePath || "";
|
|
2014
1965
|
if (filePath && filePath !== "auto-detect") {
|
|
2015
|
-
const sourceFile =
|
|
2016
|
-
if (
|
|
1966
|
+
const sourceFile = join7(this.projectPath, filePath);
|
|
1967
|
+
if (existsSync7(sourceFile)) {
|
|
2017
1968
|
sourceCode = readFileSync3(sourceFile, "utf-8");
|
|
2018
1969
|
}
|
|
2019
1970
|
}
|
|
@@ -2365,8 +2316,8 @@ ${refDescriptions.join(`
|
|
|
2365
2316
|
}
|
|
2366
2317
|
|
|
2367
2318
|
// ../server/dist/services/activity-store.js
|
|
2368
|
-
import { appendFileSync as appendFileSync2, readFileSync as readFileSync4, existsSync as
|
|
2369
|
-
import { join as
|
|
2319
|
+
import { appendFileSync as appendFileSync2, readFileSync as readFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync5, rmSync as rmSync3 } from "fs";
|
|
2320
|
+
import { join as join8, dirname as dirname2 } from "path";
|
|
2370
2321
|
|
|
2371
2322
|
class ActivityStore {
|
|
2372
2323
|
cache = new Map;
|
|
@@ -2375,7 +2326,7 @@ class ActivityStore {
|
|
|
2375
2326
|
this.projectPath = projectPath;
|
|
2376
2327
|
}
|
|
2377
2328
|
jsonlPath(stashId) {
|
|
2378
|
-
return
|
|
2329
|
+
return join8(this.projectPath, ".stashes", "activity", `${stashId}.jsonl`);
|
|
2379
2330
|
}
|
|
2380
2331
|
append(event) {
|
|
2381
2332
|
const existing = this.cache.get(event.stashId) ?? [];
|
|
@@ -2383,8 +2334,8 @@ class ActivityStore {
|
|
|
2383
2334
|
this.cache.set(event.stashId, existing);
|
|
2384
2335
|
const filePath = this.jsonlPath(event.stashId);
|
|
2385
2336
|
const dir = dirname2(filePath);
|
|
2386
|
-
if (!
|
|
2387
|
-
|
|
2337
|
+
if (!existsSync8(dir))
|
|
2338
|
+
mkdirSync5(dir, { recursive: true });
|
|
2388
2339
|
appendFileSync2(filePath, JSON.stringify(event) + `
|
|
2389
2340
|
`, "utf-8");
|
|
2390
2341
|
}
|
|
@@ -2393,7 +2344,7 @@ class ActivityStore {
|
|
|
2393
2344
|
if (cached && cached.length > 0)
|
|
2394
2345
|
return cached;
|
|
2395
2346
|
const filePath = this.jsonlPath(stashId);
|
|
2396
|
-
if (!
|
|
2347
|
+
if (!existsSync8(filePath))
|
|
2397
2348
|
return [];
|
|
2398
2349
|
const lines = readFileSync4(filePath, "utf-8").trim().split(`
|
|
2399
2350
|
`).filter(Boolean);
|
|
@@ -2419,14 +2370,14 @@ class ActivityStore {
|
|
|
2419
2370
|
clear(stashId) {
|
|
2420
2371
|
this.cache.delete(stashId);
|
|
2421
2372
|
const filePath = this.jsonlPath(stashId);
|
|
2422
|
-
if (
|
|
2373
|
+
if (existsSync8(filePath)) {
|
|
2423
2374
|
rmSync3(filePath);
|
|
2424
2375
|
}
|
|
2425
2376
|
}
|
|
2426
2377
|
has(stashId) {
|
|
2427
2378
|
if (this.cache.has(stashId) && (this.cache.get(stashId)?.length ?? 0) > 0)
|
|
2428
2379
|
return true;
|
|
2429
|
-
return
|
|
2380
|
+
return existsSync8(this.jsonlPath(stashId));
|
|
2430
2381
|
}
|
|
2431
2382
|
}
|
|
2432
2383
|
|
|
@@ -2668,8 +2619,8 @@ app.get("/dev-server-status", async (c) => {
|
|
|
2668
2619
|
});
|
|
2669
2620
|
app.get("/screenshots/:filename", (c) => {
|
|
2670
2621
|
const filename = c.req.param("filename");
|
|
2671
|
-
const filePath =
|
|
2672
|
-
if (!
|
|
2622
|
+
const filePath = join9(serverState.projectPath, ".stashes", "screenshots", filename);
|
|
2623
|
+
if (!existsSync9(filePath))
|
|
2673
2624
|
return c.json({ error: "Not found" }, 404);
|
|
2674
2625
|
const content = readFileSync5(filePath);
|
|
2675
2626
|
return new Response(content, {
|
|
@@ -2721,12 +2672,12 @@ app2.route("/api", apiRoutes);
|
|
|
2721
2672
|
app2.get("/*", async (c) => {
|
|
2722
2673
|
const path = c.req.path;
|
|
2723
2674
|
const selfDir = dirname3(fileURLToPath(import.meta.url));
|
|
2724
|
-
const bundledWebDir =
|
|
2725
|
-
const monorepoWebDir =
|
|
2726
|
-
const webDistDir =
|
|
2675
|
+
const bundledWebDir = join10(selfDir, "web");
|
|
2676
|
+
const monorepoWebDir = join10(selfDir, "../../web/dist");
|
|
2677
|
+
const webDistDir = existsSync10(join10(bundledWebDir, "index.html")) ? bundledWebDir : monorepoWebDir;
|
|
2727
2678
|
const requestPath = path === "/" ? "/index.html" : path;
|
|
2728
|
-
const filePath =
|
|
2729
|
-
if (
|
|
2679
|
+
const filePath = join10(webDistDir, requestPath);
|
|
2680
|
+
if (existsSync10(filePath) && !filePath.includes("..")) {
|
|
2730
2681
|
const content = readFileSync6(filePath);
|
|
2731
2682
|
const ext = filePath.split(".").pop() || "";
|
|
2732
2683
|
const contentTypes = {
|
|
@@ -2745,8 +2696,8 @@ app2.get("/*", async (c) => {
|
|
|
2745
2696
|
headers: { "content-type": contentTypes[ext] || "application/octet-stream" }
|
|
2746
2697
|
});
|
|
2747
2698
|
}
|
|
2748
|
-
const indexPath =
|
|
2749
|
-
if (
|
|
2699
|
+
const indexPath = join10(webDistDir, "index.html");
|
|
2700
|
+
if (existsSync10(indexPath)) {
|
|
2750
2701
|
const html = readFileSync6(indexPath, "utf-8");
|
|
2751
2702
|
return new Response(html, {
|
|
2752
2703
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
@@ -2807,11 +2758,11 @@ async function startServer(projectPath, userDevPort, requestedPort = STASHES_POR
|
|
|
2807
2758
|
}
|
|
2808
2759
|
|
|
2809
2760
|
// ../server/dist/services/detector.js
|
|
2810
|
-
import { existsSync as
|
|
2811
|
-
import { join as
|
|
2761
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
|
|
2762
|
+
import { join as join11 } from "path";
|
|
2812
2763
|
function detectFramework(projectPath) {
|
|
2813
|
-
const packageJsonPath =
|
|
2814
|
-
if (!
|
|
2764
|
+
const packageJsonPath = join11(projectPath, "package.json");
|
|
2765
|
+
if (!existsSync11(packageJsonPath)) {
|
|
2815
2766
|
return {
|
|
2816
2767
|
framework: "unknown",
|
|
2817
2768
|
devCommand: "npm run dev",
|
|
@@ -2873,7 +2824,7 @@ function getDevCommand(packageJson, fallback) {
|
|
|
2873
2824
|
}
|
|
2874
2825
|
function findConfig(projectPath, candidates) {
|
|
2875
2826
|
for (const candidate of candidates) {
|
|
2876
|
-
if (
|
|
2827
|
+
if (existsSync11(join11(projectPath, candidate))) {
|
|
2877
2828
|
return candidate;
|
|
2878
2829
|
}
|
|
2879
2830
|
}
|
|
@@ -3065,8 +3016,8 @@ Cleaning up all stashes and worktrees...`);
|
|
|
3065
3016
|
}
|
|
3066
3017
|
|
|
3067
3018
|
// src/commands/setup.ts
|
|
3068
|
-
import { existsSync as
|
|
3069
|
-
import { dirname as dirname4, join as
|
|
3019
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync2, mkdirSync as mkdirSync6 } from "fs";
|
|
3020
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
3070
3021
|
import { homedir } from "os";
|
|
3071
3022
|
import * as p from "@clack/prompts";
|
|
3072
3023
|
import pc from "picocolors";
|
|
@@ -3085,60 +3036,60 @@ var MCP_ENTRY_ZED = {
|
|
|
3085
3036
|
};
|
|
3086
3037
|
function buildToolDefinitions() {
|
|
3087
3038
|
const home = homedir();
|
|
3088
|
-
const appSupport =
|
|
3039
|
+
const appSupport = join12(home, "Library", "Application Support");
|
|
3089
3040
|
return [
|
|
3090
3041
|
{
|
|
3091
3042
|
id: "claude-code",
|
|
3092
3043
|
name: "Claude Code",
|
|
3093
|
-
configPath:
|
|
3044
|
+
configPath: join12(home, ".claude.json"),
|
|
3094
3045
|
serversKey: "mcpServers",
|
|
3095
3046
|
format: "standard",
|
|
3096
|
-
detect: () =>
|
|
3047
|
+
detect: () => existsSync12(join12(home, ".claude.json")) || existsSync12(join12(home, ".claude"))
|
|
3097
3048
|
},
|
|
3098
3049
|
{
|
|
3099
3050
|
id: "claude-desktop",
|
|
3100
3051
|
name: "Claude Desktop",
|
|
3101
|
-
configPath:
|
|
3052
|
+
configPath: join12(appSupport, "Claude", "claude_desktop_config.json"),
|
|
3102
3053
|
serversKey: "mcpServers",
|
|
3103
3054
|
format: "standard",
|
|
3104
|
-
detect: () =>
|
|
3055
|
+
detect: () => existsSync12(join12(appSupport, "Claude")) || existsSync12("/Applications/Claude.app")
|
|
3105
3056
|
},
|
|
3106
3057
|
{
|
|
3107
3058
|
id: "vscode",
|
|
3108
3059
|
name: "VS Code",
|
|
3109
|
-
configPath:
|
|
3060
|
+
configPath: join12(appSupport, "Code", "User", "mcp.json"),
|
|
3110
3061
|
serversKey: "servers",
|
|
3111
3062
|
format: "standard",
|
|
3112
|
-
detect: () =>
|
|
3063
|
+
detect: () => existsSync12(join12(appSupport, "Code", "User"))
|
|
3113
3064
|
},
|
|
3114
3065
|
{
|
|
3115
3066
|
id: "cursor",
|
|
3116
3067
|
name: "Cursor",
|
|
3117
|
-
configPath:
|
|
3068
|
+
configPath: join12(home, ".cursor", "mcp.json"),
|
|
3118
3069
|
serversKey: "mcpServers",
|
|
3119
3070
|
format: "standard",
|
|
3120
|
-
detect: () =>
|
|
3071
|
+
detect: () => existsSync12(join12(home, ".cursor"))
|
|
3121
3072
|
},
|
|
3122
3073
|
{
|
|
3123
3074
|
id: "windsurf",
|
|
3124
3075
|
name: "Windsurf",
|
|
3125
|
-
configPath:
|
|
3076
|
+
configPath: join12(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
3126
3077
|
serversKey: "mcpServers",
|
|
3127
3078
|
format: "standard",
|
|
3128
|
-
detect: () =>
|
|
3079
|
+
detect: () => existsSync12(join12(home, ".codeium", "windsurf"))
|
|
3129
3080
|
},
|
|
3130
3081
|
{
|
|
3131
3082
|
id: "zed",
|
|
3132
3083
|
name: "Zed",
|
|
3133
|
-
configPath:
|
|
3084
|
+
configPath: join12(appSupport, "Zed", "settings.json"),
|
|
3134
3085
|
serversKey: "context_servers",
|
|
3135
3086
|
format: "zed",
|
|
3136
|
-
detect: () =>
|
|
3087
|
+
detect: () => existsSync12(join12(appSupport, "Zed"))
|
|
3137
3088
|
}
|
|
3138
3089
|
];
|
|
3139
3090
|
}
|
|
3140
3091
|
function readJsonFile(path) {
|
|
3141
|
-
if (!
|
|
3092
|
+
if (!existsSync12(path))
|
|
3142
3093
|
return {};
|
|
3143
3094
|
try {
|
|
3144
3095
|
const raw = readFileSync8(path, "utf-8").trim();
|
|
@@ -3150,8 +3101,8 @@ function readJsonFile(path) {
|
|
|
3150
3101
|
}
|
|
3151
3102
|
}
|
|
3152
3103
|
function writeJsonFile(path, data) {
|
|
3153
|
-
|
|
3154
|
-
|
|
3104
|
+
mkdirSync6(dirname4(path), { recursive: true });
|
|
3105
|
+
writeFileSync2(path, JSON.stringify(data, null, 2) + `
|
|
3155
3106
|
`);
|
|
3156
3107
|
}
|
|
3157
3108
|
function isConfigured(tool) {
|
|
@@ -3289,15 +3240,15 @@ async function setupCommand(options) {
|
|
|
3289
3240
|
|
|
3290
3241
|
// src/commands/update.ts
|
|
3291
3242
|
import { execFileSync, execSync } from "child_process";
|
|
3292
|
-
import { writeFileSync as
|
|
3293
|
-
import { tmpdir
|
|
3294
|
-
import { join as
|
|
3243
|
+
import { writeFileSync as writeFileSync3, unlinkSync, chmodSync, readFileSync as readFileSync9 } from "fs";
|
|
3244
|
+
import { tmpdir } from "os";
|
|
3245
|
+
import { join as join13, dirname as dirname5 } from "path";
|
|
3295
3246
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3296
3247
|
import * as p2 from "@clack/prompts";
|
|
3297
3248
|
import pc2 from "picocolors";
|
|
3298
3249
|
function getCurrentVersion() {
|
|
3299
3250
|
const selfDir = dirname5(fileURLToPath2(import.meta.url));
|
|
3300
|
-
const pkgPath =
|
|
3251
|
+
const pkgPath = join13(selfDir, "..", "package.json");
|
|
3301
3252
|
return JSON.parse(readFileSync9(pkgPath, "utf-8")).version;
|
|
3302
3253
|
}
|
|
3303
3254
|
function fetchLatestVersion() {
|
|
@@ -3374,8 +3325,8 @@ async function updateCommand() {
|
|
|
3374
3325
|
}
|
|
3375
3326
|
s.stop(`Removed from ${configuredTools.length} tool${configuredTools.length === 1 ? "" : "s"}`);
|
|
3376
3327
|
}
|
|
3377
|
-
const scriptPath =
|
|
3378
|
-
|
|
3328
|
+
const scriptPath = join13(tmpdir(), `stashes-update-${Date.now()}.sh`);
|
|
3329
|
+
writeFileSync3(scriptPath, buildUpdateScript(), "utf-8");
|
|
3379
3330
|
chmodSync(scriptPath, 493);
|
|
3380
3331
|
try {
|
|
3381
3332
|
execFileSync("bash", [scriptPath], { stdio: "inherit" });
|
|
@@ -3393,7 +3344,7 @@ Update failed. Try manually:`);
|
|
|
3393
3344
|
|
|
3394
3345
|
// src/index.ts
|
|
3395
3346
|
var selfDir = dirname6(fileURLToPath3(import.meta.url));
|
|
3396
|
-
var pkgPath =
|
|
3347
|
+
var pkgPath = join14(selfDir, "..", "package.json");
|
|
3397
3348
|
var version = JSON.parse(readFileSync10(pkgPath, "utf-8")).version;
|
|
3398
3349
|
var program = new Command;
|
|
3399
3350
|
program.name("stashes").description("Generate AI-powered UI design explorations in your project").version(version, "-v, --version");
|
package/dist/mcp.js
CHANGED
|
@@ -26,8 +26,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
26
26
|
import { z } from "zod";
|
|
27
27
|
|
|
28
28
|
// ../core/dist/generation.js
|
|
29
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
30
|
-
import { join as
|
|
29
|
+
import { readFileSync as readFileSync2, existsSync as existsSync6 } from "fs";
|
|
30
|
+
import { join as join6 } from "path";
|
|
31
31
|
var {spawn: spawn3 } = globalThis.Bun;
|
|
32
32
|
import simpleGit3 from "simple-git";
|
|
33
33
|
// ../shared/dist/constants/index.js
|
|
@@ -529,65 +529,19 @@ class PersistenceService {
|
|
|
529
529
|
|
|
530
530
|
// ../core/dist/ai-process.js
|
|
531
531
|
var {spawn } = globalThis.Bun;
|
|
532
|
-
import { writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
533
|
-
import { join as join4 } from "path";
|
|
534
|
-
import { tmpdir } from "os";
|
|
535
532
|
var CLAUDE_BIN = "/opt/homebrew/bin/claude";
|
|
536
|
-
function getPlaywrightMcpConfigPath() {
|
|
537
|
-
const configDir = join4(tmpdir(), "stashes-mcp");
|
|
538
|
-
const configPath = join4(configDir, "playwright.json");
|
|
539
|
-
if (!existsSync4(configPath)) {
|
|
540
|
-
mkdirSync3(configDir, { recursive: true });
|
|
541
|
-
writeFileSync2(configPath, JSON.stringify({
|
|
542
|
-
mcpServers: {
|
|
543
|
-
playwright: { command: "npx", args: ["@playwright/mcp@latest"] }
|
|
544
|
-
}
|
|
545
|
-
}), "utf-8");
|
|
546
|
-
}
|
|
547
|
-
return configPath;
|
|
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
|
-
];
|
|
568
533
|
var processes = new Map;
|
|
569
534
|
function startAiProcess(idOrOpts, prompt, cwd, resumeSessionId, model) {
|
|
570
535
|
const opts = typeof idOrOpts === "string" ? { id: idOrOpts, prompt, cwd, resumeSessionId, model } : idOrOpts;
|
|
571
|
-
const restricted = opts.tools !== undefined;
|
|
572
536
|
killAiProcess(opts.id);
|
|
573
537
|
logger.info("claude", `spawning process: ${opts.id}`, {
|
|
574
538
|
cwd: opts.cwd,
|
|
575
539
|
promptLength: opts.prompt.length,
|
|
576
540
|
promptPreview: opts.prompt.substring(0, 100),
|
|
577
541
|
resumeSessionId: opts.resumeSessionId,
|
|
578
|
-
model: opts.model
|
|
579
|
-
restricted,
|
|
580
|
-
tools: restricted ? opts.tools.join(",") || "none" : "all"
|
|
542
|
+
model: opts.model
|
|
581
543
|
});
|
|
582
544
|
const cmd = [CLAUDE_BIN, "-p", opts.prompt, "--output-format=stream-json", "--verbose", "--dangerously-skip-permissions"];
|
|
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");
|
|
587
|
-
if (opts.mcpConfigPath) {
|
|
588
|
-
cmd.push("--mcp-config", opts.mcpConfigPath);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
545
|
if (opts.resumeSessionId) {
|
|
592
546
|
cmd.push("--resume", opts.resumeSessionId);
|
|
593
547
|
}
|
|
@@ -718,35 +672,47 @@ async function* parseClaudeStream(proc) {
|
|
|
718
672
|
}
|
|
719
673
|
|
|
720
674
|
// ../core/dist/smart-screenshot.js
|
|
721
|
-
import { join as
|
|
722
|
-
import { mkdirSync as
|
|
675
|
+
import { join as join5 } from "path";
|
|
676
|
+
import { mkdirSync as mkdirSync4, existsSync as existsSync5 } from "fs";
|
|
723
677
|
import simpleGit2 from "simple-git";
|
|
724
678
|
|
|
725
679
|
// ../core/dist/screenshot.js
|
|
726
680
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
727
|
-
import { join as
|
|
728
|
-
import { mkdirSync as
|
|
681
|
+
import { join as join4 } from "path";
|
|
682
|
+
import { mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
|
|
729
683
|
var SCREENSHOTS_DIR = ".stashes/screenshots";
|
|
730
|
-
async function captureScreenshot(
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const
|
|
684
|
+
async function captureScreenshot(portOrOpts, projectPath, stashId) {
|
|
685
|
+
const opts = typeof portOrOpts === "number" ? { port: portOrOpts, projectPath, stashId } : portOrOpts;
|
|
686
|
+
const screenshotsDir = join4(opts.projectPath, SCREENSHOTS_DIR);
|
|
687
|
+
if (!existsSync4(screenshotsDir)) {
|
|
688
|
+
mkdirSync3(screenshotsDir, { recursive: true });
|
|
689
|
+
}
|
|
690
|
+
const filename = `${opts.stashId}.png`;
|
|
691
|
+
const outputPath = join4(screenshotsDir, filename);
|
|
692
|
+
const url = `http://localhost:${opts.port}${opts.route || "/"}`;
|
|
693
|
+
const scrollCode = opts.scrollToSelector ? `
|
|
694
|
+
try {
|
|
695
|
+
const el = await page.waitForSelector('${opts.scrollToSelector.replace(/'/g, "\\'")}', { timeout: 5000 });
|
|
696
|
+
if (el) {
|
|
697
|
+
await el.scrollIntoViewIfNeeded();
|
|
698
|
+
await page.waitForTimeout(500);
|
|
699
|
+
}
|
|
700
|
+
} catch(e) { /* selector not found, capture viewport */ }
|
|
701
|
+
` : "";
|
|
737
702
|
const playwrightScript = `
|
|
738
703
|
const { chromium } = require('playwright');
|
|
739
704
|
(async () => {
|
|
740
705
|
const browser = await chromium.launch({ headless: true });
|
|
741
706
|
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
|
|
742
707
|
try {
|
|
743
|
-
await page.goto('
|
|
708
|
+
await page.goto('${url}', { waitUntil: 'networkidle', timeout: 20000 });
|
|
744
709
|
await page.waitForTimeout(2000);
|
|
745
710
|
} catch(e) {
|
|
746
|
-
await page.goto('
|
|
711
|
+
await page.goto('${url}', { waitUntil: 'domcontentloaded', timeout: 10000 });
|
|
747
712
|
await page.waitForTimeout(3000);
|
|
748
713
|
}
|
|
749
|
-
|
|
714
|
+
${scrollCode}
|
|
715
|
+
await page.screenshot({ path: '${outputPath.replace(/'/g, "\\'")}', type: 'png'${opts.fullPage ? ", fullPage: true" : ""} });
|
|
750
716
|
await browser.close();
|
|
751
717
|
})();
|
|
752
718
|
`;
|
|
@@ -782,62 +748,56 @@ ${diffStat}
|
|
|
782
748
|
## Partial diff (truncated):
|
|
783
749
|
${truncatedDiff}`;
|
|
784
750
|
} catch (err) {
|
|
785
|
-
logger.warn("smart-screenshot", `git diff failed
|
|
751
|
+
logger.warn("smart-screenshot", `git diff failed`, {
|
|
786
752
|
error: err instanceof Error ? err.message : String(err)
|
|
787
753
|
});
|
|
788
754
|
return "";
|
|
789
755
|
}
|
|
790
756
|
}
|
|
791
757
|
function buildScreenshotPrompt(port, diff, screenshotDir, stashId) {
|
|
792
|
-
const outputPath =
|
|
758
|
+
const outputPath = join5(screenshotDir, `${stashId}.png`);
|
|
793
759
|
return [
|
|
794
|
-
"You are
|
|
795
|
-
"Be fast \u2014 you have a strict time limit.",
|
|
760
|
+
"## CRITICAL: You are an automated screenshot subprocess. Follow these rules exactly.",
|
|
796
761
|
"",
|
|
797
|
-
|
|
762
|
+
"FORBIDDEN \u2014 do NOT call any of these tools:",
|
|
763
|
+
"- ToolSearch, Skill, Agent, TodoWrite, TaskCreate, TaskUpdate, TaskList",
|
|
764
|
+
"- mcp__UseAI__useai_start, mcp__UseAI__useai_end, mcp__UseAI__useai_heartbeat",
|
|
765
|
+
"- mcp__stashes__*, mcp__plugin_drills__*, mcp__plugin_coverit__*",
|
|
766
|
+
"- EnterPlanMode, ExitPlanMode, WebSearch, WebFetch, LSP",
|
|
767
|
+
"- Read, Write, Edit, Glob, Grep (you do NOT need to read code)",
|
|
798
768
|
"",
|
|
799
|
-
"
|
|
800
|
-
"```",
|
|
801
|
-
diff,
|
|
802
|
-
"```",
|
|
769
|
+
"YOUR ONLY JOB: navigate to the app and take a screenshot using Playwright.",
|
|
803
770
|
"",
|
|
804
|
-
|
|
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.",
|
|
771
|
+
`## App URL: http://localhost:${port}`,
|
|
807
772
|
"",
|
|
808
|
-
"
|
|
773
|
+
"## Git diff (shows what changed \u2014 use this to decide where to navigate):",
|
|
809
774
|
"```",
|
|
810
|
-
|
|
811
|
-
"```",
|
|
812
|
-
"",
|
|
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
|
-
`}`,
|
|
775
|
+
diff,
|
|
825
776
|
"```",
|
|
826
777
|
"",
|
|
827
|
-
"
|
|
828
|
-
|
|
778
|
+
"## Steps (do these in order, nothing else):",
|
|
779
|
+
`1. Navigate: call mcp__plugin_playwright_playwright__browser_navigate with url "http://localhost:${port}"`,
|
|
780
|
+
"2. Wait 3 seconds for the page to render",
|
|
781
|
+
"3. If the diff shows changes below the fold, scroll down using mcp__plugin_playwright_playwright__browser_evaluate",
|
|
782
|
+
`4. Take screenshot: call mcp__plugin_playwright_playwright__browser_take_screenshot with type "png"`,
|
|
783
|
+
` The screenshot will be saved automatically.`,
|
|
784
|
+
"5. Output ONLY this JSON (replace the screenshot path with the actual path returned):",
|
|
829
785
|
"",
|
|
830
|
-
|
|
786
|
+
"```json",
|
|
831
787
|
"{",
|
|
832
788
|
' "screenshots": [',
|
|
833
789
|
" {",
|
|
834
790
|
` "path": "${outputPath}",`,
|
|
835
|
-
' "label": "
|
|
836
|
-
' "route": "/
|
|
791
|
+
' "label": "Description of what is shown",',
|
|
792
|
+
' "route": "/",',
|
|
837
793
|
' "isPrimary": true',
|
|
838
794
|
" }",
|
|
839
795
|
" ]",
|
|
840
|
-
"}"
|
|
796
|
+
"}",
|
|
797
|
+
"```",
|
|
798
|
+
"",
|
|
799
|
+
"REMEMBER: Do NOT call ToolSearch, Skill, UseAI, Read, or any tool not listed above.",
|
|
800
|
+
"You have a strict time limit. Just navigate, screenshot, output JSON, done."
|
|
841
801
|
].join(`
|
|
842
802
|
`);
|
|
843
803
|
}
|
|
@@ -872,9 +832,9 @@ async function fallbackScreenshot(port, projectPath, stashId) {
|
|
|
872
832
|
}
|
|
873
833
|
async function captureSmartScreenshots(opts) {
|
|
874
834
|
const { projectPath, stashId, stashBranch, parentBranch, worktreePath, port, model = "sonnet", timeout = DEFAULT_TIMEOUT } = opts;
|
|
875
|
-
const screenshotDir =
|
|
876
|
-
if (!
|
|
877
|
-
|
|
835
|
+
const screenshotDir = join5(projectPath, SCREENSHOTS_DIR2);
|
|
836
|
+
if (!existsSync5(screenshotDir)) {
|
|
837
|
+
mkdirSync4(screenshotDir, { recursive: true });
|
|
878
838
|
}
|
|
879
839
|
const diff = await getStashDiff(worktreePath, parentBranch);
|
|
880
840
|
if (!diff) {
|
|
@@ -888,9 +848,7 @@ async function captureSmartScreenshots(opts) {
|
|
|
888
848
|
id: processId,
|
|
889
849
|
prompt,
|
|
890
850
|
cwd: worktreePath,
|
|
891
|
-
model: modelFlag
|
|
892
|
-
tools: [],
|
|
893
|
-
mcpConfigPath: getPlaywrightMcpConfigPath()
|
|
851
|
+
model: modelFlag
|
|
894
852
|
});
|
|
895
853
|
let textOutput = "";
|
|
896
854
|
let timedOut = false;
|
|
@@ -916,9 +874,9 @@ async function captureSmartScreenshots(opts) {
|
|
|
916
874
|
}
|
|
917
875
|
const result = parseAiResult(textOutput);
|
|
918
876
|
if (!result || !result.screenshots || result.screenshots.length === 0) {
|
|
919
|
-
const expectedPath =
|
|
920
|
-
if (
|
|
921
|
-
logger.info("smart-screenshot", `AI saved screenshot for ${stashId}
|
|
877
|
+
const expectedPath = join5(screenshotDir, `${stashId}.png`);
|
|
878
|
+
if (existsSync5(expectedPath)) {
|
|
879
|
+
logger.info("smart-screenshot", `AI saved screenshot for ${stashId} (no JSON response, but file exists)`);
|
|
922
880
|
const url = `/api/screenshots/${stashId}.png`;
|
|
923
881
|
return {
|
|
924
882
|
primary: url,
|
|
@@ -932,18 +890,12 @@ async function captureSmartScreenshots(opts) {
|
|
|
932
890
|
let primaryUrl = "";
|
|
933
891
|
for (const shot of result.screenshots) {
|
|
934
892
|
const filename = shot.path.split("/").pop() || "";
|
|
935
|
-
if (!
|
|
893
|
+
if (!existsSync5(shot.path)) {
|
|
936
894
|
logger.warn("smart-screenshot", `Screenshot file not found: ${shot.path}`);
|
|
937
895
|
continue;
|
|
938
896
|
}
|
|
939
897
|
const url = `/api/screenshots/${filename}`;
|
|
940
|
-
|
|
941
|
-
url,
|
|
942
|
-
label: shot.label,
|
|
943
|
-
route: shot.route,
|
|
944
|
-
isPrimary: shot.isPrimary
|
|
945
|
-
};
|
|
946
|
-
screenshots.push(screenshot);
|
|
898
|
+
screenshots.push({ url, label: shot.label, route: shot.route, isPrimary: shot.isPrimary });
|
|
947
899
|
if (shot.isPrimary)
|
|
948
900
|
primaryUrl = url;
|
|
949
901
|
}
|
|
@@ -1026,8 +978,8 @@ async function generate(opts) {
|
|
|
1026
978
|
const selectedDirectives = directives.slice(0, count);
|
|
1027
979
|
let sourceCode = "";
|
|
1028
980
|
if (component?.filePath) {
|
|
1029
|
-
const sourceFile =
|
|
1030
|
-
if (
|
|
981
|
+
const sourceFile = join6(projectPath, component.filePath);
|
|
982
|
+
if (existsSync6(sourceFile)) {
|
|
1031
983
|
sourceCode = readFileSync2(sourceFile, "utf-8");
|
|
1032
984
|
}
|
|
1033
985
|
}
|
|
@@ -1626,18 +1578,18 @@ async function handleRemove(args, projectPath) {
|
|
|
1626
1578
|
// ../server/dist/index.js
|
|
1627
1579
|
import { Hono as Hono2 } from "hono";
|
|
1628
1580
|
import { cors } from "hono/cors";
|
|
1629
|
-
import { join as
|
|
1581
|
+
import { join as join10, dirname as dirname3 } from "path";
|
|
1630
1582
|
import { fileURLToPath } from "url";
|
|
1631
|
-
import { existsSync as
|
|
1583
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
|
|
1632
1584
|
|
|
1633
1585
|
// ../server/dist/routes/api.js
|
|
1634
1586
|
import { Hono } from "hono";
|
|
1635
|
-
import { join as
|
|
1636
|
-
import { existsSync as
|
|
1587
|
+
import { join as join9, basename } from "path";
|
|
1588
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
|
|
1637
1589
|
|
|
1638
1590
|
// ../server/dist/services/stash-service.js
|
|
1639
|
-
import { readFileSync as readFileSync3, existsSync as
|
|
1640
|
-
import { join as
|
|
1591
|
+
import { readFileSync as readFileSync3, existsSync as existsSync7 } from "fs";
|
|
1592
|
+
import { join as join7 } from "path";
|
|
1641
1593
|
|
|
1642
1594
|
// ../server/dist/services/app-proxy.js
|
|
1643
1595
|
import { spawn as spawn5 } from "child_process";
|
|
@@ -2173,8 +2125,7 @@ class StashService {
|
|
|
2173
2125
|
id: "resolve-component",
|
|
2174
2126
|
prompt,
|
|
2175
2127
|
cwd: this.projectPath,
|
|
2176
|
-
model: "claude-haiku-4-5-20251001"
|
|
2177
|
-
tools: ["Read", "Grep", "Glob", "Bash"]
|
|
2128
|
+
model: "claude-haiku-4-5-20251001"
|
|
2178
2129
|
});
|
|
2179
2130
|
let resolvedPath = "";
|
|
2180
2131
|
try {
|
|
@@ -2236,8 +2187,8 @@ class StashService {
|
|
|
2236
2187
|
let sourceCode = "";
|
|
2237
2188
|
const filePath = component?.filePath || "";
|
|
2238
2189
|
if (filePath && filePath !== "auto-detect") {
|
|
2239
|
-
const sourceFile =
|
|
2240
|
-
if (
|
|
2190
|
+
const sourceFile = join7(this.projectPath, filePath);
|
|
2191
|
+
if (existsSync7(sourceFile)) {
|
|
2241
2192
|
sourceCode = readFileSync3(sourceFile, "utf-8");
|
|
2242
2193
|
}
|
|
2243
2194
|
}
|
|
@@ -2589,8 +2540,8 @@ ${refDescriptions.join(`
|
|
|
2589
2540
|
}
|
|
2590
2541
|
|
|
2591
2542
|
// ../server/dist/services/activity-store.js
|
|
2592
|
-
import { appendFileSync as appendFileSync2, readFileSync as readFileSync4, existsSync as
|
|
2593
|
-
import { join as
|
|
2543
|
+
import { appendFileSync as appendFileSync2, readFileSync as readFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync5, rmSync as rmSync3 } from "fs";
|
|
2544
|
+
import { join as join8, dirname as dirname2 } from "path";
|
|
2594
2545
|
|
|
2595
2546
|
class ActivityStore {
|
|
2596
2547
|
cache = new Map;
|
|
@@ -2599,7 +2550,7 @@ class ActivityStore {
|
|
|
2599
2550
|
this.projectPath = projectPath;
|
|
2600
2551
|
}
|
|
2601
2552
|
jsonlPath(stashId) {
|
|
2602
|
-
return
|
|
2553
|
+
return join8(this.projectPath, ".stashes", "activity", `${stashId}.jsonl`);
|
|
2603
2554
|
}
|
|
2604
2555
|
append(event) {
|
|
2605
2556
|
const existing = this.cache.get(event.stashId) ?? [];
|
|
@@ -2607,8 +2558,8 @@ class ActivityStore {
|
|
|
2607
2558
|
this.cache.set(event.stashId, existing);
|
|
2608
2559
|
const filePath = this.jsonlPath(event.stashId);
|
|
2609
2560
|
const dir = dirname2(filePath);
|
|
2610
|
-
if (!
|
|
2611
|
-
|
|
2561
|
+
if (!existsSync8(dir))
|
|
2562
|
+
mkdirSync5(dir, { recursive: true });
|
|
2612
2563
|
appendFileSync2(filePath, JSON.stringify(event) + `
|
|
2613
2564
|
`, "utf-8");
|
|
2614
2565
|
}
|
|
@@ -2617,7 +2568,7 @@ class ActivityStore {
|
|
|
2617
2568
|
if (cached && cached.length > 0)
|
|
2618
2569
|
return cached;
|
|
2619
2570
|
const filePath = this.jsonlPath(stashId);
|
|
2620
|
-
if (!
|
|
2571
|
+
if (!existsSync8(filePath))
|
|
2621
2572
|
return [];
|
|
2622
2573
|
const lines = readFileSync4(filePath, "utf-8").trim().split(`
|
|
2623
2574
|
`).filter(Boolean);
|
|
@@ -2643,14 +2594,14 @@ class ActivityStore {
|
|
|
2643
2594
|
clear(stashId) {
|
|
2644
2595
|
this.cache.delete(stashId);
|
|
2645
2596
|
const filePath = this.jsonlPath(stashId);
|
|
2646
|
-
if (
|
|
2597
|
+
if (existsSync8(filePath)) {
|
|
2647
2598
|
rmSync3(filePath);
|
|
2648
2599
|
}
|
|
2649
2600
|
}
|
|
2650
2601
|
has(stashId) {
|
|
2651
2602
|
if (this.cache.has(stashId) && (this.cache.get(stashId)?.length ?? 0) > 0)
|
|
2652
2603
|
return true;
|
|
2653
|
-
return
|
|
2604
|
+
return existsSync8(this.jsonlPath(stashId));
|
|
2654
2605
|
}
|
|
2655
2606
|
}
|
|
2656
2607
|
|
|
@@ -2892,8 +2843,8 @@ app.get("/dev-server-status", async (c) => {
|
|
|
2892
2843
|
});
|
|
2893
2844
|
app.get("/screenshots/:filename", (c) => {
|
|
2894
2845
|
const filename = c.req.param("filename");
|
|
2895
|
-
const filePath =
|
|
2896
|
-
if (!
|
|
2846
|
+
const filePath = join9(serverState.projectPath, ".stashes", "screenshots", filename);
|
|
2847
|
+
if (!existsSync9(filePath))
|
|
2897
2848
|
return c.json({ error: "Not found" }, 404);
|
|
2898
2849
|
const content = readFileSync5(filePath);
|
|
2899
2850
|
return new Response(content, {
|
|
@@ -2945,12 +2896,12 @@ app2.route("/api", apiRoutes);
|
|
|
2945
2896
|
app2.get("/*", async (c) => {
|
|
2946
2897
|
const path = c.req.path;
|
|
2947
2898
|
const selfDir = dirname3(fileURLToPath(import.meta.url));
|
|
2948
|
-
const bundledWebDir =
|
|
2949
|
-
const monorepoWebDir =
|
|
2950
|
-
const webDistDir =
|
|
2899
|
+
const bundledWebDir = join10(selfDir, "web");
|
|
2900
|
+
const monorepoWebDir = join10(selfDir, "../../web/dist");
|
|
2901
|
+
const webDistDir = existsSync10(join10(bundledWebDir, "index.html")) ? bundledWebDir : monorepoWebDir;
|
|
2951
2902
|
const requestPath = path === "/" ? "/index.html" : path;
|
|
2952
|
-
const filePath =
|
|
2953
|
-
if (
|
|
2903
|
+
const filePath = join10(webDistDir, requestPath);
|
|
2904
|
+
if (existsSync10(filePath) && !filePath.includes("..")) {
|
|
2954
2905
|
const content = readFileSync6(filePath);
|
|
2955
2906
|
const ext = filePath.split(".").pop() || "";
|
|
2956
2907
|
const contentTypes = {
|
|
@@ -2969,8 +2920,8 @@ app2.get("/*", async (c) => {
|
|
|
2969
2920
|
headers: { "content-type": contentTypes[ext] || "application/octet-stream" }
|
|
2970
2921
|
});
|
|
2971
2922
|
}
|
|
2972
|
-
const indexPath =
|
|
2973
|
-
if (
|
|
2923
|
+
const indexPath = join10(webDistDir, "index.html");
|
|
2924
|
+
if (existsSync10(indexPath)) {
|
|
2974
2925
|
const html = readFileSync6(indexPath, "utf-8");
|
|
2975
2926
|
return new Response(html, {
|
|
2976
2927
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
@@ -3034,11 +2985,11 @@ async function startServer(projectPath, userDevPort, requestedPort = STASHES_POR
|
|
|
3034
2985
|
import open from "open";
|
|
3035
2986
|
|
|
3036
2987
|
// ../server/dist/services/detector.js
|
|
3037
|
-
import { existsSync as
|
|
3038
|
-
import { join as
|
|
2988
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
|
|
2989
|
+
import { join as join11 } from "path";
|
|
3039
2990
|
function detectFramework(projectPath) {
|
|
3040
|
-
const packageJsonPath =
|
|
3041
|
-
if (!
|
|
2991
|
+
const packageJsonPath = join11(projectPath, "package.json");
|
|
2992
|
+
if (!existsSync11(packageJsonPath)) {
|
|
3042
2993
|
return {
|
|
3043
2994
|
framework: "unknown",
|
|
3044
2995
|
devCommand: "npm run dev",
|
|
@@ -3100,7 +3051,7 @@ function getDevCommand(packageJson, fallback) {
|
|
|
3100
3051
|
}
|
|
3101
3052
|
function findConfig(projectPath, candidates) {
|
|
3102
3053
|
for (const candidate of candidates) {
|
|
3103
|
-
if (
|
|
3054
|
+
if (existsSync11(join11(projectPath, candidate))) {
|
|
3104
3055
|
return candidate;
|
|
3105
3056
|
}
|
|
3106
3057
|
}
|