happy-coder 0.3.1-beta.1 → 0.4.0

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 (49) hide show
  1. package/dist/index.cjs +76 -27
  2. package/dist/index.mjs +76 -27
  3. package/dist/lib.cjs +1 -1
  4. package/dist/lib.d.cts +1 -0
  5. package/dist/lib.d.mts +1 -0
  6. package/dist/lib.mjs +1 -1
  7. package/dist/{types-CzYKFAYa.mjs → types-VkaGP8up.mjs} +1 -1
  8. package/dist/{types-D7u2DxfV.cjs → types-eN-YHsuj.cjs} +1 -0
  9. package/package.json +8 -6
  10. package/dist/index-B2GqfEZV.cjs +0 -1564
  11. package/dist/index-QItBXhux.mjs +0 -1540
  12. package/dist/install-B0DnBGS_.mjs +0 -29
  13. package/dist/install-B2r_gX72.cjs +0 -109
  14. package/dist/install-C809w0Cj.cjs +0 -31
  15. package/dist/install-DEPy62QN.mjs +0 -97
  16. package/dist/install-GZIzyuIE.cjs +0 -99
  17. package/dist/install-HKe7dyS4.mjs +0 -107
  18. package/dist/run-BmEaINbl.cjs +0 -250
  19. package/dist/run-DMbKhYfb.mjs +0 -247
  20. package/dist/run-FBXkmmN7.mjs +0 -32
  21. package/dist/run-q2To6b-c.cjs +0 -34
  22. package/dist/types-BG9AgCI4.mjs +0 -875
  23. package/dist/types-BRICSarm.mjs +0 -870
  24. package/dist/types-BTQRfIr3.cjs +0 -892
  25. package/dist/types-BX4xv8Ty.mjs +0 -881
  26. package/dist/types-BeUppqJU.cjs +0 -886
  27. package/dist/types-C6Wx_bRW.cjs +0 -886
  28. package/dist/types-CEvzGLMI.cjs +0 -882
  29. package/dist/types-CKUdOV6c.mjs +0 -875
  30. package/dist/types-CNuBtNA5.cjs +0 -884
  31. package/dist/types-Cg4664gs.cjs +0 -879
  32. package/dist/types-CkPUFpfr.cjs +0 -885
  33. package/dist/types-D39L8JSd.mjs +0 -850
  34. package/dist/types-DD9P_5rj.mjs +0 -868
  35. package/dist/types-DNu8okOb.mjs +0 -874
  36. package/dist/types-DXK5YldG.cjs +0 -892
  37. package/dist/types-DYBiuNUQ.cjs +0 -883
  38. package/dist/types-Df5dlWLV.mjs +0 -871
  39. package/dist/types-fXgEaaqP.mjs +0 -861
  40. package/dist/types-hotUTaWz.cjs +0 -863
  41. package/dist/types-ikrrEcJm.mjs +0 -873
  42. package/dist/types-mykDX2xe.cjs +0 -872
  43. package/dist/types-tLWMaptR.mjs +0 -879
  44. package/dist/uninstall-BGgl5V8F.mjs +0 -29
  45. package/dist/uninstall-BWHglipH.mjs +0 -40
  46. package/dist/uninstall-C42CoSCI.cjs +0 -53
  47. package/dist/uninstall-CLkTtlMv.mjs +0 -51
  48. package/dist/uninstall-CdHMb6wi.cjs +0 -31
  49. package/dist/uninstall-FXyyAuGU.cjs +0 -42
@@ -1,1564 +0,0 @@
1
- 'use strict';
2
-
3
- var chalk = require('chalk');
4
- var types = require('./types-DYBiuNUQ.cjs');
5
- var node_crypto = require('node:crypto');
6
- var claudeCode = require('@anthropic-ai/claude-code');
7
- var node_fs = require('node:fs');
8
- var os = require('node:os');
9
- var node_path = require('node:path');
10
- var node_child_process = require('node:child_process');
11
- var node_readline = require('node:readline');
12
- var node_url = require('node:url');
13
- var promises = require('node:fs/promises');
14
- var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
15
- var node_http = require('node:http');
16
- var streamableHttp_js = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
17
- var z = require('zod');
18
- var node_https = require('node:https');
19
- var net = require('node:net');
20
- var tweetnacl = require('tweetnacl');
21
- var axios = require('axios');
22
- var qrcode = require('qrcode-terminal');
23
-
24
- var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
25
- function _interopNamespaceDefault(e) {
26
- var n = Object.create(null);
27
- if (e) {
28
- Object.keys(e).forEach(function (k) {
29
- if (k !== 'default') {
30
- var d = Object.getOwnPropertyDescriptor(e, k);
31
- Object.defineProperty(n, k, d.get ? d : {
32
- enumerable: true,
33
- get: function () { return e[k]; }
34
- });
35
- }
36
- });
37
- }
38
- n.default = e;
39
- return Object.freeze(n);
40
- }
41
-
42
- var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
43
-
44
- function formatClaudeMessage(message, onAssistantResult) {
45
- types.logger.debugLargeJson("[CLAUDE] Message from non interactive & remote mode:", message);
46
- switch (message.type) {
47
- case "system": {
48
- const sysMsg = message;
49
- if (sysMsg.subtype === "init") {
50
- console.log(chalk.gray("\u2500".repeat(60)));
51
- console.log(chalk.blue.bold("\u{1F680} Session initialized:"), chalk.cyan(sysMsg.session_id));
52
- console.log(chalk.gray(` Model: ${sysMsg.model}`));
53
- console.log(chalk.gray(` CWD: ${sysMsg.cwd}`));
54
- if (sysMsg.tools && sysMsg.tools.length > 0) {
55
- console.log(chalk.gray(` Tools: ${sysMsg.tools.join(", ")}`));
56
- }
57
- console.log(chalk.gray("\u2500".repeat(60)));
58
- }
59
- break;
60
- }
61
- case "user": {
62
- const userMsg = message;
63
- if (userMsg.message && typeof userMsg.message === "object" && "content" in userMsg.message) {
64
- const content = userMsg.message.content;
65
- if (typeof content === "string") {
66
- console.log(chalk.magenta.bold("\n\u{1F464} User:"), content);
67
- } else if (Array.isArray(content)) {
68
- for (const block of content) {
69
- if (block.type === "text") {
70
- console.log(chalk.magenta.bold("\n\u{1F464} User:"), block.text);
71
- } else if (block.type === "tool_result") {
72
- console.log(chalk.green.bold("\n\u2705 Tool Result:"), chalk.gray(`(Tool ID: ${block.tool_use_id})`));
73
- if (block.content) {
74
- const outputStr = typeof block.content === "string" ? block.content : JSON.stringify(block.content, null, 2);
75
- const maxLength = 200;
76
- if (outputStr.length > maxLength) {
77
- console.log(outputStr.substring(0, maxLength) + chalk.gray("\n... (truncated)"));
78
- } else {
79
- console.log(outputStr);
80
- }
81
- }
82
- }
83
- }
84
- } else {
85
- console.log(chalk.magenta.bold("\n\u{1F464} User:"), JSON.stringify(content, null, 2));
86
- }
87
- }
88
- break;
89
- }
90
- case "assistant": {
91
- const assistantMsg = message;
92
- if (assistantMsg.message && assistantMsg.message.content) {
93
- console.log(chalk.cyan.bold("\n\u{1F916} Assistant:"));
94
- for (const block of assistantMsg.message.content) {
95
- if (block.type === "text") {
96
- console.log(block.text);
97
- } else if (block.type === "tool_use") {
98
- console.log(chalk.yellow.bold(`
99
- \u{1F527} Tool: ${block.name}`));
100
- if (block.input) {
101
- const inputStr = JSON.stringify(block.input, null, 2);
102
- const maxLength = 500;
103
- if (inputStr.length > maxLength) {
104
- console.log(chalk.gray("Input:"), inputStr.substring(0, maxLength) + chalk.gray("\n... (truncated)"));
105
- } else {
106
- console.log(chalk.gray("Input:"), inputStr);
107
- }
108
- }
109
- }
110
- }
111
- }
112
- break;
113
- }
114
- case "result": {
115
- const resultMsg = message;
116
- if (resultMsg.subtype === "success") {
117
- if ("result" in resultMsg && resultMsg.result) {
118
- console.log(chalk.green.bold("\n\u2728 Summary:"));
119
- console.log(resultMsg.result);
120
- }
121
- if (resultMsg.usage) {
122
- console.log(chalk.gray("\n\u{1F4CA} Session Stats:"));
123
- console.log(chalk.gray(` \u2022 Turns: ${resultMsg.num_turns}`));
124
- console.log(chalk.gray(` \u2022 Input tokens: ${resultMsg.usage.input_tokens}`));
125
- console.log(chalk.gray(` \u2022 Output tokens: ${resultMsg.usage.output_tokens}`));
126
- if (resultMsg.usage.cache_read_input_tokens) {
127
- console.log(chalk.gray(` \u2022 Cache read tokens: ${resultMsg.usage.cache_read_input_tokens}`));
128
- }
129
- if (resultMsg.usage.cache_creation_input_tokens) {
130
- console.log(chalk.gray(` \u2022 Cache creation tokens: ${resultMsg.usage.cache_creation_input_tokens}`));
131
- }
132
- console.log(chalk.gray(` \u2022 Cost: $${resultMsg.total_cost_usd.toFixed(4)}`));
133
- console.log(chalk.gray(` \u2022 Duration: ${resultMsg.duration_ms}ms`));
134
- console.log(chalk.gray("\n\u{1F440} Back already?"));
135
- console.log(chalk.green("\u{1F449} Press any key to continue your session in `claude`"));
136
- if (onAssistantResult) {
137
- Promise.resolve(onAssistantResult(resultMsg)).catch((err) => {
138
- types.logger.debug("Error in onAssistantResult callback:", err);
139
- });
140
- }
141
- }
142
- } else if (resultMsg.subtype === "error_max_turns") {
143
- console.log(chalk.red.bold("\n\u274C Error: Maximum turns reached"));
144
- console.log(chalk.gray(`Completed ${resultMsg.num_turns} turns`));
145
- } else if (resultMsg.subtype === "error_during_execution") {
146
- console.log(chalk.red.bold("\n\u274C Error during execution"));
147
- console.log(chalk.gray(`Completed ${resultMsg.num_turns} turns before error`));
148
- types.logger.debugLargeJson("[RESULT] Error during execution", resultMsg);
149
- }
150
- break;
151
- }
152
- default: {
153
- const exhaustiveCheck = message;
154
- if (process.env.DEBUG) {
155
- console.log(chalk.gray(`[Unknown message type]`), exhaustiveCheck);
156
- }
157
- }
158
- }
159
- }
160
- function printDivider() {
161
- console.log(chalk.gray("\u2550".repeat(60)));
162
- }
163
-
164
- function claudeCheckSession(sessionId, path) {
165
- const projectName = node_path.resolve(path).replace(/\//g, "-");
166
- const projectDir = node_path.join(os.homedir(), ".claude", "projects", projectName);
167
- const sessionFile = node_path.join(projectDir, `${sessionId}.jsonl`);
168
- const sessionExists = node_fs.existsSync(sessionFile);
169
- if (!sessionExists) {
170
- types.logger.debug(`[claudeCheckSession] Path ${sessionFile} does not exist`);
171
- return false;
172
- }
173
- const sessionData = node_fs.readFileSync(sessionFile, "utf-8").split("\n");
174
- const hasGoodMessage = !!sessionData.find((v) => {
175
- try {
176
- return typeof JSON.parse(v).uuid === "string";
177
- } catch (e) {
178
- return false;
179
- }
180
- });
181
- return hasGoodMessage;
182
- }
183
-
184
- async function claudeRemote(opts) {
185
- let startFrom = opts.sessionId;
186
- if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path)) {
187
- startFrom = null;
188
- }
189
- const abortController = new AbortController();
190
- const sdkOptions = {
191
- cwd: opts.path,
192
- resume: startFrom ?? void 0,
193
- mcpServers: opts.mcpServers,
194
- permissionPromptToolName: opts.permissionPromptToolName,
195
- executable: "node",
196
- abortController
197
- };
198
- let aborted = false;
199
- let response;
200
- opts.abort.addEventListener("abort", () => {
201
- if (!aborted) {
202
- aborted = true;
203
- if (response) {
204
- (async () => {
205
- try {
206
- const r = await response.interrupt();
207
- } catch (e) {
208
- }
209
- abortController.abort();
210
- })();
211
- } else {
212
- abortController.abort();
213
- }
214
- }
215
- });
216
- types.logger.debug(`[claudeRemote] Starting query with messages`);
217
- response = claudeCode.query({
218
- prompt: opts.messages,
219
- abortController,
220
- options: sdkOptions
221
- });
222
- if (opts.interruptController) {
223
- opts.interruptController.register(async () => {
224
- types.logger.debug("[claudeRemote] Interrupting Claude via SDK");
225
- await response.interrupt();
226
- });
227
- }
228
- printDivider();
229
- try {
230
- types.logger.debug(`[claudeRemote] Starting to iterate over response`);
231
- for await (const message of response) {
232
- types.logger.debug(`[claudeRemote] Received message from SDK: ${message.type}`);
233
- formatClaudeMessage(message, opts.onAssistantResult);
234
- if (message.type === "system" && message.subtype === "init") {
235
- const projectName = node_path.resolve(opts.path).replace(/\//g, "-");
236
- const projectDir = node_path.join(os.homedir(), ".claude", "projects", projectName);
237
- node_fs.mkdirSync(projectDir, { recursive: true });
238
- const watcher = node_fs.watch(projectDir).on("change", (_, filename) => {
239
- if (filename === `${message.session_id}.jsonl`) {
240
- opts.onSessionFound(message.session_id);
241
- watcher.close();
242
- }
243
- });
244
- }
245
- }
246
- types.logger.debug(`[claudeRemote] Finished iterating over response`);
247
- } catch (e) {
248
- if (abortController.signal.aborted) {
249
- types.logger.debug(`[claudeRemote] Aborted`);
250
- }
251
- if (e instanceof claudeCode.AbortError) {
252
- types.logger.debug(`[claudeRemote] Aborted`);
253
- } else {
254
- throw e;
255
- }
256
- } finally {
257
- if (opts.interruptController) {
258
- opts.interruptController.unregister();
259
- }
260
- }
261
- printDivider();
262
- types.logger.debug(`[claudeRemote] Function completed`);
263
- }
264
-
265
- const __dirname$1 = node_path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-B2GqfEZV.cjs', document.baseURI).href))));
266
- async function claudeLocal(opts) {
267
- const projectName = node_path.resolve(opts.path).replace(/\//g, "-");
268
- const projectDir = node_path.join(os.homedir(), ".claude", "projects", projectName);
269
- node_fs.mkdirSync(projectDir, { recursive: true });
270
- const watcher = node_fs.watch(projectDir);
271
- let resolvedSessionId = null;
272
- const detectedIdsRandomUUID = /* @__PURE__ */ new Set();
273
- const detectedIdsFileSystem = /* @__PURE__ */ new Set();
274
- watcher.on("change", (event, filename) => {
275
- if (typeof filename === "string" && filename.toLowerCase().endsWith(".jsonl")) {
276
- types.logger.debug("change", event, filename);
277
- const sessionId = filename.replace(".jsonl", "");
278
- if (detectedIdsFileSystem.has(sessionId)) {
279
- return;
280
- }
281
- detectedIdsFileSystem.add(sessionId);
282
- if (resolvedSessionId) {
283
- return;
284
- }
285
- if (detectedIdsRandomUUID.has(sessionId)) {
286
- resolvedSessionId = sessionId;
287
- opts.onSessionFound(sessionId);
288
- }
289
- }
290
- });
291
- let startFrom = opts.sessionId;
292
- if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path)) {
293
- startFrom = null;
294
- }
295
- try {
296
- process.stdin.pause();
297
- await new Promise((r, reject) => {
298
- const args = [];
299
- if (startFrom) {
300
- args.push("--resume", startFrom);
301
- }
302
- const claudeCliPath = process.env.HAPPY_CLAUDE_CLI_PATH || node_path.resolve(node_path.join(__dirname$1, "..", "scripts", "claudeInteractiveLaunch.cjs"));
303
- const child = node_child_process.spawn("node", [claudeCliPath, ...args], {
304
- stdio: ["inherit", "inherit", "inherit", "pipe"],
305
- signal: opts.abort,
306
- cwd: opts.path
307
- });
308
- if (child.stdio[3]) {
309
- const rl = node_readline.createInterface({
310
- input: child.stdio[3],
311
- crlfDelay: Infinity
312
- });
313
- rl.on("line", (line) => {
314
- const sessionMatch = line.match(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i);
315
- if (sessionMatch) {
316
- detectedIdsRandomUUID.add(sessionMatch[0]);
317
- if (resolvedSessionId) {
318
- return;
319
- }
320
- if (detectedIdsFileSystem.has(sessionMatch[0])) {
321
- resolvedSessionId = sessionMatch[0];
322
- opts.onSessionFound(sessionMatch[0]);
323
- }
324
- }
325
- });
326
- rl.on("error", (err) => {
327
- console.error("Error reading from fd 3:", err);
328
- });
329
- }
330
- child.on("error", (error) => {
331
- });
332
- child.on("exit", (code, signal) => {
333
- if (signal === "SIGTERM" && opts.abort.aborted) {
334
- r();
335
- } else if (signal) {
336
- reject(new Error(`Process terminated with signal: ${signal}`));
337
- } else {
338
- r();
339
- }
340
- });
341
- });
342
- } finally {
343
- watcher.close();
344
- process.stdin.resume();
345
- }
346
- return resolvedSessionId;
347
- }
348
-
349
- class MessageQueue {
350
- queue = [];
351
- waiters = [];
352
- closed = false;
353
- closePromise;
354
- closeResolve;
355
- constructor() {
356
- this.closePromise = new Promise((resolve) => {
357
- this.closeResolve = resolve;
358
- });
359
- }
360
- /**
361
- * Push a message to the queue
362
- */
363
- push(message) {
364
- if (this.closed) {
365
- throw new Error("Cannot push to closed queue");
366
- }
367
- types.logger.debug(`[MessageQueue] push() called. Waiters: ${this.waiters.length}, Queue size before: ${this.queue.length}`);
368
- const waiter = this.waiters.shift();
369
- if (waiter) {
370
- types.logger.debug(`[MessageQueue] Found waiter! Delivering message directly: "${message}"`);
371
- waiter({
372
- type: "user",
373
- message: {
374
- role: "user",
375
- content: message
376
- },
377
- parent_tool_use_id: null,
378
- session_id: ""
379
- });
380
- } else {
381
- types.logger.debug(`[MessageQueue] No waiter found. Adding to queue: "${message}"`);
382
- this.queue.push({
383
- type: "user",
384
- message: {
385
- role: "user",
386
- content: message
387
- },
388
- parent_tool_use_id: null,
389
- session_id: ""
390
- });
391
- }
392
- types.logger.debug(`[MessageQueue] push() completed. Waiters: ${this.waiters.length}, Queue size after: ${this.queue.length}`);
393
- }
394
- /**
395
- * Close the queue - no more messages can be pushed
396
- */
397
- close() {
398
- types.logger.debug(`[MessageQueue] close() called. Waiters: ${this.waiters.length}`);
399
- this.closed = true;
400
- this.closeResolve?.();
401
- }
402
- /**
403
- * Check if the queue is closed
404
- */
405
- isClosed() {
406
- return this.closed;
407
- }
408
- /**
409
- * Get the current queue size
410
- */
411
- size() {
412
- return this.queue.length;
413
- }
414
- /**
415
- * Async iterator implementation
416
- */
417
- async *[Symbol.asyncIterator]() {
418
- types.logger.debug(`[MessageQueue] Iterator started`);
419
- while (true) {
420
- const message = this.queue.shift();
421
- if (message !== void 0) {
422
- types.logger.debug(`[MessageQueue] Iterator yielding queued message`);
423
- yield message;
424
- continue;
425
- }
426
- if (this.closed) {
427
- types.logger.debug(`[MessageQueue] Iterator ending - queue closed`);
428
- return;
429
- }
430
- types.logger.debug(`[MessageQueue] Iterator waiting for next message...`);
431
- const nextMessage = await this.waitForNext();
432
- if (nextMessage === void 0) {
433
- types.logger.debug(`[MessageQueue] Iterator ending - no more messages`);
434
- return;
435
- }
436
- types.logger.debug(`[MessageQueue] Iterator yielding waited message`);
437
- yield nextMessage;
438
- }
439
- }
440
- /**
441
- * Wait for the next message or queue closure
442
- */
443
- waitForNext() {
444
- return new Promise((resolve) => {
445
- if (this.closed) {
446
- types.logger.debug(`[MessageQueue] waitForNext() called but queue is closed`);
447
- resolve(void 0);
448
- return;
449
- }
450
- const waiter = (value) => resolve(value);
451
- this.waiters.push(waiter);
452
- types.logger.debug(`[MessageQueue] waitForNext() adding waiter. Total waiters: ${this.waiters.length}`);
453
- this.closePromise?.then(() => {
454
- const index = this.waiters.indexOf(waiter);
455
- if (index !== -1) {
456
- this.waiters.splice(index, 1);
457
- types.logger.debug(`[MessageQueue] waitForNext() waiter removed due to close. Remaining waiters: ${this.waiters.length}`);
458
- resolve(void 0);
459
- }
460
- });
461
- });
462
- }
463
- }
464
-
465
- class InvalidateSync {
466
- _invalidated = false;
467
- _invalidatedDouble = false;
468
- _stopped = false;
469
- _command;
470
- _pendings = [];
471
- constructor(command) {
472
- this._command = command;
473
- }
474
- invalidate() {
475
- if (this._stopped) {
476
- return;
477
- }
478
- if (!this._invalidated) {
479
- this._invalidated = true;
480
- this._invalidatedDouble = false;
481
- this._doSync();
482
- } else {
483
- if (!this._invalidatedDouble) {
484
- this._invalidatedDouble = true;
485
- }
486
- }
487
- }
488
- async invalidateAndAwait() {
489
- if (this._stopped) {
490
- return;
491
- }
492
- await new Promise((resolve) => {
493
- this._pendings.push(resolve);
494
- this.invalidate();
495
- });
496
- }
497
- stop() {
498
- if (this._stopped) {
499
- return;
500
- }
501
- this._notifyPendings();
502
- this._stopped = true;
503
- }
504
- _notifyPendings = () => {
505
- for (let pending of this._pendings) {
506
- pending();
507
- }
508
- this._pendings = [];
509
- };
510
- _doSync = async () => {
511
- await types.backoff(async () => {
512
- if (this._stopped) {
513
- return;
514
- }
515
- await this._command();
516
- });
517
- if (this._stopped) {
518
- this._notifyPendings();
519
- return;
520
- }
521
- if (this._invalidatedDouble) {
522
- this._invalidatedDouble = false;
523
- this._doSync();
524
- } else {
525
- this._invalidated = false;
526
- this._notifyPendings();
527
- }
528
- };
529
- }
530
-
531
- function createSessionScanner(opts) {
532
- const projectName = node_path.resolve(opts.workingDirectory).replace(/\//g, "-");
533
- const projectDir = node_path.join(os.homedir(), ".claude", "projects", projectName);
534
- let finishedSessions = /* @__PURE__ */ new Set();
535
- let pendingSessions = /* @__PURE__ */ new Set();
536
- let currentSessionId = null;
537
- let currentSessionWatcherAbortController = null;
538
- let processedMessages = /* @__PURE__ */ new Set();
539
- let seenRemoteUserMessageCounters = /* @__PURE__ */ new Map();
540
- const sync = new InvalidateSync(async () => {
541
- let sessions = [];
542
- for (let p of pendingSessions) {
543
- sessions.push(p);
544
- }
545
- if (currentSessionId) {
546
- sessions.push(currentSessionId);
547
- }
548
- let processSessionFile = async (sessionId) => {
549
- const expectedSessionFile = node_path.join(projectDir, `${sessionId}.jsonl`);
550
- let file;
551
- try {
552
- file = await promises.readFile(expectedSessionFile, "utf-8");
553
- } catch (error) {
554
- return;
555
- }
556
- let lines = file.split("\n");
557
- for (let l of lines) {
558
- try {
559
- let message = JSON.parse(l);
560
- let parsed = types.RawJSONLinesSchema.safeParse(message);
561
- if (!parsed.success) {
562
- types.logger.debugLargeJson(`[SESSION_SCANNER] Failed to parse message`, message);
563
- continue;
564
- }
565
- let key = getMessageKey(parsed.data);
566
- if (processedMessages.has(key)) {
567
- continue;
568
- }
569
- processedMessages.add(key);
570
- types.logger.debugLargeJson(`[SESSION_SCANNER] Processing message`, parsed.data);
571
- types.logger.debug(`[SESSION_SCANNER] Message key (new): ${key}`);
572
- if (parsed.data.type === "user" && typeof parsed.data.message.content === "string") {
573
- const currentCounter = seenRemoteUserMessageCounters.get(parsed.data.message.content);
574
- if (currentCounter && currentCounter > 0) {
575
- seenRemoteUserMessageCounters.set(parsed.data.message.content, currentCounter - 1);
576
- continue;
577
- }
578
- }
579
- opts.onMessage(parsed.data);
580
- } catch (e) {
581
- continue;
582
- }
583
- }
584
- };
585
- for (let session of sessions) {
586
- await processSessionFile(session);
587
- }
588
- for (let p of sessions) {
589
- if (pendingSessions.has(p)) {
590
- pendingSessions.delete(p);
591
- finishedSessions.add(p);
592
- }
593
- }
594
- currentSessionWatcherAbortController?.abort();
595
- currentSessionWatcherAbortController = new AbortController();
596
- void (async () => {
597
- if (currentSessionId) {
598
- const sessionFile = node_path.join(projectDir, `${currentSessionId}.jsonl`);
599
- try {
600
- for await (const change of promises.watch(sessionFile, { persistent: true, signal: currentSessionWatcherAbortController.signal })) {
601
- await processSessionFile(currentSessionId);
602
- }
603
- } catch (error) {
604
- if (error.name !== "AbortError") {
605
- types.logger.debug(`[SESSION_SCANNER] Watch error: ${error.message}`);
606
- }
607
- }
608
- }
609
- })();
610
- });
611
- const intervalId = setInterval(() => {
612
- sync.invalidate();
613
- }, 3e3);
614
- return {
615
- refresh: () => sync.invalidate(),
616
- cleanup: () => {
617
- clearInterval(intervalId);
618
- currentSessionWatcherAbortController?.abort();
619
- },
620
- onNewSession: (sessionId) => {
621
- if (currentSessionId === sessionId) {
622
- return;
623
- }
624
- if (finishedSessions.has(sessionId)) {
625
- return;
626
- }
627
- if (pendingSessions.has(sessionId)) {
628
- return;
629
- }
630
- if (currentSessionId) {
631
- pendingSessions.add(currentSessionId);
632
- }
633
- types.logger.debug(`[SESSION_SCANNER] New session: ${sessionId}`);
634
- currentSessionId = sessionId;
635
- sync.invalidate();
636
- },
637
- onRemoteUserMessageForDeduplication: (messageContent) => {
638
- seenRemoteUserMessageCounters.set(messageContent, (seenRemoteUserMessageCounters.get(messageContent) || 0) + 1);
639
- }
640
- };
641
- }
642
- function getMessageKey(message) {
643
- if (message.type === "user") {
644
- return `user:${message.uuid}`;
645
- } else if (message.type === "assistant") {
646
- const { usage, ...messageWithoutUsage } = message.message;
647
- return stableStringify(messageWithoutUsage);
648
- } else if (message.type === "summary") {
649
- return `summary:${message.leafUuid}`;
650
- } else if (message.type === "system") {
651
- return `system:${message.uuid}`;
652
- }
653
- return `unknown:<error, this should be unreachable>`;
654
- }
655
- function stableStringify(obj) {
656
- return JSON.stringify(sortKeys(obj), null, 2);
657
- }
658
- function sortKeys(value) {
659
- if (Array.isArray(value)) {
660
- return value.map(sortKeys);
661
- } else if (value && typeof value === "object" && value.constructor === Object) {
662
- return Object.keys(value).sort().reduce((result, key) => {
663
- result[key] = sortKeys(value[key]);
664
- return result;
665
- }, {});
666
- } else {
667
- return value;
668
- }
669
- }
670
-
671
- async function loop(opts) {
672
- let mode = opts.startingMode ?? "interactive";
673
- let currentMessageQueue = new MessageQueue();
674
- let sessionId = null;
675
- let onMessage = null;
676
- const sessionScanner = createSessionScanner({
677
- workingDirectory: opts.path,
678
- onMessage: (message) => {
679
- opts.session.sendClaudeSessionMessage(message);
680
- }
681
- });
682
- opts.session.onUserMessage((message) => {
683
- sessionScanner.onRemoteUserMessageForDeduplication(message.content.text);
684
- currentMessageQueue.push(message.content.text);
685
- types.logger.debugLargeJson("User message pushed to queue:", message);
686
- if (onMessage) {
687
- onMessage();
688
- }
689
- });
690
- let onSessionFound = (newSessionId) => {
691
- sessionId = newSessionId;
692
- sessionScanner.onNewSession(newSessionId);
693
- };
694
- while (true) {
695
- if (currentMessageQueue.size() > 0) {
696
- mode = "remote";
697
- continue;
698
- }
699
- if (mode === "interactive") {
700
- let abortedOutside = false;
701
- const interactiveAbortController = new AbortController();
702
- opts.session.setHandler("switch", () => {
703
- if (!interactiveAbortController.signal.aborted) {
704
- abortedOutside = true;
705
- mode = "remote";
706
- interactiveAbortController.abort();
707
- }
708
- });
709
- onMessage = () => {
710
- if (!interactiveAbortController.signal.aborted) {
711
- abortedOutside = true;
712
- mode = "remote";
713
- interactiveAbortController.abort();
714
- }
715
- onMessage = null;
716
- };
717
- await claudeLocal({
718
- path: opts.path,
719
- sessionId,
720
- onSessionFound,
721
- abort: interactiveAbortController.signal
722
- });
723
- onMessage = null;
724
- if (!abortedOutside) {
725
- return;
726
- }
727
- if (mode !== "interactive") {
728
- console.log("Switching to remote mode...");
729
- }
730
- }
731
- if (mode === "remote") {
732
- types.logger.debug("Starting " + sessionId);
733
- const remoteAbortController = new AbortController();
734
- opts.session.setHandler("abort", () => {
735
- if (!remoteAbortController.signal.aborted) {
736
- remoteAbortController.abort();
737
- }
738
- });
739
- const abortHandler = () => {
740
- if (!remoteAbortController.signal.aborted) {
741
- mode = "interactive";
742
- remoteAbortController.abort();
743
- }
744
- if (process.stdin.isTTY) {
745
- process.stdin.setRawMode(false);
746
- }
747
- };
748
- process.stdin.resume();
749
- if (process.stdin.isTTY) {
750
- process.stdin.setRawMode(true);
751
- }
752
- process.stdin.setEncoding("utf8");
753
- process.stdin.on("data", abortHandler);
754
- try {
755
- types.logger.debug(`Starting claudeRemote with messages: ${currentMessageQueue.size()}`);
756
- await claudeRemote({
757
- abort: remoteAbortController.signal,
758
- sessionId,
759
- path: opts.path,
760
- mcpServers: opts.mcpServers,
761
- permissionPromptToolName: opts.permissionPromptToolName,
762
- onSessionFound,
763
- messages: currentMessageQueue,
764
- onAssistantResult: opts.onAssistantResult,
765
- interruptController: opts.interruptController
766
- });
767
- } finally {
768
- process.stdin.off("data", abortHandler);
769
- if (process.stdin.isTTY) {
770
- process.stdin.setRawMode(false);
771
- }
772
- currentMessageQueue.close();
773
- currentMessageQueue = new MessageQueue();
774
- }
775
- if (mode !== "remote") {
776
- console.log("Switching back to good old claude...");
777
- }
778
- }
779
- }
780
- }
781
-
782
- async function startPermissionServerV2(handler) {
783
- const mcp = new mcp_js.McpServer({
784
- name: "Permission Server",
785
- version: "1.0.0",
786
- description: "A server that allows you to request permissions from the user"
787
- });
788
- mcp.registerTool("ask_permission", {
789
- description: "Request permission to execute a tool",
790
- title: "Request Permission",
791
- inputSchema: {
792
- tool_name: z.z.string().describe("The tool that needs permission"),
793
- input: z.z.any().describe("The arguments for the tool")
794
- }
795
- // outputSchema: {
796
- // approved: z.boolean().describe('Whether the tool was approved'),
797
- // reason: z.string().describe('The reason for the approval or denial'),
798
- // },
799
- }, async (args) => {
800
- const response = await handler({ name: args.tool_name, arguments: args.input });
801
- const result = response.approved ? { behavior: "allow", updatedInput: args.input || {} } : { behavior: "deny", message: response.reason || "Permission denied by user" };
802
- return {
803
- content: [
804
- {
805
- type: "text",
806
- text: JSON.stringify(result)
807
- }
808
- ],
809
- isError: false
810
- };
811
- });
812
- const transport = new streamableHttp_js.StreamableHTTPServerTransport({
813
- // NOTE: Returning session id here will result in claude
814
- // sdk spawn to fail with `Invalid Request: Server already initialized`
815
- sessionIdGenerator: void 0
816
- });
817
- await mcp.connect(transport);
818
- const server = node_http.createServer(async (req, res) => {
819
- try {
820
- await transport.handleRequest(req, res);
821
- } catch (error) {
822
- types.logger.debug("Error handling request:", error);
823
- if (!res.headersSent) {
824
- res.writeHead(500).end();
825
- }
826
- }
827
- });
828
- const baseUrl = await new Promise((resolve) => {
829
- server.listen(0, "127.0.0.1", () => {
830
- const addr = server.address();
831
- resolve(new URL(`http://127.0.0.1:${addr.port}`));
832
- });
833
- });
834
- return {
835
- url: baseUrl.toString(),
836
- toolName: "ask_permission"
837
- };
838
- }
839
-
840
- class InterruptController {
841
- interruptFn;
842
- isInterrupting = false;
843
- /**
844
- * Register an interrupt function from claudeRemote
845
- */
846
- register(fn) {
847
- this.interruptFn = fn;
848
- }
849
- /**
850
- * Unregister the interrupt function (cleanup)
851
- */
852
- unregister() {
853
- this.interruptFn = void 0;
854
- this.isInterrupting = false;
855
- }
856
- /**
857
- * Trigger the interrupt - can be called from anywhere
858
- */
859
- async interrupt() {
860
- if (!this.interruptFn || this.isInterrupting) {
861
- return false;
862
- }
863
- this.isInterrupting = true;
864
- try {
865
- await this.interruptFn();
866
- return true;
867
- } catch (error) {
868
- types.logger.debug("Failed to interrupt Claude:", error);
869
- return false;
870
- } finally {
871
- this.isInterrupting = false;
872
- }
873
- }
874
- /**
875
- * Check if interrupt is available
876
- */
877
- canInterrupt() {
878
- return !!this.interruptFn && !this.isInterrupting;
879
- }
880
- }
881
-
882
- var version = "0.1.9";
883
- var packageJson = {
884
- version: version};
885
-
886
- async function startAnthropicActivityProxy(onClaudeActivity) {
887
- const requestTimeouts = /* @__PURE__ */ new Map();
888
- let requestCounter = 0;
889
- let idleTimer = null;
890
- const maxTimeBeforeIdle = 50;
891
- const requestTimeout = 5 * 60 * 1e3;
892
- const cleanupRequest = (requestId, reason) => {
893
- const timeout = requestTimeouts.get(requestId);
894
- if (timeout) {
895
- clearTimeout(timeout);
896
- requestTimeouts.delete(requestId);
897
- types.logger.debug(`[AnthropicProxy #${requestId}] Cleaned up (${reason}), active requests: ${requestTimeouts.size}`);
898
- claudeDidSomeWork();
899
- }
900
- };
901
- const claudeDidSomeWork = () => {
902
- if (idleTimer) clearTimeout(idleTimer);
903
- if (requestTimeouts.size === 0) {
904
- idleTimer = setTimeout(() => {
905
- types.logger.debug(`[AnthropicProxy] Idle for ${maxTimeBeforeIdle}ms, active requests: ${requestTimeouts.size}`);
906
- onClaudeActivity("idle");
907
- }, maxTimeBeforeIdle);
908
- }
909
- };
910
- const server = node_http.createServer((req, res) => {
911
- const requestId = ++requestCounter;
912
- const isAnthropicRequest = req.headers.host === "api.anthropic.com" || req.url?.includes("anthropic.com");
913
- if (isAnthropicRequest) {
914
- const timeout = setTimeout(() => {
915
- types.logger.debug(`[AnthropicProxy #${requestId}] Request timeout after ${requestTimeout}ms`);
916
- cleanupRequest(requestId, "timeout");
917
- }, requestTimeout);
918
- requestTimeouts.set(requestId, timeout);
919
- onClaudeActivity("working");
920
- types.logger.debug(`[AnthropicProxy #${requestId}] Anthropic request: ${req.method} ${req.url}, active requests: ${requestTimeouts.size}`);
921
- }
922
- const chunks = [];
923
- req.on("data", (chunk) => {
924
- chunks.push(chunk);
925
- if (isAnthropicRequest) {
926
- claudeDidSomeWork();
927
- }
928
- });
929
- req.on("end", () => {
930
- const body = Buffer.concat(chunks);
931
- let targetUrl;
932
- if (isAnthropicRequest) {
933
- targetUrl = new node_url.URL(req.url || "/", "https://api.anthropic.com");
934
- } else {
935
- const protocol = req.headers["x-forwarded-proto"] || "https";
936
- const host = req.headers.host || "localhost";
937
- targetUrl = new node_url.URL(req.url || "/", `${protocol}://${host}`);
938
- }
939
- const options = {
940
- hostname: targetUrl.hostname,
941
- port: targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80),
942
- path: targetUrl.pathname + targetUrl.search,
943
- method: req.method,
944
- headers: {
945
- ...req.headers,
946
- host: targetUrl.hostname
947
- }
948
- };
949
- const requestMethod = targetUrl.protocol === "https:" ? node_https.request : node_http.request;
950
- const proxyReq = requestMethod(options, (proxyRes) => {
951
- res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
952
- proxyRes.pipe(res);
953
- proxyRes.on("end", () => {
954
- if (isAnthropicRequest) {
955
- cleanupRequest(requestId, "completed");
956
- }
957
- });
958
- });
959
- proxyReq.on("error", (error) => {
960
- if (isAnthropicRequest) {
961
- cleanupRequest(requestId, `error: ${error.message}`);
962
- } else {
963
- types.logger.debug(`[AnthropicProxy #${requestId}] Error:`, error.message);
964
- }
965
- res.writeHead(502);
966
- res.end("Bad Gateway");
967
- });
968
- if (body.length > 0) {
969
- proxyReq.write(body);
970
- }
971
- proxyReq.end();
972
- });
973
- });
974
- server.on("connect", (req, clientSocket, head) => {
975
- const requestId = ++requestCounter;
976
- const [hostname, port] = req.url?.split(":") || ["", "443"];
977
- const isAnthropicRequest = hostname === "api.anthropic.com";
978
- if (isAnthropicRequest) {
979
- const timeout = setTimeout(() => {
980
- types.logger.debug(`[AnthropicProxy #${requestId}] CONNECT timeout after ${requestTimeout}ms`);
981
- cleanupRequest(requestId, "timeout");
982
- }, requestTimeout);
983
- requestTimeouts.set(requestId, timeout);
984
- onClaudeActivity("working");
985
- types.logger.debug(`[AnthropicProxy #${requestId}] CONNECT to api.anthropic.com, active requests: ${requestTimeouts.size}`);
986
- }
987
- const serverSocket = net.connect(parseInt(port) || 443, hostname, () => {
988
- clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
989
- serverSocket.write(head);
990
- serverSocket.pipe(clientSocket);
991
- clientSocket.pipe(serverSocket);
992
- });
993
- const cleanup = () => {
994
- if (isAnthropicRequest) {
995
- cleanupRequest(requestId, "CONNECT closed");
996
- }
997
- };
998
- serverSocket.on("error", (err) => {
999
- types.logger.debug(`[AnthropicProxy #${requestId}] CONNECT error:`, err.message);
1000
- clientSocket.end();
1001
- cleanup();
1002
- });
1003
- clientSocket.on("error", cleanup);
1004
- clientSocket.on("end", cleanup);
1005
- serverSocket.on("end", cleanup);
1006
- });
1007
- const url = await new Promise((resolve) => {
1008
- server.listen(0, "127.0.0.1", () => {
1009
- const addr = server.address();
1010
- if (addr && typeof addr === "object") {
1011
- resolve(`http://127.0.0.1:${addr.port}`);
1012
- }
1013
- });
1014
- });
1015
- types.logger.debug(`[AnthropicProxy] Started at ${url}`);
1016
- return {
1017
- url,
1018
- cleanup: () => {
1019
- if (idleTimer) clearTimeout(idleTimer);
1020
- for (const [requestId, timeout] of requestTimeouts) {
1021
- clearTimeout(timeout);
1022
- types.logger.debug(`[AnthropicProxy] Cleaning up timeout for request #${requestId}`);
1023
- }
1024
- requestTimeouts.clear();
1025
- if (requestTimeouts.size > 0) {
1026
- types.logger.debug(`[AnthropicProxy] Warning: ${requestTimeouts.size} active requests still pending at cleanup:`, Array.from(requestTimeouts.keys()));
1027
- }
1028
- server.close();
1029
- }
1030
- };
1031
- }
1032
-
1033
- async function start(credentials, options = {}) {
1034
- const workingDirectory = process.cwd();
1035
- const sessionTag = node_crypto.randomUUID();
1036
- const api = new types.ApiClient(credentials.token, credentials.secret);
1037
- let state = {};
1038
- let metadata = { path: workingDirectory, host: os.hostname(), version: packageJson.version };
1039
- const response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
1040
- types.logger.debug(`Session created: ${response.id}`);
1041
- const session = api.session(response);
1042
- const pushClient = api.push();
1043
- let thinking = false;
1044
- let pingInterval = setInterval(() => {
1045
- session.keepAlive(thinking);
1046
- }, 2e3);
1047
- const antropicActivityProxy = await startAnthropicActivityProxy(
1048
- (activity) => {
1049
- const newThinking = activity === "working";
1050
- if (newThinking !== thinking) {
1051
- thinking = newThinking;
1052
- types.logger.debug(`[PING] Thinking state changed: ${thinking}`);
1053
- session.keepAlive(thinking);
1054
- }
1055
- }
1056
- );
1057
- process.env.HTTP_PROXY = antropicActivityProxy.url;
1058
- process.env.HTTPS_PROXY = antropicActivityProxy.url;
1059
- types.logger.debug(`[AnthropicProxy] Set HTTP_PROXY and HTTPS_PROXY to ${antropicActivityProxy.url}`);
1060
- const logPath = await types.logger.logFilePathPromise;
1061
- types.logger.info(`Session: ${response.id}`);
1062
- types.logger.infoDeveloper(`Logs: ${logPath}`);
1063
- const interruptController = new InterruptController();
1064
- let requests = /* @__PURE__ */ new Map();
1065
- const permissionServer = await startPermissionServerV2(async (request) => {
1066
- const id = node_crypto.randomUUID();
1067
- let promise = new Promise((resolve) => {
1068
- requests.set(id, resolve);
1069
- });
1070
- let timeout = setTimeout(async () => {
1071
- types.logger.info("Permission timeout - attempting to interrupt Claude");
1072
- const interrupted = await interruptController.interrupt();
1073
- if (interrupted) {
1074
- types.logger.info("Claude interrupted successfully");
1075
- }
1076
- requests.delete(id);
1077
- session.updateAgentState((currentState) => {
1078
- let r = { ...currentState.requests };
1079
- delete r[id];
1080
- return {
1081
- ...currentState,
1082
- requests: r
1083
- };
1084
- });
1085
- }, 1e3 * 60 * 4.5);
1086
- types.logger.info("Permission request" + id + " " + JSON.stringify(request));
1087
- try {
1088
- await pushClient.sendToAllDevices(
1089
- "Permission Request",
1090
- `Claude wants to use ${request.name}`,
1091
- {
1092
- sessionId: response.id,
1093
- requestId: id,
1094
- tool: request.name,
1095
- type: "permission_request"
1096
- }
1097
- );
1098
- types.logger.info("Push notification sent for permission request");
1099
- } catch (error) {
1100
- types.logger.debug("Failed to send push notification:", error);
1101
- }
1102
- session.updateAgentState((currentState) => ({
1103
- ...currentState,
1104
- requests: {
1105
- ...currentState.requests,
1106
- [id]: {
1107
- tool: request.name,
1108
- arguments: request.arguments
1109
- }
1110
- }
1111
- }));
1112
- promise.then(() => clearTimeout(timeout)).catch(() => clearTimeout(timeout));
1113
- return promise;
1114
- });
1115
- session.setHandler("permission", async (message) => {
1116
- types.logger.info("Permission response" + JSON.stringify(message));
1117
- const id = message.id;
1118
- const resolve = requests.get(id);
1119
- if (resolve) {
1120
- if (!message.approved) {
1121
- types.logger.debug("Permission denied, interrupting Claude");
1122
- await interruptController.interrupt();
1123
- }
1124
- resolve({ approved: message.approved, reason: message.reason });
1125
- } else {
1126
- types.logger.info("Permission request stale, likely timed out");
1127
- return;
1128
- }
1129
- session.updateAgentState((currentState) => {
1130
- let r = { ...currentState.requests };
1131
- delete r[id];
1132
- return {
1133
- ...currentState,
1134
- requests: r
1135
- };
1136
- });
1137
- });
1138
- session.setHandler("abort", async () => {
1139
- types.logger.info("Abort request - interrupting Claude");
1140
- await interruptController.interrupt();
1141
- });
1142
- const onAssistantResult = async (result) => {
1143
- try {
1144
- const summary = "result" in result && result.result ? result.result.substring(0, 100) + (result.result.length > 100 ? "..." : "") : "";
1145
- await pushClient.sendToAllDevices(
1146
- "Your move :D",
1147
- summary,
1148
- {
1149
- sessionId: response.id,
1150
- type: "assistant_result",
1151
- turns: result.num_turns,
1152
- duration_ms: result.duration_ms,
1153
- cost_usd: result.total_cost_usd
1154
- }
1155
- );
1156
- types.logger.debug("Push notification sent: Assistant result");
1157
- } catch (error) {
1158
- types.logger.debug("Failed to send assistant result push notification:", error);
1159
- }
1160
- };
1161
- await loop({
1162
- path: workingDirectory,
1163
- model: options.model,
1164
- permissionMode: options.permissionMode,
1165
- startingMode: options.startingMode,
1166
- mcpServers: {
1167
- "permission": {
1168
- type: "http",
1169
- url: permissionServer.url
1170
- }
1171
- },
1172
- permissionPromptToolName: "mcp__permission__" + permissionServer.toolName,
1173
- session,
1174
- onAssistantResult,
1175
- interruptController
1176
- });
1177
- clearInterval(pingInterval);
1178
- if (antropicActivityProxy) {
1179
- types.logger.info("[AnthropicProxy] Shutting down activity monitoring proxy");
1180
- antropicActivityProxy.cleanup();
1181
- }
1182
- process.exit(0);
1183
- }
1184
-
1185
- const defaultSettings = {
1186
- onboardingCompleted: false
1187
- };
1188
- async function readSettings() {
1189
- if (!node_fs.existsSync(types.configuration.settingsFile)) {
1190
- return { ...defaultSettings };
1191
- }
1192
- try {
1193
- const content = await promises.readFile(types.configuration.settingsFile, "utf8");
1194
- return JSON.parse(content);
1195
- } catch {
1196
- return { ...defaultSettings };
1197
- }
1198
- }
1199
- async function writeSettings(settings) {
1200
- if (!node_fs.existsSync(types.configuration.happyDir)) {
1201
- await promises.mkdir(types.configuration.happyDir, { recursive: true });
1202
- }
1203
- await promises.writeFile(types.configuration.settingsFile, JSON.stringify(settings, null, 2));
1204
- }
1205
- const credentialsSchema = z__namespace.object({
1206
- secret: z__namespace.string().base64(),
1207
- token: z__namespace.string()
1208
- });
1209
- async function readCredentials() {
1210
- if (!node_fs.existsSync(types.configuration.privateKeyFile)) {
1211
- return null;
1212
- }
1213
- try {
1214
- const keyBase64 = await promises.readFile(types.configuration.privateKeyFile, "utf8");
1215
- const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
1216
- return {
1217
- secret: new Uint8Array(Buffer.from(credentials.secret, "base64")),
1218
- token: credentials.token
1219
- };
1220
- } catch {
1221
- return null;
1222
- }
1223
- }
1224
- async function writeCredentials(credentials) {
1225
- if (!node_fs.existsSync(types.configuration.happyDir)) {
1226
- await promises.mkdir(types.configuration.happyDir, { recursive: true });
1227
- }
1228
- await promises.writeFile(types.configuration.privateKeyFile, JSON.stringify({
1229
- secret: types.encodeBase64(credentials.secret),
1230
- token: credentials.token
1231
- }, null, 2));
1232
- }
1233
-
1234
- function displayQRCode(url) {
1235
- console.log("=".repeat(80));
1236
- console.log("\u{1F4F1} To authenticate, scan this QR code with your mobile device:");
1237
- console.log("=".repeat(80));
1238
- qrcode.generate(url, { small: true }, (qr) => {
1239
- for (let l of qr.split("\n")) {
1240
- console.log(" ".repeat(10) + l);
1241
- }
1242
- });
1243
- console.log("=".repeat(80));
1244
- }
1245
-
1246
- async function doAuth() {
1247
- console.log("Starting authentication...");
1248
- const secret = new Uint8Array(node_crypto.randomBytes(32));
1249
- const keypair = tweetnacl.box.keyPair.fromSecretKey(secret);
1250
- try {
1251
- await axios.post(`${types.configuration.serverUrl}/v1/auth/request`, {
1252
- publicKey: types.encodeBase64(keypair.publicKey)
1253
- });
1254
- } catch (error) {
1255
- console.log("Failed to create authentication request, please try again later.");
1256
- return null;
1257
- }
1258
- console.log("Please, authenticate using mobile app");
1259
- const authUrl = "happy://terminal?" + types.encodeBase64Url(keypair.publicKey);
1260
- displayQRCode(authUrl);
1261
- if (process.env.DEBUG === "1") {
1262
- console.log("\n\u{1F4CB} For manual entry, copy this URL:");
1263
- console.log(authUrl);
1264
- }
1265
- let credentials = null;
1266
- while (true) {
1267
- try {
1268
- const response = await axios.post(`${types.configuration.serverUrl}/v1/auth/request`, {
1269
- publicKey: types.encodeBase64(keypair.publicKey)
1270
- });
1271
- if (response.data.state === "authorized") {
1272
- let token = response.data.token;
1273
- let r = types.decodeBase64(response.data.response);
1274
- let decrypted = decryptWithEphemeralKey(r, keypair.secretKey);
1275
- if (decrypted) {
1276
- credentials = {
1277
- secret: decrypted,
1278
- token
1279
- };
1280
- await writeCredentials(credentials);
1281
- return credentials;
1282
- } else {
1283
- console.log("Failed to decrypt response, please try again later.");
1284
- return null;
1285
- }
1286
- }
1287
- } catch (error) {
1288
- console.log("Failed to create authentication request, please try again later.");
1289
- return null;
1290
- }
1291
- await types.delay(1e3);
1292
- }
1293
- return null;
1294
- }
1295
- function decryptWithEphemeralKey(encryptedBundle, recipientSecretKey) {
1296
- const ephemeralPublicKey = encryptedBundle.slice(0, 32);
1297
- const nonce = encryptedBundle.slice(32, 32 + tweetnacl.box.nonceLength);
1298
- const encrypted = encryptedBundle.slice(32 + tweetnacl.box.nonceLength);
1299
- const decrypted = tweetnacl.box.open(encrypted, nonce, ephemeralPublicKey, recipientSecretKey);
1300
- if (!decrypted) {
1301
- return null;
1302
- }
1303
- return decrypted;
1304
- }
1305
-
1306
- (async () => {
1307
- const args = process.argv.slice(2);
1308
- let installationLocation = args.includes("--local") || process.env.HANDY_LOCAL ? "local" : "global";
1309
- types.initializeConfiguration(installationLocation);
1310
- types.initLoggerWithGlobalConfiguration();
1311
- types.logger.debug("Starting happy CLI with args: ", process.argv);
1312
- const subcommand = args[0];
1313
- if (subcommand === "logout") {
1314
- try {
1315
- await cleanKey();
1316
- } catch (error) {
1317
- console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
1318
- if (process.env.DEBUG) {
1319
- console.error(error);
1320
- }
1321
- process.exit(1);
1322
- }
1323
- return;
1324
- } else if (subcommand === "daemon") {
1325
- if (process.env.HAPPY_DAEMON_MODE) {
1326
- const { run } = await Promise.resolve().then(function () { return require('./run-BmEaINbl.cjs'); });
1327
- await run();
1328
- } else {
1329
- const daemonSubcommand = args[1];
1330
- if (daemonSubcommand === "install") {
1331
- const { install } = await Promise.resolve().then(function () { return require('./install-C809w0Cj.cjs'); });
1332
- try {
1333
- await install();
1334
- } catch (error) {
1335
- console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
1336
- process.exit(1);
1337
- }
1338
- } else if (daemonSubcommand === "uninstall") {
1339
- const { uninstall } = await Promise.resolve().then(function () { return require('./uninstall-CdHMb6wi.cjs'); });
1340
- try {
1341
- await uninstall();
1342
- } catch (error) {
1343
- console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
1344
- process.exit(1);
1345
- }
1346
- } else {
1347
- console.log(`
1348
- ${chalk.bold("happy daemon")} - Daemon management
1349
-
1350
- ${chalk.bold("Usage:")}
1351
- sudo happy daemon install Install the daemon (requires sudo)
1352
- sudo happy daemon uninstall Uninstall the daemon (requires sudo)
1353
-
1354
- ${chalk.bold("Note:")} The daemon runs in the background and provides persistent services.
1355
- Currently only supported on macOS.
1356
- `);
1357
- }
1358
- }
1359
- return;
1360
- } else {
1361
- const options = {};
1362
- let showHelp = false;
1363
- let showVersion = false;
1364
- let forceAuth = false;
1365
- for (let i = 0; i < args.length; i++) {
1366
- const arg = args[i];
1367
- if (arg === "-h" || arg === "--help") {
1368
- showHelp = true;
1369
- } else if (arg === "-v" || arg === "--version") {
1370
- showVersion = true;
1371
- } else if (arg === "--auth" || arg === "--login") {
1372
- forceAuth = true;
1373
- } else if (arg === "-m" || arg === "--model") {
1374
- options.model = args[++i];
1375
- } else if (arg === "-p" || arg === "--permission-mode") {
1376
- options.permissionMode = z.z.enum(["auto", "default", "plan"]).parse(args[++i]);
1377
- } else if (arg === "--local") {
1378
- i++;
1379
- } else if (arg === "--happy-starting-mode") {
1380
- options.startingMode = z.z.enum(["interactive", "remote"]).parse(args[++i]);
1381
- } else if (arg === "--happy-daemon") {
1382
- process.env.HAPPY_DAEMON_MODE = "true";
1383
- const { run } = await Promise.resolve().then(function () { return require('./run-BmEaINbl.cjs'); });
1384
- await run();
1385
- return;
1386
- } else if (arg === "--happy-daemon-start") {
1387
- const { spawn } = await import('child_process');
1388
- const { isDaemonRunning } = await Promise.resolve().then(function () { return require('./run-BmEaINbl.cjs'); });
1389
- if (isDaemonRunning()) {
1390
- console.log("Happy daemon is already running");
1391
- return;
1392
- }
1393
- const happyPath = process.argv[1];
1394
- const daemonProcess = spawn("node", [happyPath, "--happy-daemon"], {
1395
- detached: true,
1396
- stdio: "ignore",
1397
- env: process.env
1398
- });
1399
- daemonProcess.unref();
1400
- console.log("Happy daemon started");
1401
- return;
1402
- } else if (arg === "--happy-daemon-stop") {
1403
- const { isDaemonRunning } = await Promise.resolve().then(function () { return require('./run-BmEaINbl.cjs'); });
1404
- const pidFile = node_path.join(os.homedir(), ".happy", "daemon-pid");
1405
- if (!node_fs.existsSync(pidFile)) {
1406
- console.log("Happy daemon is not running");
1407
- return;
1408
- }
1409
- try {
1410
- const pid = parseInt(await promises.readFile(pidFile, "utf-8"));
1411
- process.kill(pid, "SIGTERM");
1412
- console.log("Happy daemon stopped");
1413
- } catch (error) {
1414
- console.error("Failed to stop daemon:", error);
1415
- }
1416
- return;
1417
- } else if (arg === "--happy-daemon-install") {
1418
- const { install } = await Promise.resolve().then(function () { return require('./install-GZIzyuIE.cjs'); });
1419
- await install();
1420
- return;
1421
- } else if (arg === "--happy-daemon-uninstall") {
1422
- const { uninstall } = await Promise.resolve().then(function () { return require('./uninstall-FXyyAuGU.cjs'); });
1423
- await uninstall();
1424
- return;
1425
- } else {
1426
- console.error(chalk.red(`Unknown argument: ${arg}`));
1427
- process.exit(1);
1428
- }
1429
- }
1430
- if (showHelp) {
1431
- console.log(`
1432
- ${chalk.bold("happy")} - Claude Code session sharing
1433
-
1434
- ${chalk.bold("Usage:")}
1435
- happy [options]
1436
- happy logout Logs out of your account and removes data directory
1437
- happy daemon Manage the background daemon (macOS only)
1438
-
1439
- ${chalk.bold("Options:")}
1440
- -h, --help Show this help message
1441
- -v, --version Show version
1442
- -m, --model <model> Claude model to use (default: sonnet)
1443
- -p, --permission-mode Permission mode: auto, default, or plan
1444
- --auth, --login Force re-authentication
1445
-
1446
- [Daemon Management]
1447
- --happy-daemon-start Start the daemon in background
1448
- --happy-daemon-stop Stop the daemon
1449
- --happy-daemon-install Install daemon to run on startup
1450
- --happy-daemon-uninstall Uninstall daemon from startup
1451
-
1452
- [Advanced]
1453
- --local < global | local >
1454
- Will use .happy folder in the current directory for storing your private key and debug logs.
1455
- You will require re-login each time you run this in a new directory.
1456
- --happy-starting-mode <interactive|remote>
1457
- Set the starting mode for new sessions (default: remote)
1458
-
1459
- ${chalk.bold("Examples:")}
1460
- happy Start a session with default settings
1461
- happy -m opus Use Claude Opus model
1462
- happy -p plan Use plan permission mode
1463
- happy --auth Force re-authentication before starting session
1464
- happy logout Logs out of your account and removes data directory
1465
- `);
1466
- process.exit(0);
1467
- }
1468
- if (showVersion) {
1469
- console.log(packageJson.version);
1470
- process.exit(0);
1471
- }
1472
- let credentials = await readCredentials();
1473
- if (!credentials || forceAuth) {
1474
- let res = await doAuth();
1475
- if (!res) {
1476
- process.exit(1);
1477
- }
1478
- credentials = res;
1479
- }
1480
- const settings = await readSettings() || { onboardingCompleted: false };
1481
- if (settings.daemonInstalled === void 0 && process.platform === "darwin") {
1482
- console.log(chalk.cyan("\n\u{1F680} Happy Daemon Setup\n"));
1483
- console.log("The Happy daemon runs in the background to enable faster session startup");
1484
- console.log("and remote control from the mobile app.\n");
1485
- const rl = node_readline.createInterface({
1486
- input: process.stdin,
1487
- output: process.stdout
1488
- });
1489
- const answer = await new Promise((resolve) => {
1490
- rl.question(chalk.green("Would you like to install the daemon to run on startup? (recommended) [Y/n]: "), resolve);
1491
- });
1492
- rl.close();
1493
- const shouldInstall = answer.toLowerCase() !== "n";
1494
- if (shouldInstall) {
1495
- try {
1496
- const { install } = await Promise.resolve().then(function () { return require('./install-GZIzyuIE.cjs'); });
1497
- await install();
1498
- settings.daemonInstalled = true;
1499
- const { isDaemonRunning } = await Promise.resolve().then(function () { return require('./run-BmEaINbl.cjs'); });
1500
- if (!isDaemonRunning()) {
1501
- const { spawn } = await import('child_process');
1502
- const happyPath = process.argv[1];
1503
- const daemonProcess = spawn("node", [happyPath, "--happy-daemon"], {
1504
- detached: true,
1505
- stdio: "ignore",
1506
- env: process.env
1507
- });
1508
- daemonProcess.unref();
1509
- }
1510
- console.log(chalk.green("\u2713 Daemon installed and started"));
1511
- } catch (error) {
1512
- console.error(chalk.red("Failed to install daemon:"), error);
1513
- console.log(chalk.yellow("You can install the daemon later with: sudo happy daemon install"));
1514
- }
1515
- } else {
1516
- settings.daemonInstalled = false;
1517
- console.log(chalk.yellow("You can install the daemon later with: sudo happy daemon install"));
1518
- }
1519
- await writeSettings(settings);
1520
- }
1521
- try {
1522
- await start(credentials, options);
1523
- } catch (error) {
1524
- console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
1525
- if (process.env.DEBUG) {
1526
- console.error(error);
1527
- }
1528
- process.exit(1);
1529
- }
1530
- }
1531
- })();
1532
- async function cleanKey() {
1533
- const happyDir = types.configuration.happyDir;
1534
- if (!node_fs.existsSync(happyDir)) {
1535
- console.log(chalk.yellow("No happy data directory found at:"), happyDir);
1536
- return;
1537
- }
1538
- console.log(chalk.blue("Found happy data directory at:"), happyDir);
1539
- console.log(chalk.yellow("\u26A0\uFE0F This will remove all authentication data and require reconnecting your phone."));
1540
- const rl = node_readline.createInterface({
1541
- input: process.stdin,
1542
- output: process.stdout
1543
- });
1544
- const answer = await new Promise((resolve) => {
1545
- rl.question(chalk.yellow("Are you sure you want to remove the happy data directory? (y/N): "), resolve);
1546
- });
1547
- rl.close();
1548
- if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
1549
- try {
1550
- node_fs.rmSync(happyDir, { recursive: true, force: true });
1551
- console.log(chalk.green("\u2713 Happy data directory removed successfully"));
1552
- console.log(chalk.blue("\u2139\uFE0F You will need to reconnect your phone on the next session"));
1553
- } catch (error) {
1554
- throw new Error(`Failed to remove data directory: ${error instanceof Error ? error.message : "Unknown error"}`);
1555
- }
1556
- } else {
1557
- console.log(chalk.blue("Operation cancelled"));
1558
- }
1559
- }
1560
-
1561
- exports.doAuth = doAuth;
1562
- exports.readCredentials = readCredentials;
1563
- exports.readSettings = readSettings;
1564
- exports.writeSettings = writeSettings;