stashes 0.1.53 → 0.1.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +76 -62
  2. package/dist/mcp.js +76 -62
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -804,33 +804,49 @@ ${truncatedDiff}`;
804
804
  }
805
805
  }
806
806
  function buildScreenshotPrompt(port, diff, screenshotDir, stashId) {
807
+ const outputPath = join6(screenshotDir, `${stashId}.png`);
807
808
  return [
808
- "You are a screenshot assistant. Take screenshots of a running web app using Playwright MCP tools.",
809
+ "You are a screenshot assistant. Take a screenshot of a running web app.",
809
810
  "Be fast \u2014 you have a strict time limit.",
810
811
  "",
811
- `## The app is running at: http://localhost:${port}`,
812
+ `The app is running at: http://localhost:${port}`,
812
813
  "",
813
- "## Git diff of changes:",
814
+ "## Git diff of changes (tells you what was modified):",
814
815
  "```",
815
816
  diff,
816
817
  "```",
817
818
  "",
818
- "## Steps:",
819
- `1. Call mcp__playwright__browser_navigate with url "http://localhost:${port}" to open the app`,
820
- "2. From the diff, determine which page/route shows the changed components",
821
- "3. If the change is on a different route, navigate to it",
822
- "4. If the change is behind an interaction (tab click, modal open), perform it with mcp__playwright__browser_click",
823
- `5. Call mcp__playwright__browser_take_screenshot to capture the page`,
824
- "6. Respond with the JSON below",
819
+ "## Instructions",
820
+ "Use mcp__playwright__browser_run_code to navigate AND screenshot in a single call.",
821
+ "From the diff, determine the route/section that shows the changes.",
825
822
  "",
826
- `## Screenshot save directory: ${screenshotDir}`,
827
- `Use filename "${stashId}.png" for the primary screenshot.`,
823
+ "**Call this tool:**",
824
+ "```",
825
+ "mcp__playwright__browser_run_code",
826
+ "```",
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
+ `}`,
840
+ "```",
841
+ "",
842
+ "If the diff shows changes to a component that requires scrolling or navigation,",
843
+ "adjust the code: change the URL path, add scrollTo, or click a tab first.",
828
844
  "",
829
- "## After taking screenshots, respond with ONLY this JSON:",
845
+ `After the tool call succeeds, respond with ONLY this JSON:`,
830
846
  "{",
831
847
  ' "screenshots": [',
832
848
  " {",
833
- ` "path": "${join6(screenshotDir, `${stashId}.png`)}",`,
849
+ ` "path": "${outputPath}",`,
834
850
  ' "label": "Short description of what is shown",',
835
851
  ' "route": "/the-url-path",',
836
852
  ' "isPrimary": true',
@@ -1124,8 +1140,53 @@ async function generate(opts) {
1124
1140
  return;
1125
1141
  }
1126
1142
  const generatedStash = { ...stash, status: "screenshotting" };
1127
- completedStashes.push(generatedStash);
1128
1143
  persistence.saveStash(generatedStash);
1144
+ emit(onProgress, { type: "screenshotting", stashId });
1145
+ try {
1146
+ const port = await allocatePort();
1147
+ const screenshotWorktree = await worktreeManager.createForGeneration(`screenshot-${stashId}`);
1148
+ const screenshotGit = simpleGit3(screenshotWorktree.path);
1149
+ await screenshotGit.checkout(["--detach", stash.branch]);
1150
+ const devServer = spawn3({
1151
+ cmd: ["npm", "run", "dev"],
1152
+ cwd: screenshotWorktree.path,
1153
+ stdin: "ignore",
1154
+ stdout: "pipe",
1155
+ stderr: "pipe",
1156
+ env: { ...process.env, PORT: String(port), BROWSER: "none" }
1157
+ });
1158
+ try {
1159
+ await waitForPort(port, 60000);
1160
+ await new Promise((r) => setTimeout(r, 2000));
1161
+ const mainGit = simpleGit3(projectPath);
1162
+ const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
1163
+ const { primary, screenshots } = await captureSmartScreenshots({
1164
+ projectPath,
1165
+ stashId,
1166
+ stashBranch: stash.branch,
1167
+ parentBranch,
1168
+ worktreePath: screenshotWorktree.path,
1169
+ port,
1170
+ model: opts.screenshotModel,
1171
+ timeout: opts.screenshotTimeout
1172
+ });
1173
+ const readyStash = { ...generatedStash, status: "ready", screenshotUrl: primary, screenshots };
1174
+ completedStashes.push(readyStash);
1175
+ persistence.saveStash(readyStash);
1176
+ emit(onProgress, { type: "ready", stashId, screenshotPath: primary });
1177
+ } finally {
1178
+ devServer.kill();
1179
+ }
1180
+ await worktreeManager.removeGeneration(`screenshot-${stashId}`);
1181
+ } catch (err) {
1182
+ logger.error("generation", `screenshot failed for ${stashId}`, {
1183
+ error: err instanceof Error ? err.message : String(err)
1184
+ });
1185
+ const readyStash = { ...generatedStash, status: "ready" };
1186
+ completedStashes.push(readyStash);
1187
+ persistence.saveStash(readyStash);
1188
+ emit(onProgress, { type: "ready", stashId, screenshotPath: "" });
1189
+ }
1129
1190
  } catch (error) {
1130
1191
  const errorMsg = error instanceof Error ? error.message : "Unknown error";
1131
1192
  persistence.saveStash({ ...stash, status: "error", error: errorMsg });
@@ -1135,53 +1196,6 @@ async function generate(opts) {
1135
1196
  }
1136
1197
  });
1137
1198
  await Promise.all(stashPromises);
1138
- for (const stash of completedStashes) {
1139
- emit(onProgress, { type: "screenshotting", stashId: stash.id });
1140
- try {
1141
- const port = await allocatePort();
1142
- const worktree = await worktreeManager.createForGeneration(`screenshot-${stash.id}`);
1143
- const screenshotGit = simpleGit3(worktree.path);
1144
- await screenshotGit.checkout(["--detach", stash.branch]);
1145
- const devServer = spawn3({
1146
- cmd: ["npm", "run", "dev"],
1147
- cwd: worktree.path,
1148
- stdin: "ignore",
1149
- stdout: "pipe",
1150
- stderr: "pipe",
1151
- env: { ...process.env, PORT: String(port), BROWSER: "none" }
1152
- });
1153
- try {
1154
- await waitForPort(port, 60000);
1155
- await new Promise((r) => setTimeout(r, 2000));
1156
- const mainGit = simpleGit3(projectPath);
1157
- const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
1158
- const { primary, screenshots } = await captureSmartScreenshots({
1159
- projectPath,
1160
- stashId: stash.id,
1161
- stashBranch: stash.branch,
1162
- parentBranch,
1163
- worktreePath: worktree.path,
1164
- port,
1165
- model: opts.screenshotModel,
1166
- timeout: opts.screenshotTimeout
1167
- });
1168
- const updatedStash = { ...stash, status: "ready", screenshotUrl: primary, screenshots };
1169
- persistence.saveStash(updatedStash);
1170
- const idx = completedStashes.indexOf(stash);
1171
- completedStashes[idx] = updatedStash;
1172
- emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: primary });
1173
- } finally {
1174
- devServer.kill();
1175
- }
1176
- await worktreeManager.removeGeneration(`screenshot-${stash.id}`);
1177
- } catch (err) {
1178
- logger.error("generation", `screenshot failed for ${stash.id}`, {
1179
- error: err instanceof Error ? err.message : String(err)
1180
- });
1181
- persistence.saveStash({ ...stash, status: "ready" });
1182
- emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: "" });
1183
- }
1184
- }
1185
1199
  return completedStashes;
1186
1200
  }
1187
1201
  // ../core/dist/vary.js
package/dist/mcp.js CHANGED
@@ -789,33 +789,49 @@ ${truncatedDiff}`;
789
789
  }
790
790
  }
791
791
  function buildScreenshotPrompt(port, diff, screenshotDir, stashId) {
792
+ const outputPath = join6(screenshotDir, `${stashId}.png`);
792
793
  return [
793
- "You are a screenshot assistant. Take screenshots of a running web app using Playwright MCP tools.",
794
+ "You are a screenshot assistant. Take a screenshot of a running web app.",
794
795
  "Be fast \u2014 you have a strict time limit.",
795
796
  "",
796
- `## The app is running at: http://localhost:${port}`,
797
+ `The app is running at: http://localhost:${port}`,
797
798
  "",
798
- "## Git diff of changes:",
799
+ "## Git diff of changes (tells you what was modified):",
799
800
  "```",
800
801
  diff,
801
802
  "```",
802
803
  "",
803
- "## Steps:",
804
- `1. Call mcp__playwright__browser_navigate with url "http://localhost:${port}" to open the app`,
805
- "2. From the diff, determine which page/route shows the changed components",
806
- "3. If the change is on a different route, navigate to it",
807
- "4. If the change is behind an interaction (tab click, modal open), perform it with mcp__playwright__browser_click",
808
- `5. Call mcp__playwright__browser_take_screenshot to capture the page`,
809
- "6. Respond with the JSON below",
804
+ "## Instructions",
805
+ "Use mcp__playwright__browser_run_code to navigate AND screenshot in a single call.",
806
+ "From the diff, determine the route/section that shows the changes.",
810
807
  "",
811
- `## Screenshot save directory: ${screenshotDir}`,
812
- `Use filename "${stashId}.png" for the primary screenshot.`,
808
+ "**Call this tool:**",
809
+ "```",
810
+ "mcp__playwright__browser_run_code",
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
+ `}`,
825
+ "```",
826
+ "",
827
+ "If the diff shows changes to a component that requires scrolling or navigation,",
828
+ "adjust the code: change the URL path, add scrollTo, or click a tab first.",
813
829
  "",
814
- "## After taking screenshots, respond with ONLY this JSON:",
830
+ `After the tool call succeeds, respond with ONLY this JSON:`,
815
831
  "{",
816
832
  ' "screenshots": [',
817
833
  " {",
818
- ` "path": "${join6(screenshotDir, `${stashId}.png`)}",`,
834
+ ` "path": "${outputPath}",`,
819
835
  ' "label": "Short description of what is shown",',
820
836
  ' "route": "/the-url-path",',
821
837
  ' "isPrimary": true',
@@ -1109,8 +1125,53 @@ async function generate(opts) {
1109
1125
  return;
1110
1126
  }
1111
1127
  const generatedStash = { ...stash, status: "screenshotting" };
1112
- completedStashes.push(generatedStash);
1113
1128
  persistence.saveStash(generatedStash);
1129
+ emit(onProgress, { type: "screenshotting", stashId });
1130
+ try {
1131
+ const port = await allocatePort();
1132
+ const screenshotWorktree = await worktreeManager.createForGeneration(`screenshot-${stashId}`);
1133
+ const screenshotGit = simpleGit3(screenshotWorktree.path);
1134
+ await screenshotGit.checkout(["--detach", stash.branch]);
1135
+ const devServer = spawn3({
1136
+ cmd: ["npm", "run", "dev"],
1137
+ cwd: screenshotWorktree.path,
1138
+ stdin: "ignore",
1139
+ stdout: "pipe",
1140
+ stderr: "pipe",
1141
+ env: { ...process.env, PORT: String(port), BROWSER: "none" }
1142
+ });
1143
+ try {
1144
+ await waitForPort(port, 60000);
1145
+ await new Promise((r) => setTimeout(r, 2000));
1146
+ const mainGit = simpleGit3(projectPath);
1147
+ const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
1148
+ const { primary, screenshots } = await captureSmartScreenshots({
1149
+ projectPath,
1150
+ stashId,
1151
+ stashBranch: stash.branch,
1152
+ parentBranch,
1153
+ worktreePath: screenshotWorktree.path,
1154
+ port,
1155
+ model: opts.screenshotModel,
1156
+ timeout: opts.screenshotTimeout
1157
+ });
1158
+ const readyStash = { ...generatedStash, status: "ready", screenshotUrl: primary, screenshots };
1159
+ completedStashes.push(readyStash);
1160
+ persistence.saveStash(readyStash);
1161
+ emit(onProgress, { type: "ready", stashId, screenshotPath: primary });
1162
+ } finally {
1163
+ devServer.kill();
1164
+ }
1165
+ await worktreeManager.removeGeneration(`screenshot-${stashId}`);
1166
+ } catch (err) {
1167
+ logger.error("generation", `screenshot failed for ${stashId}`, {
1168
+ error: err instanceof Error ? err.message : String(err)
1169
+ });
1170
+ const readyStash = { ...generatedStash, status: "ready" };
1171
+ completedStashes.push(readyStash);
1172
+ persistence.saveStash(readyStash);
1173
+ emit(onProgress, { type: "ready", stashId, screenshotPath: "" });
1174
+ }
1114
1175
  } catch (error) {
1115
1176
  const errorMsg = error instanceof Error ? error.message : "Unknown error";
1116
1177
  persistence.saveStash({ ...stash, status: "error", error: errorMsg });
@@ -1120,53 +1181,6 @@ async function generate(opts) {
1120
1181
  }
1121
1182
  });
1122
1183
  await Promise.all(stashPromises);
1123
- for (const stash of completedStashes) {
1124
- emit(onProgress, { type: "screenshotting", stashId: stash.id });
1125
- try {
1126
- const port = await allocatePort();
1127
- const worktree = await worktreeManager.createForGeneration(`screenshot-${stash.id}`);
1128
- const screenshotGit = simpleGit3(worktree.path);
1129
- await screenshotGit.checkout(["--detach", stash.branch]);
1130
- const devServer = spawn3({
1131
- cmd: ["npm", "run", "dev"],
1132
- cwd: worktree.path,
1133
- stdin: "ignore",
1134
- stdout: "pipe",
1135
- stderr: "pipe",
1136
- env: { ...process.env, PORT: String(port), BROWSER: "none" }
1137
- });
1138
- try {
1139
- await waitForPort(port, 60000);
1140
- await new Promise((r) => setTimeout(r, 2000));
1141
- const mainGit = simpleGit3(projectPath);
1142
- const parentBranch = (await mainGit.revparse(["HEAD"])).trim();
1143
- const { primary, screenshots } = await captureSmartScreenshots({
1144
- projectPath,
1145
- stashId: stash.id,
1146
- stashBranch: stash.branch,
1147
- parentBranch,
1148
- worktreePath: worktree.path,
1149
- port,
1150
- model: opts.screenshotModel,
1151
- timeout: opts.screenshotTimeout
1152
- });
1153
- const updatedStash = { ...stash, status: "ready", screenshotUrl: primary, screenshots };
1154
- persistence.saveStash(updatedStash);
1155
- const idx = completedStashes.indexOf(stash);
1156
- completedStashes[idx] = updatedStash;
1157
- emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: primary });
1158
- } finally {
1159
- devServer.kill();
1160
- }
1161
- await worktreeManager.removeGeneration(`screenshot-${stash.id}`);
1162
- } catch (err) {
1163
- logger.error("generation", `screenshot failed for ${stash.id}`, {
1164
- error: err instanceof Error ? err.message : String(err)
1165
- });
1166
- persistence.saveStash({ ...stash, status: "ready" });
1167
- emit(onProgress, { type: "ready", stashId: stash.id, screenshotPath: "" });
1168
- }
1169
- }
1170
1184
  return completedStashes;
1171
1185
  }
1172
1186
  // ../core/dist/vary.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stashes",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
4
4
  "type": "module",
5
5
  "description": "Generate AI-powered UI design explorations in your project",
6
6
  "keywords": [