@zonease/happy 0.6.42 → 0.6.44

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 (88) hide show
  1. package/dist/{api-CIE4_cWd.cjs → api-Ct7Z1lwF.cjs} +65 -5
  2. package/dist/{api-Ew6xr81i.mjs → api-D3j3YL3m.mjs} +65 -5
  3. package/dist/{auth-G9fZpVJ2.cjs → auth-B9kOli6i.cjs} +2 -2
  4. package/dist/{auth-BzYevJo2.mjs → auth-C2ZyAGMu.mjs} +4 -4
  5. package/dist/{auth-Q2STUNI-.mjs → auth-DPe89SQx.mjs} +2 -2
  6. package/dist/{auth-Dor79zTL.cjs → auth-W1aK_a-P.cjs} +4 -4
  7. package/dist/{browser-qDIIAL7S.cjs → browser-BPEhi6te.cjs} +1 -1
  8. package/dist/{browser-BI7s-V8E.mjs → browser-Byx5gq-W.mjs} +1 -1
  9. package/dist/{claudeLocal-Db2tDC4X.mjs → claudeLocal-BtxBYzs7.mjs} +1 -1
  10. package/dist/{claudeLocal-f4uBLJaM.cjs → claudeLocal-CcarNVE9.cjs} +1 -1
  11. package/dist/{config-CHjDs8AS.mjs → config-DJLOlgjr.mjs} +1 -1
  12. package/dist/{config-CSBlEik9.cjs → config-Djr2JMWO.cjs} +1 -1
  13. package/dist/{connect-O_nA5BOC.mjs → connect-PrUuHNLU.mjs} +4 -4
  14. package/dist/{connect-CJoTliii.cjs → connect-rcgRx9US.cjs} +4 -4
  15. package/dist/{controlClient-DoS9JqQC.mjs → controlClient-DSiES8Un.mjs} +1 -1
  16. package/dist/{controlClient-CEz_p1zc.cjs → controlClient-FDsMeGVn.cjs} +1 -1
  17. package/dist/{cursorApprovalHook-DRAHfTXU.cjs → cursorApprovalHook-CTJiiPO6.cjs} +29 -11
  18. package/dist/{cursorApprovalHook-B0v1kGa7.mjs → cursorApprovalHook-DGYeNaLe.mjs} +29 -11
  19. package/dist/{cursorApprovalServer-DzY8yJlI.cjs → cursorApprovalServer-C_wqI7wm.cjs} +109 -8
  20. package/dist/{cursorApprovalServer-JjPkdjGR.mjs → cursorApprovalServer-fjPBXSAb.mjs} +109 -10
  21. package/dist/{deviceKeys-B4aqjnE6.mjs → deviceKeys-BYHzWSyg.mjs} +2 -2
  22. package/dist/{deviceKeys-DyUn0bEx.cjs → deviceKeys-DPut1U07.cjs} +2 -2
  23. package/dist/{doctor-DRnft_5-.mjs → doctor-BA28TuMy.mjs} +2 -2
  24. package/dist/{doctor-BJSuVRUZ.cjs → doctor-B_W7odmC.cjs} +2 -2
  25. package/dist/{downloadImage-Cijmufpr.cjs → downloadImage-B7MKUI0u.cjs} +1 -372
  26. package/dist/downloadImage-DKWzC11M.mjs +485 -0
  27. package/dist/{enroll-CysDL6ee.mjs → enroll-Bc3Lke1H.mjs} +3 -3
  28. package/dist/{enroll-C2gunHdt.cjs → enroll-Blqx_Kg8.cjs} +3 -3
  29. package/dist/{evidence-DPutCuUr.mjs → evidence-D8nvQ8pQ.mjs} +1 -1
  30. package/dist/{evidence-DsQ-h-_W.cjs → evidence-jfqT04hc.cjs} +1 -1
  31. package/dist/{formatImageMessage-6aV9YFWr.mjs → formatImageMessage-BFWnadBR.mjs} +1 -1
  32. package/dist/{formatImageMessage-BPK_Ivfe.cjs → formatImageMessage-CZeSzoFO.cjs} +1 -1
  33. package/dist/index.cjs +50 -46
  34. package/dist/index.mjs +50 -46
  35. package/dist/{install-DBg0xQ3g.mjs → install-BvuadnKW.mjs} +1 -1
  36. package/dist/{install-BsTA2tZn.mjs → install-ChK8Eb58.mjs} +2 -2
  37. package/dist/{install-Tyhs6rUp.cjs → install-DFmp8KKp.cjs} +1 -1
  38. package/dist/{install-DlOppA-e.cjs → install-nJv8rYWA.cjs} +2 -2
  39. package/dist/lib.cjs +3 -3
  40. package/dist/lib.d.cts +1 -0
  41. package/dist/lib.d.mts +1 -0
  42. package/dist/lib.mjs +3 -3
  43. package/dist/{loadableToolOutput-DPGTtKJM.cjs → loadableToolOutput-CqMfcEh7.cjs} +2 -2
  44. package/dist/{loadableToolOutput-2dMPAp4N.mjs → loadableToolOutput-D6crYom_.mjs} +2 -2
  45. package/dist/{persistence-D9tMm6bj.mjs → persistence-BXtirP1k.mjs} +1 -1
  46. package/dist/{persistence-BcQmjobZ.cjs → persistence-DOgft4tZ.cjs} +1 -1
  47. package/dist/{push-Dxsugkes.mjs → push-188EjPTP.mjs} +6 -6
  48. package/dist/{push-Cnm6JQMD.cjs → push-DykEVhRR.cjs} +6 -6
  49. package/dist/registerKillSessionHandler-6loIZYD9.mjs +407 -0
  50. package/dist/registerKillSessionHandler-ks8iPY9a.cjs +411 -0
  51. package/dist/{run-CiPWWhXt.cjs → run-DSJ7IIWW.cjs} +10 -8
  52. package/dist/{run-BEflQaQ2.mjs → run-Dp4_De-r.mjs} +10 -8
  53. package/dist/{runClaude-BVXsyCHb.cjs → runClaude-C3be35z0.cjs} +26 -26
  54. package/dist/{runClaude-sPkSqHWY.mjs → runClaude-gj4urJvB.mjs} +21 -21
  55. package/dist/{runCodex-C4rHkIyw.mjs → runCodex-DRZidxNB.mjs} +26 -26
  56. package/dist/{runCodex-BAwhdmSA.cjs → runCodex-p_fToj9T.cjs} +28 -28
  57. package/dist/{runCursor-CD5wC3gA.cjs → runCursor-CJlqkSYR.cjs} +210 -34
  58. package/dist/{runCursor-DiHLWMs8.mjs → runCursor-SFd9G7X_.mjs} +210 -34
  59. package/dist/{runGemini-B-93gOqB.mjs → runGemini-BOsfh6dX.mjs} +17 -17
  60. package/dist/{runGemini-Cto-adir.cjs → runGemini-lxvXq70R.cjs} +19 -19
  61. package/dist/{runOneShot-BfIzE9W4.cjs → runOneShot-BExFkHmA.cjs} +2 -2
  62. package/dist/{runOneShot-DGjlYVhR.mjs → runOneShot-CtzlkF1l.mjs} +2 -2
  63. package/dist/{serve-CqL6NWDr.cjs → serve-Bl1Caf6x.cjs} +6 -6
  64. package/dist/{serve-ttlqF5jn.mjs → serve-DfvSXAD2.mjs} +6 -6
  65. package/dist/{session-BZhGpQUJ.cjs → session--R6FqsxL.cjs} +5 -5
  66. package/dist/{session-Co0REU9e.mjs → session-BSzNJiAb.mjs} +5 -5
  67. package/dist/{sessionKeyCache-BwFUGgMs.cjs → sessionKeyCache-CVkksG68.cjs} +1 -1
  68. package/dist/{sessionKeyCache-pXTjpETo.mjs → sessionKeyCache-DYiCIvYC.mjs} +1 -1
  69. package/dist/{sessionList-fG5uAPzU.cjs → sessionList-C1q9fy5j.cjs} +2 -2
  70. package/dist/{sessionList-DaufR-sA.mjs → sessionList-D3pCseut.mjs} +2 -2
  71. package/dist/{sessionSummary-CjZPt6nr.cjs → sessionSummary-CUZxEBEZ.cjs} +3 -3
  72. package/dist/{sessionSummary-Dv6dPABg.mjs → sessionSummary-WpOlgXe1.mjs} +3 -3
  73. package/dist/{setupOfflineReconnection-DW5il97B.mjs → setupOfflineReconnection-BeWF3SGN.mjs} +5 -3
  74. package/dist/{setupOfflineReconnection-B6coH9QN.cjs → setupOfflineReconnection-CixoiWTS.cjs} +7 -5
  75. package/dist/{spawnHappyCLI-DhUeycct.mjs → spawnHappyCLI-Cyc-69jO.mjs} +1 -1
  76. package/dist/{spawnHappyCLI-Cfu7C1uM.cjs → spawnHappyCLI-hRc7u16s.cjs} +1 -1
  77. package/dist/{ssh-C4YTSHXL.cjs → ssh-B_TYtrys.cjs} +5 -5
  78. package/dist/{ssh-8tPIKk-i.mjs → ssh-BsxAgWgG.mjs} +5 -5
  79. package/dist/{task-DFW-PecP.cjs → task-BInh42e3.cjs} +6 -6
  80. package/dist/{task-CH-oidFy.mjs → task-BS4usaxx.mjs} +6 -6
  81. package/dist/{uninstall-CeioRZNI.mjs → uninstall-BKuDaZKB.mjs} +2 -2
  82. package/dist/{uninstall-yTSL3X6Y.cjs → uninstall-DNFGxNLl.cjs} +2 -2
  83. package/dist/{update-Bju28vNP.cjs → update-B9LoI5l8.cjs} +3 -3
  84. package/dist/{update-Derd2hoX.mjs → update-DC300VOW.mjs} +3 -3
  85. package/package.json +1 -1
  86. package/dist/downloadImage-BpVF7A79.mjs +0 -854
  87. package/dist/gitWorktree-C6jLoaMw.cjs +0 -39
  88. package/dist/gitWorktree-CzJDU2BV.mjs +0 -37
@@ -5,11 +5,62 @@ var node_crypto = require('node:crypto');
5
5
  var promises = require('node:fs/promises');
6
6
  var fs = require('node:fs');
7
7
  var path = require('node:path');
8
- var logger = require('./persistence-BcQmjobZ.cjs');
8
+ var os = require('node:os');
9
+ var logger = require('./persistence-DOgft4tZ.cjs');
9
10
 
11
+ const TOOL_NAME_MAP = {
12
+ read: "Read",
13
+ write: "Write",
14
+ edit: "Edit",
15
+ grep: "Grep",
16
+ glob: "Glob",
17
+ ls: "LS",
18
+ list: "LS",
19
+ // Cursor calls it Shell; the app's card for running commands is Bash.
20
+ shell: "Bash",
21
+ terminal: "Bash",
22
+ task: "Task",
23
+ todowrite: "TodoWrite",
24
+ websearch: "WebSearch",
25
+ webfetch: "WebFetch"
26
+ };
27
+ function normalizeCursorToolName(rawName) {
28
+ const mapped = TOOL_NAME_MAP[rawName.toLowerCase()];
29
+ if (mapped) return mapped;
30
+ return rawName.charAt(0).toUpperCase() + rawName.slice(1);
31
+ }
32
+ function normalizeCursorToolInput(input) {
33
+ const out = { ...input };
34
+ if (typeof out.path === "string" && out.file_path === void 0) {
35
+ out.file_path = out.path;
36
+ }
37
+ if (typeof out.file_path === "string" && out.path === void 0) {
38
+ out.path = out.file_path;
39
+ }
40
+ if (typeof out.workingDirectory === "string" && out.cwd === void 0) {
41
+ out.cwd = out.workingDirectory;
42
+ }
43
+ if (typeof out.streamContent === "string" && out.content === void 0) {
44
+ out.content = out.streamContent;
45
+ }
46
+ return out;
47
+ }
48
+ function normalizeCursorTool(rawName, rawInput) {
49
+ const input = rawInput && typeof rawInput === "object" ? rawInput : {};
50
+ return {
51
+ name: normalizeCursorToolName(rawName),
52
+ input: normalizeCursorToolInput(input)
53
+ };
54
+ }
55
+
56
+ function approvalRegistryDir() {
57
+ return path.join(os.homedir(), ".happy", "cursor-approvals");
58
+ }
10
59
  const APPROVAL_ENDPOINT_FILE = ".happy-approval.json";
11
60
  const HOOKS_FILE = "hooks.json";
12
61
  const HOOKS_BACKUP_FILE = "hooks.json.happy-backup";
62
+ const MCP_FILE = "mcp.json";
63
+ const MCP_BACKUP_FILE = "mcp.json.happy-backup";
13
64
  const HOOK_TIMEOUT_SECONDS = 900;
14
65
  class CursorApprovalBridge {
15
66
  cwd;
@@ -18,12 +69,17 @@ class CursorApprovalBridge {
18
69
  server = null;
19
70
  port = 0;
20
71
  hadExistingHooks = false;
72
+ hadExistingMcp = false;
73
+ mcpBridge;
21
74
  /** True when .cursor/ did not exist before this session created it. */
22
75
  createdCursorDir = false;
23
76
  started = false;
77
+ /** Chat ids this session has registered, so they can be cleaned up. */
78
+ registeredChatIds = /* @__PURE__ */ new Set();
24
79
  constructor(options) {
25
80
  this.cwd = options.cwd;
26
81
  this.handler = options.handler;
82
+ this.mcpBridge = options.mcpBridge ?? null;
27
83
  }
28
84
  get cursorDir() {
29
85
  return path.join(this.cwd, ".cursor");
@@ -34,6 +90,23 @@ class CursorApprovalBridge {
34
90
  this.started = true;
35
91
  logger.logger.debug(`[cursor] approval bridge listening on 127.0.0.1:${this.port}`);
36
92
  }
93
+ /**
94
+ * Claims a chat id for this session. Called as soon as cursor-agent reveals
95
+ * it, which is what lets the hook route an approval back to the session
96
+ * that actually made the call.
97
+ */
98
+ async registerChat(chatId) {
99
+ if (!this.started || this.registeredChatIds.has(chatId)) return;
100
+ const dir = approvalRegistryDir();
101
+ await promises.mkdir(dir, { recursive: true });
102
+ await promises.writeFile(path.join(dir, `${chatId}.json`), JSON.stringify({
103
+ port: this.port,
104
+ token: this.token,
105
+ pid: process.pid
106
+ }), "utf8");
107
+ this.registeredChatIds.add(chatId);
108
+ logger.logger.debug(`[cursor] chat ${chatId} routed to this session's bridge`);
109
+ }
37
110
  listen() {
38
111
  return new Promise((resolve, reject) => {
39
112
  const server = node_http.createServer((req, res) => {
@@ -80,9 +153,12 @@ class CursorApprovalBridge {
80
153
  logger.logger.debug("[cursor] rejecting approval request with a bad token");
81
154
  return false;
82
155
  }
83
- const toolName = typeof parsed.tool_name === "string" && parsed.tool_name.length > 0 ? parsed.tool_name : "unknown tool";
156
+ const { name: toolName, input: toolInput } = normalizeCursorTool(
157
+ typeof parsed.tool_name === "string" && parsed.tool_name.length > 0 ? parsed.tool_name : "unknown",
158
+ parsed.tool_input
159
+ );
84
160
  const toolCallId = typeof parsed.tool_use_id === "string" && parsed.tool_use_id.length > 0 ? parsed.tool_use_id : `cursor-${node_crypto.randomUUID()}`;
85
- return this.handler.requestApproval(toolCallId, toolName, parsed.tool_input ?? {});
161
+ return this.handler.requestApproval(toolCallId, toolName, toolInput);
86
162
  }
87
163
  /**
88
164
  * Writes .cursor/hooks.json pointing at this CLI. Any file already there is
@@ -98,11 +174,6 @@ class CursorApprovalBridge {
98
174
  this.hadExistingHooks = true;
99
175
  logger.logger.debug("[cursor] existing hooks.json moved aside for the session");
100
176
  }
101
- await promises.writeFile(path.join(this.cursorDir, APPROVAL_ENDPOINT_FILE), JSON.stringify({
102
- port: this.port,
103
- token: this.token,
104
- pid: process.pid
105
- }), "utf8");
106
177
  const hookCommand = `${process.execPath} ${process.argv[1]} cursor-approval-hook`;
107
178
  await promises.writeFile(hooksPath, JSON.stringify({
108
179
  version: 1,
@@ -115,8 +186,24 @@ class CursorApprovalBridge {
115
186
  ]
116
187
  }
117
188
  }, null, 2), "utf8");
189
+ await this.installMcpConfig();
118
190
  await this.excludeFromGit();
119
191
  }
192
+ /** Points Cursor at Happy's tools (change_title, preview_html, devices…). */
193
+ async installMcpConfig() {
194
+ if (!this.mcpBridge) return;
195
+ const mcpPath = path.join(this.cursorDir, MCP_FILE);
196
+ if (fs.existsSync(mcpPath)) {
197
+ await promises.rename(mcpPath, path.join(this.cursorDir, MCP_BACKUP_FILE));
198
+ this.hadExistingMcp = true;
199
+ logger.logger.debug("[cursor] existing mcp.json moved aside for the session");
200
+ }
201
+ await promises.writeFile(mcpPath, JSON.stringify({
202
+ mcpServers: {
203
+ happy: { command: this.mcpBridge.command, args: this.mcpBridge.args }
204
+ }
205
+ }, null, 2), "utf8");
206
+ }
120
207
  /** Keep the session's scaffolding out of `git status`. */
121
208
  async excludeFromGit() {
122
209
  const excludePath = path.join(this.cwd, ".git", "info", "exclude");
@@ -128,6 +215,7 @@ class CursorApprovalBridge {
128
215
  excludePath,
129
216
  `${current}${current.endsWith("\n") ? "" : "\n"}# added by happy cursor session
130
217
  .cursor/hooks.json
218
+ .cursor/mcp.json
131
219
  .cursor/.happy-approval.json
132
220
  `,
133
221
  "utf8"
@@ -140,6 +228,10 @@ class CursorApprovalBridge {
140
228
  if (!this.started) return;
141
229
  this.started = false;
142
230
  try {
231
+ for (const chatId of this.registeredChatIds) {
232
+ await promises.rm(path.join(approvalRegistryDir(), `${chatId}.json`), { force: true });
233
+ }
234
+ this.registeredChatIds.clear();
143
235
  await promises.rm(path.join(this.cursorDir, APPROVAL_ENDPOINT_FILE), { force: true });
144
236
  const hooksPath = path.join(this.cursorDir, HOOKS_FILE);
145
237
  const backupPath = path.join(this.cursorDir, HOOKS_BACKUP_FILE);
@@ -148,6 +240,13 @@ class CursorApprovalBridge {
148
240
  } else {
149
241
  await promises.rm(hooksPath, { force: true });
150
242
  }
243
+ const mcpPath = path.join(this.cursorDir, MCP_FILE);
244
+ const mcpBackup = path.join(this.cursorDir, MCP_BACKUP_FILE);
245
+ if (this.hadExistingMcp && fs.existsSync(mcpBackup)) {
246
+ await promises.rename(mcpBackup, mcpPath);
247
+ } else if (this.mcpBridge) {
248
+ await promises.rm(mcpPath, { force: true });
249
+ }
151
250
  if (this.createdCursorDir && (await promises.readdir(this.cursorDir)).length === 0) {
152
251
  await promises.rmdir(this.cursorDir);
153
252
  }
@@ -165,3 +264,5 @@ class CursorApprovalBridge {
165
264
 
166
265
  exports.APPROVAL_ENDPOINT_FILE = APPROVAL_ENDPOINT_FILE;
167
266
  exports.CursorApprovalBridge = CursorApprovalBridge;
267
+ exports.approvalRegistryDir = approvalRegistryDir;
268
+ exports.normalizeCursorTool = normalizeCursorTool;
@@ -1,13 +1,64 @@
1
1
  import { createServer } from 'node:http';
2
2
  import { randomUUID } from 'node:crypto';
3
- import { mkdir, rename, writeFile, readFile, rm, readdir, rmdir } from 'node:fs/promises';
3
+ import { mkdir, writeFile, rename, readFile, rm, readdir, rmdir } from 'node:fs/promises';
4
4
  import { existsSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
- import { l as logger } from './persistence-D9tMm6bj.mjs';
6
+ import { homedir } from 'node:os';
7
+ import { l as logger } from './persistence-BXtirP1k.mjs';
7
8
 
9
+ const TOOL_NAME_MAP = {
10
+ read: "Read",
11
+ write: "Write",
12
+ edit: "Edit",
13
+ grep: "Grep",
14
+ glob: "Glob",
15
+ ls: "LS",
16
+ list: "LS",
17
+ // Cursor calls it Shell; the app's card for running commands is Bash.
18
+ shell: "Bash",
19
+ terminal: "Bash",
20
+ task: "Task",
21
+ todowrite: "TodoWrite",
22
+ websearch: "WebSearch",
23
+ webfetch: "WebFetch"
24
+ };
25
+ function normalizeCursorToolName(rawName) {
26
+ const mapped = TOOL_NAME_MAP[rawName.toLowerCase()];
27
+ if (mapped) return mapped;
28
+ return rawName.charAt(0).toUpperCase() + rawName.slice(1);
29
+ }
30
+ function normalizeCursorToolInput(input) {
31
+ const out = { ...input };
32
+ if (typeof out.path === "string" && out.file_path === void 0) {
33
+ out.file_path = out.path;
34
+ }
35
+ if (typeof out.file_path === "string" && out.path === void 0) {
36
+ out.path = out.file_path;
37
+ }
38
+ if (typeof out.workingDirectory === "string" && out.cwd === void 0) {
39
+ out.cwd = out.workingDirectory;
40
+ }
41
+ if (typeof out.streamContent === "string" && out.content === void 0) {
42
+ out.content = out.streamContent;
43
+ }
44
+ return out;
45
+ }
46
+ function normalizeCursorTool(rawName, rawInput) {
47
+ const input = rawInput && typeof rawInput === "object" ? rawInput : {};
48
+ return {
49
+ name: normalizeCursorToolName(rawName),
50
+ input: normalizeCursorToolInput(input)
51
+ };
52
+ }
53
+
54
+ function approvalRegistryDir() {
55
+ return join(homedir(), ".happy", "cursor-approvals");
56
+ }
8
57
  const APPROVAL_ENDPOINT_FILE = ".happy-approval.json";
9
58
  const HOOKS_FILE = "hooks.json";
10
59
  const HOOKS_BACKUP_FILE = "hooks.json.happy-backup";
60
+ const MCP_FILE = "mcp.json";
61
+ const MCP_BACKUP_FILE = "mcp.json.happy-backup";
11
62
  const HOOK_TIMEOUT_SECONDS = 900;
12
63
  class CursorApprovalBridge {
13
64
  cwd;
@@ -16,12 +67,17 @@ class CursorApprovalBridge {
16
67
  server = null;
17
68
  port = 0;
18
69
  hadExistingHooks = false;
70
+ hadExistingMcp = false;
71
+ mcpBridge;
19
72
  /** True when .cursor/ did not exist before this session created it. */
20
73
  createdCursorDir = false;
21
74
  started = false;
75
+ /** Chat ids this session has registered, so they can be cleaned up. */
76
+ registeredChatIds = /* @__PURE__ */ new Set();
22
77
  constructor(options) {
23
78
  this.cwd = options.cwd;
24
79
  this.handler = options.handler;
80
+ this.mcpBridge = options.mcpBridge ?? null;
25
81
  }
26
82
  get cursorDir() {
27
83
  return join(this.cwd, ".cursor");
@@ -32,6 +88,23 @@ class CursorApprovalBridge {
32
88
  this.started = true;
33
89
  logger.debug(`[cursor] approval bridge listening on 127.0.0.1:${this.port}`);
34
90
  }
91
+ /**
92
+ * Claims a chat id for this session. Called as soon as cursor-agent reveals
93
+ * it, which is what lets the hook route an approval back to the session
94
+ * that actually made the call.
95
+ */
96
+ async registerChat(chatId) {
97
+ if (!this.started || this.registeredChatIds.has(chatId)) return;
98
+ const dir = approvalRegistryDir();
99
+ await mkdir(dir, { recursive: true });
100
+ await writeFile(join(dir, `${chatId}.json`), JSON.stringify({
101
+ port: this.port,
102
+ token: this.token,
103
+ pid: process.pid
104
+ }), "utf8");
105
+ this.registeredChatIds.add(chatId);
106
+ logger.debug(`[cursor] chat ${chatId} routed to this session's bridge`);
107
+ }
35
108
  listen() {
36
109
  return new Promise((resolve, reject) => {
37
110
  const server = createServer((req, res) => {
@@ -78,9 +151,12 @@ class CursorApprovalBridge {
78
151
  logger.debug("[cursor] rejecting approval request with a bad token");
79
152
  return false;
80
153
  }
81
- const toolName = typeof parsed.tool_name === "string" && parsed.tool_name.length > 0 ? parsed.tool_name : "unknown tool";
154
+ const { name: toolName, input: toolInput } = normalizeCursorTool(
155
+ typeof parsed.tool_name === "string" && parsed.tool_name.length > 0 ? parsed.tool_name : "unknown",
156
+ parsed.tool_input
157
+ );
82
158
  const toolCallId = typeof parsed.tool_use_id === "string" && parsed.tool_use_id.length > 0 ? parsed.tool_use_id : `cursor-${randomUUID()}`;
83
- return this.handler.requestApproval(toolCallId, toolName, parsed.tool_input ?? {});
159
+ return this.handler.requestApproval(toolCallId, toolName, toolInput);
84
160
  }
85
161
  /**
86
162
  * Writes .cursor/hooks.json pointing at this CLI. Any file already there is
@@ -96,11 +172,6 @@ class CursorApprovalBridge {
96
172
  this.hadExistingHooks = true;
97
173
  logger.debug("[cursor] existing hooks.json moved aside for the session");
98
174
  }
99
- await writeFile(join(this.cursorDir, APPROVAL_ENDPOINT_FILE), JSON.stringify({
100
- port: this.port,
101
- token: this.token,
102
- pid: process.pid
103
- }), "utf8");
104
175
  const hookCommand = `${process.execPath} ${process.argv[1]} cursor-approval-hook`;
105
176
  await writeFile(hooksPath, JSON.stringify({
106
177
  version: 1,
@@ -113,8 +184,24 @@ class CursorApprovalBridge {
113
184
  ]
114
185
  }
115
186
  }, null, 2), "utf8");
187
+ await this.installMcpConfig();
116
188
  await this.excludeFromGit();
117
189
  }
190
+ /** Points Cursor at Happy's tools (change_title, preview_html, devices…). */
191
+ async installMcpConfig() {
192
+ if (!this.mcpBridge) return;
193
+ const mcpPath = join(this.cursorDir, MCP_FILE);
194
+ if (existsSync(mcpPath)) {
195
+ await rename(mcpPath, join(this.cursorDir, MCP_BACKUP_FILE));
196
+ this.hadExistingMcp = true;
197
+ logger.debug("[cursor] existing mcp.json moved aside for the session");
198
+ }
199
+ await writeFile(mcpPath, JSON.stringify({
200
+ mcpServers: {
201
+ happy: { command: this.mcpBridge.command, args: this.mcpBridge.args }
202
+ }
203
+ }, null, 2), "utf8");
204
+ }
118
205
  /** Keep the session's scaffolding out of `git status`. */
119
206
  async excludeFromGit() {
120
207
  const excludePath = join(this.cwd, ".git", "info", "exclude");
@@ -126,6 +213,7 @@ class CursorApprovalBridge {
126
213
  excludePath,
127
214
  `${current}${current.endsWith("\n") ? "" : "\n"}# added by happy cursor session
128
215
  .cursor/hooks.json
216
+ .cursor/mcp.json
129
217
  .cursor/.happy-approval.json
130
218
  `,
131
219
  "utf8"
@@ -138,6 +226,10 @@ class CursorApprovalBridge {
138
226
  if (!this.started) return;
139
227
  this.started = false;
140
228
  try {
229
+ for (const chatId of this.registeredChatIds) {
230
+ await rm(join(approvalRegistryDir(), `${chatId}.json`), { force: true });
231
+ }
232
+ this.registeredChatIds.clear();
141
233
  await rm(join(this.cursorDir, APPROVAL_ENDPOINT_FILE), { force: true });
142
234
  const hooksPath = join(this.cursorDir, HOOKS_FILE);
143
235
  const backupPath = join(this.cursorDir, HOOKS_BACKUP_FILE);
@@ -146,6 +238,13 @@ class CursorApprovalBridge {
146
238
  } else {
147
239
  await rm(hooksPath, { force: true });
148
240
  }
241
+ const mcpPath = join(this.cursorDir, MCP_FILE);
242
+ const mcpBackup = join(this.cursorDir, MCP_BACKUP_FILE);
243
+ if (this.hadExistingMcp && existsSync(mcpBackup)) {
244
+ await rename(mcpBackup, mcpPath);
245
+ } else if (this.mcpBridge) {
246
+ await rm(mcpPath, { force: true });
247
+ }
149
248
  if (this.createdCursorDir && (await readdir(this.cursorDir)).length === 0) {
150
249
  await rmdir(this.cursorDir);
151
250
  }
@@ -161,4 +260,4 @@ class CursorApprovalBridge {
161
260
  }
162
261
  }
163
262
 
164
- export { APPROVAL_ENDPOINT_FILE as A, CursorApprovalBridge as C };
263
+ export { APPROVAL_ENDPOINT_FILE as A, CursorApprovalBridge as C, approvalRegistryDir as a, normalizeCursorTool as n };
@@ -1,6 +1,6 @@
1
1
  import axios from 'axios';
2
- import { h as decodeBase64, e as encodeBase64, f as encrypt, g as decrypt, c as configuration } from './persistence-D9tMm6bj.mjs';
3
- import { decryptWithEphemeralKey } from './auth-Q2STUNI-.mjs';
2
+ import { h as decodeBase64, e as encodeBase64, f as encrypt, g as decrypt, c as configuration } from './persistence-BXtirP1k.mjs';
3
+ import { decryptWithEphemeralKey } from './auth-DPe89SQx.mjs';
4
4
  import tweetnacl from 'tweetnacl';
5
5
  import { existsSync } from 'node:fs';
6
6
  import { writeFile, mkdir, readFile } from 'node:fs/promises';
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var axios = require('axios');
4
- var logger = require('./persistence-BcQmjobZ.cjs');
5
- var auth = require('./auth-G9fZpVJ2.cjs');
4
+ var logger = require('./persistence-DOgft4tZ.cjs');
5
+ var auth = require('./auth-B9kOli6i.cjs');
6
6
  var tweetnacl = require('tweetnacl');
7
7
  var fs = require('node:fs');
8
8
  var promises = require('node:fs/promises');
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
- import { p as packageJson, c as configuration, a as readSettings, r as readCredentials, m as readDaemonState } from './persistence-D9tMm6bj.mjs';
3
- import { checkIfDaemonRunningAndCleanupStaleState } from './controlClient-DoS9JqQC.mjs';
2
+ import { p as packageJson, c as configuration, a as readSettings, r as readCredentials, m as readDaemonState } from './persistence-BXtirP1k.mjs';
3
+ import { checkIfDaemonRunningAndCleanupStaleState } from './controlClient-DSiES8Un.mjs';
4
4
  import { findAllHappyProcesses } from './doctor-ON7pN5G5.mjs';
5
5
  import { existsSync, readdirSync, statSync } from 'node:fs';
6
6
  import { join } from 'node:path';
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var chalk = require('chalk');
4
- var logger = require('./persistence-BcQmjobZ.cjs');
5
- var controlClient = require('./controlClient-CEz_p1zc.cjs');
4
+ var logger = require('./persistence-DOgft4tZ.cjs');
5
+ var controlClient = require('./controlClient-FDsMeGVn.cjs');
6
6
  var doctor = require('./doctor-C08m5Z9f.cjs');
7
7
  var fs = require('node:fs');
8
8
  var path = require('node:path');