@zonease/happy 0.6.45 → 0.6.47

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-L9jLLtI1.mjs} +2 -2
  2. package/dist/{api-CPQUJQGJ.cjs → api-mv48hjhj.cjs} +3 -3
  3. package/dist/{auth-Bk9bRj3Q.cjs → auth-0qbD9TEm.cjs} +4 -4
  4. package/dist/{auth-BxRfxsPn.mjs → auth-BoWfky2q.mjs} +2 -2
  5. package/dist/{auth-Du-LmcLH.cjs → auth-DfQYhTlg.cjs} +2 -2
  6. package/dist/{auth-Byjbkudh.mjs → auth-InL7fBsz.mjs} +4 -4
  7. package/dist/{browser-CBrNk2b6.cjs → browser-CF-r2ami.cjs} +1 -1
  8. package/dist/{browser-CrNqwWO0.mjs → browser-D9jMe1me.mjs} +1 -1
  9. package/dist/{claudeLocal-CR1aG4FT.mjs → claudeLocal-CFEXiYts.mjs} +1 -1
  10. package/dist/{claudeLocal-DMn1kiJq.cjs → claudeLocal-DOfhf6VK.cjs} +1 -1
  11. package/dist/codex/happyMcpStdioBridge.cjs +18 -1
  12. package/dist/codex/happyMcpStdioBridge.mjs +18 -1
  13. package/dist/{config-DIUIjSc3.mjs → config-BBm54d_L.mjs} +1 -1
  14. package/dist/{config-DfXlAE9f.cjs → config-ByyclMMi.cjs} +1 -1
  15. package/dist/{connect-Blv2AOQ7.mjs → connect-B5JhnDTy.mjs} +4 -4
  16. package/dist/{connect-DxKG4c89.cjs → connect-CIOfZYj-.cjs} +4 -4
  17. package/dist/{controlClient-Aga5KOuL.cjs → controlClient-DMEMVw4Y.cjs} +1 -1
  18. package/dist/{controlClient-D-5BGcGC.mjs → controlClient-P5wJhu1R.mjs} +1 -1
  19. package/dist/{cursorApprovalHook-DhdW7SxP.cjs → cursorApprovalHook-C9mdmRIx.cjs} +2 -2
  20. package/dist/{cursorApprovalHook-G462ilpH.mjs → cursorApprovalHook-DplHRbGK.mjs} +2 -2
  21. package/dist/{cursorApprovalServer-DJDjSGu1.cjs → cursorApprovalServer-BoI6qBTg.cjs} +1 -1
  22. package/dist/{cursorApprovalServer-CajHEZIy.mjs → cursorApprovalServer-C9y5dYDE.mjs} +1 -1
  23. package/dist/{deviceKeys-DjItabvw.cjs → deviceKeys-CzqNNlG1.cjs} +2 -2
  24. package/dist/{deviceKeys-DL_YhAVs.mjs → deviceKeys-tcvwm4KA.mjs} +2 -2
  25. package/dist/{doctor-s5r5tBV2.cjs → doctor-CBbj6YEl.cjs} +2 -2
  26. package/dist/{doctor-DpCWRLts.mjs → doctor-CXYhUN9A.mjs} +2 -2
  27. package/dist/{downloadImage-AYf-3ZYW.cjs → downloadImage-CRkycWIl.cjs} +1 -1
  28. package/dist/{downloadImage-aU6-_LTd.mjs → downloadImage-CpnQ3aS1.mjs} +1 -1
  29. package/dist/{enroll-AafyH7YB.mjs → enroll-CYNDVSIH.mjs} +3 -3
  30. package/dist/{enroll-DDmZIfwr.cjs → enroll-CyikRwm7.cjs} +3 -3
  31. package/dist/{evidence-BeL77p_3.cjs → evidence-CuGxhBQm.cjs} +1 -1
  32. package/dist/{evidence-s4HSdwJW.mjs → evidence-DAu9vtgh.mjs} +1 -1
  33. package/dist/{formatImageMessage-D09-zSuq.cjs → formatImageMessage-CqonqpiB.cjs} +1 -1
  34. package/dist/{formatImageMessage-X7lBbNk9.mjs → formatImageMessage-ZJZza9-9.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-5H5Lh58Z.mjs} +2 -2
  38. package/dist/{install-MLAZRCd1.mjs → install-Cma2vLWa.mjs} +1 -1
  39. package/dist/{install-CUhg7JSa.cjs → install-X4JLGAi3.cjs} +2 -2
  40. package/dist/{install-CV4tyU-f.cjs → install-_GiK-K1X.cjs} +1 -1
  41. package/dist/lib.cjs +3 -3
  42. package/dist/lib.mjs +3 -3
  43. package/dist/{loadableToolOutput-B8h8vRAi.mjs → loadableToolOutput-4qF6EX0y.mjs} +2 -2
  44. package/dist/{loadableToolOutput-CyxrSX6o.cjs → loadableToolOutput-sf6E72GE.cjs} +2 -2
  45. package/dist/{persistence-C8YmO6e-.cjs → persistence-B_w4o5z-.cjs} +1 -1
  46. package/dist/{persistence-CIuIRPwE.mjs → persistence-D2aRTiT1.mjs} +1 -1
  47. package/dist/{push-CmNOCsuP.mjs → push-BiBqn-pk.mjs} +6 -6
  48. package/dist/{push-C3zA8d8v.cjs → push-Df6QabEp.cjs} +6 -6
  49. package/dist/{registerKillSessionHandler-CH_7VXKc.mjs → registerKillSessionHandler-AcpB6OSk.mjs} +130 -9
  50. package/dist/{registerKillSessionHandler-LeIqHfbo.cjs → registerKillSessionHandler-DUrOhiGo.cjs} +129 -8
  51. package/dist/{run-C4WAd7tO.mjs → run-D6oXm1Jk.mjs} +8 -8
  52. package/dist/{run-0LQDK84Z.cjs → run-DclH1MRm.cjs} +8 -8
  53. package/dist/{runClaude-CUKs1uIM.mjs → runClaude--M8lVtqr.mjs} +15 -15
  54. package/dist/{runClaude-C35zHmK5.cjs → runClaude-BfLi7f2N.cjs} +16 -16
  55. package/dist/{runCodex-DyZ2jjPq.mjs → runCodex-DatWk57y.mjs} +15 -15
  56. package/dist/{runCodex-DqH5zG0r.cjs → runCodex-DdcEe5bM.cjs} +15 -15
  57. package/dist/{runCursor-CUYlNNzB.cjs → runCursor-CszdTKDp.cjs} +14 -14
  58. package/dist/{runCursor-DBJr4Grx.mjs → runCursor-DxcT7Qtj.mjs} +14 -14
  59. package/dist/{runGemini-wXzzfjlb.mjs → runGemini-41p6X_zo.mjs} +26 -18
  60. package/dist/{runGemini-DR8URN73.cjs → runGemini-sAOJNh2V.cjs} +26 -18
  61. package/dist/{runOneShot-C7rpjuvK.mjs → runOneShot-NBUWEobi.mjs} +2 -2
  62. package/dist/{runOneShot-CyQgMHdu.cjs → runOneShot-m6HO3H5c.cjs} +2 -2
  63. package/dist/{serve-9sPqPPfs.mjs → serve-B84oK7m-.mjs} +6 -6
  64. package/dist/{serve-CLUZny1h.cjs → serve-CUNdZ2ps.cjs} +6 -6
  65. package/dist/{session-DwFwW1If.mjs → session-9-fr4sTn.mjs} +5 -5
  66. package/dist/{session-DsK6TYKl.cjs → session-De8UTFFI.cjs} +5 -5
  67. package/dist/{sessionKeyCache-Jman_FzK.mjs → sessionKeyCache-CEJH5cHj.mjs} +1 -1
  68. package/dist/{sessionKeyCache-CCCCQahC.cjs → sessionKeyCache-DYuPkw5Z.cjs} +1 -1
  69. package/dist/{sessionList-CRAr1LIi.mjs → sessionList-Dd4fyhNe.mjs} +2 -2
  70. package/dist/{sessionList-jb1iwUO8.cjs → sessionList-KhgmL10v.cjs} +2 -2
  71. package/dist/{sessionSummary-By9LYnr1.mjs → sessionSummary-CYsvjLpK.mjs} +3 -3
  72. package/dist/{sessionSummary-C4ttdYkc.cjs → sessionSummary-u-kSmPto.cjs} +3 -3
  73. package/dist/{setupOfflineReconnection-B8LXjrYU.mjs → setupOfflineReconnection-2m0GAIU5.mjs} +3 -3
  74. package/dist/{setupOfflineReconnection-EeK8Zibv.cjs → setupOfflineReconnection-BkAr6jAB.cjs} +3 -3
  75. package/dist/{spawnHappyCLI-ZuC3trh3.mjs → spawnHappyCLI-CG_S7LH1.mjs} +1 -1
  76. package/dist/{spawnHappyCLI-DH1071fN.cjs → spawnHappyCLI-T9EQFmAR.cjs} +1 -1
  77. package/dist/{ssh-bVM6-eHl.mjs → ssh-BTVqCokU.mjs} +5 -5
  78. package/dist/{ssh-DsBRhE6K.cjs → ssh-C4nI67Vq.cjs} +5 -5
  79. package/dist/{task-rXw2soeh.cjs → task-AWrQbs-h.cjs} +6 -6
  80. package/dist/{task-QsscHPOu.mjs → task-DzqzVDDn.mjs} +6 -6
  81. package/dist/{uninstall-D-DMUz_6.mjs → uninstall-394a0fgf.mjs} +2 -2
  82. package/dist/{uninstall-CFVmL2EA.cjs → uninstall-BRDV8214.cjs} +2 -2
  83. package/dist/{update-lVVrNorG.cjs → update-CZIaB80S.cjs} +3 -3
  84. package/dist/{update-CS-sauOX.mjs → update-DLqQQtYc.mjs} +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.45";
38
+ var version = "0.6.47";
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.45";
17
+ var version = "0.6.47";
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-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-D2aRTiT1.mjs';
4
+ import { resolveDeviceForCommand } from './ssh-BTVqCokU.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-tcvwm4KA.mjs';
14
14
  import 'axios';
15
- import './auth-BxRfxsPn.mjs';
15
+ import './auth-BoWfky2q.mjs';
16
16
  import 'qrcode-terminal';
17
17
  import './time-DgsBLJA_.mjs';
18
- import './browser-CrNqwWO0.mjs';
18
+ import './browser-D9jMe1me.mjs';
19
19
  import 'open';
20
20
  import 'react';
21
21
  import 'ink';
22
- import './controlClient-D-5BGcGC.mjs';
22
+ import './controlClient-P5wJhu1R.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-C8YmO6e-.cjs');
6
- var ssh = require('./ssh-DsBRhE6K.cjs');
5
+ var logger = require('./persistence-B_w4o5z-.cjs');
6
+ var ssh = require('./ssh-C4nI67Vq.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-CzqNNlG1.cjs');
16
16
  require('axios');
17
- require('./auth-Du-LmcLH.cjs');
17
+ require('./auth-DfQYhTlg.cjs');
18
18
  require('qrcode-terminal');
19
19
  require('./time-Bj2HRkKH.cjs');
20
- require('./browser-CBrNk2b6.cjs');
20
+ require('./browser-CF-r2ami.cjs');
21
21
  require('open');
22
22
  require('react');
23
23
  require('ink');
24
- require('./controlClient-Aga5KOuL.cjs');
24
+ require('./controlClient-DMEMVw4Y.cjs');
25
25
  require('./projectPath-ChLhdOyt.cjs');
26
26
  require('path');
27
27
  require('url');
@@ -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-D2aRTiT1.mjs';
9
+ import { l as listDevices, d as deviceExec } from './deviceKeys-tcvwm4KA.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,69 @@ 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-L9jLLtI1.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
+ }
71
+ async function attachImages(credentials, bugId, images, alreadyAttached) {
72
+ const room = Math.max(MAX_BUG_IMAGES - alreadyAttached, 0);
73
+ const attachments = [];
74
+ for (const file of images.slice(0, room)) {
75
+ 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) }));
76
+ }
77
+ for (const file of images.slice(room)) {
78
+ attachments.push({ file, ok: false, error: `skipped \u2014 a bug takes at most ${MAX_BUG_IMAGES} images` });
79
+ }
80
+ return attachments;
81
+ }
31
82
  async function submitBug(credentials, input) {
32
83
  const response = await axios.post(
33
84
  `${configuration.serverUrl}/v1/bugs`,
@@ -35,9 +86,38 @@ async function submitBug(credentials, input) {
35
86
  description: input.description,
36
87
  ...input.visibility ? { visibility: input.visibility } : {}
37
88
  },
38
- { headers: { Authorization: `Bearer ${credentials.token}` }, timeout: 15e3 }
89
+ authHeaders(credentials)
39
90
  );
40
- return response.data.bug;
91
+ const bug = response.data.bug;
92
+ const images = input.images ?? [];
93
+ if (!images.length) {
94
+ return bug;
95
+ }
96
+ return { ...bug, attachments: await attachImages(credentials, bug.id, images, 0) };
97
+ }
98
+ async function editBug(credentials, input) {
99
+ const images = input.images ?? [];
100
+ if (!input.description && !images.length) {
101
+ throw new Error("nothing to change \u2014 pass a description, images, or both");
102
+ }
103
+ const bugId = await resolveBugId(credentials, input.bug);
104
+ const bug = input.description ? (await axios.patch(
105
+ `${configuration.serverUrl}/v1/bugs/${bugId}/content`,
106
+ { description: input.description },
107
+ authHeaders(credentials)
108
+ )).data.bug : (await axios.get(
109
+ `${configuration.serverUrl}/v1/bugs/${bugId}`,
110
+ authHeaders(credentials)
111
+ )).data.bug;
112
+ if (!images.length) {
113
+ return bug;
114
+ }
115
+ return { ...bug, attachments: await attachImages(credentials, bugId, images, bug.attachmentCount ?? 0) };
116
+ }
117
+ async function deleteBug(credentials, reference) {
118
+ const bugId = await resolveBugId(credentials, reference);
119
+ await axios.delete(`${configuration.serverUrl}/v1/bugs/${bugId}`, authHeaders(credentials));
120
+ return { id: bugId };
41
121
  }
42
122
 
43
123
  function toToolSuccess(data) {
@@ -200,7 +280,8 @@ ${result.stderr}`);
200
280
  title: "Submit Bug",
201
281
  inputSchema: {
202
282
  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")
283
+ visibility: z.enum(["shared", "private"]).optional().describe("shared (default) puts it on the shared board; private keeps it to the owner"),
284
+ 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
285
  }
205
286
  }, async (args) => {
206
287
  const credentials = await readCredentials();
@@ -215,12 +296,52 @@ ${result.stderr}`);
215
296
  return toToolError(`Failed to submit bug: ${error instanceof Error ? error.message : String(error)}`);
216
297
  }
217
298
  });
299
+ mcp.registerTool("edit_bug", {
300
+ 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.",
301
+ title: "Edit Bug",
302
+ inputSchema: {
303
+ bug: z.string().describe('Which bug, as the user refers to it: "BUG-236", "#236" or "236". An internal bug id also works.'),
304
+ 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."),
305
+ 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.")
306
+ }
307
+ }, async (args) => {
308
+ const credentials = await readCredentials();
309
+ if (!credentials) {
310
+ return toToolError("No Happy credentials on this machine.");
311
+ }
312
+ try {
313
+ const bug = await editBug(credentials, args);
314
+ logger.debug("[happyMCP] Edited bug:", bug.displayId);
315
+ return toToolSuccess({ ok: true, ...bug });
316
+ } catch (error) {
317
+ return toToolError(`Failed to edit bug: ${error instanceof Error ? error.message : String(error)}`);
318
+ }
319
+ });
320
+ mcp.registerTool("delete_bug", {
321
+ 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.",
322
+ title: "Delete Bug",
323
+ inputSchema: {
324
+ bug: z.string().describe('Which bug, as the user refers to it: "BUG-236", "#236" or "236". An internal bug id also works.')
325
+ }
326
+ }, async (args) => {
327
+ const credentials = await readCredentials();
328
+ if (!credentials) {
329
+ return toToolError("No Happy credentials on this machine.");
330
+ }
331
+ try {
332
+ const deleted = await deleteBug(credentials, args.bug);
333
+ logger.debug("[happyMCP] Deleted bug:", args.bug);
334
+ return toToolSuccess({ ok: true, ...deleted });
335
+ } catch (error) {
336
+ return toToolError(`Failed to delete bug: ${error instanceof Error ? error.message : String(error)}`);
337
+ }
338
+ });
218
339
  return mcp;
219
340
  }
220
341
  async function startHappyServer(client) {
221
342
  const transports = /* @__PURE__ */ new Map();
222
343
  const enableOrchestratorTools = shouldEnableOrchestratorTools();
223
- const toolNames = ["change_title", "preview_html", "device_list", "device_exec", "submit_bug"];
344
+ const toolNames = ["change_title", "preview_html", "device_list", "device_exec", "submit_bug", "edit_bug", "delete_bug"];
224
345
  const originalConsoleError = console.error;
225
346
  console.error = (...args) => {
226
347
  logger.debug("[happyMCP] console.error:", ...args);
@@ -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-B_w4o5z-.cjs');
11
+ var deviceKeys = require('./deviceKeys-CzqNNlG1.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,69 @@ 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-mv48hjhj.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
+ }
73
+ async function attachImages(credentials, bugId, images, alreadyAttached) {
74
+ const room = Math.max(MAX_BUG_IMAGES - alreadyAttached, 0);
75
+ const attachments = [];
76
+ for (const file of images.slice(0, room)) {
77
+ 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) }));
78
+ }
79
+ for (const file of images.slice(room)) {
80
+ attachments.push({ file, ok: false, error: `skipped \u2014 a bug takes at most ${MAX_BUG_IMAGES} images` });
81
+ }
82
+ return attachments;
83
+ }
33
84
  async function submitBug(credentials, input) {
34
85
  const response = await axios.post(
35
86
  `${logger.configuration.serverUrl}/v1/bugs`,
@@ -37,9 +88,38 @@ async function submitBug(credentials, input) {
37
88
  description: input.description,
38
89
  ...input.visibility ? { visibility: input.visibility } : {}
39
90
  },
40
- { headers: { Authorization: `Bearer ${credentials.token}` }, timeout: 15e3 }
91
+ authHeaders(credentials)
41
92
  );
42
- return response.data.bug;
93
+ const bug = response.data.bug;
94
+ const images = input.images ?? [];
95
+ if (!images.length) {
96
+ return bug;
97
+ }
98
+ return { ...bug, attachments: await attachImages(credentials, bug.id, images, 0) };
99
+ }
100
+ async function editBug(credentials, input) {
101
+ const images = input.images ?? [];
102
+ if (!input.description && !images.length) {
103
+ throw new Error("nothing to change \u2014 pass a description, images, or both");
104
+ }
105
+ const bugId = await resolveBugId(credentials, input.bug);
106
+ const bug = input.description ? (await axios.patch(
107
+ `${logger.configuration.serverUrl}/v1/bugs/${bugId}/content`,
108
+ { description: input.description },
109
+ authHeaders(credentials)
110
+ )).data.bug : (await axios.get(
111
+ `${logger.configuration.serverUrl}/v1/bugs/${bugId}`,
112
+ authHeaders(credentials)
113
+ )).data.bug;
114
+ if (!images.length) {
115
+ return bug;
116
+ }
117
+ return { ...bug, attachments: await attachImages(credentials, bugId, images, bug.attachmentCount ?? 0) };
118
+ }
119
+ async function deleteBug(credentials, reference) {
120
+ const bugId = await resolveBugId(credentials, reference);
121
+ await axios.delete(`${logger.configuration.serverUrl}/v1/bugs/${bugId}`, authHeaders(credentials));
122
+ return { id: bugId };
43
123
  }
44
124
 
45
125
  function toToolSuccess(data) {
@@ -202,7 +282,8 @@ ${result.stderr}`);
202
282
  title: "Submit Bug",
203
283
  inputSchema: {
204
284
  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")
285
+ visibility: z.z.enum(["shared", "private"]).optional().describe("shared (default) puts it on the shared board; private keeps it to the owner"),
286
+ 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
287
  }
207
288
  }, async (args) => {
208
289
  const credentials = await logger.readCredentials();
@@ -217,12 +298,52 @@ ${result.stderr}`);
217
298
  return toToolError(`Failed to submit bug: ${error instanceof Error ? error.message : String(error)}`);
218
299
  }
219
300
  });
301
+ mcp.registerTool("edit_bug", {
302
+ 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.",
303
+ title: "Edit Bug",
304
+ inputSchema: {
305
+ bug: z.z.string().describe('Which bug, as the user refers to it: "BUG-236", "#236" or "236". An internal bug id also works.'),
306
+ 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."),
307
+ 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.")
308
+ }
309
+ }, async (args) => {
310
+ const credentials = await logger.readCredentials();
311
+ if (!credentials) {
312
+ return toToolError("No Happy credentials on this machine.");
313
+ }
314
+ try {
315
+ const bug = await editBug(credentials, args);
316
+ logger.logger.debug("[happyMCP] Edited bug:", bug.displayId);
317
+ return toToolSuccess({ ok: true, ...bug });
318
+ } catch (error) {
319
+ return toToolError(`Failed to edit bug: ${error instanceof Error ? error.message : String(error)}`);
320
+ }
321
+ });
322
+ mcp.registerTool("delete_bug", {
323
+ 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.",
324
+ title: "Delete Bug",
325
+ inputSchema: {
326
+ bug: z.z.string().describe('Which bug, as the user refers to it: "BUG-236", "#236" or "236". An internal bug id also works.')
327
+ }
328
+ }, async (args) => {
329
+ const credentials = await logger.readCredentials();
330
+ if (!credentials) {
331
+ return toToolError("No Happy credentials on this machine.");
332
+ }
333
+ try {
334
+ const deleted = await deleteBug(credentials, args.bug);
335
+ logger.logger.debug("[happyMCP] Deleted bug:", args.bug);
336
+ return toToolSuccess({ ok: true, ...deleted });
337
+ } catch (error) {
338
+ return toToolError(`Failed to delete bug: ${error instanceof Error ? error.message : String(error)}`);
339
+ }
340
+ });
220
341
  return mcp;
221
342
  }
222
343
  async function startHappyServer(client) {
223
344
  const transports = /* @__PURE__ */ new Map();
224
345
  const enableOrchestratorTools = prompt.shouldEnableOrchestratorTools();
225
- const toolNames = ["change_title", "preview_html", "device_list", "device_exec", "submit_bug"];
346
+ const toolNames = ["change_title", "preview_html", "device_list", "device_exec", "submit_bug", "edit_bug", "delete_bug"];
226
347
  const originalConsoleError = console.error;
227
348
  console.error = (...args) => {
228
349
  logger.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-L9jLLtI1.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-D2aRTiT1.mjs';
6
+ import { authAndSetupMachineIfNeeded } from './auth-BoWfky2q.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-CXYhUN9A.mjs';
9
+ import { s as spawnHappyCLI } from './spawnHappyCLI-CG_S7LH1.mjs';
10
+ import { isDaemonRunningCurrentlyInstalledHappyVersion, stopDaemon, cleanupDaemonState } from './controlClient-P5wJhu1R.mjs';
11
+ import { a as readCachedDeviceKey, d as deviceExec } from './deviceKeys-tcvwm4KA.mjs';
12
+ import { P as PLIST_LABEL } from './install-Cma2vLWa.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-mv48hjhj.cjs');
7
+ var logger = require('./persistence-B_w4o5z-.cjs');
8
+ var auth = require('./auth-DfQYhTlg.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-CBbj6YEl.cjs');
11
+ var spawnHappyCLI = require('./spawnHappyCLI-T9EQFmAR.cjs');
12
+ var controlClient = require('./controlClient-DMEMVw4Y.cjs');
13
+ var deviceKeys = require('./deviceKeys-CzqNNlG1.cjs');
14
+ var install = require('./install-_GiK-K1X.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-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-L9jLLtI1.mjs';
6
+ import { l as logger, i as isDebug, c as configuration, a as readSettings, p as packageJson } from './persistence-D2aRTiT1.mjs';
7
+ import { g as getProjectPath, a as claudeLocal, E as ExitCodeError, b as claudeCheckSession, s as systemPrompt } from './claudeLocal-CFEXiYts.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-CpnQ3aS1.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-CG_S7LH1.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-ZJZza9-9.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-D6oXm1Jk.mjs';
32
+ import { getEnvironmentInfo } from './doctor-CXYhUN9A.mjs';
33
+ import { notifyDaemonSessionStarted } from './controlClient-P5wJhu1R.mjs';
34
+ import { c as createMcpContext, r as registerKillSessionHandler, d as detectGitWorktree } from './registerKillSessionHandler-AcpB6OSk.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-CEJH5cHj.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-BoWfky2q.mjs';
52
52
  import 'qrcode-terminal';
53
- import './browser-CrNqwWO0.mjs';
53
+ import './browser-D9jMe1me.mjs';
54
54
  import 'open';
55
- import './deviceKeys-DL_YhAVs.mjs';
56
- import './install-MLAZRCd1.mjs';
55
+ import './deviceKeys-tcvwm4KA.mjs';
56
+ import './install-Cma2vLWa.mjs';
57
57
  import 'fastify';
58
58
  import 'fastify-type-provider-zod';
59
59
  import './doctor-ON7pN5G5.mjs';