stashes 0.1.19 → 0.1.20

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/mcp.js CHANGED
@@ -538,6 +538,7 @@ function killAiProcess(id) {
538
538
  }
539
539
  return false;
540
540
  }
541
+ var toolNameMap = new Map;
541
542
  async function* parseClaudeStream(proc) {
542
543
  const stdout = proc.stdout;
543
544
  if (!stdout || typeof stdout === "number") {
@@ -574,15 +575,58 @@ async function* parseClaudeStream(proc) {
574
575
  } else if (block.type === "thinking" && block.thinking) {
575
576
  yield { type: "thinking", content: block.thinking };
576
577
  } else if (block.type === "tool_use") {
577
- logger.debug("claude", `tool_use: ${block.name}`);
578
- yield { type: "tool_use", content: JSON.stringify({ tool: block.name, input: block.input }) };
578
+ const toolId = block.id;
579
+ const toolName = block.name;
580
+ toolNameMap.set(toolId, toolName);
581
+ logger.debug("claude", `tool_use: ${toolName}`);
582
+ yield {
583
+ type: "tool_use",
584
+ content: JSON.stringify({ tool: toolName, input: block.input }),
585
+ toolName,
586
+ toolInput: block.input,
587
+ toolUseId: toolId
588
+ };
579
589
  }
580
590
  }
581
591
  }
592
+ if (parsed.type === "user" && parsed.message) {
593
+ const message = parsed.message;
594
+ for (const block of message.content || []) {
595
+ if (block.type === "tool_result") {
596
+ const toolUseId = block.tool_use_id;
597
+ const isError = block.is_error || false;
598
+ const toolName = toolNameMap.get(toolUseId) || "unknown";
599
+ let resultContent;
600
+ if (typeof block.content === "string") {
601
+ resultContent = block.content;
602
+ } else if (Array.isArray(block.content)) {
603
+ resultContent = block.content.filter((c) => c.type === "text").map((c) => c.text).join(`
604
+ `);
605
+ } else {
606
+ resultContent = "";
607
+ }
608
+ yield {
609
+ type: "tool_result",
610
+ content: resultContent.substring(0, 500),
611
+ toolName,
612
+ toolUseId,
613
+ isError
614
+ };
615
+ }
616
+ }
617
+ }
618
+ if (parsed.type === "result") {
619
+ yield {
620
+ type: "result",
621
+ content: "",
622
+ resultSubtype: parsed.subtype
623
+ };
624
+ }
582
625
  }
583
626
  }
584
627
  } finally {
585
628
  logger.debug("claude", `stream ended`, { totalChunks: chunkCount });
629
+ toolNameMap.clear();
586
630
  reader.releaseLock();
587
631
  }
588
632
  }
@@ -964,6 +1008,34 @@ async function remove(projectPath, stashId) {
964
1008
  } catch {}
965
1009
  logger.info("manage", `removed stash: ${stashId}`);
966
1010
  }
1011
+ async function show(projectPath, stashId) {
1012
+ const persistence = new PersistenceService(projectPath);
1013
+ let found;
1014
+ for (const project of persistence.listProjects()) {
1015
+ found = persistence.getStash(project.id, stashId);
1016
+ if (found)
1017
+ break;
1018
+ }
1019
+ if (!found)
1020
+ return null;
1021
+ const git = simpleGit4(projectPath);
1022
+ const branch = found.branch || `stashes/${stashId}`;
1023
+ let diff = "";
1024
+ let files = [];
1025
+ try {
1026
+ const base = await git.raw(["merge-base", "HEAD", branch]);
1027
+ diff = await git.raw(["diff", base.trim(), branch, "--stat"]);
1028
+ const fullDiff = await git.raw(["diff", base.trim(), branch]);
1029
+ diff = fullDiff.length > 8000 ? fullDiff.substring(0, 8000) + `
1030
+ ... (truncated)` : fullDiff;
1031
+ const fileList = await git.raw(["diff", "--name-only", base.trim(), branch]);
1032
+ files = fileList.trim().split(`
1033
+ `).filter(Boolean);
1034
+ } catch {
1035
+ diff = "(branch not found or no diff available)";
1036
+ }
1037
+ return { stash: found, diff, files };
1038
+ }
967
1039
  // ../mcp/src/tools/generate.ts
968
1040
  var generateParams = {
969
1041
  prompt: z.string().describe('What UI changes to generate (e.g. "make the hero section bolder")'),
@@ -1088,10 +1160,48 @@ async function handleVary(args, projectPath) {
1088
1160
  };
1089
1161
  }
1090
1162
 
1091
- // ../mcp/src/tools/remove.ts
1163
+ // ../mcp/src/tools/show.ts
1092
1164
  import { z as z4 } from "zod";
1165
+ var showParams = {
1166
+ stashId: z4.string().describe('The stash ID to inspect (e.g. "stash_a1b2c3d4")')
1167
+ };
1168
+ async function handleShow(args, projectPath) {
1169
+ const { stashId } = args;
1170
+ initLogFile(projectPath);
1171
+ const result = await show(projectPath, stashId);
1172
+ if (!result) {
1173
+ return {
1174
+ content: [{
1175
+ type: "text",
1176
+ text: `No stash found with ID "${stashId}".`
1177
+ }]
1178
+ };
1179
+ }
1180
+ const { stash, diff, files } = result;
1181
+ return {
1182
+ content: [{
1183
+ type: "text",
1184
+ text: JSON.stringify({
1185
+ id: stash.id,
1186
+ number: stash.number,
1187
+ prompt: stash.prompt,
1188
+ status: stash.status,
1189
+ branch: stash.branch,
1190
+ componentPath: stash.componentPath ?? null,
1191
+ screenshotPath: stash.screenshotUrl,
1192
+ relatedTo: stash.relatedTo,
1193
+ createdAt: stash.createdAt,
1194
+ filesChanged: files,
1195
+ diff
1196
+ }, null, 2)
1197
+ }]
1198
+ };
1199
+ }
1200
+
1201
+ // ../mcp/src/tools/remove.ts
1202
+ import { z as z5 } from "zod";
1093
1203
  var removeParams = {
1094
- stashId: z4.string().describe("The stash ID to remove")
1204
+ stashId: z5.string().describe("The stash ID to remove")
1095
1205
  };
1096
1206
  async function handleRemove(args, projectPath) {
1097
1207
  const { stashId } = args;
@@ -1482,9 +1592,12 @@ ${refs.join(`
1482
1592
  }
1483
1593
  const chatPrompt = [
1484
1594
  "You are helping the user explore UI design variations for their project.",
1485
- "You have access to stashes MCP tools to generate, list, browse, vary, and apply stashes.",
1595
+ "You have access to stashes MCP tools to generate, list, show, browse, vary, and apply stashes.",
1486
1596
  "If the user asks you to generate, create, or make variations, use the stashes_generate tool.",
1487
1597
  "If the user asks to vary an existing stash, use the stashes_vary tool.",
1598
+ "If the user asks about what a stash changed, its diff, or its contents, use stashes_show to inspect it.",
1599
+ 'IMPORTANT: NEVER call stashes_apply unless the user explicitly asks to "apply" or "merge" a stash.',
1600
+ 'IMPORTANT: NEVER call stashes_remove unless the user explicitly asks to "delete" or "remove" a stash.',
1488
1601
  "Otherwise, respond conversationally about their project and stashes.",
1489
1602
  "",
1490
1603
  component ? `Component: ${component.name}` : "",
@@ -1519,40 +1632,24 @@ ${sourceCode.substring(0, 3000)}
1519
1632
  source: "chat"
1520
1633
  });
1521
1634
  } else if (chunk.type === "tool_use") {
1522
- let toolName = "unknown";
1523
- let toolParams = {};
1524
- try {
1525
- const parsed = JSON.parse(chunk.content);
1526
- toolName = parsed.tool || parsed.name || "unknown";
1527
- toolParams = parsed.input || parsed.params || {};
1528
- } catch {}
1529
1635
  this.broadcast({
1530
1636
  type: "ai_stream",
1531
1637
  content: chunk.content,
1532
1638
  streamType: "tool_start",
1533
1639
  source: "chat",
1534
- toolName,
1535
- toolParams,
1640
+ toolName: chunk.toolName ?? "unknown",
1641
+ toolParams: chunk.toolInput ?? {},
1536
1642
  toolStatus: "running"
1537
1643
  });
1538
1644
  } else if (chunk.type === "tool_result") {
1539
- let toolName = "unknown";
1540
- let toolResult = "";
1541
- try {
1542
- const parsed = JSON.parse(chunk.content);
1543
- toolName = parsed.tool || parsed.name || "unknown";
1544
- toolResult = typeof parsed.result === "string" ? parsed.result.substring(0, 200) : JSON.stringify(parsed.result).substring(0, 200);
1545
- } catch {
1546
- toolResult = chunk.content.substring(0, 200);
1547
- }
1548
1645
  this.broadcast({
1549
1646
  type: "ai_stream",
1550
1647
  content: chunk.content,
1551
1648
  streamType: "tool_end",
1552
1649
  source: "chat",
1553
- toolName,
1554
- toolStatus: "completed",
1555
- toolResult
1650
+ toolName: chunk.toolName ?? "unknown",
1651
+ toolStatus: chunk.isError ? "error" : "completed",
1652
+ toolResult: chunk.content.substring(0, 300)
1556
1653
  });
1557
1654
  }
1558
1655
  }
@@ -2196,7 +2293,8 @@ var server = new McpServer({
2196
2293
  });
2197
2294
  server.tool("stashes_generate", "Generate multiple AI-powered UI design explorations (stashes) for a given prompt. Each stash applies a different creative direction.", generateParams, async (args) => handleGenerate(args, projectPath));
2198
2295
  server.tool("stashes_list", "List all existing stashes in the current project. Shows ID, prompt, status, branch, and screenshot path.", listParams, async (args) => handleList(args, projectPath));
2199
- server.tool("stashes_apply", "Merge a stash branch into the current git branch. Applies the AI-generated UI changes and cleans up all worktrees.", applyParams, async (args) => handleApply(args, projectPath));
2296
+ server.tool("stashes_show", "Show detailed information about a specific stash including its git diff, changed files, prompt, and metadata. Use this to inspect what a stash changed without applying it.", showParams, async (args) => handleShow(args, projectPath));
2297
+ server.tool("stashes_apply", "Merge a stash branch into the current git branch. Applies the AI-generated UI changes and cleans up all worktrees. ONLY use when the user explicitly asks to apply or merge.", applyParams, async (args) => handleApply(args, projectPath));
2200
2298
  server.tool("stashes_vary", "Create a variation of an existing stash by applying additional changes on top of it.", varyParams, async (args) => handleVary(args, projectPath));
2201
2299
  server.tool("stashes_remove", "Delete a stash \u2014 removes its persistence entry and git branch.", removeParams, async (args) => handleRemove(args, projectPath));
2202
2300
  server.tool("stashes_browse", "Start the Stashes web server and open the browser for interactive browsing of generated stashes.", browseParams, async (args) => handleBrowse(args, projectPath));