@zonease/happy 0.6.46 → 0.6.48

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 (85) hide show
  1. package/dist/{api-CxdKKE-w.mjs → api-BQORFd9R.mjs} +2 -2
  2. package/dist/{api-DGgccR6B.cjs → api-lxlaOAan.cjs} +3 -3
  3. package/dist/{auth-CcMvoMIH.mjs → auth-Cch5XGgf.mjs} +4 -4
  4. package/dist/{auth-XO55Tcek.cjs → auth-D3n2c6ef.cjs} +4 -4
  5. package/dist/{auth-C7oghkN1.mjs → auth-DEvweBty.mjs} +2 -2
  6. package/dist/{auth-Bf-VkNDK.cjs → auth-DO429YIh.cjs} +2 -2
  7. package/dist/{browser-DedYcRMI.mjs → browser-C6JpWqen.mjs} +1 -1
  8. package/dist/{browser-Cn_RttD9.cjs → browser-ChToh76t.cjs} +1 -1
  9. package/dist/{claudeLocal-CupQ03sq.cjs → claudeLocal-BRkqm7Dv.cjs} +1 -1
  10. package/dist/{claudeLocal-G2km5rB3.mjs → claudeLocal-D7uEQO2e.mjs} +1 -1
  11. package/dist/codex/happyMcpStdioBridge.cjs +12 -2
  12. package/dist/codex/happyMcpStdioBridge.mjs +12 -2
  13. package/dist/{config-BDbpXufc.cjs → config-D1pd0h7A.cjs} +1 -1
  14. package/dist/{config-DimzIdjy.mjs → config-oC3LtNiN.mjs} +1 -1
  15. package/dist/{connect-CjFR0V4l.mjs → connect-CyFpNr11.mjs} +4 -4
  16. package/dist/{connect-DWElyqHy.cjs → connect-OAvNEIm0.cjs} +4 -4
  17. package/dist/{controlClient-DTS16hKY.cjs → controlClient-BAJAvvWO.cjs} +1 -1
  18. package/dist/{controlClient-C8MF8Qxi.mjs → controlClient-CEliu-dY.mjs} +1 -1
  19. package/dist/{cursorApprovalHook-B9dUHp7N.cjs → cursorApprovalHook-BWYkdy2I.cjs} +2 -2
  20. package/dist/{cursorApprovalHook-DmVCQFLi.mjs → cursorApprovalHook-P8q2-Ot5.mjs} +2 -2
  21. package/dist/{cursorApprovalServer-DeyL8InE.mjs → cursorApprovalServer-DNOUUpbJ.mjs} +1 -1
  22. package/dist/{cursorApprovalServer-BkQ3JeOR.cjs → cursorApprovalServer-VR5ZdRqZ.cjs} +1 -1
  23. package/dist/{deviceKeys-CcXkzhNB.cjs → deviceKeys-CFpY68YY.cjs} +2 -2
  24. package/dist/{deviceKeys-CUdnv0Yf.mjs → deviceKeys-CxID9XkJ.mjs} +2 -2
  25. package/dist/{doctor-C59DXnte.mjs → doctor-CrBPDAJR.mjs} +2 -2
  26. package/dist/{doctor-B1Tm6BSe.cjs → doctor-qquYvrNu.cjs} +2 -2
  27. package/dist/{downloadImage-fpJ_y7st.cjs → downloadImage-Cqy7ZDq9.cjs} +1 -1
  28. package/dist/{downloadImage-tso3BJBM.mjs → downloadImage-DFwUJ1-t.mjs} +1 -1
  29. package/dist/{enroll-DJwHeZQv.mjs → enroll-BS4x3OUQ.mjs} +3 -3
  30. package/dist/{enroll-qLqzLVlX.cjs → enroll-CPhX2YYW.cjs} +3 -3
  31. package/dist/{evidence-B2t1PILg.mjs → evidence-HQhiWcci.mjs} +1 -1
  32. package/dist/{evidence-IqM8YLhr.cjs → evidence-mn6vjFpr.cjs} +1 -1
  33. package/dist/{formatImageMessage-BzBJVvC5.cjs → formatImageMessage-DFfcusj_.cjs} +1 -1
  34. package/dist/{formatImageMessage-R9j4fTqw.mjs → formatImageMessage-cI_AEqo1.mjs} +1 -1
  35. package/dist/index.cjs +46 -46
  36. package/dist/index.mjs +46 -46
  37. package/dist/{install-C4UgnaVt.cjs → install-Bm5eNojk.cjs} +1 -1
  38. package/dist/{install-BvOb90B1.mjs → install-Bn2199Kq.mjs} +2 -2
  39. package/dist/{install-DVsfVhR4.mjs → install-sbpQfY_l.mjs} +1 -1
  40. package/dist/{install-CAWGzh-y.cjs → install-tBBR7NUn.cjs} +2 -2
  41. package/dist/lib.cjs +3 -3
  42. package/dist/lib.mjs +3 -3
  43. package/dist/{loadableToolOutput-JbPnwQD1.cjs → loadableToolOutput-BzTaDndK.cjs} +2 -2
  44. package/dist/{loadableToolOutput-Pxv9LNrC.mjs → loadableToolOutput-DbXbVycg.mjs} +2 -2
  45. package/dist/{persistence-DflTrEXm.cjs → persistence-ACqGUEtH.cjs} +1 -1
  46. package/dist/{persistence-BGgHa7Mf.mjs → persistence-YyHJPrEJ.mjs} +1 -1
  47. package/dist/{push-DOwuDkzH.mjs → push-CICE33Pk.mjs} +6 -6
  48. package/dist/{push-CavNF4-w.cjs → push-DN6X8puN.cjs} +6 -6
  49. package/dist/{registerKillSessionHandler-B9qo40Js.cjs → registerKillSessionHandler-BvYc-FOQ.cjs} +76 -17
  50. package/dist/{registerKillSessionHandler-Bcg7ph3f.mjs → registerKillSessionHandler-CaFN7Ti_.mjs} +76 -17
  51. package/dist/{run-CS7zNIOF.mjs → run-DEIpQXCf.mjs} +8 -8
  52. package/dist/{run-vWYjxpj7.cjs → run-Djrrraug.cjs} +8 -8
  53. package/dist/{runClaude-uzuESWTN.mjs → runClaude-BuXbk4Hk.mjs} +15 -15
  54. package/dist/{runClaude-DPv8wwz5.cjs → runClaude-DEnJA94E.cjs} +16 -16
  55. package/dist/{runCodex-BQj7asY1.mjs → runCodex-DDJ4ld9g.mjs} +17 -17
  56. package/dist/{runCodex-CukxaOtw.cjs → runCodex-xfey8ybA.cjs} +17 -17
  57. package/dist/{runCursor-DDFZIPow.mjs → runCursor-Ea-0SqPz.mjs} +15 -15
  58. package/dist/{runCursor-DRTH8eya.cjs → runCursor-HDQXcY5a.cjs} +15 -15
  59. package/dist/{runGemini-Dq1n_3Il.mjs → runGemini-DNXknLIG.mjs} +25 -20
  60. package/dist/{runGemini-fCjqaRxV.cjs → runGemini-DupL8lXI.cjs} +25 -20
  61. package/dist/{runOneShot-B58c3uwD.mjs → runOneShot-C3adjQdt.mjs} +2 -2
  62. package/dist/{runOneShot-cFeh3BT0.cjs → runOneShot-ncBtn_Gm.cjs} +2 -2
  63. package/dist/{serve-DlQJZwiG.mjs → serve-B8J5nW6c.mjs} +6 -6
  64. package/dist/{serve-Dow3CBz9.cjs → serve-DS7_dJ2V.cjs} +6 -6
  65. package/dist/{session-xYoR4mck.mjs → session-DLX5ZdxL.mjs} +5 -5
  66. package/dist/{session-DKY0LmV1.cjs → session-DqtoNEcE.cjs} +5 -5
  67. package/dist/{sessionKeyCache-B7Cr3M7u.cjs → sessionKeyCache-BnR0ARqZ.cjs} +1 -1
  68. package/dist/{sessionKeyCache-Pyx1Ydwx.mjs → sessionKeyCache-D0Y_V05P.mjs} +1 -1
  69. package/dist/{sessionList-DGnoq1GY.cjs → sessionList-B20S2H2v.cjs} +2 -2
  70. package/dist/{sessionList-C4JvaNZK.mjs → sessionList-D1jByB-c.mjs} +2 -2
  71. package/dist/{sessionSummary-DBDFmDZc.cjs → sessionSummary-BfkUnQew.cjs} +3 -3
  72. package/dist/{sessionSummary-CTGgh_U0.mjs → sessionSummary-HZWtQ6mW.mjs} +3 -3
  73. package/dist/{setupOfflineReconnection-Cjcq7f53.cjs → setupOfflineReconnection-C8Ll2khg.cjs} +3 -3
  74. package/dist/{setupOfflineReconnection-CD7ix1EA.mjs → setupOfflineReconnection-E5sAFKRm.mjs} +3 -3
  75. package/dist/{spawnHappyCLI-Bf5U61Km.cjs → spawnHappyCLI-BabByXDO.cjs} +1 -1
  76. package/dist/{spawnHappyCLI-CcZ1W5ck.mjs → spawnHappyCLI-iEcKfItK.mjs} +1 -1
  77. package/dist/{ssh-X7pcNZhV.mjs → ssh-BMawo5IG.mjs} +5 -5
  78. package/dist/{ssh-DVtjvpXO.cjs → ssh-BWLfu_Bu.cjs} +5 -5
  79. package/dist/{task-CDCM3UoN.cjs → task-CmzA8KBB.cjs} +6 -6
  80. package/dist/{task-B38P7nUL.mjs → task-JIXYT6Ut.mjs} +6 -6
  81. package/dist/{uninstall-BzSiuDRd.cjs → uninstall-CRICQerq.cjs} +2 -2
  82. package/dist/{uninstall-CnsQ85RV.mjs → uninstall-DE2hMEa8.mjs} +2 -2
  83. package/dist/{update-CffXErUk.mjs → update-Bo6YcGpg.mjs} +3 -3
  84. package/dist/{update-VzbqRv5g.cjs → update-DYLwPQB5.cjs} +3 -3
  85. package/package.json +1 -1
@@ -35,7 +35,7 @@ function isDebug() {
35
35
  }
36
36
 
37
37
  var name = "@zonease/happy";
38
- var version = "0.6.46";
38
+ var version = "0.6.48";
39
39
  var description = "Mobile and Web client for Claude Code, Codex, and Gemini";
40
40
  var author = "ZonEaseTech";
41
41
  var license = "MIT";
@@ -14,7 +14,7 @@ function isDebug() {
14
14
  }
15
15
 
16
16
  var name = "@zonease/happy";
17
- var version = "0.6.46";
17
+ var version = "0.6.48";
18
18
  var description = "Mobile and Web client for Claude Code, Codex, and Gemini";
19
19
  var author = "ZonEaseTech";
20
20
  var license = "MIT";
@@ -1,7 +1,7 @@
1
1
  import { stat, readFile } from 'node:fs/promises';
2
2
  import { io } from 'socket.io-client';
3
- import { c as configuration, e as encodeBase64, f as encrypt, g as decrypt, h as decodeBase64 } from './persistence-BGgHa7Mf.mjs';
4
- import { resolveDeviceForCommand } from './ssh-X7pcNZhV.mjs';
3
+ import { c as configuration, e as encodeBase64, f as encrypt, g as decrypt, h as decodeBase64 } from './persistence-YyHJPrEJ.mjs';
4
+ import { resolveDeviceForCommand } from './ssh-BMawo5IG.mjs';
5
5
  import 'node:fs';
6
6
  import 'node:os';
7
7
  import 'node:path';
@@ -10,16 +10,16 @@ import 'node:crypto';
10
10
  import 'tweetnacl';
11
11
  import 'chalk';
12
12
  import 'fs';
13
- import './deviceKeys-CUdnv0Yf.mjs';
13
+ import './deviceKeys-CxID9XkJ.mjs';
14
14
  import 'axios';
15
- import './auth-C7oghkN1.mjs';
15
+ import './auth-DEvweBty.mjs';
16
16
  import 'qrcode-terminal';
17
17
  import './time-DgsBLJA_.mjs';
18
- import './browser-DedYcRMI.mjs';
18
+ import './browser-C6JpWqen.mjs';
19
19
  import 'open';
20
20
  import 'react';
21
21
  import 'ink';
22
- import './controlClient-C8MF8Qxi.mjs';
22
+ import './controlClient-CEliu-dY.mjs';
23
23
  import './projectPath-BTBIMB57.mjs';
24
24
  import 'path';
25
25
  import 'url';
@@ -2,8 +2,8 @@
2
2
 
3
3
  var promises = require('node:fs/promises');
4
4
  var socket_ioClient = require('socket.io-client');
5
- var logger = require('./persistence-DflTrEXm.cjs');
6
- var ssh = require('./ssh-DVtjvpXO.cjs');
5
+ var logger = require('./persistence-ACqGUEtH.cjs');
6
+ var ssh = require('./ssh-BWLfu_Bu.cjs');
7
7
  require('node:fs');
8
8
  require('node:os');
9
9
  require('node:path');
@@ -12,16 +12,16 @@ require('node:crypto');
12
12
  require('tweetnacl');
13
13
  require('chalk');
14
14
  require('fs');
15
- require('./deviceKeys-CcXkzhNB.cjs');
15
+ require('./deviceKeys-CFpY68YY.cjs');
16
16
  require('axios');
17
- require('./auth-Bf-VkNDK.cjs');
17
+ require('./auth-DO429YIh.cjs');
18
18
  require('qrcode-terminal');
19
19
  require('./time-Bj2HRkKH.cjs');
20
- require('./browser-Cn_RttD9.cjs');
20
+ require('./browser-ChToh76t.cjs');
21
21
  require('open');
22
22
  require('react');
23
23
  require('ink');
24
- require('./controlClient-DTS16hKY.cjs');
24
+ require('./controlClient-BAJAvvWO.cjs');
25
25
  require('./projectPath-ChLhdOyt.cjs');
26
26
  require('path');
27
27
  require('url');
@@ -7,8 +7,8 @@ var node_http = require('node:http');
7
7
  var streamableHttp_js = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
8
8
  var types_js = require('@modelcontextprotocol/sdk/types.js');
9
9
  var z = require('zod');
10
- var logger = require('./persistence-DflTrEXm.cjs');
11
- var deviceKeys = require('./deviceKeys-CcXkzhNB.cjs');
10
+ var logger = require('./persistence-ACqGUEtH.cjs');
11
+ var deviceKeys = require('./deviceKeys-CFpY68YY.cjs');
12
12
  var axios = require('axios');
13
13
  var promises = require('node:fs/promises');
14
14
  var node_crypto = require('node:crypto');
@@ -18,7 +18,7 @@ require('./index-BeKrqPYR.cjs');
18
18
  var node_child_process = require('node:child_process');
19
19
  require('node:events');
20
20
  require('socket.io-client');
21
- require('./api-DGgccR6B.cjs');
21
+ require('./api-lxlaOAan.cjs');
22
22
  require('tweetnacl');
23
23
  require('ws');
24
24
  require('crypto');
@@ -40,6 +40,20 @@ const IMAGE_MIME_TYPES = {
40
40
  function authHeaders(credentials) {
41
41
  return { headers: { Authorization: `Bearer ${credentials.token}` }, timeout: 15e3 };
42
42
  }
43
+ async function listBugs(credentials, input = {}) {
44
+ const response = await axios.get(
45
+ `${logger.configuration.serverUrl}/v1/bugs`,
46
+ {
47
+ params: {
48
+ ...input.status ? { status: input.status } : {},
49
+ ...input.query ? { query: input.query } : {},
50
+ limit: input.limit ?? 50
51
+ },
52
+ ...authHeaders(credentials)
53
+ }
54
+ );
55
+ return response.data;
56
+ }
43
57
  async function resolveBugId(credentials, reference) {
44
58
  const trimmed = reference.trim();
45
59
  const displayNumber = trimmed.replace(/^#/, "").match(/^(?:bug-)?(\d+)$/i)?.[1];
@@ -70,6 +84,17 @@ async function attachBugImage(credentials, bugId, filePath) {
70
84
  form.append("file", new Blob([await promises.readFile(filePath)], { type: mimeType }), path.basename(filePath));
71
85
  await axios.post(`${logger.configuration.serverUrl}/v1/bugs/${bugId}/attachments`, form, authHeaders(credentials));
72
86
  }
87
+ async function attachImages(credentials, bugId, images, alreadyAttached) {
88
+ const room = Math.max(MAX_BUG_IMAGES - alreadyAttached, 0);
89
+ const attachments = [];
90
+ for (const file of images.slice(0, room)) {
91
+ await attachBugImage(credentials, bugId, file).then(() => attachments.push({ file, ok: true })).catch((error) => attachments.push({ file, ok: false, error: error instanceof Error ? error.message : String(error) }));
92
+ }
93
+ for (const file of images.slice(room)) {
94
+ attachments.push({ file, ok: false, error: `skipped \u2014 a bug takes at most ${MAX_BUG_IMAGES} images` });
95
+ }
96
+ return attachments;
97
+ }
73
98
  async function submitBug(credentials, input) {
74
99
  const response = await axios.post(
75
100
  `${logger.configuration.serverUrl}/v1/bugs`,
@@ -84,23 +109,26 @@ async function submitBug(credentials, input) {
84
109
  if (!images.length) {
85
110
  return bug;
86
111
  }
87
- const attachments = [];
88
- for (const file of images.slice(0, MAX_BUG_IMAGES)) {
89
- await attachBugImage(credentials, bug.id, file).then(() => attachments.push({ file, ok: true })).catch((error) => attachments.push({ file, ok: false, error: error instanceof Error ? error.message : String(error) }));
90
- }
91
- for (const file of images.slice(MAX_BUG_IMAGES)) {
92
- attachments.push({ file, ok: false, error: `skipped \u2014 a bug takes at most ${MAX_BUG_IMAGES} images` });
93
- }
94
- return { ...bug, attachments };
112
+ return { ...bug, attachments: await attachImages(credentials, bug.id, images, 0) };
95
113
  }
96
114
  async function editBug(credentials, input) {
115
+ const images = input.images ?? [];
116
+ if (!input.description && !images.length) {
117
+ throw new Error("nothing to change \u2014 pass a description, images, or both");
118
+ }
97
119
  const bugId = await resolveBugId(credentials, input.bug);
98
- const response = await axios.patch(
120
+ const bug = input.description ? (await axios.patch(
99
121
  `${logger.configuration.serverUrl}/v1/bugs/${bugId}/content`,
100
122
  { description: input.description },
101
123
  authHeaders(credentials)
102
- );
103
- return response.data.bug;
124
+ )).data.bug : (await axios.get(
125
+ `${logger.configuration.serverUrl}/v1/bugs/${bugId}`,
126
+ authHeaders(credentials)
127
+ )).data.bug;
128
+ if (!images.length) {
129
+ return bug;
130
+ }
131
+ return { ...bug, attachments: await attachImages(credentials, bugId, images, bug.attachmentCount ?? 0) };
104
132
  }
105
133
  async function deleteBug(credentials, reference) {
106
134
  const bugId = await resolveBugId(credentials, reference);
@@ -284,12 +312,43 @@ ${result.stderr}`);
284
312
  return toToolError(`Failed to submit bug: ${error instanceof Error ? error.message : String(error)}`);
285
313
  }
286
314
  });
315
+ mcp.registerTool("list_bugs", {
316
+ description: "List bugs on the user's Happy bug board. Use to answer what is open, to find a bug the user is describing, or to get its number before editing or deleting it.",
317
+ title: "List Bugs",
318
+ inputSchema: {
319
+ status: z.z.enum(["pending", "in_progress", "verify", "closed"]).optional().describe("Only bugs in this state"),
320
+ query: z.z.string().describe("Free text matched against number, title, description, author and status").optional(),
321
+ limit: z.z.number().int().min(1).max(200).optional().describe("How many to return, 50 by default")
322
+ }
323
+ }, async (args) => {
324
+ const credentials = await logger.readCredentials();
325
+ if (!credentials) {
326
+ return toToolError("No Happy credentials on this machine.");
327
+ }
328
+ try {
329
+ const { bugs, pendingCount } = await listBugs(credentials, args);
330
+ const lines = bugs.map((bug) => {
331
+ const counts = [
332
+ bug.attachmentCount ? `${bug.attachmentCount} images` : "",
333
+ bug.commentCount ? `${bug.commentCount} comments` : ""
334
+ ].filter(Boolean).join(", ");
335
+ const activity = bug.lastActivityAt ? new Date(bug.lastActivityAt).toISOString().slice(0, 10) : "";
336
+ return `${bug.displayId} [${bug.status}] ${bug.title}${bug.createdByNickname ? ` \u2014 ${bug.createdByNickname}` : ""}${counts ? ` (${counts})` : ""}${activity ? ` ${activity}` : ""}`;
337
+ });
338
+ const header = `${bugs.length} bugs shown, ${pendingCount} pending on the board`;
339
+ return { content: [{ type: "text", text: lines.length ? `${header}
340
+ ${lines.join("\n")}` : header }] };
341
+ } catch (error) {
342
+ return toToolError(`Failed to list bugs: ${error instanceof Error ? error.message : String(error)}`);
343
+ }
344
+ });
287
345
  mcp.registerTool("edit_bug", {
288
- description: "Rewrite the description of a bug already on the user's Happy bug board. Use when the user wants to correct or expand an existing bug. The description replaces the old one outright, so carry over anything still true.",
346
+ description: "Change a bug already on the user's Happy bug board: rewrite its description, add screenshots to it, or both. Use when the user wants to correct, expand or illustrate an existing bug.",
289
347
  title: "Edit Bug",
290
348
  inputSchema: {
291
349
  bug: z.z.string().describe('Which bug, as the user refers to it: "BUG-236", "#236" or "236". An internal bug id also works.'),
292
- description: z.z.string().describe("The full replacement description \u2014 what went wrong, what was expected, and how to reproduce it.")
350
+ description: z.z.string().optional().describe("The full replacement description. It replaces the old one outright, so carry over anything still true. Omit to leave the description alone."),
351
+ images: z.z.array(z.z.string()).optional().describe("Absolute paths to screenshots on this machine, added to any already on the bug. JPEG and PNG only, 10 per bug in total, 20MB each.")
293
352
  }
294
353
  }, async (args) => {
295
354
  const credentials = await logger.readCredentials();
@@ -328,7 +387,7 @@ ${result.stderr}`);
328
387
  async function startHappyServer(client) {
329
388
  const transports = /* @__PURE__ */ new Map();
330
389
  const enableOrchestratorTools = prompt.shouldEnableOrchestratorTools();
331
- const toolNames = ["change_title", "preview_html", "device_list", "device_exec", "submit_bug", "edit_bug", "delete_bug"];
390
+ const toolNames = ["change_title", "preview_html", "device_list", "device_exec", "list_bugs", "submit_bug", "edit_bug", "delete_bug"];
332
391
  const originalConsoleError = console.error;
333
392
  console.error = (...args) => {
334
393
  logger.logger.debug("[happyMCP] console.error:", ...args);
@@ -5,8 +5,8 @@ import { createServer } from 'node:http';
5
5
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
6
6
  import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
7
7
  import { z } from 'zod';
8
- import { c as configuration, l as logger, r as readCredentials } from './persistence-BGgHa7Mf.mjs';
9
- import { l as listDevices, d as deviceExec } from './deviceKeys-CUdnv0Yf.mjs';
8
+ import { c as configuration, l as logger, r as readCredentials } from './persistence-YyHJPrEJ.mjs';
9
+ import { l as listDevices, d as deviceExec } from './deviceKeys-CxID9XkJ.mjs';
10
10
  import axios from 'axios';
11
11
  import { stat, readFile } from 'node:fs/promises';
12
12
  import { randomUUID } from 'node:crypto';
@@ -16,7 +16,7 @@ import './index-DTV7drqs.mjs';
16
16
  import { execSync } from 'node:child_process';
17
17
  import 'node:events';
18
18
  import 'socket.io-client';
19
- import './api-CxdKKE-w.mjs';
19
+ import './api-BQORFd9R.mjs';
20
20
  import 'tweetnacl';
21
21
  import 'ws';
22
22
  import 'crypto';
@@ -38,6 +38,20 @@ const IMAGE_MIME_TYPES = {
38
38
  function authHeaders(credentials) {
39
39
  return { headers: { Authorization: `Bearer ${credentials.token}` }, timeout: 15e3 };
40
40
  }
41
+ async function listBugs(credentials, input = {}) {
42
+ const response = await axios.get(
43
+ `${configuration.serverUrl}/v1/bugs`,
44
+ {
45
+ params: {
46
+ ...input.status ? { status: input.status } : {},
47
+ ...input.query ? { query: input.query } : {},
48
+ limit: input.limit ?? 50
49
+ },
50
+ ...authHeaders(credentials)
51
+ }
52
+ );
53
+ return response.data;
54
+ }
41
55
  async function resolveBugId(credentials, reference) {
42
56
  const trimmed = reference.trim();
43
57
  const displayNumber = trimmed.replace(/^#/, "").match(/^(?:bug-)?(\d+)$/i)?.[1];
@@ -68,6 +82,17 @@ async function attachBugImage(credentials, bugId, filePath) {
68
82
  form.append("file", new Blob([await readFile(filePath)], { type: mimeType }), basename(filePath));
69
83
  await axios.post(`${configuration.serverUrl}/v1/bugs/${bugId}/attachments`, form, authHeaders(credentials));
70
84
  }
85
+ async function attachImages(credentials, bugId, images, alreadyAttached) {
86
+ const room = Math.max(MAX_BUG_IMAGES - alreadyAttached, 0);
87
+ const attachments = [];
88
+ for (const file of images.slice(0, room)) {
89
+ await attachBugImage(credentials, bugId, file).then(() => attachments.push({ file, ok: true })).catch((error) => attachments.push({ file, ok: false, error: error instanceof Error ? error.message : String(error) }));
90
+ }
91
+ for (const file of images.slice(room)) {
92
+ attachments.push({ file, ok: false, error: `skipped \u2014 a bug takes at most ${MAX_BUG_IMAGES} images` });
93
+ }
94
+ return attachments;
95
+ }
71
96
  async function submitBug(credentials, input) {
72
97
  const response = await axios.post(
73
98
  `${configuration.serverUrl}/v1/bugs`,
@@ -82,23 +107,26 @@ async function submitBug(credentials, input) {
82
107
  if (!images.length) {
83
108
  return bug;
84
109
  }
85
- const attachments = [];
86
- for (const file of images.slice(0, MAX_BUG_IMAGES)) {
87
- await attachBugImage(credentials, bug.id, file).then(() => attachments.push({ file, ok: true })).catch((error) => attachments.push({ file, ok: false, error: error instanceof Error ? error.message : String(error) }));
88
- }
89
- for (const file of images.slice(MAX_BUG_IMAGES)) {
90
- attachments.push({ file, ok: false, error: `skipped \u2014 a bug takes at most ${MAX_BUG_IMAGES} images` });
91
- }
92
- return { ...bug, attachments };
110
+ return { ...bug, attachments: await attachImages(credentials, bug.id, images, 0) };
93
111
  }
94
112
  async function editBug(credentials, input) {
113
+ const images = input.images ?? [];
114
+ if (!input.description && !images.length) {
115
+ throw new Error("nothing to change \u2014 pass a description, images, or both");
116
+ }
95
117
  const bugId = await resolveBugId(credentials, input.bug);
96
- const response = await axios.patch(
118
+ const bug = input.description ? (await axios.patch(
97
119
  `${configuration.serverUrl}/v1/bugs/${bugId}/content`,
98
120
  { description: input.description },
99
121
  authHeaders(credentials)
100
- );
101
- return response.data.bug;
122
+ )).data.bug : (await axios.get(
123
+ `${configuration.serverUrl}/v1/bugs/${bugId}`,
124
+ authHeaders(credentials)
125
+ )).data.bug;
126
+ if (!images.length) {
127
+ return bug;
128
+ }
129
+ return { ...bug, attachments: await attachImages(credentials, bugId, images, bug.attachmentCount ?? 0) };
102
130
  }
103
131
  async function deleteBug(credentials, reference) {
104
132
  const bugId = await resolveBugId(credentials, reference);
@@ -282,12 +310,43 @@ ${result.stderr}`);
282
310
  return toToolError(`Failed to submit bug: ${error instanceof Error ? error.message : String(error)}`);
283
311
  }
284
312
  });
313
+ mcp.registerTool("list_bugs", {
314
+ description: "List bugs on the user's Happy bug board. Use to answer what is open, to find a bug the user is describing, or to get its number before editing or deleting it.",
315
+ title: "List Bugs",
316
+ inputSchema: {
317
+ status: z.enum(["pending", "in_progress", "verify", "closed"]).optional().describe("Only bugs in this state"),
318
+ query: z.string().describe("Free text matched against number, title, description, author and status").optional(),
319
+ limit: z.number().int().min(1).max(200).optional().describe("How many to return, 50 by default")
320
+ }
321
+ }, async (args) => {
322
+ const credentials = await readCredentials();
323
+ if (!credentials) {
324
+ return toToolError("No Happy credentials on this machine.");
325
+ }
326
+ try {
327
+ const { bugs, pendingCount } = await listBugs(credentials, args);
328
+ const lines = bugs.map((bug) => {
329
+ const counts = [
330
+ bug.attachmentCount ? `${bug.attachmentCount} images` : "",
331
+ bug.commentCount ? `${bug.commentCount} comments` : ""
332
+ ].filter(Boolean).join(", ");
333
+ const activity = bug.lastActivityAt ? new Date(bug.lastActivityAt).toISOString().slice(0, 10) : "";
334
+ return `${bug.displayId} [${bug.status}] ${bug.title}${bug.createdByNickname ? ` \u2014 ${bug.createdByNickname}` : ""}${counts ? ` (${counts})` : ""}${activity ? ` ${activity}` : ""}`;
335
+ });
336
+ const header = `${bugs.length} bugs shown, ${pendingCount} pending on the board`;
337
+ return { content: [{ type: "text", text: lines.length ? `${header}
338
+ ${lines.join("\n")}` : header }] };
339
+ } catch (error) {
340
+ return toToolError(`Failed to list bugs: ${error instanceof Error ? error.message : String(error)}`);
341
+ }
342
+ });
285
343
  mcp.registerTool("edit_bug", {
286
- description: "Rewrite the description of a bug already on the user's Happy bug board. Use when the user wants to correct or expand an existing bug. The description replaces the old one outright, so carry over anything still true.",
344
+ description: "Change a bug already on the user's Happy bug board: rewrite its description, add screenshots to it, or both. Use when the user wants to correct, expand or illustrate an existing bug.",
287
345
  title: "Edit Bug",
288
346
  inputSchema: {
289
347
  bug: z.string().describe('Which bug, as the user refers to it: "BUG-236", "#236" or "236". An internal bug id also works.'),
290
- description: z.string().describe("The full replacement description \u2014 what went wrong, what was expected, and how to reproduce it.")
348
+ description: z.string().optional().describe("The full replacement description. It replaces the old one outright, so carry over anything still true. Omit to leave the description alone."),
349
+ images: z.array(z.string()).optional().describe("Absolute paths to screenshots on this machine, added to any already on the bug. JPEG and PNG only, 10 per bug in total, 20MB each.")
291
350
  }
292
351
  }, async (args) => {
293
352
  const credentials = await readCredentials();
@@ -326,7 +385,7 @@ ${result.stderr}`);
326
385
  async function startHappyServer(client) {
327
386
  const transports = /* @__PURE__ */ new Map();
328
387
  const enableOrchestratorTools = shouldEnableOrchestratorTools();
329
- const toolNames = ["change_title", "preview_html", "device_list", "device_exec", "submit_bug", "edit_bug", "delete_bug"];
388
+ const toolNames = ["change_title", "preview_html", "device_list", "device_exec", "list_bugs", "submit_bug", "edit_bug", "delete_bug"];
330
389
  const originalConsoleError = console.error;
331
390
  console.error = (...args) => {
332
391
  logger.debug("[happyMCP] console.error:", ...args);
@@ -1,15 +1,15 @@
1
1
  import fs from 'fs/promises';
2
2
  import os from 'os';
3
3
  import * as tmp from 'tmp';
4
- import { A as ApiClient } from './api-CxdKKE-w.mjs';
5
- import { c as configuration, l as logger, p as packageJson, v as acquireDaemonLock, a as readSettings, i as isDebug, e as encodeBase64, x as writeDaemonState, m as readDaemonState, y as releaseDaemonLock, z as validateProfileForAgent, A as getProfileEnvironmentVariables } from './persistence-BGgHa7Mf.mjs';
6
- import { authAndSetupMachineIfNeeded } from './auth-C7oghkN1.mjs';
4
+ import { A as ApiClient } from './api-BQORFd9R.mjs';
5
+ import { c as configuration, l as logger, p as packageJson, v as acquireDaemonLock, a as readSettings, i as isDebug, e as encodeBase64, x as writeDaemonState, m as readDaemonState, y as releaseDaemonLock, z as validateProfileForAgent, A as getProfileEnvironmentVariables } from './persistence-YyHJPrEJ.mjs';
6
+ import { authAndSetupMachineIfNeeded } from './auth-DEvweBty.mjs';
7
7
  import { spawn, execSync, exec } from 'child_process';
8
- import { getEnvironmentInfo } from './doctor-C59DXnte.mjs';
9
- import { s as spawnHappyCLI } from './spawnHappyCLI-CcZ1W5ck.mjs';
10
- import { isDaemonRunningCurrentlyInstalledHappyVersion, stopDaemon, cleanupDaemonState } from './controlClient-C8MF8Qxi.mjs';
11
- import { a as readCachedDeviceKey, d as deviceExec } from './deviceKeys-CUdnv0Yf.mjs';
12
- import { P as PLIST_LABEL } from './install-DVsfVhR4.mjs';
8
+ import { getEnvironmentInfo } from './doctor-CrBPDAJR.mjs';
9
+ import { s as spawnHappyCLI } from './spawnHappyCLI-iEcKfItK.mjs';
10
+ import { isDaemonRunningCurrentlyInstalledHappyVersion, stopDaemon, cleanupDaemonState } from './controlClient-CEliu-dY.mjs';
11
+ import { a as readCachedDeviceKey, d as deviceExec } from './deviceKeys-CxID9XkJ.mjs';
12
+ import { P as PLIST_LABEL } from './install-sbpQfY_l.mjs';
13
13
  import fastify from 'fastify';
14
14
  import { createReadStream } from 'node:fs';
15
15
  import { stat } from 'node:fs/promises';
@@ -3,15 +3,15 @@
3
3
  var fs$1 = require('fs/promises');
4
4
  var os = require('os');
5
5
  var tmp = require('tmp');
6
- var api = require('./api-DGgccR6B.cjs');
7
- var logger = require('./persistence-DflTrEXm.cjs');
8
- var auth = require('./auth-Bf-VkNDK.cjs');
6
+ var api = require('./api-lxlaOAan.cjs');
7
+ var logger = require('./persistence-ACqGUEtH.cjs');
8
+ var auth = require('./auth-DO429YIh.cjs');
9
9
  var child_process = require('child_process');
10
- var doctor = require('./doctor-B1Tm6BSe.cjs');
11
- var spawnHappyCLI = require('./spawnHappyCLI-Bf5U61Km.cjs');
12
- var controlClient = require('./controlClient-DTS16hKY.cjs');
13
- var deviceKeys = require('./deviceKeys-CcXkzhNB.cjs');
14
- var install = require('./install-C4UgnaVt.cjs');
10
+ var doctor = require('./doctor-qquYvrNu.cjs');
11
+ var spawnHappyCLI = require('./spawnHappyCLI-BabByXDO.cjs');
12
+ var controlClient = require('./controlClient-BAJAvvWO.cjs');
13
+ var deviceKeys = require('./deviceKeys-CFpY68YY.cjs');
14
+ var install = require('./install-Bm5eNojk.cjs');
15
15
  var fastify = require('fastify');
16
16
  var fs = require('node:fs');
17
17
  var promises = require('node:fs/promises');
@@ -2,20 +2,20 @@ import os, { homedir } from 'node:os';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { existsSync, mkdirSync, writeFileSync, unlinkSync, readdirSync } from 'node:fs';
4
4
  import { readFile, mkdir, writeFile, stat, copyFile } from 'node:fs/promises';
5
- import { I as InvalidateSync, k as AsyncLock, d as connectionState, A as ApiClient, r as resolveToolsDir, b as startOfflineReconnection, l as findClaudeProjectId } from './api-CxdKKE-w.mjs';
6
- import { l as logger, i as isDebug, c as configuration, a as readSettings, p as packageJson } from './persistence-BGgHa7Mf.mjs';
7
- import { g as getProjectPath, a as claudeLocal, E as ExitCodeError, b as claudeCheckSession, s as systemPrompt } from './claudeLocal-G2km5rB3.mjs';
5
+ import { I as InvalidateSync, k as AsyncLock, d as connectionState, A as ApiClient, r as resolveToolsDir, b as startOfflineReconnection, l as findClaudeProjectId } from './api-BQORFd9R.mjs';
6
+ import { l as logger, i as isDebug, c as configuration, a as readSettings, p as packageJson } from './persistence-YyHJPrEJ.mjs';
7
+ import { g as getProjectPath, a as claudeLocal, E as ExitCodeError, b as claudeCheckSession, s as systemPrompt } from './claudeLocal-D7uEQO2e.mjs';
8
8
  import { R as RawJSONLinesSchema } from './types-BoJh4Qfv.mjs';
9
9
  import { join, resolve } from 'node:path';
10
10
  import { d as delay } from './time-DgsBLJA_.mjs';
11
11
  import { watch, access } from 'fs/promises';
12
12
  import { useStdout, useInput, Box, Text, render } from 'ink';
13
- import { b as parseSpecialCommand, a as MessageBuffer, M as MessageQueue2, h as hashObject } from './downloadImage-tso3BJBM.mjs';
13
+ import { b as parseSpecialCommand, a as MessageBuffer, M as MessageQueue2, h as hashObject } from './downloadImage-DFwUJ1-t.mjs';
14
14
  import React, { useState, useRef, useEffect, useCallback } from 'react';
15
15
  import { execSync, spawn } from 'node:child_process';
16
16
  import { createInterface } from 'node:readline';
17
17
  import { fileURLToPath } from 'node:url';
18
- import { i as isBun } from './spawnHappyCLI-CcZ1W5ck.mjs';
18
+ import { i as isBun } from './spawnHappyCLI-iEcKfItK.mjs';
19
19
  import { p as projectPath } from './projectPath-BTBIMB57.mjs';
20
20
  import 'axios';
21
21
  import 'node:events';
@@ -26,12 +26,12 @@ import 'crypto';
26
26
  import '@stablelib/base64';
27
27
  import 'expo-server-sdk';
28
28
  import 'chalk';
29
- import { a as formatMessageForClaude } from './formatImageMessage-R9j4fTqw.mjs';
29
+ import { a as formatMessageForClaude } from './formatImageMessage-cI_AEqo1.mjs';
30
30
  import { isDeepStrictEqual } from 'node:util';
31
- import { i as initialMachineMetadata, s as stopCaffeinate, a as startCaffeinate } from './run-CS7zNIOF.mjs';
32
- import { getEnvironmentInfo } from './doctor-C59DXnte.mjs';
33
- import { notifyDaemonSessionStarted } from './controlClient-C8MF8Qxi.mjs';
34
- import { c as createMcpContext, r as registerKillSessionHandler, d as detectGitWorktree } from './registerKillSessionHandler-Bcg7ph3f.mjs';
31
+ import { i as initialMachineMetadata, s as stopCaffeinate, a as startCaffeinate } from './run-DEIpQXCf.mjs';
32
+ import { getEnvironmentInfo } from './doctor-CrBPDAJR.mjs';
33
+ import { notifyDaemonSessionStarted } from './controlClient-CEliu-dY.mjs';
34
+ import { c as createMcpContext, r as registerKillSessionHandler, d as detectGitWorktree } from './registerKillSessionHandler-CaFN7Ti_.mjs';
35
35
  import { createServer } from 'node:http';
36
36
  import 'zod';
37
37
  import './index-DTV7drqs.mjs';
@@ -40,7 +40,7 @@ import 'child_process';
40
40
  import 'util';
41
41
  import 'path';
42
42
  import 'node:module';
43
- import './sessionKeyCache-Pyx1Ydwx.mjs';
43
+ import './sessionKeyCache-D0Y_V05P.mjs';
44
44
  import 'fs';
45
45
  import './prompt-BsAZsPbo.mjs';
46
46
  import './trimIdent-BAjmv_TG.mjs';
@@ -48,12 +48,12 @@ import './common-BdNgghUJ.mjs';
48
48
  import 'url';
49
49
  import 'os';
50
50
  import 'tmp';
51
- import './auth-C7oghkN1.mjs';
51
+ import './auth-DEvweBty.mjs';
52
52
  import 'qrcode-terminal';
53
- import './browser-DedYcRMI.mjs';
53
+ import './browser-C6JpWqen.mjs';
54
54
  import 'open';
55
- import './deviceKeys-CUdnv0Yf.mjs';
56
- import './install-DVsfVhR4.mjs';
55
+ import './deviceKeys-CxID9XkJ.mjs';
56
+ import './install-sbpQfY_l.mjs';
57
57
  import 'fastify';
58
58
  import 'fastify-type-provider-zod';
59
59
  import './doctor-ON7pN5G5.mjs';
@@ -4,20 +4,20 @@ var os = require('node:os');
4
4
  var node_crypto = require('node:crypto');
5
5
  var fs$1 = require('node:fs');
6
6
  var promises = require('node:fs/promises');
7
- var api = require('./api-DGgccR6B.cjs');
8
- var logger = require('./persistence-DflTrEXm.cjs');
9
- var claudeLocal = require('./claudeLocal-CupQ03sq.cjs');
7
+ var api = require('./api-lxlaOAan.cjs');
8
+ var logger = require('./persistence-ACqGUEtH.cjs');
9
+ var claudeLocal = require('./claudeLocal-BRkqm7Dv.cjs');
10
10
  var types = require('./types-DWeB7aqh.cjs');
11
11
  var path = require('node:path');
12
12
  var time = require('./time-Bj2HRkKH.cjs');
13
13
  var fs = require('fs/promises');
14
14
  var ink = require('ink');
15
- var downloadImage = require('./downloadImage-fpJ_y7st.cjs');
15
+ var downloadImage = require('./downloadImage-Cqy7ZDq9.cjs');
16
16
  var React = require('react');
17
17
  var node_child_process = require('node:child_process');
18
18
  var node_readline = require('node:readline');
19
19
  var node_url = require('node:url');
20
- var spawnHappyCLI = require('./spawnHappyCLI-Bf5U61Km.cjs');
20
+ var spawnHappyCLI = require('./spawnHappyCLI-BabByXDO.cjs');
21
21
  var projectPath = require('./projectPath-ChLhdOyt.cjs');
22
22
  require('axios');
23
23
  require('node:events');
@@ -28,12 +28,12 @@ require('crypto');
28
28
  require('@stablelib/base64');
29
29
  require('expo-server-sdk');
30
30
  require('chalk');
31
- var formatImageMessage = require('./formatImageMessage-BzBJVvC5.cjs');
31
+ var formatImageMessage = require('./formatImageMessage-DFfcusj_.cjs');
32
32
  var node_util = require('node:util');
33
- var run = require('./run-vWYjxpj7.cjs');
34
- var doctor = require('./doctor-B1Tm6BSe.cjs');
35
- var controlClient = require('./controlClient-DTS16hKY.cjs');
36
- var registerKillSessionHandler = require('./registerKillSessionHandler-B9qo40Js.cjs');
33
+ var run = require('./run-Djrrraug.cjs');
34
+ var doctor = require('./doctor-qquYvrNu.cjs');
35
+ var controlClient = require('./controlClient-BAJAvvWO.cjs');
36
+ var registerKillSessionHandler = require('./registerKillSessionHandler-BvYc-FOQ.cjs');
37
37
  var node_http = require('node:http');
38
38
  require('zod');
39
39
  require('./index-BeKrqPYR.cjs');
@@ -42,7 +42,7 @@ require('child_process');
42
42
  require('util');
43
43
  require('path');
44
44
  require('node:module');
45
- require('./sessionKeyCache-B7Cr3M7u.cjs');
45
+ require('./sessionKeyCache-BnR0ARqZ.cjs');
46
46
  require('fs');
47
47
  require('./prompt-BTWDZZO0.cjs');
48
48
  require('./trimIdent-4gvk9Wk6.cjs');
@@ -50,12 +50,12 @@ require('./common-CZvSRU4p.cjs');
50
50
  require('url');
51
51
  require('os');
52
52
  require('tmp');
53
- require('./auth-Bf-VkNDK.cjs');
53
+ require('./auth-DO429YIh.cjs');
54
54
  require('qrcode-terminal');
55
- require('./browser-Cn_RttD9.cjs');
55
+ require('./browser-ChToh76t.cjs');
56
56
  require('open');
57
- require('./deviceKeys-CcXkzhNB.cjs');
58
- require('./install-C4UgnaVt.cjs');
57
+ require('./deviceKeys-CFpY68YY.cjs');
58
+ require('./install-Bm5eNojk.cjs');
59
59
  require('fastify');
60
60
  require('fastify-type-provider-zod');
61
61
  require('./doctor-C08m5Z9f.cjs');
@@ -732,7 +732,7 @@ class AbortError extends Error {
732
732
  }
733
733
  }
734
734
 
735
- const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('runClaude-DPv8wwz5.cjs', document.baseURI).href)));
735
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('runClaude-DEnJA94E.cjs', document.baseURI).href)));
736
736
  const __dirname$1 = path.join(__filename$1, "..");
737
737
  function getGlobalClaudeVersion() {
738
738
  try {