@zonease/happy 0.6.45 → 0.6.46

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-Cj4RF4ga.mjs → api-CxdKKE-w.mjs} +2 -2
  2. package/dist/{api-CPQUJQGJ.cjs → api-DGgccR6B.cjs} +3 -3
  3. package/dist/{auth-Du-LmcLH.cjs → auth-Bf-VkNDK.cjs} +2 -2
  4. package/dist/{auth-BxRfxsPn.mjs → auth-C7oghkN1.mjs} +2 -2
  5. package/dist/{auth-Byjbkudh.mjs → auth-CcMvoMIH.mjs} +4 -4
  6. package/dist/{auth-Bk9bRj3Q.cjs → auth-XO55Tcek.cjs} +4 -4
  7. package/dist/{browser-CBrNk2b6.cjs → browser-Cn_RttD9.cjs} +1 -1
  8. package/dist/{browser-CrNqwWO0.mjs → browser-DedYcRMI.mjs} +1 -1
  9. package/dist/{claudeLocal-DMn1kiJq.cjs → claudeLocal-CupQ03sq.cjs} +1 -1
  10. package/dist/{claudeLocal-CR1aG4FT.mjs → claudeLocal-G2km5rB3.mjs} +1 -1
  11. package/dist/codex/happyMcpStdioBridge.cjs +17 -1
  12. package/dist/codex/happyMcpStdioBridge.mjs +17 -1
  13. package/dist/{config-DfXlAE9f.cjs → config-BDbpXufc.cjs} +1 -1
  14. package/dist/{config-DIUIjSc3.mjs → config-DimzIdjy.mjs} +1 -1
  15. package/dist/{connect-Blv2AOQ7.mjs → connect-CjFR0V4l.mjs} +4 -4
  16. package/dist/{connect-DxKG4c89.cjs → connect-DWElyqHy.cjs} +4 -4
  17. package/dist/{controlClient-D-5BGcGC.mjs → controlClient-C8MF8Qxi.mjs} +1 -1
  18. package/dist/{controlClient-Aga5KOuL.cjs → controlClient-DTS16hKY.cjs} +1 -1
  19. package/dist/{cursorApprovalHook-DhdW7SxP.cjs → cursorApprovalHook-B9dUHp7N.cjs} +2 -2
  20. package/dist/{cursorApprovalHook-G462ilpH.mjs → cursorApprovalHook-DmVCQFLi.mjs} +2 -2
  21. package/dist/{cursorApprovalServer-DJDjSGu1.cjs → cursorApprovalServer-BkQ3JeOR.cjs} +1 -1
  22. package/dist/{cursorApprovalServer-CajHEZIy.mjs → cursorApprovalServer-DeyL8InE.mjs} +1 -1
  23. package/dist/{deviceKeys-DL_YhAVs.mjs → deviceKeys-CUdnv0Yf.mjs} +2 -2
  24. package/dist/{deviceKeys-DjItabvw.cjs → deviceKeys-CcXkzhNB.cjs} +2 -2
  25. package/dist/{doctor-s5r5tBV2.cjs → doctor-B1Tm6BSe.cjs} +2 -2
  26. package/dist/{doctor-DpCWRLts.mjs → doctor-C59DXnte.mjs} +2 -2
  27. package/dist/{downloadImage-AYf-3ZYW.cjs → downloadImage-fpJ_y7st.cjs} +1 -1
  28. package/dist/{downloadImage-aU6-_LTd.mjs → downloadImage-tso3BJBM.mjs} +1 -1
  29. package/dist/{enroll-AafyH7YB.mjs → enroll-DJwHeZQv.mjs} +3 -3
  30. package/dist/{enroll-DDmZIfwr.cjs → enroll-qLqzLVlX.cjs} +3 -3
  31. package/dist/{evidence-s4HSdwJW.mjs → evidence-B2t1PILg.mjs} +1 -1
  32. package/dist/{evidence-BeL77p_3.cjs → evidence-IqM8YLhr.cjs} +1 -1
  33. package/dist/{formatImageMessage-D09-zSuq.cjs → formatImageMessage-BzBJVvC5.cjs} +1 -1
  34. package/dist/{formatImageMessage-X7lBbNk9.mjs → formatImageMessage-R9j4fTqw.mjs} +1 -1
  35. package/dist/index.cjs +46 -46
  36. package/dist/index.mjs +46 -46
  37. package/dist/{install-D3-4H76L.mjs → install-BvOb90B1.mjs} +2 -2
  38. package/dist/{install-CV4tyU-f.cjs → install-C4UgnaVt.cjs} +1 -1
  39. package/dist/{install-CUhg7JSa.cjs → install-CAWGzh-y.cjs} +2 -2
  40. package/dist/{install-MLAZRCd1.mjs → install-DVsfVhR4.mjs} +1 -1
  41. package/dist/lib.cjs +3 -3
  42. package/dist/lib.mjs +3 -3
  43. package/dist/{loadableToolOutput-CyxrSX6o.cjs → loadableToolOutput-JbPnwQD1.cjs} +2 -2
  44. package/dist/{loadableToolOutput-B8h8vRAi.mjs → loadableToolOutput-Pxv9LNrC.mjs} +2 -2
  45. package/dist/{persistence-CIuIRPwE.mjs → persistence-BGgHa7Mf.mjs} +1 -1
  46. package/dist/{persistence-C8YmO6e-.cjs → persistence-DflTrEXm.cjs} +1 -1
  47. package/dist/{push-C3zA8d8v.cjs → push-CavNF4-w.cjs} +6 -6
  48. package/dist/{push-CmNOCsuP.mjs → push-DOwuDkzH.mjs} +6 -6
  49. package/dist/{registerKillSessionHandler-LeIqHfbo.cjs → registerKillSessionHandler-B9qo40Js.cjs} +113 -7
  50. package/dist/{registerKillSessionHandler-CH_7VXKc.mjs → registerKillSessionHandler-Bcg7ph3f.mjs} +114 -8
  51. package/dist/{run-C4WAd7tO.mjs → run-CS7zNIOF.mjs} +8 -8
  52. package/dist/{run-0LQDK84Z.cjs → run-vWYjxpj7.cjs} +8 -8
  53. package/dist/{runClaude-C35zHmK5.cjs → runClaude-DPv8wwz5.cjs} +16 -16
  54. package/dist/{runClaude-CUKs1uIM.mjs → runClaude-uzuESWTN.mjs} +15 -15
  55. package/dist/{runCodex-DyZ2jjPq.mjs → runCodex-BQj7asY1.mjs} +15 -15
  56. package/dist/{runCodex-DqH5zG0r.cjs → runCodex-CukxaOtw.cjs} +15 -15
  57. package/dist/{runCursor-DBJr4Grx.mjs → runCursor-DDFZIPow.mjs} +14 -14
  58. package/dist/{runCursor-CUYlNNzB.cjs → runCursor-DRTH8eya.cjs} +14 -14
  59. package/dist/{runGemini-wXzzfjlb.mjs → runGemini-Dq1n_3Il.mjs} +26 -18
  60. package/dist/{runGemini-DR8URN73.cjs → runGemini-fCjqaRxV.cjs} +26 -18
  61. package/dist/{runOneShot-C7rpjuvK.mjs → runOneShot-B58c3uwD.mjs} +2 -2
  62. package/dist/{runOneShot-CyQgMHdu.cjs → runOneShot-cFeh3BT0.cjs} +2 -2
  63. package/dist/{serve-9sPqPPfs.mjs → serve-DlQJZwiG.mjs} +6 -6
  64. package/dist/{serve-CLUZny1h.cjs → serve-Dow3CBz9.cjs} +6 -6
  65. package/dist/{session-DsK6TYKl.cjs → session-DKY0LmV1.cjs} +5 -5
  66. package/dist/{session-DwFwW1If.mjs → session-xYoR4mck.mjs} +5 -5
  67. package/dist/{sessionKeyCache-CCCCQahC.cjs → sessionKeyCache-B7Cr3M7u.cjs} +1 -1
  68. package/dist/{sessionKeyCache-Jman_FzK.mjs → sessionKeyCache-Pyx1Ydwx.mjs} +1 -1
  69. package/dist/{sessionList-CRAr1LIi.mjs → sessionList-C4JvaNZK.mjs} +2 -2
  70. package/dist/{sessionList-jb1iwUO8.cjs → sessionList-DGnoq1GY.cjs} +2 -2
  71. package/dist/{sessionSummary-By9LYnr1.mjs → sessionSummary-CTGgh_U0.mjs} +3 -3
  72. package/dist/{sessionSummary-C4ttdYkc.cjs → sessionSummary-DBDFmDZc.cjs} +3 -3
  73. package/dist/{setupOfflineReconnection-B8LXjrYU.mjs → setupOfflineReconnection-CD7ix1EA.mjs} +3 -3
  74. package/dist/{setupOfflineReconnection-EeK8Zibv.cjs → setupOfflineReconnection-Cjcq7f53.cjs} +3 -3
  75. package/dist/{spawnHappyCLI-DH1071fN.cjs → spawnHappyCLI-Bf5U61Km.cjs} +1 -1
  76. package/dist/{spawnHappyCLI-ZuC3trh3.mjs → spawnHappyCLI-CcZ1W5ck.mjs} +1 -1
  77. package/dist/{ssh-DsBRhE6K.cjs → ssh-DVtjvpXO.cjs} +5 -5
  78. package/dist/{ssh-bVM6-eHl.mjs → ssh-X7pcNZhV.mjs} +5 -5
  79. package/dist/{task-QsscHPOu.mjs → task-B38P7nUL.mjs} +6 -6
  80. package/dist/{task-rXw2soeh.cjs → task-CDCM3UoN.cjs} +6 -6
  81. package/dist/{uninstall-CFVmL2EA.cjs → uninstall-BzSiuDRd.cjs} +2 -2
  82. package/dist/{uninstall-D-DMUz_6.mjs → uninstall-CnsQ85RV.mjs} +2 -2
  83. package/dist/{update-CS-sauOX.mjs → update-CffXErUk.mjs} +3 -3
  84. package/dist/{update-lVVrNorG.cjs → update-VzbqRv5g.cjs} +3 -3
  85. package/package.json +1 -1
@@ -14,7 +14,7 @@ function isDebug() {
14
14
  }
15
15
 
16
16
  var name = "@zonease/happy";
17
- var version = "0.6.45";
17
+ var version = "0.6.46";
18
18
  var description = "Mobile and Web client for Claude Code, Codex, and Gemini";
19
19
  var author = "ZonEaseTech";
20
20
  var license = "MIT";
@@ -35,7 +35,7 @@ function isDebug() {
35
35
  }
36
36
 
37
37
  var name = "@zonease/happy";
38
- var version = "0.6.45";
38
+ var version = "0.6.46";
39
39
  var description = "Mobile and Web client for Claude Code, Codex, and Gemini";
40
40
  var author = "ZonEaseTech";
41
41
  var license = "MIT";
@@ -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-C8YmO6e-.cjs');
6
- var ssh = require('./ssh-DsBRhE6K.cjs');
5
+ var logger = require('./persistence-DflTrEXm.cjs');
6
+ var ssh = require('./ssh-DVtjvpXO.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-DjItabvw.cjs');
15
+ require('./deviceKeys-CcXkzhNB.cjs');
16
16
  require('axios');
17
- require('./auth-Du-LmcLH.cjs');
17
+ require('./auth-Bf-VkNDK.cjs');
18
18
  require('qrcode-terminal');
19
19
  require('./time-Bj2HRkKH.cjs');
20
- require('./browser-CBrNk2b6.cjs');
20
+ require('./browser-Cn_RttD9.cjs');
21
21
  require('open');
22
22
  require('react');
23
23
  require('ink');
24
- require('./controlClient-Aga5KOuL.cjs');
24
+ require('./controlClient-DTS16hKY.cjs');
25
25
  require('./projectPath-ChLhdOyt.cjs');
26
26
  require('path');
27
27
  require('url');
@@ -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-CIuIRPwE.mjs';
4
- import { resolveDeviceForCommand } from './ssh-bVM6-eHl.mjs';
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';
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-DL_YhAVs.mjs';
13
+ import './deviceKeys-CUdnv0Yf.mjs';
14
14
  import 'axios';
15
- import './auth-BxRfxsPn.mjs';
15
+ import './auth-C7oghkN1.mjs';
16
16
  import 'qrcode-terminal';
17
17
  import './time-DgsBLJA_.mjs';
18
- import './browser-CrNqwWO0.mjs';
18
+ import './browser-DedYcRMI.mjs';
19
19
  import 'open';
20
20
  import 'react';
21
21
  import 'ink';
22
- import './controlClient-D-5BGcGC.mjs';
22
+ import './controlClient-C8MF8Qxi.mjs';
23
23
  import './projectPath-BTBIMB57.mjs';
24
24
  import 'path';
25
25
  import 'url';
@@ -7,9 +7,10 @@ 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-C8YmO6e-.cjs');
11
- var deviceKeys = require('./deviceKeys-DjItabvw.cjs');
10
+ var logger = require('./persistence-DflTrEXm.cjs');
11
+ var deviceKeys = require('./deviceKeys-CcXkzhNB.cjs');
12
12
  var axios = require('axios');
13
+ var promises = require('node:fs/promises');
13
14
  var node_crypto = require('node:crypto');
14
15
  var prompt = require('./prompt-BTWDZZO0.cjs');
15
16
  require('./mcpToolSchemas-DK42yoZ_.cjs');
@@ -17,19 +18,58 @@ require('./index-BeKrqPYR.cjs');
17
18
  var node_child_process = require('node:child_process');
18
19
  require('node:events');
19
20
  require('socket.io-client');
20
- require('./api-CPQUJQGJ.cjs');
21
+ require('./api-DGgccR6B.cjs');
21
22
  require('tweetnacl');
22
23
  require('ws');
23
24
  require('crypto');
24
25
  require('@stablelib/base64');
25
26
  require('node:os');
26
- require('node:fs/promises');
27
27
  require('node:fs');
28
28
  require('node:readline');
29
29
  require('expo-server-sdk');
30
30
  require('chalk');
31
31
  require('./types-DWeB7aqh.cjs');
32
32
 
33
+ const MAX_BUG_IMAGES = 10;
34
+ const MAX_BUG_IMAGE_BYTES = 20 * 1024 * 1024;
35
+ const IMAGE_MIME_TYPES = {
36
+ ".jpg": "image/jpeg",
37
+ ".jpeg": "image/jpeg",
38
+ ".png": "image/png"
39
+ };
40
+ function authHeaders(credentials) {
41
+ return { headers: { Authorization: `Bearer ${credentials.token}` }, timeout: 15e3 };
42
+ }
43
+ async function resolveBugId(credentials, reference) {
44
+ const trimmed = reference.trim();
45
+ const displayNumber = trimmed.replace(/^#/, "").match(/^(?:bug-)?(\d+)$/i)?.[1];
46
+ if (!displayNumber) {
47
+ return trimmed;
48
+ }
49
+ const displayId = `BUG-${displayNumber}`;
50
+ const response = await axios.get(
51
+ `${logger.configuration.serverUrl}/v1/bugs`,
52
+ { params: { query: displayId, limit: 200 }, ...authHeaders(credentials) }
53
+ );
54
+ const match = response.data.bugs.find((bug) => bug.displayId === displayId);
55
+ if (!match) {
56
+ throw new Error(`${displayId} not found on the bug board`);
57
+ }
58
+ return match.id;
59
+ }
60
+ async function attachBugImage(credentials, bugId, filePath) {
61
+ const mimeType = IMAGE_MIME_TYPES[path.extname(filePath).toLowerCase()];
62
+ if (!mimeType) {
63
+ throw new Error("only .jpg, .jpeg and .png are supported");
64
+ }
65
+ const { size } = await promises.stat(filePath);
66
+ if (size > MAX_BUG_IMAGE_BYTES) {
67
+ throw new Error(`image is ${Math.round(size / 1024 / 1024)}MB, over the 20MB limit`);
68
+ }
69
+ const form = new FormData();
70
+ form.append("file", new Blob([await promises.readFile(filePath)], { type: mimeType }), path.basename(filePath));
71
+ await axios.post(`${logger.configuration.serverUrl}/v1/bugs/${bugId}/attachments`, form, authHeaders(credentials));
72
+ }
33
73
  async function submitBug(credentials, input) {
34
74
  const response = await axios.post(
35
75
  `${logger.configuration.serverUrl}/v1/bugs`,
@@ -37,10 +77,36 @@ async function submitBug(credentials, input) {
37
77
  description: input.description,
38
78
  ...input.visibility ? { visibility: input.visibility } : {}
39
79
  },
40
- { headers: { Authorization: `Bearer ${credentials.token}` }, timeout: 15e3 }
80
+ authHeaders(credentials)
81
+ );
82
+ const bug = response.data.bug;
83
+ const images = input.images ?? [];
84
+ if (!images.length) {
85
+ return bug;
86
+ }
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 };
95
+ }
96
+ async function editBug(credentials, input) {
97
+ const bugId = await resolveBugId(credentials, input.bug);
98
+ const response = await axios.patch(
99
+ `${logger.configuration.serverUrl}/v1/bugs/${bugId}/content`,
100
+ { description: input.description },
101
+ authHeaders(credentials)
41
102
  );
42
103
  return response.data.bug;
43
104
  }
105
+ async function deleteBug(credentials, reference) {
106
+ const bugId = await resolveBugId(credentials, reference);
107
+ await axios.delete(`${logger.configuration.serverUrl}/v1/bugs/${bugId}`, authHeaders(credentials));
108
+ return { id: bugId };
109
+ }
44
110
 
45
111
  function toToolSuccess(data) {
46
112
  return {
@@ -202,7 +268,8 @@ ${result.stderr}`);
202
268
  title: "Submit Bug",
203
269
  inputSchema: {
204
270
  description: z.z.string().describe("What went wrong, what was expected, and how to reproduce it. The title is derived from the first line."),
205
- visibility: z.z.enum(["shared", "private"]).optional().describe("shared (default) puts it on the shared board; private keeps it to the owner")
271
+ visibility: z.z.enum(["shared", "private"]).optional().describe("shared (default) puts it on the shared board; private keeps it to the owner"),
272
+ images: z.z.array(z.z.string()).optional().describe("Absolute paths to screenshots on this machine. JPEG and PNG only, up to 10 images, 20MB each.")
206
273
  }
207
274
  }, async (args) => {
208
275
  const credentials = await logger.readCredentials();
@@ -217,12 +284,51 @@ ${result.stderr}`);
217
284
  return toToolError(`Failed to submit bug: ${error instanceof Error ? error.message : String(error)}`);
218
285
  }
219
286
  });
287
+ 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.",
289
+ title: "Edit Bug",
290
+ inputSchema: {
291
+ 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.")
293
+ }
294
+ }, async (args) => {
295
+ const credentials = await logger.readCredentials();
296
+ if (!credentials) {
297
+ return toToolError("No Happy credentials on this machine.");
298
+ }
299
+ try {
300
+ const bug = await editBug(credentials, args);
301
+ logger.logger.debug("[happyMCP] Edited bug:", bug.displayId);
302
+ return toToolSuccess({ ok: true, ...bug });
303
+ } catch (error) {
304
+ return toToolError(`Failed to edit bug: ${error instanceof Error ? error.message : String(error)}`);
305
+ }
306
+ });
307
+ mcp.registerTool("delete_bug", {
308
+ description: "Remove a bug from the user's Happy bug board. The server keeps the row and hides it, so this can be undone by an admin, but it disappears from the board immediately.",
309
+ title: "Delete Bug",
310
+ inputSchema: {
311
+ bug: z.z.string().describe('Which bug, as the user refers to it: "BUG-236", "#236" or "236". An internal bug id also works.')
312
+ }
313
+ }, async (args) => {
314
+ const credentials = await logger.readCredentials();
315
+ if (!credentials) {
316
+ return toToolError("No Happy credentials on this machine.");
317
+ }
318
+ try {
319
+ const deleted = await deleteBug(credentials, args.bug);
320
+ logger.logger.debug("[happyMCP] Deleted bug:", args.bug);
321
+ return toToolSuccess({ ok: true, ...deleted });
322
+ } catch (error) {
323
+ return toToolError(`Failed to delete bug: ${error instanceof Error ? error.message : String(error)}`);
324
+ }
325
+ });
220
326
  return mcp;
221
327
  }
222
328
  async function startHappyServer(client) {
223
329
  const transports = /* @__PURE__ */ new Map();
224
330
  const enableOrchestratorTools = prompt.shouldEnableOrchestratorTools();
225
- const toolNames = ["change_title", "preview_html", "device_list", "device_exec", "submit_bug"];
331
+ const toolNames = ["change_title", "preview_html", "device_list", "device_exec", "submit_bug", "edit_bug", "delete_bug"];
226
332
  const originalConsoleError = console.error;
227
333
  console.error = (...args) => {
228
334
  logger.logger.debug("[happyMCP] console.error:", ...args);
@@ -1,13 +1,14 @@
1
- import { join, resolve, dirname } from 'node:path';
1
+ import { extname, basename, join, resolve, dirname } from 'node:path';
2
2
  import { p as projectPath } from './projectPath-BTBIMB57.mjs';
3
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  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-CIuIRPwE.mjs';
9
- import { l as listDevices, d as deviceExec } from './deviceKeys-DL_YhAVs.mjs';
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';
10
10
  import axios from 'axios';
11
+ import { stat, readFile } from 'node:fs/promises';
11
12
  import { randomUUID } from 'node:crypto';
12
13
  import { s as shouldEnableOrchestratorTools } from './prompt-BsAZsPbo.mjs';
13
14
  import './mcpToolSchemas-CjeTfrAs.mjs';
@@ -15,19 +16,58 @@ import './index-DTV7drqs.mjs';
15
16
  import { execSync } from 'node:child_process';
16
17
  import 'node:events';
17
18
  import 'socket.io-client';
18
- import './api-Cj4RF4ga.mjs';
19
+ import './api-CxdKKE-w.mjs';
19
20
  import 'tweetnacl';
20
21
  import 'ws';
21
22
  import 'crypto';
22
23
  import '@stablelib/base64';
23
24
  import 'node:os';
24
- import 'node:fs/promises';
25
25
  import 'node:fs';
26
26
  import 'node:readline';
27
27
  import 'expo-server-sdk';
28
28
  import 'chalk';
29
29
  import './types-BoJh4Qfv.mjs';
30
30
 
31
+ const MAX_BUG_IMAGES = 10;
32
+ const MAX_BUG_IMAGE_BYTES = 20 * 1024 * 1024;
33
+ const IMAGE_MIME_TYPES = {
34
+ ".jpg": "image/jpeg",
35
+ ".jpeg": "image/jpeg",
36
+ ".png": "image/png"
37
+ };
38
+ function authHeaders(credentials) {
39
+ return { headers: { Authorization: `Bearer ${credentials.token}` }, timeout: 15e3 };
40
+ }
41
+ async function resolveBugId(credentials, reference) {
42
+ const trimmed = reference.trim();
43
+ const displayNumber = trimmed.replace(/^#/, "").match(/^(?:bug-)?(\d+)$/i)?.[1];
44
+ if (!displayNumber) {
45
+ return trimmed;
46
+ }
47
+ const displayId = `BUG-${displayNumber}`;
48
+ const response = await axios.get(
49
+ `${configuration.serverUrl}/v1/bugs`,
50
+ { params: { query: displayId, limit: 200 }, ...authHeaders(credentials) }
51
+ );
52
+ const match = response.data.bugs.find((bug) => bug.displayId === displayId);
53
+ if (!match) {
54
+ throw new Error(`${displayId} not found on the bug board`);
55
+ }
56
+ return match.id;
57
+ }
58
+ async function attachBugImage(credentials, bugId, filePath) {
59
+ const mimeType = IMAGE_MIME_TYPES[extname(filePath).toLowerCase()];
60
+ if (!mimeType) {
61
+ throw new Error("only .jpg, .jpeg and .png are supported");
62
+ }
63
+ const { size } = await stat(filePath);
64
+ if (size > MAX_BUG_IMAGE_BYTES) {
65
+ throw new Error(`image is ${Math.round(size / 1024 / 1024)}MB, over the 20MB limit`);
66
+ }
67
+ const form = new FormData();
68
+ form.append("file", new Blob([await readFile(filePath)], { type: mimeType }), basename(filePath));
69
+ await axios.post(`${configuration.serverUrl}/v1/bugs/${bugId}/attachments`, form, authHeaders(credentials));
70
+ }
31
71
  async function submitBug(credentials, input) {
32
72
  const response = await axios.post(
33
73
  `${configuration.serverUrl}/v1/bugs`,
@@ -35,10 +75,36 @@ async function submitBug(credentials, input) {
35
75
  description: input.description,
36
76
  ...input.visibility ? { visibility: input.visibility } : {}
37
77
  },
38
- { headers: { Authorization: `Bearer ${credentials.token}` }, timeout: 15e3 }
78
+ authHeaders(credentials)
79
+ );
80
+ const bug = response.data.bug;
81
+ const images = input.images ?? [];
82
+ if (!images.length) {
83
+ return bug;
84
+ }
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 };
93
+ }
94
+ async function editBug(credentials, input) {
95
+ const bugId = await resolveBugId(credentials, input.bug);
96
+ const response = await axios.patch(
97
+ `${configuration.serverUrl}/v1/bugs/${bugId}/content`,
98
+ { description: input.description },
99
+ authHeaders(credentials)
39
100
  );
40
101
  return response.data.bug;
41
102
  }
103
+ async function deleteBug(credentials, reference) {
104
+ const bugId = await resolveBugId(credentials, reference);
105
+ await axios.delete(`${configuration.serverUrl}/v1/bugs/${bugId}`, authHeaders(credentials));
106
+ return { id: bugId };
107
+ }
42
108
 
43
109
  function toToolSuccess(data) {
44
110
  return {
@@ -200,7 +266,8 @@ ${result.stderr}`);
200
266
  title: "Submit Bug",
201
267
  inputSchema: {
202
268
  description: z.string().describe("What went wrong, what was expected, and how to reproduce it. The title is derived from the first line."),
203
- visibility: z.enum(["shared", "private"]).optional().describe("shared (default) puts it on the shared board; private keeps it to the owner")
269
+ visibility: z.enum(["shared", "private"]).optional().describe("shared (default) puts it on the shared board; private keeps it to the owner"),
270
+ images: z.array(z.string()).optional().describe("Absolute paths to screenshots on this machine. JPEG and PNG only, up to 10 images, 20MB each.")
204
271
  }
205
272
  }, async (args) => {
206
273
  const credentials = await readCredentials();
@@ -215,12 +282,51 @@ ${result.stderr}`);
215
282
  return toToolError(`Failed to submit bug: ${error instanceof Error ? error.message : String(error)}`);
216
283
  }
217
284
  });
285
+ 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.",
287
+ title: "Edit Bug",
288
+ inputSchema: {
289
+ 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.")
291
+ }
292
+ }, async (args) => {
293
+ const credentials = await readCredentials();
294
+ if (!credentials) {
295
+ return toToolError("No Happy credentials on this machine.");
296
+ }
297
+ try {
298
+ const bug = await editBug(credentials, args);
299
+ logger.debug("[happyMCP] Edited bug:", bug.displayId);
300
+ return toToolSuccess({ ok: true, ...bug });
301
+ } catch (error) {
302
+ return toToolError(`Failed to edit bug: ${error instanceof Error ? error.message : String(error)}`);
303
+ }
304
+ });
305
+ mcp.registerTool("delete_bug", {
306
+ description: "Remove a bug from the user's Happy bug board. The server keeps the row and hides it, so this can be undone by an admin, but it disappears from the board immediately.",
307
+ title: "Delete Bug",
308
+ inputSchema: {
309
+ bug: z.string().describe('Which bug, as the user refers to it: "BUG-236", "#236" or "236". An internal bug id also works.')
310
+ }
311
+ }, async (args) => {
312
+ const credentials = await readCredentials();
313
+ if (!credentials) {
314
+ return toToolError("No Happy credentials on this machine.");
315
+ }
316
+ try {
317
+ const deleted = await deleteBug(credentials, args.bug);
318
+ logger.debug("[happyMCP] Deleted bug:", args.bug);
319
+ return toToolSuccess({ ok: true, ...deleted });
320
+ } catch (error) {
321
+ return toToolError(`Failed to delete bug: ${error instanceof Error ? error.message : String(error)}`);
322
+ }
323
+ });
218
324
  return mcp;
219
325
  }
220
326
  async function startHappyServer(client) {
221
327
  const transports = /* @__PURE__ */ new Map();
222
328
  const enableOrchestratorTools = shouldEnableOrchestratorTools();
223
- const toolNames = ["change_title", "preview_html", "device_list", "device_exec", "submit_bug"];
329
+ const toolNames = ["change_title", "preview_html", "device_list", "device_exec", "submit_bug", "edit_bug", "delete_bug"];
224
330
  const originalConsoleError = console.error;
225
331
  console.error = (...args) => {
226
332
  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-Cj4RF4ga.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-CIuIRPwE.mjs';
6
- import { authAndSetupMachineIfNeeded } from './auth-BxRfxsPn.mjs';
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';
7
7
  import { spawn, execSync, exec } from 'child_process';
8
- import { getEnvironmentInfo } from './doctor-DpCWRLts.mjs';
9
- import { s as spawnHappyCLI } from './spawnHappyCLI-ZuC3trh3.mjs';
10
- import { isDaemonRunningCurrentlyInstalledHappyVersion, stopDaemon, cleanupDaemonState } from './controlClient-D-5BGcGC.mjs';
11
- import { a as readCachedDeviceKey, d as deviceExec } from './deviceKeys-DL_YhAVs.mjs';
12
- import { P as PLIST_LABEL } from './install-MLAZRCd1.mjs';
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';
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-CPQUJQGJ.cjs');
7
- var logger = require('./persistence-C8YmO6e-.cjs');
8
- var auth = require('./auth-Du-LmcLH.cjs');
6
+ var api = require('./api-DGgccR6B.cjs');
7
+ var logger = require('./persistence-DflTrEXm.cjs');
8
+ var auth = require('./auth-Bf-VkNDK.cjs');
9
9
  var child_process = require('child_process');
10
- var doctor = require('./doctor-s5r5tBV2.cjs');
11
- var spawnHappyCLI = require('./spawnHappyCLI-DH1071fN.cjs');
12
- var controlClient = require('./controlClient-Aga5KOuL.cjs');
13
- var deviceKeys = require('./deviceKeys-DjItabvw.cjs');
14
- var install = require('./install-CV4tyU-f.cjs');
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');
15
15
  var fastify = require('fastify');
16
16
  var fs = require('node:fs');
17
17
  var promises = require('node:fs/promises');
@@ -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-CPQUJQGJ.cjs');
8
- var logger = require('./persistence-C8YmO6e-.cjs');
9
- var claudeLocal = require('./claudeLocal-DMn1kiJq.cjs');
7
+ var api = require('./api-DGgccR6B.cjs');
8
+ var logger = require('./persistence-DflTrEXm.cjs');
9
+ var claudeLocal = require('./claudeLocal-CupQ03sq.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-AYf-3ZYW.cjs');
15
+ var downloadImage = require('./downloadImage-fpJ_y7st.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-DH1071fN.cjs');
20
+ var spawnHappyCLI = require('./spawnHappyCLI-Bf5U61Km.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-D09-zSuq.cjs');
31
+ var formatImageMessage = require('./formatImageMessage-BzBJVvC5.cjs');
32
32
  var node_util = require('node:util');
33
- var run = require('./run-0LQDK84Z.cjs');
34
- var doctor = require('./doctor-s5r5tBV2.cjs');
35
- var controlClient = require('./controlClient-Aga5KOuL.cjs');
36
- var registerKillSessionHandler = require('./registerKillSessionHandler-LeIqHfbo.cjs');
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');
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-CCCCQahC.cjs');
45
+ require('./sessionKeyCache-B7Cr3M7u.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-Du-LmcLH.cjs');
53
+ require('./auth-Bf-VkNDK.cjs');
54
54
  require('qrcode-terminal');
55
- require('./browser-CBrNk2b6.cjs');
55
+ require('./browser-Cn_RttD9.cjs');
56
56
  require('open');
57
- require('./deviceKeys-DjItabvw.cjs');
58
- require('./install-CV4tyU-f.cjs');
57
+ require('./deviceKeys-CcXkzhNB.cjs');
58
+ require('./install-C4UgnaVt.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-C35zHmK5.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-DPv8wwz5.cjs', document.baseURI).href)));
736
736
  const __dirname$1 = path.join(__filename$1, "..");
737
737
  function getGlobalClaudeVersion() {
738
738
  try {
@@ -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-Cj4RF4ga.mjs';
6
- import { l as logger, i as isDebug, c as configuration, a as readSettings, p as packageJson } from './persistence-CIuIRPwE.mjs';
7
- import { g as getProjectPath, a as claudeLocal, E as ExitCodeError, b as claudeCheckSession, s as systemPrompt } from './claudeLocal-CR1aG4FT.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-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';
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-aU6-_LTd.mjs';
13
+ import { b as parseSpecialCommand, a as MessageBuffer, M as MessageQueue2, h as hashObject } from './downloadImage-tso3BJBM.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-ZuC3trh3.mjs';
18
+ import { i as isBun } from './spawnHappyCLI-CcZ1W5ck.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-X7lBbNk9.mjs';
29
+ import { a as formatMessageForClaude } from './formatImageMessage-R9j4fTqw.mjs';
30
30
  import { isDeepStrictEqual } from 'node:util';
31
- import { i as initialMachineMetadata, s as stopCaffeinate, a as startCaffeinate } from './run-C4WAd7tO.mjs';
32
- import { getEnvironmentInfo } from './doctor-DpCWRLts.mjs';
33
- import { notifyDaemonSessionStarted } from './controlClient-D-5BGcGC.mjs';
34
- import { c as createMcpContext, r as registerKillSessionHandler, d as detectGitWorktree } from './registerKillSessionHandler-CH_7VXKc.mjs';
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';
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-Jman_FzK.mjs';
43
+ import './sessionKeyCache-Pyx1Ydwx.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-BxRfxsPn.mjs';
51
+ import './auth-C7oghkN1.mjs';
52
52
  import 'qrcode-terminal';
53
- import './browser-CrNqwWO0.mjs';
53
+ import './browser-DedYcRMI.mjs';
54
54
  import 'open';
55
- import './deviceKeys-DL_YhAVs.mjs';
56
- import './install-MLAZRCd1.mjs';
55
+ import './deviceKeys-CUdnv0Yf.mjs';
56
+ import './install-DVsfVhR4.mjs';
57
57
  import 'fastify';
58
58
  import 'fastify-type-provider-zod';
59
59
  import './doctor-ON7pN5G5.mjs';