happy-coder 0.9.0-6 → 0.9.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.
package/dist/index.cjs CHANGED
@@ -1,18 +1,18 @@
1
1
  'use strict';
2
2
 
3
3
  var chalk = require('chalk');
4
- var types$1 = require('./types-a-nJyP-e.cjs');
4
+ var os = require('node:os');
5
5
  var node_crypto = require('node:crypto');
6
+ var types = require('./types-CyOnnZ8M.cjs');
6
7
  var node_child_process = require('node:child_process');
7
8
  var node_path = require('node:path');
8
9
  var node_readline = require('node:readline');
9
10
  var node_url = require('node:url');
10
11
  var node_fs = require('node:fs');
11
- var os = require('node:os');
12
12
  var path = require('path');
13
13
  var url = require('url');
14
- var promises$1 = require('node:fs/promises');
15
- var promises = require('fs/promises');
14
+ var promises = require('node:fs/promises');
15
+ var fs = require('fs/promises');
16
16
  var ink = require('ink');
17
17
  var React = require('react');
18
18
  var axios = require('axios');
@@ -23,37 +23,18 @@ require('expo-server-sdk');
23
23
  var child_process = require('child_process');
24
24
  var util = require('util');
25
25
  var crypto = require('crypto');
26
- var z = require('zod');
27
- var fastify = require('fastify');
28
- var fastifyTypeProviderZod = require('fastify-type-provider-zod');
29
26
  var os$1 = require('os');
30
27
  var qrcode = require('qrcode-terminal');
31
28
  var open = require('open');
29
+ var fastify = require('fastify');
30
+ var z = require('zod');
31
+ var fastifyTypeProviderZod = require('fastify-type-provider-zod');
32
+ var fs$1 = require('fs');
32
33
  var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
33
34
  var node_http = require('node:http');
34
35
  var streamableHttp_js = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
35
- var fs = require('fs');
36
36
 
37
37
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
38
- function _interopNamespaceDefault(e) {
39
- var n = Object.create(null);
40
- if (e) {
41
- Object.keys(e).forEach(function (k) {
42
- if (k !== 'default') {
43
- var d = Object.getOwnPropertyDescriptor(e, k);
44
- Object.defineProperty(n, k, d.get ? d : {
45
- enumerable: true,
46
- get: function () { return e[k]; }
47
- });
48
- }
49
- });
50
- }
51
- n.default = e;
52
- return Object.freeze(n);
53
- }
54
-
55
- var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
56
-
57
38
  class Session {
58
39
  path;
59
40
  logPath;
@@ -102,7 +83,7 @@ class Session {
102
83
  */
103
84
  clearSessionId = () => {
104
85
  this.sessionId = null;
105
- types$1.logger.debug("[Session] Session ID cleared");
86
+ types.logger.debug("[Session] Session ID cleared");
106
87
  };
107
88
  }
108
89
 
@@ -117,7 +98,7 @@ function claudeCheckSession(sessionId, path) {
117
98
  const sessionFile = node_path.join(projectDir, `${sessionId}.jsonl`);
118
99
  const sessionExists = node_fs.existsSync(sessionFile);
119
100
  if (!sessionExists) {
120
- types$1.logger.debug(`[claudeCheckSession] Path ${sessionFile} does not exist`);
101
+ types.logger.debug(`[claudeCheckSession] Path ${sessionFile} does not exist`);
121
102
  return false;
122
103
  }
123
104
  const sessionData = node_fs.readFileSync(sessionFile, "utf-8").split("\n");
@@ -170,7 +151,7 @@ async function claudeLocal(opts) {
170
151
  const detectedIdsFileSystem = /* @__PURE__ */ new Set();
171
152
  watcher.on("change", (event, filename) => {
172
153
  if (typeof filename === "string" && filename.toLowerCase().endsWith(".jsonl")) {
173
- types$1.logger.debug("change", event, filename);
154
+ types.logger.debug("change", event, filename);
174
155
  const sessionId = filename.replace(".jsonl", "");
175
156
  if (detectedIdsFileSystem.has(sessionId)) {
176
157
  return;
@@ -194,7 +175,7 @@ async function claudeLocal(opts) {
194
175
  const updateThinking = (newThinking) => {
195
176
  if (thinking !== newThinking) {
196
177
  thinking = newThinking;
197
- types$1.logger.debug(`[ClaudeLocal] Thinking state changed to: ${thinking}`);
178
+ types.logger.debug(`[ClaudeLocal] Thinking state changed to: ${thinking}`);
198
179
  if (opts.onThinkingChange) {
199
180
  opts.onThinkingChange(thinking);
200
181
  }
@@ -272,10 +253,10 @@ async function claudeLocal(opts) {
272
253
  }
273
254
  break;
274
255
  default:
275
- types$1.logger.debug(`[ClaudeLocal] Unknown message type: ${message.type}`);
256
+ types.logger.debug(`[ClaudeLocal] Unknown message type: ${message.type}`);
276
257
  }
277
258
  } catch (e) {
278
- types$1.logger.debug(`[ClaudeLocal] Non-JSON line from fd3: ${line}`);
259
+ types.logger.debug(`[ClaudeLocal] Non-JSON line from fd3: ${line}`);
279
260
  }
280
261
  });
281
262
  rl.on("error", (err) => {
@@ -379,7 +360,7 @@ class InvalidateSync {
379
360
  this._pendings = [];
380
361
  };
381
362
  _doSync = async () => {
382
- await types$1.backoff(async () => {
363
+ await types.backoff(async () => {
383
364
  if (this._stopped) {
384
365
  return;
385
366
  }
@@ -404,21 +385,21 @@ function startFileWatcher(file, onFileChange) {
404
385
  void (async () => {
405
386
  while (true) {
406
387
  try {
407
- types$1.logger.debug(`[FILE_WATCHER] Starting watcher for ${file}`);
408
- const watcher = promises.watch(file, { persistent: true, signal: abortController.signal });
388
+ types.logger.debug(`[FILE_WATCHER] Starting watcher for ${file}`);
389
+ const watcher = fs.watch(file, { persistent: true, signal: abortController.signal });
409
390
  for await (const event of watcher) {
410
391
  if (abortController.signal.aborted) {
411
392
  return;
412
393
  }
413
- types$1.logger.debug(`[FILE_WATCHER] File changed: ${file}`);
394
+ types.logger.debug(`[FILE_WATCHER] File changed: ${file}`);
414
395
  onFileChange(file);
415
396
  }
416
397
  } catch (e) {
417
398
  if (abortController.signal.aborted) {
418
399
  return;
419
400
  }
420
- types$1.logger.debug(`[FILE_WATCHER] Watch error: ${e.message}, restarting watcher in a second`);
421
- await types$1.delay(1e3);
401
+ types.logger.debug(`[FILE_WATCHER] Watch error: ${e.message}, restarting watcher in a second`);
402
+ await types.delay(1e3);
422
403
  }
423
404
  }
424
405
  })();
@@ -488,21 +469,21 @@ async function createSessionScanner(opts) {
488
469
  },
489
470
  onNewSession: (sessionId) => {
490
471
  if (currentSessionId === sessionId) {
491
- types$1.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is the same as the current session, skipping`);
472
+ types.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is the same as the current session, skipping`);
492
473
  return;
493
474
  }
494
475
  if (finishedSessions.has(sessionId)) {
495
- types$1.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is already finished, skipping`);
476
+ types.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is already finished, skipping`);
496
477
  return;
497
478
  }
498
479
  if (pendingSessions.has(sessionId)) {
499
- types$1.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is already pending, skipping`);
480
+ types.logger.debug(`[SESSION_SCANNER] New session: ${sessionId} is already pending, skipping`);
500
481
  return;
501
482
  }
502
483
  if (currentSessionId) {
503
484
  pendingSessions.add(currentSessionId);
504
485
  }
505
- types$1.logger.debug(`[SESSION_SCANNER] New session: ${sessionId}`);
486
+ types.logger.debug(`[SESSION_SCANNER] New session: ${sessionId}`);
506
487
  currentSessionId = sessionId;
507
488
  sync.invalidate();
508
489
  }
@@ -523,12 +504,12 @@ function messageKey(message) {
523
504
  }
524
505
  async function readSessionLog(projectDir, sessionId) {
525
506
  const expectedSessionFile = node_path.join(projectDir, `${sessionId}.jsonl`);
526
- types$1.logger.debug(`[SESSION_SCANNER] Reading session file: ${expectedSessionFile}`);
507
+ types.logger.debug(`[SESSION_SCANNER] Reading session file: ${expectedSessionFile}`);
527
508
  let file;
528
509
  try {
529
- file = await promises$1.readFile(expectedSessionFile, "utf-8");
510
+ file = await promises.readFile(expectedSessionFile, "utf-8");
530
511
  } catch (error) {
531
- types$1.logger.debug(`[SESSION_SCANNER] Session file not found: ${expectedSessionFile}`);
512
+ types.logger.debug(`[SESSION_SCANNER] Session file not found: ${expectedSessionFile}`);
532
513
  return [];
533
514
  }
534
515
  let lines = file.split("\n");
@@ -539,14 +520,14 @@ async function readSessionLog(projectDir, sessionId) {
539
520
  continue;
540
521
  }
541
522
  let message = JSON.parse(l);
542
- let parsed = types$1.RawJSONLinesSchema.safeParse(message);
523
+ let parsed = types.RawJSONLinesSchema.safeParse(message);
543
524
  if (!parsed.success) {
544
- types$1.logger.debugLargeJson(`[SESSION_SCANNER] Failed to parse message`, message);
525
+ types.logger.debugLargeJson(`[SESSION_SCANNER] Failed to parse message`, message);
545
526
  continue;
546
527
  }
547
528
  messages.push(parsed.data);
548
529
  } catch (e) {
549
- types$1.logger.debug(`[SESSION_SCANNER] Error processing message: ${e}`);
530
+ types.logger.debug(`[SESSION_SCANNER] Error processing message: ${e}`);
550
531
  continue;
551
532
  }
552
533
  }
@@ -574,7 +555,7 @@ async function claudeLocalLauncher(session) {
574
555
  await exutFuture.promise;
575
556
  }
576
557
  async function doAbort() {
577
- types$1.logger.debug("[local]: doAbort");
558
+ types.logger.debug("[local]: doAbort");
578
559
  if (!exitReason) {
579
560
  exitReason = "switch";
580
561
  }
@@ -582,7 +563,7 @@ async function claudeLocalLauncher(session) {
582
563
  await abort();
583
564
  }
584
565
  async function doSwitch() {
585
- types$1.logger.debug("[local]: doSwitch");
566
+ types.logger.debug("[local]: doSwitch");
586
567
  if (!exitReason) {
587
568
  exitReason = "switch";
588
569
  }
@@ -604,7 +585,7 @@ async function claudeLocalLauncher(session) {
604
585
  if (exitReason) {
605
586
  return exitReason;
606
587
  }
607
- types$1.logger.debug("[local]: launch");
588
+ types.logger.debug("[local]: launch");
608
589
  try {
609
590
  await claudeLocal({
610
591
  path: session.path,
@@ -622,7 +603,7 @@ async function claudeLocalLauncher(session) {
622
603
  break;
623
604
  }
624
605
  } catch (e) {
625
- types$1.logger.debug("[local]: launch error", e);
606
+ types.logger.debug("[local]: launch error", e);
626
607
  if (!exitReason) {
627
608
  session.client.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
628
609
  continue;
@@ -630,7 +611,7 @@ async function claudeLocalLauncher(session) {
630
611
  break;
631
612
  }
632
613
  }
633
- types$1.logger.debug("[local]: launch done");
614
+ types.logger.debug("[local]: launch done");
634
615
  }
635
616
  } finally {
636
617
  exutFuture.resolve(void 0);
@@ -910,7 +891,7 @@ function getDefaultClaudeCodePath() {
910
891
  }
911
892
  function logDebug(message) {
912
893
  if (process.env.DEBUG) {
913
- types$1.logger.debug(message);
894
+ types.logger.debug(message);
914
895
  console.log(message);
915
896
  }
916
897
  }
@@ -989,7 +970,7 @@ class Query {
989
970
  }
990
971
  this.inputStream.enqueue(message);
991
972
  } catch (e) {
992
- types$1.logger.debug(line);
973
+ types.logger.debug(line);
993
974
  }
994
975
  }
995
976
  }
@@ -1406,10 +1387,10 @@ async function awaitFileExist(file, timeout = 1e4) {
1406
1387
  const startTime = Date.now();
1407
1388
  while (Date.now() - startTime < timeout) {
1408
1389
  try {
1409
- await promises.access(file);
1390
+ await fs.access(file);
1410
1391
  return true;
1411
1392
  } catch (e) {
1412
- await types$1.delay(1e3);
1393
+ await types.delay(1e3);
1413
1394
  }
1414
1395
  }
1415
1396
  return false;
@@ -1441,7 +1422,7 @@ async function claudeRemote(opts) {
1441
1422
  }
1442
1423
  let isCompactCommand = false;
1443
1424
  if (specialCommand.type === "compact") {
1444
- types$1.logger.debug("[claudeRemote] /compact command detected - will process as normal but with compaction behavior");
1425
+ types.logger.debug("[claudeRemote] /compact command detected - will process as normal but with compaction behavior");
1445
1426
  isCompactCommand = true;
1446
1427
  if (opts.onCompletionEvent) {
1447
1428
  opts.onCompletionEvent("Compaction started");
@@ -1470,7 +1451,7 @@ async function claudeRemote(opts) {
1470
1451
  const updateThinking = (newThinking) => {
1471
1452
  if (thinking !== newThinking) {
1472
1453
  thinking = newThinking;
1473
- types$1.logger.debug(`[claudeRemote] Thinking state changed to: ${thinking}`);
1454
+ types.logger.debug(`[claudeRemote] Thinking state changed to: ${thinking}`);
1474
1455
  if (opts.onThinkingChange) {
1475
1456
  opts.onThinkingChange(thinking);
1476
1457
  }
@@ -1490,26 +1471,26 @@ async function claudeRemote(opts) {
1490
1471
  });
1491
1472
  updateThinking(true);
1492
1473
  try {
1493
- types$1.logger.debug(`[claudeRemote] Starting to iterate over response`);
1474
+ types.logger.debug(`[claudeRemote] Starting to iterate over response`);
1494
1475
  for await (const message of response) {
1495
- types$1.logger.debugLargeJson(`[claudeRemote] Message ${message.type}`, message);
1476
+ types.logger.debugLargeJson(`[claudeRemote] Message ${message.type}`, message);
1496
1477
  opts.onMessage(message);
1497
1478
  if (message.type === "system" && message.subtype === "init") {
1498
1479
  updateThinking(true);
1499
1480
  const systemInit = message;
1500
1481
  if (systemInit.session_id) {
1501
- types$1.logger.debug(`[claudeRemote] Waiting for session file to be written to disk: ${systemInit.session_id}`);
1482
+ types.logger.debug(`[claudeRemote] Waiting for session file to be written to disk: ${systemInit.session_id}`);
1502
1483
  const projectDir = getProjectPath(opts.path);
1503
1484
  const found = await awaitFileExist(node_path.join(projectDir, `${systemInit.session_id}.jsonl`));
1504
- types$1.logger.debug(`[claudeRemote] Session file found: ${systemInit.session_id} ${found}`);
1485
+ types.logger.debug(`[claudeRemote] Session file found: ${systemInit.session_id} ${found}`);
1505
1486
  opts.onSessionFound(systemInit.session_id);
1506
1487
  }
1507
1488
  }
1508
1489
  if (message.type === "result") {
1509
1490
  updateThinking(false);
1510
- types$1.logger.debug("[claudeRemote] Result received, exiting claudeRemote");
1491
+ types.logger.debug("[claudeRemote] Result received, exiting claudeRemote");
1511
1492
  if (isCompactCommand) {
1512
- types$1.logger.debug("[claudeRemote] Compaction completed");
1493
+ types.logger.debug("[claudeRemote] Compaction completed");
1513
1494
  if (opts.onCompletionEvent) {
1514
1495
  opts.onCompletionEvent("Compaction completed");
1515
1496
  }
@@ -1529,7 +1510,7 @@ async function claudeRemote(opts) {
1529
1510
  if (msg.message.role === "user" && Array.isArray(msg.message.content)) {
1530
1511
  for (let c of msg.message.content) {
1531
1512
  if (c.type === "tool_result" && c.tool_use_id && opts.isAborted(c.tool_use_id)) {
1532
- types$1.logger.debug("[claudeRemote] Tool aborted, exiting claudeRemote");
1513
+ types.logger.debug("[claudeRemote] Tool aborted, exiting claudeRemote");
1533
1514
  return;
1534
1515
  }
1535
1516
  }
@@ -1538,7 +1519,7 @@ async function claudeRemote(opts) {
1538
1519
  }
1539
1520
  } catch (e) {
1540
1521
  if (e instanceof AbortError) {
1541
- types$1.logger.debug(`[claudeRemote] Aborted`);
1522
+ types.logger.debug(`[claudeRemote] Aborted`);
1542
1523
  } else {
1543
1524
  throw e;
1544
1525
  }
@@ -1658,9 +1639,9 @@ class PermissionHandler {
1658
1639
  this.permissionMode = response.mode;
1659
1640
  }
1660
1641
  if (pending.toolName === "exit_plan_mode" || pending.toolName === "ExitPlanMode") {
1661
- types$1.logger.debug("Plan mode result received", response);
1642
+ types.logger.debug("Plan mode result received", response);
1662
1643
  if (response.approved) {
1663
- types$1.logger.debug("Plan approved - injecting PLAN_FAKE_RESTART");
1644
+ types.logger.debug("Plan approved - injecting PLAN_FAKE_RESTART");
1664
1645
  if (response.mode && ["default", "acceptEdits", "bypassPermissions"].includes(response.mode)) {
1665
1646
  this.session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: response.mode });
1666
1647
  } else {
@@ -1703,7 +1684,7 @@ class PermissionHandler {
1703
1684
  }
1704
1685
  let toolCallId = this.resolveToolCallId(toolName, input);
1705
1686
  if (!toolCallId) {
1706
- await types$1.delay(1e3);
1687
+ await types.delay(1e3);
1707
1688
  toolCallId = this.resolveToolCallId(toolName, input);
1708
1689
  if (!toolCallId) {
1709
1690
  throw new Error(`Could not resolve tool call ID for ${toolName}`);
@@ -1757,7 +1738,7 @@ class PermissionHandler {
1757
1738
  }
1758
1739
  }
1759
1740
  }));
1760
- types$1.logger.debug(`Permission request sent for tool call ${id}: ${toolName}`);
1741
+ types.logger.debug(`Permission request sent for tool call ${id}: ${toolName}`);
1761
1742
  });
1762
1743
  }
1763
1744
  /**
@@ -1879,11 +1860,11 @@ class PermissionHandler {
1879
1860
  */
1880
1861
  setupClientHandler() {
1881
1862
  this.session.client.setHandler("permission", async (message) => {
1882
- types$1.logger.debug(`Permission response: ${JSON.stringify(message)}`);
1863
+ types.logger.debug(`Permission response: ${JSON.stringify(message)}`);
1883
1864
  const id = message.id;
1884
1865
  const pending = this.pendingRequests.get(id);
1885
1866
  if (!pending) {
1886
- types$1.logger.debug("Permission request not found or already resolved");
1867
+ types.logger.debug("Permission request not found or already resolved");
1887
1868
  return;
1888
1869
  }
1889
1870
  this.responses.set(id, { ...message, receivedAt: Date.now() });
@@ -1921,7 +1902,7 @@ class PermissionHandler {
1921
1902
  }
1922
1903
 
1923
1904
  function formatClaudeMessageForInk(message, messageBuffer, onAssistantResult) {
1924
- types$1.logger.debugLargeJson("[CLAUDE INK] Message from remote mode:", message);
1905
+ types.logger.debugLargeJson("[CLAUDE INK] Message from remote mode:", message);
1925
1906
  switch (message.type) {
1926
1907
  case "system": {
1927
1908
  const sysMsg = message;
@@ -2016,7 +1997,7 @@ function formatClaudeMessageForInk(message, messageBuffer, onAssistantResult) {
2016
1997
  } else if (resultMsg.subtype === "error_during_execution") {
2017
1998
  messageBuffer.addMessage("\u274C Error during execution", "result");
2018
1999
  messageBuffer.addMessage(`Completed ${resultMsg.num_turns} turns before error`, "status");
2019
- types$1.logger.debugLargeJson("[RESULT] Error during execution", resultMsg);
2000
+ types.logger.debugLargeJson("[RESULT] Error during execution", resultMsg);
2020
2001
  }
2021
2002
  break;
2022
2003
  }
@@ -2263,7 +2244,7 @@ class OutgoingMessageQueue {
2263
2244
  }
2264
2245
  queue = [];
2265
2246
  nextId = 1;
2266
- lock = new types$1.AsyncLock();
2247
+ lock = new types.AsyncLock();
2267
2248
  processTimer;
2268
2249
  delayTimers = /* @__PURE__ */ new Map();
2269
2250
  /**
@@ -2395,9 +2376,9 @@ class OutgoingMessageQueue {
2395
2376
  }
2396
2377
 
2397
2378
  async function claudeRemoteLauncher(session) {
2398
- types$1.logger.debug("[claudeRemoteLauncher] Starting remote launcher");
2379
+ types.logger.debug("[claudeRemoteLauncher] Starting remote launcher");
2399
2380
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
2400
- types$1.logger.debug(`[claudeRemoteLauncher] TTY available: ${hasTTY}`);
2381
+ types.logger.debug(`[claudeRemoteLauncher] TTY available: ${hasTTY}`);
2401
2382
  let messageBuffer = new MessageBuffer();
2402
2383
  let inkInstance = null;
2403
2384
  if (hasTTY) {
@@ -2406,14 +2387,14 @@ async function claudeRemoteLauncher(session) {
2406
2387
  messageBuffer,
2407
2388
  logPath: process.env.DEBUG ? session.logPath : void 0,
2408
2389
  onExit: async () => {
2409
- types$1.logger.debug("[remote]: Exiting client via Ctrl-C");
2390
+ types.logger.debug("[remote]: Exiting client via Ctrl-C");
2410
2391
  if (!exitReason) {
2411
2392
  exitReason = "exit";
2412
2393
  }
2413
2394
  await abort();
2414
2395
  },
2415
2396
  onSwitchToLocal: () => {
2416
- types$1.logger.debug("[remote]: Switching to local mode via double space");
2397
+ types.logger.debug("[remote]: Switching to local mode via double space");
2417
2398
  doSwitch();
2418
2399
  }
2419
2400
  }), {
@@ -2438,11 +2419,11 @@ async function claudeRemoteLauncher(session) {
2438
2419
  await abortFuture?.promise;
2439
2420
  }
2440
2421
  async function doAbort() {
2441
- types$1.logger.debug("[remote]: doAbort");
2422
+ types.logger.debug("[remote]: doAbort");
2442
2423
  await abort();
2443
2424
  }
2444
2425
  async function doSwitch() {
2445
- types$1.logger.debug("[remote]: doSwitch");
2426
+ types.logger.debug("[remote]: doSwitch");
2446
2427
  if (!exitReason) {
2447
2428
  exitReason = "switch";
2448
2429
  }
@@ -2472,7 +2453,7 @@ async function claudeRemoteLauncher(session) {
2472
2453
  if (umessage.message.content && Array.isArray(umessage.message.content)) {
2473
2454
  for (let c of umessage.message.content) {
2474
2455
  if (c.type === "tool_use" && (c.name === "exit_plan_mode" || c.name === "ExitPlanMode")) {
2475
- types$1.logger.debug("[remote]: detected plan mode tool call " + c.id);
2456
+ types.logger.debug("[remote]: detected plan mode tool call " + c.id);
2476
2457
  planModeToolCalls.add(c.id);
2477
2458
  }
2478
2459
  }
@@ -2483,7 +2464,7 @@ async function claudeRemoteLauncher(session) {
2483
2464
  if (umessage.message.content && Array.isArray(umessage.message.content)) {
2484
2465
  for (let c of umessage.message.content) {
2485
2466
  if (c.type === "tool_use") {
2486
- types$1.logger.debug("[remote]: detected tool use " + c.id + " parent: " + umessage.parent_tool_use_id);
2467
+ types.logger.debug("[remote]: detected tool use " + c.id + " parent: " + umessage.parent_tool_use_id);
2487
2468
  ongoingToolCalls.set(c.id, { parentToolCallId: umessage.parent_tool_use_id ?? null });
2488
2469
  }
2489
2470
  }
@@ -2511,8 +2492,8 @@ async function claudeRemoteLauncher(session) {
2511
2492
  content: umessage.message.content.map((c) => {
2512
2493
  if (c.type === "tool_result" && c.tool_use_id && planModeToolCalls.has(c.tool_use_id)) {
2513
2494
  if (c.content === PLAN_FAKE_REJECT) {
2514
- types$1.logger.debug("[remote]: hack plan mode exit");
2515
- types$1.logger.debugLargeJson("[remote]: hack plan mode exit", c);
2495
+ types.logger.debug("[remote]: hack plan mode exit");
2496
+ types.logger.debugLargeJson("[remote]: hack plan mode exit", c);
2516
2497
  return {
2517
2498
  ...c,
2518
2499
  is_error: false,
@@ -2597,7 +2578,7 @@ async function claudeRemoteLauncher(session) {
2597
2578
  try {
2598
2579
  let pending = null;
2599
2580
  while (!exitReason) {
2600
- types$1.logger.debug("[remote]: launch");
2581
+ types.logger.debug("[remote]: launch");
2601
2582
  messageBuffer.addMessage("\u2550".repeat(40), "status");
2602
2583
  messageBuffer.addMessage("Starting new Claude session...", "status");
2603
2584
  const controller = new AbortController();
@@ -2627,7 +2608,7 @@ async function claudeRemoteLauncher(session) {
2627
2608
  let msg = await session.queue.waitForMessagesAndGetAsString(controller.signal);
2628
2609
  if (msg) {
2629
2610
  if (modeHash && msg.hash !== modeHash || msg.isolate) {
2630
- types$1.logger.debug("[remote]: mode has changed, pending message");
2611
+ types.logger.debug("[remote]: mode has changed, pending message");
2631
2612
  pending = msg;
2632
2613
  return null;
2633
2614
  }
@@ -2650,11 +2631,11 @@ async function claudeRemoteLauncher(session) {
2650
2631
  claudeArgs: session.claudeArgs,
2651
2632
  onMessage,
2652
2633
  onCompletionEvent: (message) => {
2653
- types$1.logger.debug(`[remote]: Completion event: ${message}`);
2634
+ types.logger.debug(`[remote]: Completion event: ${message}`);
2654
2635
  session.client.sendSessionEvent({ type: "message", message });
2655
2636
  },
2656
2637
  onSessionReset: () => {
2657
- types$1.logger.debug("[remote]: Session reset");
2638
+ types.logger.debug("[remote]: Session reset");
2658
2639
  session.clearSessionId();
2659
2640
  },
2660
2641
  onReady: () => {
@@ -2673,29 +2654,29 @@ async function claudeRemoteLauncher(session) {
2673
2654
  session.client.sendSessionEvent({ type: "message", message: "Aborted by user" });
2674
2655
  }
2675
2656
  } catch (e) {
2676
- types$1.logger.debug("[remote]: launch error", e);
2657
+ types.logger.debug("[remote]: launch error", e);
2677
2658
  if (!exitReason) {
2678
2659
  session.client.sendSessionEvent({ type: "message", message: "Process exited unexpectedly" });
2679
2660
  continue;
2680
2661
  }
2681
2662
  } finally {
2682
- types$1.logger.debug("[remote]: launch finally");
2663
+ types.logger.debug("[remote]: launch finally");
2683
2664
  for (let [toolCallId, { parentToolCallId }] of ongoingToolCalls) {
2684
2665
  const converted = sdkToLogConverter.generateInterruptedToolResult(toolCallId, parentToolCallId);
2685
2666
  if (converted) {
2686
- types$1.logger.debug("[remote]: terminating tool call " + toolCallId + " parent: " + parentToolCallId);
2667
+ types.logger.debug("[remote]: terminating tool call " + toolCallId + " parent: " + parentToolCallId);
2687
2668
  session.client.sendClaudeSessionMessage(converted);
2688
2669
  }
2689
2670
  }
2690
2671
  ongoingToolCalls.clear();
2691
- types$1.logger.debug("[remote]: flushing message queue");
2672
+ types.logger.debug("[remote]: flushing message queue");
2692
2673
  await messageQueue.flush();
2693
2674
  messageQueue.destroy();
2694
- types$1.logger.debug("[remote]: message queue flushed");
2675
+ types.logger.debug("[remote]: message queue flushed");
2695
2676
  abortController = null;
2696
2677
  abortFuture?.resolve(void 0);
2697
2678
  abortFuture = null;
2698
- types$1.logger.debug("[remote]: launch done");
2679
+ types.logger.debug("[remote]: launch done");
2699
2680
  permissionHandler.reset();
2700
2681
  modeHash = null;
2701
2682
  mode = null;
@@ -2719,7 +2700,7 @@ async function claudeRemoteLauncher(session) {
2719
2700
  }
2720
2701
 
2721
2702
  async function loop(opts) {
2722
- const logPath = await types$1.logger.logFilePathPromise;
2703
+ const logPath = types.logger.logFilePath;
2723
2704
  let session = new Session({
2724
2705
  api: opts.api,
2725
2706
  client: opts.session,
@@ -2738,7 +2719,7 @@ async function loop(opts) {
2738
2719
  }
2739
2720
  let mode = opts.startingMode ?? "local";
2740
2721
  while (true) {
2741
- types$1.logger.debug(`[loop] Iteration with mode: ${mode}`);
2722
+ types.logger.debug(`[loop] Iteration with mode: ${mode}`);
2742
2723
  if (mode === "local") {
2743
2724
  let reason = await claudeLocalLauncher(session);
2744
2725
  if (reason === "exit") {
@@ -2764,133 +2745,6 @@ async function loop(opts) {
2764
2745
  }
2765
2746
  }
2766
2747
 
2767
- var name = "happy-coder";
2768
- var version = "0.9.0-6";
2769
- var description = "Claude Code session sharing CLI";
2770
- var author = "Kirill Dubovitskiy";
2771
- var license = "MIT";
2772
- var type = "module";
2773
- var homepage = "https://github.com/slopus/happy-cli";
2774
- var bugs = "https://github.com/slopus/happy-cli/issues";
2775
- var repository = "slopus/happy-cli";
2776
- var bin = {
2777
- happy: "./bin/happy.mjs"
2778
- };
2779
- var main = "./dist/index.cjs";
2780
- var module$1 = "./dist/index.mjs";
2781
- var types = "./dist/index.d.cts";
2782
- var exports$1 = {
2783
- ".": {
2784
- require: {
2785
- types: "./dist/index.d.cts",
2786
- "default": "./dist/index.cjs"
2787
- },
2788
- "import": {
2789
- types: "./dist/index.d.mts",
2790
- "default": "./dist/index.mjs"
2791
- }
2792
- },
2793
- "./lib": {
2794
- require: {
2795
- types: "./dist/lib.d.cts",
2796
- "default": "./dist/lib.cjs"
2797
- },
2798
- "import": {
2799
- types: "./dist/lib.d.mts",
2800
- "default": "./dist/lib.mjs"
2801
- }
2802
- }
2803
- };
2804
- var files = [
2805
- "dist",
2806
- "bin",
2807
- "scripts",
2808
- "ripgrep",
2809
- "package.json"
2810
- ];
2811
- var scripts = {
2812
- "why do we need to build before running tests / dev?": "We need the binary to be built so we run daemon commands which directly run the binary - we don't want them to go out of sync or have custom spawn logic depending how we started happy",
2813
- typecheck: "tsc --noEmit",
2814
- build: "shx rm -rf dist && npx tsc --noEmit && pkgroll",
2815
- test: "yarn build && vitest run",
2816
- "test:watch": "vitest",
2817
- "test:integration-test-env": "yarn build && tsx --env-file .env.integration-test node_modules/.bin/vitest run",
2818
- dev: "yarn build && DEBUG=1 npx tsx src/index.ts",
2819
- "dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
2820
- "dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
2821
- prepublishOnly: "yarn build && yarn test",
2822
- release: "release-it"
2823
- };
2824
- var dependencies = {
2825
- "@anthropic-ai/claude-code": "^1.0.89",
2826
- "@anthropic-ai/sdk": "^0.56.0",
2827
- "@modelcontextprotocol/sdk": "^1.15.1",
2828
- "@stablelib/base64": "^2.0.1",
2829
- "@types/http-proxy": "^1.17.16",
2830
- "@types/qrcode-terminal": "^0.12.2",
2831
- "@types/react": "^19.1.9",
2832
- axios: "^1.10.0",
2833
- chalk: "^5.4.1",
2834
- "expo-server-sdk": "^3.15.0",
2835
- fastify: "^5.5.0",
2836
- "fastify-type-provider-zod": "4.0.2",
2837
- "http-proxy": "^1.18.1",
2838
- "http-proxy-middleware": "^3.0.5",
2839
- ink: "^6.1.0",
2840
- open: "^10.2.0",
2841
- "qrcode-terminal": "^0.12.0",
2842
- react: "^19.1.1",
2843
- "socket.io-client": "^4.8.1",
2844
- tweetnacl: "^1.0.3",
2845
- zod: "^3.23.8"
2846
- };
2847
- var devDependencies = {
2848
- "@eslint/compat": "^1",
2849
- "@types/node": ">=20",
2850
- "cross-env": "^10.0.0",
2851
- eslint: "^9",
2852
- "eslint-config-prettier": "^10",
2853
- pkgroll: "^2.14.2",
2854
- "release-it": "^19.0.4",
2855
- shx: "^0.3.3",
2856
- "ts-node": "^10",
2857
- tsx: "^4.20.3",
2858
- typescript: "^5",
2859
- vitest: "^3.2.4"
2860
- };
2861
- var resolutions = {
2862
- "whatwg-url": "14.2.0",
2863
- "parse-path": "7.0.3",
2864
- "@types/parse-path": "7.0.3"
2865
- };
2866
- var publishConfig = {
2867
- registry: "https://registry.npmjs.org"
2868
- };
2869
- var packageManager = "yarn@1.22.22";
2870
- var packageJson = {
2871
- name: name,
2872
- version: version,
2873
- description: description,
2874
- author: author,
2875
- license: license,
2876
- type: type,
2877
- homepage: homepage,
2878
- bugs: bugs,
2879
- repository: repository,
2880
- bin: bin,
2881
- main: main,
2882
- module: module$1,
2883
- types: types,
2884
- exports: exports$1,
2885
- files: files,
2886
- scripts: scripts,
2887
- dependencies: dependencies,
2888
- devDependencies: devDependencies,
2889
- resolutions: resolutions,
2890
- publishConfig: publishConfig,
2891
- packageManager: packageManager
2892
- };
2893
-
2894
2748
  function run(args, options) {
2895
2749
  const RUNNER_PATH = path.resolve(path.join(projectPath(), "scripts", "ripgrep_launcher.cjs"));
2896
2750
  return new Promise((resolve2, reject) => {
@@ -2922,7 +2776,7 @@ function run(args, options) {
2922
2776
  const execAsync = util.promisify(child_process.exec);
2923
2777
  function registerHandlers(session) {
2924
2778
  session.setHandler("bash", async (data) => {
2925
- types$1.logger.debug("Shell command request:", data.command);
2779
+ types.logger.debug("Shell command request:", data.command);
2926
2780
  try {
2927
2781
  const options = {
2928
2782
  cwd: data.cwd,
@@ -2957,22 +2811,22 @@ function registerHandlers(session) {
2957
2811
  }
2958
2812
  });
2959
2813
  session.setHandler("readFile", async (data) => {
2960
- types$1.logger.debug("Read file request:", data.path);
2814
+ types.logger.debug("Read file request:", data.path);
2961
2815
  try {
2962
- const buffer = await promises.readFile(data.path);
2816
+ const buffer = await fs.readFile(data.path);
2963
2817
  const content = buffer.toString("base64");
2964
2818
  return { success: true, content };
2965
2819
  } catch (error) {
2966
- types$1.logger.debug("Failed to read file:", error);
2820
+ types.logger.debug("Failed to read file:", error);
2967
2821
  return { success: false, error: error instanceof Error ? error.message : "Failed to read file" };
2968
2822
  }
2969
2823
  });
2970
2824
  session.setHandler("writeFile", async (data) => {
2971
- types$1.logger.debug("Write file request:", data.path);
2825
+ types.logger.debug("Write file request:", data.path);
2972
2826
  try {
2973
2827
  if (data.expectedHash !== null && data.expectedHash !== void 0) {
2974
2828
  try {
2975
- const existingBuffer = await promises.readFile(data.path);
2829
+ const existingBuffer = await fs.readFile(data.path);
2976
2830
  const existingHash = crypto.createHash("sha256").update(existingBuffer).digest("hex");
2977
2831
  if (existingHash !== data.expectedHash) {
2978
2832
  return {
@@ -2992,7 +2846,7 @@ function registerHandlers(session) {
2992
2846
  }
2993
2847
  } else {
2994
2848
  try {
2995
- await promises.stat(data.path);
2849
+ await fs.stat(data.path);
2996
2850
  return {
2997
2851
  success: false,
2998
2852
  error: "File already exists but was expected to be new"
@@ -3005,18 +2859,18 @@ function registerHandlers(session) {
3005
2859
  }
3006
2860
  }
3007
2861
  const buffer = Buffer.from(data.content, "base64");
3008
- await promises.writeFile(data.path, buffer);
2862
+ await fs.writeFile(data.path, buffer);
3009
2863
  const hash = crypto.createHash("sha256").update(buffer).digest("hex");
3010
2864
  return { success: true, hash };
3011
2865
  } catch (error) {
3012
- types$1.logger.debug("Failed to write file:", error);
2866
+ types.logger.debug("Failed to write file:", error);
3013
2867
  return { success: false, error: error instanceof Error ? error.message : "Failed to write file" };
3014
2868
  }
3015
2869
  });
3016
2870
  session.setHandler("listDirectory", async (data) => {
3017
- types$1.logger.debug("List directory request:", data.path);
2871
+ types.logger.debug("List directory request:", data.path);
3018
2872
  try {
3019
- const entries = await promises.readdir(data.path, { withFileTypes: true });
2873
+ const entries = await fs.readdir(data.path, { withFileTypes: true });
3020
2874
  const directoryEntries = await Promise.all(
3021
2875
  entries.map(async (entry) => {
3022
2876
  const fullPath = path.join(data.path, entry.name);
@@ -3029,11 +2883,11 @@ function registerHandlers(session) {
3029
2883
  type = "file";
3030
2884
  }
3031
2885
  try {
3032
- const stats = await promises.stat(fullPath);
2886
+ const stats = await fs.stat(fullPath);
3033
2887
  size = stats.size;
3034
2888
  modified = stats.mtime.getTime();
3035
2889
  } catch (error) {
3036
- types$1.logger.debug(`Failed to stat ${fullPath}:`, error);
2890
+ types.logger.debug(`Failed to stat ${fullPath}:`, error);
3037
2891
  }
3038
2892
  return {
3039
2893
  name: entry.name,
@@ -3050,15 +2904,15 @@ function registerHandlers(session) {
3050
2904
  });
3051
2905
  return { success: true, entries: directoryEntries };
3052
2906
  } catch (error) {
3053
- types$1.logger.debug("Failed to list directory:", error);
2907
+ types.logger.debug("Failed to list directory:", error);
3054
2908
  return { success: false, error: error instanceof Error ? error.message : "Failed to list directory" };
3055
2909
  }
3056
2910
  });
3057
2911
  session.setHandler("getDirectoryTree", async (data) => {
3058
- types$1.logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
2912
+ types.logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
3059
2913
  async function buildTree(path$1, name, currentDepth) {
3060
2914
  try {
3061
- const stats = await promises.stat(path$1);
2915
+ const stats = await fs.stat(path$1);
3062
2916
  const node = {
3063
2917
  name,
3064
2918
  path: path$1,
@@ -3067,12 +2921,12 @@ function registerHandlers(session) {
3067
2921
  modified: stats.mtime.getTime()
3068
2922
  };
3069
2923
  if (stats.isDirectory() && currentDepth < data.maxDepth) {
3070
- const entries = await promises.readdir(path$1, { withFileTypes: true });
2924
+ const entries = await fs.readdir(path$1, { withFileTypes: true });
3071
2925
  const children = [];
3072
2926
  await Promise.all(
3073
2927
  entries.map(async (entry) => {
3074
2928
  if (entry.isSymbolicLink()) {
3075
- types$1.logger.debug(`Skipping symlink: ${path.join(path$1, entry.name)}`);
2929
+ types.logger.debug(`Skipping symlink: ${path.join(path$1, entry.name)}`);
3076
2930
  return;
3077
2931
  }
3078
2932
  const childPath = path.join(path$1, entry.name);
@@ -3091,7 +2945,7 @@ function registerHandlers(session) {
3091
2945
  }
3092
2946
  return node;
3093
2947
  } catch (error) {
3094
- types$1.logger.debug(`Failed to process ${path$1}:`, error instanceof Error ? error.message : String(error));
2948
+ types.logger.debug(`Failed to process ${path$1}:`, error instanceof Error ? error.message : String(error));
3095
2949
  return null;
3096
2950
  }
3097
2951
  }
@@ -3106,12 +2960,12 @@ function registerHandlers(session) {
3106
2960
  }
3107
2961
  return { success: true, tree };
3108
2962
  } catch (error) {
3109
- types$1.logger.debug("Failed to get directory tree:", error);
2963
+ types.logger.debug("Failed to get directory tree:", error);
3110
2964
  return { success: false, error: error instanceof Error ? error.message : "Failed to get directory tree" };
3111
2965
  }
3112
2966
  });
3113
2967
  session.setHandler("ripgrep", async (data) => {
3114
- types$1.logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
2968
+ types.logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
3115
2969
  try {
3116
2970
  const result = await run(data.args, { cwd: data.cwd });
3117
2971
  return {
@@ -3121,7 +2975,7 @@ function registerHandlers(session) {
3121
2975
  stderr: result.stderr.toString()
3122
2976
  };
3123
2977
  } catch (error) {
3124
- types$1.logger.debug("Failed to run ripgrep:", error);
2978
+ types.logger.debug("Failed to run ripgrep:", error);
3125
2979
  return {
3126
2980
  success: false,
3127
2981
  error: error instanceof Error ? error.message : "Failed to run ripgrep"
@@ -3129,19 +2983,19 @@ function registerHandlers(session) {
3129
2983
  }
3130
2984
  });
3131
2985
  session.setHandler("killSession", async () => {
3132
- types$1.logger.debug("Kill session request received");
2986
+ types.logger.debug("Kill session request received");
3133
2987
  try {
3134
2988
  const response = {
3135
2989
  success: true,
3136
2990
  message: "Session termination acknowledged, exiting in 100ms"
3137
2991
  };
3138
2992
  setTimeout(() => {
3139
- types$1.logger.debug("[KILL SESSION] Exiting process as requested");
2993
+ types.logger.debug("[KILL SESSION] Exiting process as requested");
3140
2994
  process.exit(0);
3141
2995
  }, 100);
3142
2996
  return response;
3143
2997
  } catch (error) {
3144
- types$1.logger.debug("Failed to kill session:", error);
2998
+ types.logger.debug("Failed to kill session:", error);
3145
2999
  return {
3146
3000
  success: false,
3147
3001
  message: error instanceof Error ? error.message : "Failed to kill session"
@@ -3150,129 +3004,6 @@ function registerHandlers(session) {
3150
3004
  });
3151
3005
  }
3152
3006
 
3153
- const defaultSettings = {
3154
- onboardingCompleted: false
3155
- };
3156
- async function readSettings() {
3157
- if (!node_fs.existsSync(types$1.configuration.settingsFile)) {
3158
- return { ...defaultSettings };
3159
- }
3160
- try {
3161
- const content = await promises$1.readFile(types$1.configuration.settingsFile, "utf8");
3162
- return JSON.parse(content);
3163
- } catch {
3164
- return { ...defaultSettings };
3165
- }
3166
- }
3167
- async function updateSettings(updater) {
3168
- const LOCK_RETRY_INTERVAL_MS = 100;
3169
- const MAX_LOCK_ATTEMPTS = 50;
3170
- const STALE_LOCK_TIMEOUT_MS = 1e4;
3171
- const lockFile = types$1.configuration.settingsFile + ".lock";
3172
- const tmpFile = types$1.configuration.settingsFile + ".tmp";
3173
- let fileHandle;
3174
- let attempts = 0;
3175
- while (attempts < MAX_LOCK_ATTEMPTS) {
3176
- try {
3177
- fileHandle = await promises$1.open(lockFile, node_fs.constants.O_CREAT | node_fs.constants.O_EXCL | node_fs.constants.O_WRONLY);
3178
- break;
3179
- } catch (err) {
3180
- if (err.code === "EEXIST") {
3181
- attempts++;
3182
- await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
3183
- try {
3184
- const stats = await promises$1.stat(lockFile);
3185
- if (Date.now() - stats.mtimeMs > STALE_LOCK_TIMEOUT_MS) {
3186
- await promises$1.unlink(lockFile).catch(() => {
3187
- });
3188
- }
3189
- } catch {
3190
- }
3191
- } else {
3192
- throw err;
3193
- }
3194
- }
3195
- }
3196
- if (!fileHandle) {
3197
- throw new Error(`Failed to acquire settings lock after ${MAX_LOCK_ATTEMPTS * LOCK_RETRY_INTERVAL_MS / 1e3} seconds`);
3198
- }
3199
- try {
3200
- const current = await readSettings() || { ...defaultSettings };
3201
- const updated = await updater(current);
3202
- if (!node_fs.existsSync(types$1.configuration.happyHomeDir)) {
3203
- await promises$1.mkdir(types$1.configuration.happyHomeDir, { recursive: true });
3204
- }
3205
- await promises$1.writeFile(tmpFile, JSON.stringify(updated, null, 2));
3206
- await promises$1.rename(tmpFile, types$1.configuration.settingsFile);
3207
- return updated;
3208
- } finally {
3209
- await fileHandle.close();
3210
- await promises$1.unlink(lockFile).catch(() => {
3211
- });
3212
- }
3213
- }
3214
- const credentialsSchema = z__namespace.object({
3215
- secret: z__namespace.string().base64(),
3216
- token: z__namespace.string()
3217
- });
3218
- async function readCredentials() {
3219
- if (!node_fs.existsSync(types$1.configuration.privateKeyFile)) {
3220
- return null;
3221
- }
3222
- try {
3223
- const keyBase64 = await promises$1.readFile(types$1.configuration.privateKeyFile, "utf8");
3224
- const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
3225
- return {
3226
- secret: new Uint8Array(Buffer.from(credentials.secret, "base64")),
3227
- token: credentials.token
3228
- };
3229
- } catch {
3230
- return null;
3231
- }
3232
- }
3233
- async function writeCredentials(credentials) {
3234
- if (!node_fs.existsSync(types$1.configuration.happyHomeDir)) {
3235
- await promises$1.mkdir(types$1.configuration.happyHomeDir, { recursive: true });
3236
- }
3237
- await promises$1.writeFile(types$1.configuration.privateKeyFile, JSON.stringify({
3238
- secret: types$1.encodeBase64(credentials.secret),
3239
- token: credentials.token
3240
- }, null, 2));
3241
- }
3242
- async function clearCredentials() {
3243
- if (node_fs.existsSync(types$1.configuration.privateKeyFile)) {
3244
- await promises$1.unlink(types$1.configuration.privateKeyFile);
3245
- }
3246
- }
3247
- async function clearMachineId() {
3248
- await updateSettings((settings) => ({
3249
- ...settings,
3250
- machineId: void 0
3251
- }));
3252
- }
3253
- async function readDaemonState() {
3254
- try {
3255
- if (!node_fs.existsSync(types$1.configuration.daemonStateFile)) {
3256
- return null;
3257
- }
3258
- const content = await promises$1.readFile(types$1.configuration.daemonStateFile, "utf-8");
3259
- return JSON.parse(content);
3260
- } catch (error) {
3261
- return null;
3262
- }
3263
- }
3264
- async function writeDaemonState(state) {
3265
- if (!node_fs.existsSync(types$1.configuration.happyHomeDir)) {
3266
- await promises$1.mkdir(types$1.configuration.happyHomeDir, { recursive: true });
3267
- }
3268
- await promises$1.writeFile(types$1.configuration.daemonStateFile, JSON.stringify(state, null, 2));
3269
- }
3270
- async function clearDaemonState() {
3271
- if (node_fs.existsSync(types$1.configuration.daemonStateFile)) {
3272
- await promises$1.unlink(types$1.configuration.daemonStateFile);
3273
- }
3274
- }
3275
-
3276
3007
  class MessageQueue2 {
3277
3008
  queue = [];
3278
3009
  // Made public for testing
@@ -3283,7 +3014,7 @@ class MessageQueue2 {
3283
3014
  constructor(modeHasher, onMessageHandler = null) {
3284
3015
  this.modeHasher = modeHasher;
3285
3016
  this.onMessageHandler = onMessageHandler;
3286
- types$1.logger.debug(`[MessageQueue2] Initialized`);
3017
+ types.logger.debug(`[MessageQueue2] Initialized`);
3287
3018
  }
3288
3019
  /**
3289
3020
  * Set a handler that will be called when a message arrives
@@ -3299,7 +3030,7 @@ class MessageQueue2 {
3299
3030
  throw new Error("Cannot push to closed queue");
3300
3031
  }
3301
3032
  const modeHash = this.modeHasher(mode);
3302
- types$1.logger.debug(`[MessageQueue2] push() called with mode hash: ${modeHash}`);
3033
+ types.logger.debug(`[MessageQueue2] push() called with mode hash: ${modeHash}`);
3303
3034
  this.queue.push({
3304
3035
  message,
3305
3036
  mode,
@@ -3310,12 +3041,12 @@ class MessageQueue2 {
3310
3041
  this.onMessageHandler(message, mode);
3311
3042
  }
3312
3043
  if (this.waiter) {
3313
- types$1.logger.debug(`[MessageQueue2] Notifying waiter`);
3044
+ types.logger.debug(`[MessageQueue2] Notifying waiter`);
3314
3045
  const waiter = this.waiter;
3315
3046
  this.waiter = null;
3316
3047
  waiter(true);
3317
3048
  }
3318
- types$1.logger.debug(`[MessageQueue2] push() completed. Queue size: ${this.queue.length}`);
3049
+ types.logger.debug(`[MessageQueue2] push() completed. Queue size: ${this.queue.length}`);
3319
3050
  }
3320
3051
  /**
3321
3052
  * Push a message immediately without batching delay.
@@ -3326,7 +3057,7 @@ class MessageQueue2 {
3326
3057
  throw new Error("Cannot push to closed queue");
3327
3058
  }
3328
3059
  const modeHash = this.modeHasher(mode);
3329
- types$1.logger.debug(`[MessageQueue2] pushImmediate() called with mode hash: ${modeHash}`);
3060
+ types.logger.debug(`[MessageQueue2] pushImmediate() called with mode hash: ${modeHash}`);
3330
3061
  this.queue.push({
3331
3062
  message,
3332
3063
  mode,
@@ -3337,12 +3068,12 @@ class MessageQueue2 {
3337
3068
  this.onMessageHandler(message, mode);
3338
3069
  }
3339
3070
  if (this.waiter) {
3340
- types$1.logger.debug(`[MessageQueue2] Notifying waiter for immediate message`);
3071
+ types.logger.debug(`[MessageQueue2] Notifying waiter for immediate message`);
3341
3072
  const waiter = this.waiter;
3342
3073
  this.waiter = null;
3343
3074
  waiter(true);
3344
3075
  }
3345
- types$1.logger.debug(`[MessageQueue2] pushImmediate() completed. Queue size: ${this.queue.length}`);
3076
+ types.logger.debug(`[MessageQueue2] pushImmediate() completed. Queue size: ${this.queue.length}`);
3346
3077
  }
3347
3078
  /**
3348
3079
  * Push a message that must be processed in complete isolation.
@@ -3354,7 +3085,7 @@ class MessageQueue2 {
3354
3085
  throw new Error("Cannot push to closed queue");
3355
3086
  }
3356
3087
  const modeHash = this.modeHasher(mode);
3357
- types$1.logger.debug(`[MessageQueue2] pushIsolateAndClear() called with mode hash: ${modeHash} - clearing ${this.queue.length} pending messages`);
3088
+ types.logger.debug(`[MessageQueue2] pushIsolateAndClear() called with mode hash: ${modeHash} - clearing ${this.queue.length} pending messages`);
3358
3089
  this.queue = [];
3359
3090
  this.queue.push({
3360
3091
  message,
@@ -3366,12 +3097,12 @@ class MessageQueue2 {
3366
3097
  this.onMessageHandler(message, mode);
3367
3098
  }
3368
3099
  if (this.waiter) {
3369
- types$1.logger.debug(`[MessageQueue2] Notifying waiter for isolated message`);
3100
+ types.logger.debug(`[MessageQueue2] Notifying waiter for isolated message`);
3370
3101
  const waiter = this.waiter;
3371
3102
  this.waiter = null;
3372
3103
  waiter(true);
3373
3104
  }
3374
- types$1.logger.debug(`[MessageQueue2] pushIsolateAndClear() completed. Queue size: ${this.queue.length}`);
3105
+ types.logger.debug(`[MessageQueue2] pushIsolateAndClear() completed. Queue size: ${this.queue.length}`);
3375
3106
  }
3376
3107
  /**
3377
3108
  * Push a message to the beginning of the queue with a mode.
@@ -3381,7 +3112,7 @@ class MessageQueue2 {
3381
3112
  throw new Error("Cannot unshift to closed queue");
3382
3113
  }
3383
3114
  const modeHash = this.modeHasher(mode);
3384
- types$1.logger.debug(`[MessageQueue2] unshift() called with mode hash: ${modeHash}`);
3115
+ types.logger.debug(`[MessageQueue2] unshift() called with mode hash: ${modeHash}`);
3385
3116
  this.queue.unshift({
3386
3117
  message,
3387
3118
  mode,
@@ -3392,18 +3123,18 @@ class MessageQueue2 {
3392
3123
  this.onMessageHandler(message, mode);
3393
3124
  }
3394
3125
  if (this.waiter) {
3395
- types$1.logger.debug(`[MessageQueue2] Notifying waiter`);
3126
+ types.logger.debug(`[MessageQueue2] Notifying waiter`);
3396
3127
  const waiter = this.waiter;
3397
3128
  this.waiter = null;
3398
3129
  waiter(true);
3399
3130
  }
3400
- types$1.logger.debug(`[MessageQueue2] unshift() completed. Queue size: ${this.queue.length}`);
3131
+ types.logger.debug(`[MessageQueue2] unshift() completed. Queue size: ${this.queue.length}`);
3401
3132
  }
3402
3133
  /**
3403
3134
  * Reset the queue - clears all messages and resets to empty state
3404
3135
  */
3405
3136
  reset() {
3406
- types$1.logger.debug(`[MessageQueue2] reset() called. Clearing ${this.queue.length} messages`);
3137
+ types.logger.debug(`[MessageQueue2] reset() called. Clearing ${this.queue.length} messages`);
3407
3138
  this.queue = [];
3408
3139
  this.closed = false;
3409
3140
  this.waiter = null;
@@ -3412,7 +3143,7 @@ class MessageQueue2 {
3412
3143
  * Close the queue - no more messages can be pushed
3413
3144
  */
3414
3145
  close() {
3415
- types$1.logger.debug(`[MessageQueue2] close() called`);
3146
+ types.logger.debug(`[MessageQueue2] close() called`);
3416
3147
  this.closed = true;
3417
3148
  if (this.waiter) {
3418
3149
  const waiter = this.waiter;
@@ -3464,13 +3195,13 @@ class MessageQueue2 {
3464
3195
  if (firstItem.isolate) {
3465
3196
  const item = this.queue.shift();
3466
3197
  sameModeMessages.push(item.message);
3467
- types$1.logger.debug(`[MessageQueue2] Collected isolated message with mode hash: ${targetModeHash}`);
3198
+ types.logger.debug(`[MessageQueue2] Collected isolated message with mode hash: ${targetModeHash}`);
3468
3199
  } else {
3469
3200
  while (this.queue.length > 0 && this.queue[0].modeHash === targetModeHash && !this.queue[0].isolate) {
3470
3201
  const item = this.queue.shift();
3471
3202
  sameModeMessages.push(item.message);
3472
3203
  }
3473
- types$1.logger.debug(`[MessageQueue2] Collected batch of ${sameModeMessages.length} messages with mode hash: ${targetModeHash}`);
3204
+ types.logger.debug(`[MessageQueue2] Collected batch of ${sameModeMessages.length} messages with mode hash: ${targetModeHash}`);
3474
3205
  }
3475
3206
  const combinedMessage = sameModeMessages.join("\n");
3476
3207
  return {
@@ -3488,7 +3219,7 @@ class MessageQueue2 {
3488
3219
  let abortHandler = null;
3489
3220
  if (abortSignal) {
3490
3221
  abortHandler = () => {
3491
- types$1.logger.debug("[MessageQueue2] Wait aborted");
3222
+ types.logger.debug("[MessageQueue2] Wait aborted");
3492
3223
  if (this.waiter === waiterFunc) {
3493
3224
  this.waiter = null;
3494
3225
  }
@@ -3517,7 +3248,7 @@ class MessageQueue2 {
3517
3248
  return;
3518
3249
  }
3519
3250
  this.waiter = waiterFunc;
3520
- types$1.logger.debug("[MessageQueue2] Waiting for messages...");
3251
+ types.logger.debug("[MessageQueue2] Waiting for messages...");
3521
3252
  });
3522
3253
  }
3523
3254
  }
@@ -3611,11 +3342,11 @@ function hashObject(obj, options, encoding = "hex") {
3611
3342
  let caffeinateProcess = null;
3612
3343
  function startCaffeinate() {
3613
3344
  if (process.platform !== "darwin") {
3614
- types$1.logger.debug("[caffeinate] Not on macOS, skipping caffeinate");
3345
+ types.logger.debug("[caffeinate] Not on macOS, skipping caffeinate");
3615
3346
  return false;
3616
3347
  }
3617
3348
  if (caffeinateProcess && !caffeinateProcess.killed) {
3618
- types$1.logger.debug("[caffeinate] Caffeinate already running");
3349
+ types.logger.debug("[caffeinate] Caffeinate already running");
3619
3350
  return true;
3620
3351
  }
3621
3352
  try {
@@ -3624,41 +3355,41 @@ function startCaffeinate() {
3624
3355
  detached: false
3625
3356
  });
3626
3357
  caffeinateProcess.on("error", (error) => {
3627
- types$1.logger.debug("[caffeinate] Error starting caffeinate:", error);
3358
+ types.logger.debug("[caffeinate] Error starting caffeinate:", error);
3628
3359
  caffeinateProcess = null;
3629
3360
  });
3630
3361
  caffeinateProcess.on("exit", (code, signal) => {
3631
- types$1.logger.debug(`[caffeinate] Process exited with code ${code}, signal ${signal}`);
3362
+ types.logger.debug(`[caffeinate] Process exited with code ${code}, signal ${signal}`);
3632
3363
  caffeinateProcess = null;
3633
3364
  });
3634
- types$1.logger.debug(`[caffeinate] Started with PID ${caffeinateProcess.pid}`);
3365
+ types.logger.debug(`[caffeinate] Started with PID ${caffeinateProcess.pid}`);
3635
3366
  setupCleanupHandlers();
3636
3367
  return true;
3637
3368
  } catch (error) {
3638
- types$1.logger.debug("[caffeinate] Failed to start caffeinate:", error);
3369
+ types.logger.debug("[caffeinate] Failed to start caffeinate:", error);
3639
3370
  return false;
3640
3371
  }
3641
3372
  }
3642
3373
  let isStopping = false;
3643
- function stopCaffeinate() {
3374
+ async function stopCaffeinate() {
3644
3375
  if (isStopping) {
3376
+ types.logger.debug("[caffeinate] Already stopping, skipping");
3645
3377
  return;
3646
3378
  }
3647
3379
  if (caffeinateProcess && !caffeinateProcess.killed) {
3648
3380
  isStopping = true;
3649
- types$1.logger.debug(`[caffeinate] Stopping caffeinate process PID ${caffeinateProcess.pid}`);
3381
+ types.logger.debug(`[caffeinate] Stopping caffeinate process PID ${caffeinateProcess.pid}`);
3650
3382
  try {
3651
3383
  caffeinateProcess.kill("SIGTERM");
3652
- setTimeout(() => {
3653
- if (caffeinateProcess && !caffeinateProcess.killed) {
3654
- types$1.logger.debug("[caffeinate] Force killing caffeinate process");
3655
- caffeinateProcess.kill("SIGKILL");
3656
- }
3657
- caffeinateProcess = null;
3658
- isStopping = false;
3659
- }, 1e3);
3384
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
3385
+ if (caffeinateProcess && !caffeinateProcess.killed) {
3386
+ types.logger.debug("[caffeinate] Force killing caffeinate process");
3387
+ caffeinateProcess.kill("SIGKILL");
3388
+ }
3389
+ caffeinateProcess = null;
3390
+ isStopping = false;
3660
3391
  } catch (error) {
3661
- types$1.logger.debug("[caffeinate] Error stopping caffeinate:", error);
3392
+ types.logger.debug("[caffeinate] Error stopping caffeinate:", error);
3662
3393
  isStopping = false;
3663
3394
  }
3664
3395
  }
@@ -3678,21 +3409,19 @@ function setupCleanupHandlers() {
3678
3409
  process.on("SIGUSR1", cleanup);
3679
3410
  process.on("SIGUSR2", cleanup);
3680
3411
  process.on("uncaughtException", (error) => {
3681
- types$1.logger.debug("[caffeinate] Uncaught exception, cleaning up:", error);
3412
+ types.logger.debug("[caffeinate] Uncaught exception, cleaning up:", error);
3682
3413
  cleanup();
3683
- process.exit(1);
3684
3414
  });
3685
3415
  process.on("unhandledRejection", (reason, promise) => {
3686
- types$1.logger.debug("[caffeinate] Unhandled rejection, cleaning up:", reason);
3416
+ types.logger.debug("[caffeinate] Unhandled rejection, cleaning up:", reason);
3687
3417
  cleanup();
3688
- process.exit(1);
3689
3418
  });
3690
3419
  }
3691
3420
 
3692
3421
  async function extractSDKMetadata() {
3693
3422
  const abortController = new AbortController();
3694
3423
  try {
3695
- types$1.logger.debug("[metadataExtractor] Starting SDK metadata extraction");
3424
+ types.logger.debug("[metadataExtractor] Starting SDK metadata extraction");
3696
3425
  const sdkQuery = query({
3697
3426
  prompt: "hello",
3698
3427
  options: {
@@ -3708,19 +3437,19 @@ async function extractSDKMetadata() {
3708
3437
  tools: systemMessage.tools,
3709
3438
  slashCommands: systemMessage.slash_commands
3710
3439
  };
3711
- types$1.logger.debug("[metadataExtractor] Captured SDK metadata:", metadata);
3440
+ types.logger.debug("[metadataExtractor] Captured SDK metadata:", metadata);
3712
3441
  abortController.abort();
3713
3442
  return metadata;
3714
3443
  }
3715
3444
  }
3716
- types$1.logger.debug("[metadataExtractor] No init message received from SDK");
3445
+ types.logger.debug("[metadataExtractor] No init message received from SDK");
3717
3446
  return {};
3718
3447
  } catch (error) {
3719
3448
  if (error instanceof Error && error.name === "AbortError") {
3720
- types$1.logger.debug("[metadataExtractor] SDK query aborted after capturing metadata");
3449
+ types.logger.debug("[metadataExtractor] SDK query aborted after capturing metadata");
3721
3450
  return {};
3722
3451
  }
3723
- types$1.logger.debug("[metadataExtractor] Error extracting SDK metadata:", error);
3452
+ types.logger.debug("[metadataExtractor] Error extracting SDK metadata:", error);
3724
3453
  return {};
3725
3454
  }
3726
3455
  }
@@ -3730,93 +3459,208 @@ function extractSDKMetadataAsync(onComplete) {
3730
3459
  onComplete(metadata);
3731
3460
  }
3732
3461
  }).catch((error) => {
3733
- types$1.logger.debug("[metadataExtractor] Async extraction failed:", error);
3462
+ types.logger.debug("[metadataExtractor] Async extraction failed:", error);
3734
3463
  });
3735
3464
  }
3736
3465
 
3737
- async function isDaemonRunning() {
3466
+ async function daemonPost(path, body) {
3467
+ const state = await types.readDaemonState();
3468
+ if (!state?.httpPort) {
3469
+ const errorMessage = "No daemon running, no state file found";
3470
+ types.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
3471
+ return {
3472
+ error: errorMessage
3473
+ };
3474
+ }
3738
3475
  try {
3739
- const state = await getDaemonState();
3740
- if (!state) {
3741
- return false;
3742
- }
3743
- const isRunning = await isDaemonProcessRunning(state.pid);
3744
- if (!isRunning) {
3745
- types$1.logger.debug("[DAEMON RUN] Daemon PID not running, cleaning up state");
3746
- await cleanupDaemonState();
3747
- return false;
3748
- }
3749
- return true;
3476
+ process.kill(state.pid, 0);
3750
3477
  } catch (error) {
3751
- types$1.logger.debug("[DAEMON RUN] Error checking daemon status", error);
3752
- return false;
3478
+ const errorMessage = "Daemon is not running, file is stale";
3479
+ types.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
3480
+ return {
3481
+ error: errorMessage
3482
+ };
3753
3483
  }
3754
- }
3755
- async function getDaemonState() {
3756
3484
  try {
3757
- return await readDaemonState();
3485
+ const timeout = process.env.HAPPY_DAEMON_HTTP_TIMEOUT ? parseInt(process.env.HAPPY_DAEMON_HTTP_TIMEOUT) : 1e4;
3486
+ const response = await fetch(`http://127.0.0.1:${state.httpPort}${path}`, {
3487
+ method: "POST",
3488
+ headers: { "Content-Type": "application/json" },
3489
+ body: JSON.stringify(body || {}),
3490
+ // Mostly increased for stress test
3491
+ signal: AbortSignal.timeout(timeout)
3492
+ });
3493
+ if (!response.ok) {
3494
+ const errorMessage = `Request failed: ${path}, HTTP ${response.status}`;
3495
+ types.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
3496
+ return {
3497
+ error: errorMessage
3498
+ };
3499
+ }
3500
+ return await response.json();
3758
3501
  } catch (error) {
3759
- types$1.logger.debug("[DAEMON RUN] Error reading daemon metadata", error);
3760
- return null;
3502
+ const errorMessage = `Request failed: ${path}, ${error instanceof Error ? error.message : "Unknown error"}`;
3503
+ types.logger.debug(`[CONTROL CLIENT] ${errorMessage}`);
3504
+ return {
3505
+ error: errorMessage
3506
+ };
3761
3507
  }
3762
3508
  }
3763
- async function isDaemonProcessRunning(pid) {
3509
+ async function notifyDaemonSessionStarted(sessionId, metadata) {
3510
+ return await daemonPost("/session-started", {
3511
+ sessionId,
3512
+ metadata
3513
+ });
3514
+ }
3515
+ async function listDaemonSessions() {
3516
+ const result = await daemonPost("/list");
3517
+ return result.children || [];
3518
+ }
3519
+ async function stopDaemonSession(sessionId) {
3520
+ const result = await daemonPost("/stop-session", { sessionId });
3521
+ return result.success || false;
3522
+ }
3523
+ async function stopDaemonHttp() {
3524
+ await daemonPost("/stop");
3525
+ }
3526
+ async function checkIfDaemonRunningAndCleanupStaleState() {
3527
+ const state = await types.readDaemonState();
3528
+ if (!state) {
3529
+ return false;
3530
+ }
3764
3531
  try {
3765
- process.kill(pid, 0);
3532
+ process.kill(state.pid, 0);
3766
3533
  return true;
3767
3534
  } catch {
3535
+ types.logger.debug("[DAEMON RUN] Daemon PID not running, cleaning up state");
3536
+ await cleanupDaemonState();
3537
+ return false;
3538
+ }
3539
+ }
3540
+ async function isDaemonRunningSameVersion() {
3541
+ types.logger.debug("[DAEMON CONTROL] Checking if daemon is running same version");
3542
+ const runningDaemon = await checkIfDaemonRunningAndCleanupStaleState();
3543
+ if (!runningDaemon) {
3544
+ types.logger.debug("[DAEMON CONTROL] No daemon running, returning false");
3545
+ return false;
3546
+ }
3547
+ const state = await types.readDaemonState();
3548
+ if (!state) {
3549
+ types.logger.debug("[DAEMON CONTROL] No daemon state found, returning false");
3550
+ return false;
3551
+ }
3552
+ try {
3553
+ types.logger.debug(`[DAEMON CONTROL] Current CLI version: ${types.configuration.currentCliVersion}, Daemon started with version: ${state.startedWithCliVersion}`);
3554
+ return types.configuration.currentCliVersion === state.startedWithCliVersion;
3555
+ } catch (error) {
3556
+ types.logger.debug("[DAEMON CONTROL] Error checking daemon version", error);
3768
3557
  return false;
3769
3558
  }
3770
3559
  }
3771
3560
  async function cleanupDaemonState() {
3772
3561
  try {
3773
- await clearDaemonState();
3774
- types$1.logger.debug("[DAEMON RUN] Daemon state file removed");
3562
+ await types.clearDaemonState();
3563
+ types.logger.debug("[DAEMON RUN] Daemon state file removed");
3564
+ } catch (error) {
3565
+ types.logger.debug("[DAEMON RUN] Error cleaning up daemon metadata", error);
3566
+ }
3567
+ }
3568
+ async function stopDaemon() {
3569
+ try {
3570
+ const state = await types.readDaemonState();
3571
+ if (!state) {
3572
+ types.logger.debug("No daemon state found");
3573
+ return;
3574
+ }
3575
+ types.logger.debug(`Stopping daemon with PID ${state.pid}`);
3576
+ try {
3577
+ await stopDaemonHttp();
3578
+ await waitForProcessDeath(state.pid, 2e3);
3579
+ types.logger.debug("Daemon stopped gracefully via HTTP");
3580
+ return;
3581
+ } catch (error) {
3582
+ types.logger.debug("HTTP stop failed, will force kill", error);
3583
+ }
3584
+ try {
3585
+ process.kill(state.pid, "SIGKILL");
3586
+ types.logger.debug("Force killed daemon");
3587
+ } catch (error) {
3588
+ types.logger.debug("Daemon already dead");
3589
+ }
3775
3590
  } catch (error) {
3776
- types$1.logger.debug("[DAEMON RUN] Error cleaning up daemon metadata", error);
3591
+ types.logger.debug("Error stopping daemon", error);
3592
+ }
3593
+ }
3594
+ async function waitForProcessDeath(pid, timeout) {
3595
+ const start = Date.now();
3596
+ while (Date.now() - start < timeout) {
3597
+ try {
3598
+ process.kill(pid, 0);
3599
+ await new Promise((resolve) => setTimeout(resolve, 100));
3600
+ } catch {
3601
+ return;
3602
+ }
3777
3603
  }
3604
+ throw new Error("Process did not die within timeout");
3778
3605
  }
3606
+
3779
3607
  function findAllHappyProcesses() {
3780
3608
  try {
3781
- const output = node_child_process.execSync('ps aux | grep "happy.mjs" | grep -v grep', { encoding: "utf8" });
3782
- const lines = output.trim().split("\n").filter((line) => line.trim());
3783
3609
  const allProcesses = [];
3784
- for (const line of lines) {
3785
- const parts = line.trim().split(/\s+/);
3786
- if (parts.length < 11) continue;
3787
- const pid = parseInt(parts[1]);
3788
- const command = parts.slice(10).join(" ");
3789
- let type = "unknown";
3790
- if (pid === process.pid) {
3791
- type = "current";
3792
- } else if (command.includes("daemon start-sync") || command.includes("daemon start")) {
3793
- type = "daemon";
3794
- } else if (command.includes("--started-by daemon")) {
3795
- type = "daemon-spawned-session";
3796
- } else if (command.includes("doctor")) {
3797
- type = "doctor";
3798
- } else {
3799
- type = "user-session";
3610
+ try {
3611
+ const happyOutput = node_child_process.execSync('ps aux | grep -E "(happy\\.mjs|happy-coder|happy-cli.*dist/index\\.mjs)" | grep -v grep', { encoding: "utf8" });
3612
+ const happyLines = happyOutput.trim().split("\n").filter((line) => line.trim());
3613
+ for (const line of happyLines) {
3614
+ const parts = line.trim().split(/\s+/);
3615
+ if (parts.length < 11) continue;
3616
+ const pid = parseInt(parts[1]);
3617
+ const command = parts.slice(10).join(" ");
3618
+ let type = "unknown";
3619
+ if (pid === process.pid) {
3620
+ type = "current";
3621
+ } else if (command.includes("--version")) {
3622
+ type = "daemon-version-check";
3623
+ } else if (command.includes("daemon start-sync") || command.includes("daemon start")) {
3624
+ type = "daemon";
3625
+ } else if (command.includes("--started-by daemon")) {
3626
+ type = "daemon-spawned-session";
3627
+ } else if (command.includes("doctor")) {
3628
+ type = "doctor";
3629
+ } else {
3630
+ type = "user-session";
3631
+ }
3632
+ allProcesses.push({ pid, command, type });
3800
3633
  }
3801
- allProcesses.push({ pid, command, type });
3634
+ } catch {
3802
3635
  }
3803
3636
  try {
3804
- const devOutput = node_child_process.execSync('ps aux | grep -E "(tsx.*src/index.ts|yarn.*tsx)" | grep -v grep', { encoding: "utf8" });
3637
+ const devOutput = node_child_process.execSync('ps aux | grep -E "tsx.*src/index\\.ts" | grep -v grep', { encoding: "utf8" });
3805
3638
  const devLines = devOutput.trim().split("\n").filter((line) => line.trim());
3806
3639
  for (const line of devLines) {
3807
3640
  const parts = line.trim().split(/\s+/);
3808
3641
  if (parts.length < 11) continue;
3809
3642
  const pid = parseInt(parts[1]);
3810
3643
  const command = parts.slice(10).join(" ");
3811
- let workingDir = "";
3812
- try {
3813
- const pwdOutput = node_child_process.execSync(`pwdx ${pid} 2>/dev/null`, { encoding: "utf8" });
3814
- workingDir = pwdOutput.replace(`${pid}:`, "").trim();
3815
- } catch {
3644
+ if (!command.includes("happy-cli/node_modules/tsx") && !command.includes("/bin/tsx src/index.ts")) {
3645
+ continue;
3816
3646
  }
3817
- if (workingDir.includes("happy-cli")) {
3818
- allProcesses.push({ pid, command, type: "dev-session" });
3647
+ let type = "unknown";
3648
+ if (pid === process.pid) {
3649
+ type = "current";
3650
+ } else if (command.includes("--version")) {
3651
+ type = "dev-daemon-version-check";
3652
+ } else if (command.includes("daemon start-sync") || command.includes("daemon start")) {
3653
+ type = "dev-daemon";
3654
+ } else if (command.includes("--started-by daemon")) {
3655
+ type = "dev-daemon-spawned";
3656
+ } else if (command.includes("doctor")) {
3657
+ type = "dev-doctor";
3658
+ } else if (command.includes("--yolo")) {
3659
+ type = "dev-session";
3660
+ } else {
3661
+ type = "dev-related";
3819
3662
  }
3663
+ allProcesses.push({ pid, command, type });
3820
3664
  }
3821
3665
  } catch {
3822
3666
  }
@@ -3827,18 +3671,39 @@ function findAllHappyProcesses() {
3827
3671
  }
3828
3672
  function findRunawayHappyProcesses() {
3829
3673
  try {
3830
- const output = node_child_process.execSync('ps aux | grep "happy.mjs" | grep -v grep', { encoding: "utf8" });
3831
- const lines = output.trim().split("\n").filter((line) => line.trim());
3832
3674
  const processes = [];
3833
- for (const line of lines) {
3834
- const parts = line.trim().split(/\s+/);
3835
- if (parts.length < 11) continue;
3836
- const pid = parseInt(parts[1]);
3837
- const command = parts.slice(10).join(" ");
3838
- if (pid === process.pid) continue;
3839
- if (command.includes("--started-by daemon") || command.includes("daemon start-sync") || command.includes("daemon start")) {
3840
- processes.push({ pid, command });
3675
+ try {
3676
+ const output = node_child_process.execSync('ps aux | grep -E "(happy\\.mjs|happy-coder|happy-cli.*dist/index\\.mjs)" | grep -v grep', { encoding: "utf8" });
3677
+ const lines = output.trim().split("\n").filter((line) => line.trim());
3678
+ for (const line of lines) {
3679
+ const parts = line.trim().split(/\s+/);
3680
+ if (parts.length < 11) continue;
3681
+ const pid = parseInt(parts[1]);
3682
+ const command = parts.slice(10).join(" ");
3683
+ if (pid === process.pid) continue;
3684
+ if (command.includes("--started-by daemon") || command.includes("daemon start-sync") || command.includes("daemon start") || command.includes("--version")) {
3685
+ processes.push({ pid, command });
3686
+ }
3687
+ }
3688
+ } catch {
3689
+ }
3690
+ try {
3691
+ const devOutput = node_child_process.execSync('ps aux | grep -E "tsx.*src/index\\.ts" | grep -v grep', { encoding: "utf8" });
3692
+ const devLines = devOutput.trim().split("\n").filter((line) => line.trim());
3693
+ for (const line of devLines) {
3694
+ const parts = line.trim().split(/\s+/);
3695
+ if (parts.length < 11) continue;
3696
+ const pid = parseInt(parts[1]);
3697
+ const command = parts.slice(10).join(" ");
3698
+ if (pid === process.pid) continue;
3699
+ if (!command.includes("happy-cli/node_modules/tsx") && !command.includes("/bin/tsx src/index.ts")) {
3700
+ continue;
3701
+ }
3702
+ if (command.includes("--started-by daemon") || command.includes("daemon start-sync") || command.includes("daemon start") || command.includes("--version")) {
3703
+ processes.push({ pid, command });
3704
+ }
3841
3705
  }
3706
+ } catch {
3842
3707
  }
3843
3708
  return processes;
3844
3709
  } catch (error) {
@@ -3848,10 +3713,10 @@ function findRunawayHappyProcesses() {
3848
3713
  async function killRunawayHappyProcesses() {
3849
3714
  const runawayProcesses = findRunawayHappyProcesses();
3850
3715
  const errors = [];
3851
- let killed = 0;
3852
- for (const { pid, command } of runawayProcesses) {
3716
+ const killPromises = runawayProcesses.map(async ({ pid, command }) => {
3853
3717
  try {
3854
3718
  process.kill(pid, "SIGTERM");
3719
+ console.log(`Sent SIGTERM to runaway process PID ${pid}: ${command}`);
3855
3720
  await new Promise((resolve) => setTimeout(resolve, 1e3));
3856
3721
  try {
3857
3722
  process.kill(pid, 0);
@@ -3859,66 +3724,19 @@ async function killRunawayHappyProcesses() {
3859
3724
  process.kill(pid, "SIGKILL");
3860
3725
  } catch {
3861
3726
  }
3862
- killed++;
3863
- console.log(`Killed runaway process PID ${pid}: ${command}`);
3727
+ console.log(`Successfully killed runaway process PID ${pid}`);
3728
+ return { success: true, pid, command };
3864
3729
  } catch (error) {
3865
- errors.push({ pid, error: error.message });
3730
+ const errorMessage = error.message;
3731
+ errors.push({ pid, error: errorMessage });
3732
+ console.log(`Failed to kill process PID ${pid}: ${errorMessage}`);
3733
+ return { success: false, pid, command };
3866
3734
  }
3867
- }
3735
+ });
3736
+ const results = await Promise.all(killPromises);
3737
+ const killed = results.filter((r) => r.success).length;
3868
3738
  return { killed, errors };
3869
3739
  }
3870
- async function stopDaemon() {
3871
- try {
3872
- stopCaffeinate();
3873
- types$1.logger.debug("Stopped sleep prevention");
3874
- const state = await getDaemonState();
3875
- if (!state) {
3876
- types$1.logger.debug("No daemon state found");
3877
- return;
3878
- }
3879
- types$1.logger.debug(`Stopping daemon with PID ${state.pid}`);
3880
- try {
3881
- const { stopDaemonHttp } = await Promise.resolve().then(function () { return controlClient; });
3882
- await stopDaemonHttp();
3883
- await waitForProcessDeath(state.pid, 5e3);
3884
- types$1.logger.debug("Daemon stopped gracefully via HTTP");
3885
- return;
3886
- } catch (error) {
3887
- types$1.logger.debug("HTTP stop failed, will force kill", error);
3888
- }
3889
- try {
3890
- process.kill(state.pid, "SIGKILL");
3891
- types$1.logger.debug("Force killed daemon");
3892
- } catch (error) {
3893
- types$1.logger.debug("Daemon already dead");
3894
- }
3895
- } catch (error) {
3896
- types$1.logger.debug("Error stopping daemon", error);
3897
- }
3898
- }
3899
- async function waitForProcessDeath(pid, timeout) {
3900
- const start = Date.now();
3901
- while (Date.now() - start < timeout) {
3902
- try {
3903
- process.kill(pid, 0);
3904
- await new Promise((resolve) => setTimeout(resolve, 100));
3905
- } catch {
3906
- return;
3907
- }
3908
- }
3909
- throw new Error("Process did not die within timeout");
3910
- }
3911
-
3912
- var utils = /*#__PURE__*/Object.freeze({
3913
- __proto__: null,
3914
- cleanupDaemonState: cleanupDaemonState,
3915
- findAllHappyProcesses: findAllHappyProcesses,
3916
- findRunawayHappyProcesses: findRunawayHappyProcesses,
3917
- getDaemonState: getDaemonState,
3918
- isDaemonRunning: isDaemonRunning,
3919
- killRunawayHappyProcesses: killRunawayHappyProcesses,
3920
- stopDaemon: stopDaemon
3921
- });
3922
3740
 
3923
3741
  function getEnvironmentInfo() {
3924
3742
  return {
@@ -3931,9 +3749,17 @@ function getEnvironmentInfo() {
3931
3749
  DEBUG: process.env.DEBUG,
3932
3750
  workingDirectory: process.cwd(),
3933
3751
  processArgv: process.argv,
3934
- happyDir: types$1.configuration?.happyHomeDir,
3935
- serverUrl: types$1.configuration?.serverUrl,
3936
- logsDir: types$1.configuration?.logsDir
3752
+ happyDir: types.configuration?.happyHomeDir,
3753
+ serverUrl: types.configuration?.serverUrl,
3754
+ logsDir: types.configuration?.logsDir,
3755
+ processPid: process.pid,
3756
+ nodeVersion: process.version,
3757
+ platform: process.platform,
3758
+ arch: process.arch,
3759
+ user: process.env.USER,
3760
+ home: process.env.HOME,
3761
+ shell: process.env.SHELL,
3762
+ terminal: process.env.TERM
3937
3763
  };
3938
3764
  }
3939
3765
  function getLogFiles(logDir) {
@@ -3945,62 +3771,67 @@ function getLogFiles(logDir) {
3945
3771
  const path = node_path.join(logDir, file);
3946
3772
  const stats = node_fs.statSync(path);
3947
3773
  return { file, path, modified: stats.mtime };
3948
- }).sort((a, b) => b.modified.getTime() - a.modified.getTime()).slice(0, 10);
3774
+ }).sort((a, b) => b.modified.getTime() - a.modified.getTime());
3949
3775
  } catch {
3950
3776
  return [];
3951
3777
  }
3952
3778
  }
3953
- async function runDoctorCommand() {
3954
- console.log(chalk.bold.cyan("\n\u{1FA7A} Happy CLI Doctor\n"));
3955
- console.log(chalk.bold("\u{1F4CB} Basic Information"));
3956
- console.log(`Happy CLI Version: ${chalk.green(packageJson.version)}`);
3957
- console.log(`Platform: ${chalk.green(process.platform)} ${process.arch}`);
3958
- console.log(`Node.js Version: ${chalk.green(process.version)}`);
3959
- console.log("");
3960
- console.log(chalk.bold("\u{1F527} Daemon Spawn Diagnostics"));
3961
- const projectRoot = projectPath();
3962
- const wrapperPath = node_path.join(projectRoot, "bin", "happy.mjs");
3963
- const cliEntrypoint = node_path.join(projectRoot, "dist", "index.mjs");
3964
- console.log(`Project Root: ${chalk.blue(projectRoot)}`);
3965
- console.log(`Wrapper Script: ${chalk.blue(wrapperPath)}`);
3966
- console.log(`CLI Entrypoint: ${chalk.blue(cliEntrypoint)}`);
3967
- console.log(`Wrapper Exists: ${node_fs.existsSync(wrapperPath) ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
3968
- console.log(`CLI Exists: ${node_fs.existsSync(cliEntrypoint) ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
3969
- console.log("");
3970
- console.log(chalk.bold("\u2699\uFE0F Configuration"));
3971
- console.log(`Happy Home: ${chalk.blue(types$1.configuration.happyHomeDir)}`);
3972
- console.log(`Server URL: ${chalk.blue(types$1.configuration.serverUrl)}`);
3973
- console.log(`Logs Dir: ${chalk.blue(types$1.configuration.logsDir)}`);
3974
- console.log(chalk.bold("\n\u{1F30D} Environment Variables"));
3975
- const env = getEnvironmentInfo();
3976
- console.log(`HAPPY_HOME_DIR: ${env.HAPPY_HOME_DIR ? chalk.green(env.HAPPY_HOME_DIR) : chalk.gray("not set")}`);
3977
- console.log(`HAPPY_SERVER_URL: ${env.HAPPY_SERVER_URL ? chalk.green(env.HAPPY_SERVER_URL) : chalk.gray("not set")}`);
3978
- console.log(`DANGEROUSLY_LOG_TO_SERVER: ${env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING ? chalk.yellow("ENABLED") : chalk.gray("not set")}`);
3979
- console.log(`DEBUG: ${env.DEBUG ? chalk.green(env.DEBUG) : chalk.gray("not set")}`);
3980
- console.log(`NODE_ENV: ${env.NODE_ENV ? chalk.green(env.NODE_ENV) : chalk.gray("not set")}`);
3981
- try {
3982
- const settings = await readSettings();
3983
- console.log(chalk.bold("\n\u{1F4C4} Settings (settings.json):"));
3984
- console.log(chalk.gray(JSON.stringify(settings, null, 2)));
3985
- } catch (error) {
3986
- console.log(chalk.bold("\n\u{1F4C4} Settings:"));
3987
- console.log(chalk.red("\u274C Failed to read settings"));
3779
+ async function runDoctorCommand(filter) {
3780
+ if (!filter) {
3781
+ filter = "all";
3988
3782
  }
3989
- console.log(chalk.bold("\n\u{1F510} Authentication"));
3990
- try {
3991
- const credentials = await readCredentials();
3992
- if (credentials) {
3993
- console.log(chalk.green("\u2713 Authenticated (credentials found)"));
3994
- } else {
3995
- console.log(chalk.yellow("\u26A0\uFE0F Not authenticated (no credentials)"));
3783
+ console.log(chalk.bold.cyan("\n\u{1FA7A} Happy CLI Doctor\n"));
3784
+ if (filter === "all") {
3785
+ console.log(chalk.bold("\u{1F4CB} Basic Information"));
3786
+ console.log(`Happy CLI Version: ${chalk.green(types.packageJson.version)}`);
3787
+ console.log(`Platform: ${chalk.green(process.platform)} ${process.arch}`);
3788
+ console.log(`Node.js Version: ${chalk.green(process.version)}`);
3789
+ console.log("");
3790
+ console.log(chalk.bold("\u{1F527} Daemon Spawn Diagnostics"));
3791
+ const projectRoot = projectPath();
3792
+ const wrapperPath = node_path.join(projectRoot, "bin", "happy.mjs");
3793
+ const cliEntrypoint = node_path.join(projectRoot, "dist", "index.mjs");
3794
+ console.log(`Project Root: ${chalk.blue(projectRoot)}`);
3795
+ console.log(`Wrapper Script: ${chalk.blue(wrapperPath)}`);
3796
+ console.log(`CLI Entrypoint: ${chalk.blue(cliEntrypoint)}`);
3797
+ console.log(`Wrapper Exists: ${node_fs.existsSync(wrapperPath) ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
3798
+ console.log(`CLI Exists: ${node_fs.existsSync(cliEntrypoint) ? chalk.green("\u2713 Yes") : chalk.red("\u274C No")}`);
3799
+ console.log("");
3800
+ console.log(chalk.bold("\u2699\uFE0F Configuration"));
3801
+ console.log(`Happy Home: ${chalk.blue(types.configuration.happyHomeDir)}`);
3802
+ console.log(`Server URL: ${chalk.blue(types.configuration.serverUrl)}`);
3803
+ console.log(`Logs Dir: ${chalk.blue(types.configuration.logsDir)}`);
3804
+ console.log(chalk.bold("\n\u{1F30D} Environment Variables"));
3805
+ const env = getEnvironmentInfo();
3806
+ console.log(`HAPPY_HOME_DIR: ${env.HAPPY_HOME_DIR ? chalk.green(env.HAPPY_HOME_DIR) : chalk.gray("not set")}`);
3807
+ console.log(`HAPPY_SERVER_URL: ${env.HAPPY_SERVER_URL ? chalk.green(env.HAPPY_SERVER_URL) : chalk.gray("not set")}`);
3808
+ console.log(`DANGEROUSLY_LOG_TO_SERVER: ${env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING ? chalk.yellow("ENABLED") : chalk.gray("not set")}`);
3809
+ console.log(`DEBUG: ${env.DEBUG ? chalk.green(env.DEBUG) : chalk.gray("not set")}`);
3810
+ console.log(`NODE_ENV: ${env.NODE_ENV ? chalk.green(env.NODE_ENV) : chalk.gray("not set")}`);
3811
+ try {
3812
+ const settings = await types.readSettings();
3813
+ console.log(chalk.bold("\n\u{1F4C4} Settings (settings.json):"));
3814
+ console.log(chalk.gray(JSON.stringify(settings, null, 2)));
3815
+ } catch (error) {
3816
+ console.log(chalk.bold("\n\u{1F4C4} Settings:"));
3817
+ console.log(chalk.red("\u274C Failed to read settings"));
3818
+ }
3819
+ console.log(chalk.bold("\n\u{1F510} Authentication"));
3820
+ try {
3821
+ const credentials = await types.readCredentials();
3822
+ if (credentials) {
3823
+ console.log(chalk.green("\u2713 Authenticated (credentials found)"));
3824
+ } else {
3825
+ console.log(chalk.yellow("\u26A0\uFE0F Not authenticated (no credentials)"));
3826
+ }
3827
+ } catch (error) {
3828
+ console.log(chalk.red("\u274C Error reading credentials"));
3996
3829
  }
3997
- } catch (error) {
3998
- console.log(chalk.red("\u274C Error reading credentials"));
3999
3830
  }
4000
3831
  console.log(chalk.bold("\n\u{1F916} Daemon Status"));
4001
3832
  try {
4002
- const isRunning = await isDaemonRunning();
4003
- const state = await getDaemonState();
3833
+ const isRunning = await checkIfDaemonRunningAndCleanupStaleState();
3834
+ const state = await types.readDaemonState();
4004
3835
  if (isRunning && state) {
4005
3836
  console.log(chalk.green("\u2713 Daemon is running"));
4006
3837
  console.log(` PID: ${state.pid}`);
@@ -4016,7 +3847,7 @@ async function runDoctorCommand() {
4016
3847
  }
4017
3848
  if (state) {
4018
3849
  console.log(chalk.bold("\n\u{1F4C4} Daemon State:"));
4019
- console.log(chalk.blue(`Location: ${types$1.configuration.daemonStateFile}`));
3850
+ console.log(chalk.blue(`Location: ${types.configuration.daemonStateFile}`));
4020
3851
  console.log(chalk.gray(JSON.stringify(state, null, 2)));
4021
3852
  }
4022
3853
  const allProcesses = findAllHappyProcesses();
@@ -4031,9 +3862,11 @@ async function runDoctorCommand() {
4031
3862
  const typeLabels = {
4032
3863
  "current": "\u{1F4CD} Current Process",
4033
3864
  "daemon": "\u{1F916} Daemon",
3865
+ "daemon-version-check": "\u{1F50D} Daemon Version Check (stuck)",
4034
3866
  "daemon-spawned-session": "\u{1F517} Daemon-Spawned Sessions",
4035
3867
  "user-session": "\u{1F464} User Sessions",
4036
3868
  "dev-daemon": "\u{1F6E0}\uFE0F Dev Daemon",
3869
+ "dev-daemon-version-check": "\u{1F6E0}\uFE0F Dev Daemon Version Check (stuck)",
4037
3870
  "dev-session": "\u{1F6E0}\uFE0F Dev Sessions",
4038
3871
  "dev-doctor": "\u{1F6E0}\uFE0F Dev Doctor",
4039
3872
  "dev-related": "\u{1F6E0}\uFE0F Dev Related",
@@ -4047,190 +3880,54 @@ ${typeLabels[type] || type}:`));
4047
3880
  console.log(` ${color(`PID ${pid}`)}: ${chalk.gray(command)}`);
4048
3881
  });
4049
3882
  });
3883
+ } else {
3884
+ console.log(chalk.red("\u274C No happy processes found"));
4050
3885
  }
4051
- const runawayProcesses = findRunawayHappyProcesses();
4052
- if (runawayProcesses.length > 0) {
4053
- console.log(chalk.bold("\n\u{1F6A8} Runaway Happy processes detected"));
4054
- console.log(chalk.gray("These processes were left running after daemon crashes."));
4055
- runawayProcesses.forEach(({ pid, command }) => {
4056
- console.log(` ${chalk.yellow(`PID ${pid}`)}: ${chalk.gray(command)}`);
4057
- });
4058
- console.log(chalk.blue("\nTo clean up: happy daemon kill-runaway"));
4059
- }
4060
- if (allProcesses.length > 1) {
3886
+ if (filter === "all" && allProcesses.length > 1) {
4061
3887
  console.log(chalk.bold("\n\u{1F4A1} Process Management"));
4062
- console.log(chalk.gray("To kill runaway processes: happy daemon kill-runaway"));
3888
+ console.log(chalk.gray("To clean up runaway processes: happy doctor clean"));
4063
3889
  }
4064
3890
  } catch (error) {
4065
3891
  console.log(chalk.red("\u274C Error checking daemon status"));
4066
3892
  }
4067
- console.log(chalk.bold("\n\u{1F4DD} Log Files"));
4068
- const mainLogs = getLogFiles(types$1.configuration.logsDir);
4069
- if (mainLogs.length > 0) {
4070
- console.log(chalk.blue("\nMain Logs:"));
4071
- mainLogs.forEach(({ file, path, modified }) => {
4072
- console.log(` ${chalk.green(file)} - ${modified.toLocaleString()}`);
4073
- console.log(chalk.gray(` ${path}`));
4074
- });
4075
- } else {
4076
- console.log(chalk.yellow("No main log files found"));
4077
- }
4078
- const daemonLogs = mainLogs.filter(({ file }) => file.includes("daemon"));
4079
- if (daemonLogs.length > 0) {
4080
- console.log(chalk.blue("\nDaemon Logs:"));
4081
- daemonLogs.forEach(({ file, path, modified }) => {
4082
- console.log(` ${chalk.green(file)} - ${modified.toLocaleString()}`);
4083
- console.log(chalk.gray(` ${path}`));
4084
- });
4085
- } else {
4086
- console.log(chalk.yellow("No daemon log files found"));
4087
- }
4088
- console.log(chalk.bold("\n\u{1F41B} Support & Bug Reports"));
4089
- console.log(`Report issues: ${chalk.blue("https://github.com/slopus/happy-cli/issues")}`);
4090
- console.log(`Documentation: ${chalk.blue("https://happy.engineering/")}`);
4091
- console.log(chalk.green("\n\u2705 Doctor diagnosis complete!\n"));
4092
- }
4093
-
4094
- async function daemonPost(path, body) {
4095
- const state = await getDaemonState();
4096
- if (!state?.httpPort) {
4097
- throw new Error("No daemon running");
4098
- }
4099
- try {
4100
- const response = await fetch(`http://127.0.0.1:${state.httpPort}${path}`, {
4101
- method: "POST",
4102
- headers: { "Content-Type": "application/json" },
4103
- body: JSON.stringify(body || {}),
4104
- signal: AbortSignal.timeout(5e3)
4105
- });
4106
- if (!response.ok) {
4107
- throw new Error(`HTTP ${response.status}`);
4108
- }
4109
- return await response.json();
4110
- } catch (error) {
4111
- types$1.logger.debug(`[CONTROL CLIENT] Request failed: ${path}`, error);
4112
- throw error;
4113
- }
4114
- }
4115
- async function notifyDaemonSessionStarted(sessionId, metadata) {
4116
- await daemonPost("/session-started", {
4117
- sessionId,
4118
- metadata
4119
- });
4120
- }
4121
- async function listDaemonSessions() {
4122
- const result = await daemonPost("/list");
4123
- return result.children || [];
4124
- }
4125
- async function stopDaemonSession(sessionId) {
4126
- const result = await daemonPost("/stop-session", { sessionId });
4127
- return result.success || false;
4128
- }
4129
- async function stopDaemonHttp() {
4130
- await daemonPost("/stop");
4131
- }
4132
-
4133
- var controlClient = /*#__PURE__*/Object.freeze({
4134
- __proto__: null,
4135
- listDaemonSessions: listDaemonSessions,
4136
- notifyDaemonSessionStarted: notifyDaemonSessionStarted,
4137
- stopDaemonHttp: stopDaemonHttp,
4138
- stopDaemonSession: stopDaemonSession
4139
- });
4140
-
4141
- function startDaemonControlServer({
4142
- getChildren,
4143
- stopSession,
4144
- spawnSession,
4145
- requestShutdown,
4146
- onHappySessionWebhook
4147
- }) {
4148
- return new Promise((resolve) => {
4149
- const app = fastify({
4150
- logger: false
4151
- // We use our own logger
4152
- });
4153
- app.setValidatorCompiler(fastifyTypeProviderZod.validatorCompiler);
4154
- app.setSerializerCompiler(fastifyTypeProviderZod.serializerCompiler);
4155
- const typed = app.withTypeProvider();
4156
- typed.post("/session-started", {
4157
- schema: {
4158
- body: z.z.object({
4159
- sessionId: z.z.string(),
4160
- metadata: z.z.any()
4161
- // Metadata type from API
4162
- })
4163
- }
4164
- }, async (request, reply) => {
4165
- const { sessionId, metadata } = request.body;
4166
- types$1.logger.debug(`[CONTROL SERVER] Session started: ${sessionId}`);
4167
- onHappySessionWebhook(sessionId, metadata);
4168
- return { status: "ok" };
4169
- });
4170
- typed.post("/list", async (request, reply) => {
4171
- const children = getChildren();
4172
- types$1.logger.debug(`[CONTROL SERVER] Listing ${children.length} sessions`);
4173
- return { children };
4174
- });
4175
- typed.post("/stop-session", {
4176
- schema: {
4177
- body: z.z.object({
4178
- sessionId: z.z.string()
4179
- })
4180
- }
4181
- }, async (request, reply) => {
4182
- const { sessionId } = request.body;
4183
- types$1.logger.debug(`[CONTROL SERVER] Stop session request: ${sessionId}`);
4184
- const success = stopSession(sessionId);
4185
- return { success };
4186
- });
4187
- typed.post("/spawn-session", {
4188
- schema: {
4189
- body: z.z.object({
4190
- directory: z.z.string(),
4191
- sessionId: z.z.string().optional()
4192
- })
3893
+ if (filter === "all") {
3894
+ console.log(chalk.bold("\n\u{1F4DD} Log Files"));
3895
+ const allLogs = getLogFiles(types.configuration.logsDir);
3896
+ if (allLogs.length > 0) {
3897
+ const daemonLogs = allLogs.filter(({ file }) => file.includes("daemon"));
3898
+ const regularLogs = allLogs.filter(({ file }) => !file.includes("daemon"));
3899
+ if (regularLogs.length > 0) {
3900
+ console.log(chalk.blue("\nRecent Logs:"));
3901
+ const logsToShow = regularLogs.slice(0, 10);
3902
+ logsToShow.forEach(({ file, path, modified }) => {
3903
+ console.log(` ${chalk.green(file)} - ${modified.toLocaleString()}`);
3904
+ console.log(chalk.gray(` ${path}`));
3905
+ });
3906
+ if (regularLogs.length > 10) {
3907
+ console.log(chalk.gray(` ... and ${regularLogs.length - 10} more log files`));
3908
+ }
4193
3909
  }
4194
- }, async (request, reply) => {
4195
- const { directory, sessionId } = request.body;
4196
- types$1.logger.debug(`[CONTROL SERVER] Spawn session request: dir=${directory}, sessionId=${sessionId || "new"}`);
4197
- const session = await spawnSession(directory, sessionId);
4198
- if (session) {
4199
- return {
4200
- success: true,
4201
- pid: session.pid,
4202
- sessionId: session.happySessionId || "pending"
4203
- };
3910
+ if (daemonLogs.length > 0) {
3911
+ console.log(chalk.blue("\nDaemon Logs:"));
3912
+ const daemonLogsToShow = daemonLogs.slice(0, 5);
3913
+ daemonLogsToShow.forEach(({ file, path, modified }) => {
3914
+ console.log(` ${chalk.green(file)} - ${modified.toLocaleString()}`);
3915
+ console.log(chalk.gray(` ${path}`));
3916
+ });
3917
+ if (daemonLogs.length > 5) {
3918
+ console.log(chalk.gray(` ... and ${daemonLogs.length - 5} more daemon log files`));
3919
+ }
4204
3920
  } else {
4205
- reply.code(500);
4206
- return { error: "Failed to spawn session" };
4207
- }
4208
- });
4209
- typed.post("/stop", async (request, reply) => {
4210
- types$1.logger.debug("[CONTROL SERVER] Stop daemon request received");
4211
- setTimeout(() => {
4212
- types$1.logger.debug("[CONTROL SERVER] Triggering daemon shutdown");
4213
- requestShutdown();
4214
- }, 50);
4215
- return { status: "stopping" };
4216
- });
4217
- app.listen({ port: 0, host: "127.0.0.1" }, (err, address) => {
4218
- if (err) {
4219
- types$1.logger.debug("[CONTROL SERVER] Failed to start:", err);
4220
- throw err;
3921
+ console.log(chalk.yellow("\nNo daemon log files found"));
4221
3922
  }
4222
- const port = parseInt(address.split(":").pop());
4223
- types$1.logger.debug(`[CONTROL SERVER] Started on port ${port}`);
4224
- resolve({
4225
- port,
4226
- stop: async () => {
4227
- types$1.logger.debug("[CONTROL SERVER] Stopping server");
4228
- await app.close();
4229
- types$1.logger.debug("[CONTROL SERVER] Server stopped");
4230
- }
4231
- });
4232
- });
4233
- });
3923
+ } else {
3924
+ console.log(chalk.yellow("No log files found"));
3925
+ }
3926
+ console.log(chalk.bold("\n\u{1F41B} Support & Bug Reports"));
3927
+ console.log(`Report issues: ${chalk.blue("https://github.com/slopus/happy-cli/issues")}`);
3928
+ console.log(`Documentation: ${chalk.blue("https://happy.engineering/")}`);
3929
+ }
3930
+ console.log(chalk.green("\n\u2705 Doctor diagnosis complete!\n"));
4234
3931
  }
4235
3932
 
4236
3933
  function displayQRCode(url) {
@@ -4246,22 +3943,22 @@ function displayQRCode(url) {
4246
3943
  }
4247
3944
 
4248
3945
  function generateWebAuthUrl(publicKey) {
4249
- const publicKeyBase64 = types$1.encodeBase64(publicKey, "base64url");
3946
+ const publicKeyBase64 = types.encodeBase64(publicKey, "base64url");
4250
3947
  return `https://app.happy.engineering/terminal/connect#key=${publicKeyBase64}`;
4251
3948
  }
4252
3949
 
4253
3950
  async function openBrowser(url) {
4254
3951
  try {
4255
3952
  if (!process.stdout.isTTY || process.env.CI || process.env.HEADLESS) {
4256
- types$1.logger.debug("[browser] Headless environment detected, skipping browser open");
3953
+ types.logger.debug("[browser] Headless environment detected, skipping browser open");
4257
3954
  return false;
4258
3955
  }
4259
- types$1.logger.debug(`[browser] Attempting to open URL: ${url}`);
3956
+ types.logger.debug(`[browser] Attempting to open URL: ${url}`);
4260
3957
  await open(url);
4261
- types$1.logger.debug("[browser] Browser opened successfully");
3958
+ types.logger.debug("[browser] Browser opened successfully");
4262
3959
  return true;
4263
3960
  } catch (error) {
4264
- types$1.logger.debug("[browser] Failed to open browser:", error);
3961
+ types.logger.debug("[browser] Failed to open browser:", error);
4265
3962
  return false;
4266
3963
  }
4267
3964
  }
@@ -4311,10 +4008,10 @@ async function doAuth() {
4311
4008
  const secret = new Uint8Array(node_crypto.randomBytes(32));
4312
4009
  const keypair = tweetnacl.box.keyPair.fromSecretKey(secret);
4313
4010
  try {
4314
- console.log(`[AUTH DEBUG] Sending auth request to: ${types$1.configuration.serverUrl}/v1/auth/request`);
4315
- console.log(`[AUTH DEBUG] Public key: ${types$1.encodeBase64(keypair.publicKey).substring(0, 20)}...`);
4316
- await axios.post(`${types$1.configuration.serverUrl}/v1/auth/request`, {
4317
- publicKey: types$1.encodeBase64(keypair.publicKey)
4011
+ console.log(`[AUTH DEBUG] Sending auth request to: ${types.configuration.serverUrl}/v1/auth/request`);
4012
+ console.log(`[AUTH DEBUG] Public key: ${types.encodeBase64(keypair.publicKey).substring(0, 20)}...`);
4013
+ await axios.post(`${types.configuration.serverUrl}/v1/auth/request`, {
4014
+ publicKey: types.encodeBase64(keypair.publicKey)
4318
4015
  });
4319
4016
  console.log(`[AUTH DEBUG] Auth request sent successfully`);
4320
4017
  } catch (error) {
@@ -4355,7 +4052,7 @@ async function doMobileAuth(keypair) {
4355
4052
  console.clear();
4356
4053
  console.log("\nMobile Authentication\n");
4357
4054
  console.log("Scan this QR code with your Happy mobile app:\n");
4358
- const authUrl = "happy://terminal?" + types$1.encodeBase64Url(keypair.publicKey);
4055
+ const authUrl = "happy://terminal?" + types.encodeBase64Url(keypair.publicKey);
4359
4056
  displayQRCode(authUrl);
4360
4057
  console.log("\nOr manually enter this URL:");
4361
4058
  console.log(authUrl);
@@ -4392,19 +4089,19 @@ async function waitForAuthentication(keypair) {
4392
4089
  try {
4393
4090
  while (!cancelled) {
4394
4091
  try {
4395
- const response = await axios.post(`${types$1.configuration.serverUrl}/v1/auth/request`, {
4396
- publicKey: types$1.encodeBase64(keypair.publicKey)
4092
+ const response = await axios.post(`${types.configuration.serverUrl}/v1/auth/request`, {
4093
+ publicKey: types.encodeBase64(keypair.publicKey)
4397
4094
  });
4398
4095
  if (response.data.state === "authorized") {
4399
4096
  let token = response.data.token;
4400
- let r = types$1.decodeBase64(response.data.response);
4097
+ let r = types.decodeBase64(response.data.response);
4401
4098
  let decrypted = decryptWithEphemeralKey(r, keypair.secretKey);
4402
4099
  if (decrypted) {
4403
4100
  const credentials = {
4404
4101
  secret: decrypted,
4405
4102
  token
4406
4103
  };
4407
- await writeCredentials(credentials);
4104
+ await types.writeCredentials(credentials);
4408
4105
  console.log("\n\n\u2713 Authentication successful\n");
4409
4106
  return credentials;
4410
4107
  } else {
@@ -4418,7 +4115,7 @@ async function waitForAuthentication(keypair) {
4418
4115
  }
4419
4116
  process.stdout.write("\rWaiting for authentication" + ".".repeat(dots % 3 + 1) + " ");
4420
4117
  dots++;
4421
- await types$1.delay(1e3);
4118
+ await types.delay(1e3);
4422
4119
  }
4423
4120
  } finally {
4424
4121
  process.off("SIGINT", handleInterrupt);
@@ -4436,22 +4133,22 @@ function decryptWithEphemeralKey(encryptedBundle, recipientSecretKey) {
4436
4133
  return decrypted;
4437
4134
  }
4438
4135
  async function authAndSetupMachineIfNeeded() {
4439
- types$1.logger.debug("[AUTH] Starting auth and machine setup...");
4440
- let credentials = await readCredentials();
4136
+ types.logger.debug("[AUTH] Starting auth and machine setup...");
4137
+ let credentials = await types.readCredentials();
4441
4138
  if (!credentials) {
4442
- types$1.logger.debug("[AUTH] No credentials found, starting authentication flow...");
4139
+ types.logger.debug("[AUTH] No credentials found, starting authentication flow...");
4443
4140
  const authResult = await doAuth();
4444
4141
  if (!authResult) {
4445
4142
  throw new Error("Authentication failed or was cancelled");
4446
4143
  }
4447
4144
  credentials = authResult;
4448
4145
  } else {
4449
- types$1.logger.debug("[AUTH] Using existing credentials");
4146
+ types.logger.debug("[AUTH] Using existing credentials");
4450
4147
  }
4451
- const settings = await updateSettings(async (s) => {
4148
+ const settings = await types.updateSettings(async (s) => {
4452
4149
  if (!s.machineId) {
4453
4150
  const newMachineId = node_crypto.randomUUID();
4454
- types$1.logger.debug(`[AUTH] No machine ID found, generating new one: ${newMachineId}; We will not create machine on startup since we don't have api client intialized`);
4151
+ types.logger.debug(`[AUTH] No machine ID found, generating new one: ${newMachineId}; We will not create machine on startup since we don't have api client intialized`);
4455
4152
  return {
4456
4153
  ...s,
4457
4154
  machineId: node_crypto.randomUUID()
@@ -4459,7 +4156,7 @@ async function authAndSetupMachineIfNeeded() {
4459
4156
  }
4460
4157
  return s;
4461
4158
  });
4462
- types$1.logger.debug(`[AUTH] Machine ID: ${settings.machineId}`);
4159
+ types.logger.debug(`[AUTH] Machine ID: ${settings.machineId}`);
4463
4160
  return { credentials, machineId: settings.machineId };
4464
4161
  }
4465
4162
 
@@ -4473,65 +4170,231 @@ function spawnHappyCLI(args, options = {}) {
4473
4170
  directory = process.cwd();
4474
4171
  }
4475
4172
  const fullCommand = `happy ${args.join(" ")}`;
4476
- types$1.logger.debug(`[DAEMON RUN] Spawning: ${fullCommand} in ${directory}`);
4173
+ types.logger.debug(`[SPAWN HAPPY CLI] Spawning: ${fullCommand} in ${directory}`);
4477
4174
  const nodeArgs = [
4478
4175
  "--no-warnings",
4479
4176
  "--no-deprecation",
4480
4177
  entrypoint,
4481
4178
  ...args
4482
4179
  ];
4180
+ if (!node_fs.existsSync(entrypoint)) {
4181
+ const errorMessage = `Entrypoint ${entrypoint} does not exist`;
4182
+ types.logger.debug(`[SPAWN HAPPY CLI] ${errorMessage}`);
4183
+ throw new Error(errorMessage);
4184
+ }
4483
4185
  return child_process.spawn("node", nodeArgs, options);
4484
4186
  }
4485
4187
 
4188
+ function startDaemonControlServer({
4189
+ getChildren,
4190
+ stopSession,
4191
+ spawnSession,
4192
+ requestShutdown,
4193
+ onHappySessionWebhook
4194
+ }) {
4195
+ return new Promise((resolve) => {
4196
+ const app = fastify({
4197
+ logger: false
4198
+ // We use our own logger
4199
+ });
4200
+ app.setValidatorCompiler(fastifyTypeProviderZod.validatorCompiler);
4201
+ app.setSerializerCompiler(fastifyTypeProviderZod.serializerCompiler);
4202
+ const typed = app.withTypeProvider();
4203
+ typed.post("/session-started", {
4204
+ schema: {
4205
+ body: z.z.object({
4206
+ sessionId: z.z.string(),
4207
+ metadata: z.z.any()
4208
+ // Metadata type from API
4209
+ })
4210
+ }
4211
+ }, async (request, reply) => {
4212
+ const { sessionId, metadata } = request.body;
4213
+ types.logger.debug(`[CONTROL SERVER] Session started: ${sessionId}`);
4214
+ onHappySessionWebhook(sessionId, metadata);
4215
+ return { status: "ok" };
4216
+ });
4217
+ typed.post("/list", async (request, reply) => {
4218
+ const children = getChildren();
4219
+ types.logger.debug(`[CONTROL SERVER] Listing ${children.length} sessions`);
4220
+ return {
4221
+ children: children.map((child) => {
4222
+ delete child.childProcess;
4223
+ return child;
4224
+ })
4225
+ };
4226
+ });
4227
+ typed.post("/stop-session", {
4228
+ schema: {
4229
+ body: z.z.object({
4230
+ sessionId: z.z.string()
4231
+ })
4232
+ }
4233
+ }, async (request, reply) => {
4234
+ const { sessionId } = request.body;
4235
+ types.logger.debug(`[CONTROL SERVER] Stop session request: ${sessionId}`);
4236
+ const success = stopSession(sessionId);
4237
+ return { success };
4238
+ });
4239
+ typed.post("/spawn-session", {
4240
+ schema: {
4241
+ body: z.z.object({
4242
+ directory: z.z.string(),
4243
+ sessionId: z.z.string().optional()
4244
+ })
4245
+ }
4246
+ }, async (request, reply) => {
4247
+ const { directory, sessionId } = request.body;
4248
+ types.logger.debug(`[CONTROL SERVER] Spawn session request: dir=${directory}, sessionId=${sessionId || "new"}`);
4249
+ const session = await spawnSession(directory, sessionId);
4250
+ if (session) {
4251
+ return {
4252
+ success: true,
4253
+ pid: session.pid,
4254
+ sessionId: session.happySessionId || "pending",
4255
+ message: session.message
4256
+ };
4257
+ } else {
4258
+ reply.code(500);
4259
+ return {
4260
+ success: false,
4261
+ error: "Failed to spawn session. Check the directory path and permissions."
4262
+ };
4263
+ }
4264
+ });
4265
+ typed.post("/stop", async (request, reply) => {
4266
+ types.logger.debug("[CONTROL SERVER] Stop daemon request received");
4267
+ setTimeout(() => {
4268
+ types.logger.debug("[CONTROL SERVER] Triggering daemon shutdown");
4269
+ requestShutdown();
4270
+ }, 50);
4271
+ return { status: "stopping" };
4272
+ });
4273
+ typed.post("/dev-simulate-error", {
4274
+ schema: {
4275
+ body: z.z.object({
4276
+ error: z.z.string()
4277
+ })
4278
+ }
4279
+ }, async (request, reply) => {
4280
+ const { error } = request.body;
4281
+ types.logger.debug(`[CONTROL SERVER] Dev: Simulating error: ${error}`);
4282
+ setTimeout(() => {
4283
+ types.logger.debug(`[CONTROL SERVER] Dev: Throwing simulated error now`);
4284
+ throw new Error(error);
4285
+ }, 100);
4286
+ return { status: "error will be thrown" };
4287
+ });
4288
+ app.listen({ port: 0, host: "127.0.0.1" }, (err, address) => {
4289
+ if (err) {
4290
+ types.logger.debug("[CONTROL SERVER] Failed to start:", err);
4291
+ throw err;
4292
+ }
4293
+ const port = parseInt(address.split(":").pop());
4294
+ types.logger.debug(`[CONTROL SERVER] Started on port ${port}`);
4295
+ resolve({
4296
+ port,
4297
+ stop: async () => {
4298
+ types.logger.debug("[CONTROL SERVER] Stopping server");
4299
+ await app.close();
4300
+ types.logger.debug("[CONTROL SERVER] Server stopped");
4301
+ }
4302
+ });
4303
+ });
4304
+ });
4305
+ }
4306
+
4486
4307
  const initialMachineMetadata = {
4487
4308
  host: os$1.hostname(),
4488
4309
  platform: os$1.platform(),
4489
- happyCliVersion: packageJson.version,
4310
+ happyCliVersion: types.packageJson.version,
4490
4311
  homeDir: os$1.homedir(),
4491
- happyHomeDir: types$1.configuration.happyHomeDir
4312
+ happyHomeDir: types.configuration.happyHomeDir
4492
4313
  };
4493
4314
  async function startDaemon() {
4494
- types$1.logger.debug("[DAEMON RUN] Starting daemon process...");
4495
- types$1.logger.debugLargeJson("[DAEMON RUN] Environment", getEnvironmentInfo());
4496
- const runningDaemon = await getDaemonState();
4497
- if (runningDaemon) {
4498
- try {
4499
- process.kill(runningDaemon.pid, 0);
4500
- types$1.logger.debug("[DAEMON RUN] Daemon already running");
4501
- console.log(`Daemon already running (PID: ${runningDaemon.pid})`);
4502
- process.exit(0);
4503
- } catch {
4504
- types$1.logger.debug("[DAEMON RUN] Stale state found, cleaning up");
4505
- await cleanupDaemonState();
4506
- }
4315
+ let requestShutdown;
4316
+ let resolvesWhenShutdownRequested = new Promise((resolve2) => {
4317
+ requestShutdown = (source, errorMessage) => {
4318
+ types.logger.debug(`[DAEMON RUN] Requesting shutdown (source: ${source}, errorMessage: ${errorMessage})`);
4319
+ setTimeout(async () => {
4320
+ types.logger.debug("[DAEMON RUN] Startup malfunctioned, forcing exit with code 1");
4321
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
4322
+ process.exit(1);
4323
+ }, 1e3);
4324
+ resolve2({ source, errorMessage });
4325
+ };
4326
+ });
4327
+ process.on("SIGINT", () => {
4328
+ types.logger.debug("[DAEMON RUN] Received SIGINT");
4329
+ requestShutdown("os-signal");
4330
+ });
4331
+ process.on("SIGTERM", () => {
4332
+ types.logger.debug("[DAEMON RUN] Received SIGTERM");
4333
+ requestShutdown("os-signal");
4334
+ });
4335
+ process.on("uncaughtException", (error) => {
4336
+ types.logger.debug("[DAEMON RUN] FATAL: Uncaught exception", error);
4337
+ types.logger.debug(`[DAEMON RUN] Stack trace: ${error.stack}`);
4338
+ requestShutdown("exception", error.message);
4339
+ });
4340
+ process.on("unhandledRejection", (reason, promise) => {
4341
+ types.logger.debug("[DAEMON RUN] FATAL: Unhandled promise rejection", reason);
4342
+ types.logger.debug(`[DAEMON RUN] Rejected promise:`, promise);
4343
+ const error = reason instanceof Error ? reason : new Error(`Unhandled promise rejection: ${reason}`);
4344
+ types.logger.debug(`[DAEMON RUN] Stack trace: ${error.stack}`);
4345
+ requestShutdown("exception", error.message);
4346
+ });
4347
+ process.on("exit", (code) => {
4348
+ types.logger.debug(`[DAEMON RUN] Process exiting with code: ${code}`);
4349
+ });
4350
+ process.on("beforeExit", (code) => {
4351
+ types.logger.debug(`[DAEMON RUN] Process about to exit with code: ${code}`);
4352
+ });
4353
+ types.logger.debug("[DAEMON RUN] Starting daemon process...");
4354
+ types.logger.debugLargeJson("[DAEMON RUN] Environment", getEnvironmentInfo());
4355
+ const runningDaemonVersionMatches = await isDaemonRunningSameVersion();
4356
+ if (!runningDaemonVersionMatches) {
4357
+ types.logger.debug("[DAEMON RUN] Daemon version mismatch detected, restarting daemon with current CLI version");
4358
+ await stopDaemon();
4359
+ } else {
4360
+ types.logger.debug("[DAEMON RUN] Daemon version matches, keeping existing daemon");
4361
+ console.log("Daemon already running with matching version");
4362
+ process.exit(0);
4507
4363
  }
4508
- const caffeinateStarted = startCaffeinate();
4509
- if (caffeinateStarted) {
4510
- types$1.logger.debug("[DAEMON RUN] Sleep prevention enabled");
4364
+ const daemonLockHandle = await types.acquireDaemonLock(5, 200);
4365
+ if (!daemonLockHandle) {
4366
+ types.logger.debug("[DAEMON RUN] Daemon lock file already held, another daemon is running");
4367
+ process.exit(0);
4511
4368
  }
4512
4369
  try {
4370
+ const caffeinateStarted = startCaffeinate();
4371
+ if (caffeinateStarted) {
4372
+ types.logger.debug("[DAEMON RUN] Sleep prevention enabled");
4373
+ }
4513
4374
  const { credentials, machineId } = await authAndSetupMachineIfNeeded();
4514
- types$1.logger.debug("[DAEMON RUN] Auth and machine setup complete");
4375
+ types.logger.debug("[DAEMON RUN] Auth and machine setup complete");
4515
4376
  const pidToTrackedSession = /* @__PURE__ */ new Map();
4516
4377
  const pidToAwaiter = /* @__PURE__ */ new Map();
4517
4378
  const getCurrentChildren = () => Array.from(pidToTrackedSession.values());
4518
4379
  const onHappySessionWebhook = (sessionId, sessionMetadata) => {
4380
+ types.logger.debugLargeJson(`[DAEMON RUN] Session reported`, sessionMetadata);
4519
4381
  const pid = sessionMetadata.hostPid;
4520
4382
  if (!pid) {
4521
- types$1.logger.debug(`[DAEMON RUN] Session webhook missing hostPid for session ${sessionId}`);
4383
+ types.logger.debug(`[DAEMON RUN] Session webhook missing hostPid for sessionId: ${sessionId}`);
4522
4384
  return;
4523
4385
  }
4524
- types$1.logger.debug(`[DAEMON RUN] Session webhook: ${sessionId}, PID: ${pid}, started by: ${sessionMetadata.startedBy || "unknown"}`);
4386
+ types.logger.debug(`[DAEMON RUN] Session webhook: ${sessionId}, PID: ${pid}, started by: ${sessionMetadata.startedBy || "unknown"}`);
4387
+ types.logger.debug(`[DAEMON RUN] Current tracked sessions before webhook: ${Array.from(pidToTrackedSession.keys()).join(", ")}`);
4525
4388
  const existingSession = pidToTrackedSession.get(pid);
4526
4389
  if (existingSession && existingSession.startedBy === "daemon") {
4527
4390
  existingSession.happySessionId = sessionId;
4528
4391
  existingSession.happySessionMetadataFromLocalWebhook = sessionMetadata;
4529
- types$1.logger.debug(`[DAEMON RUN] Updated daemon-spawned session ${sessionId} with metadata`);
4392
+ types.logger.debug(`[DAEMON RUN] Updated daemon-spawned session ${sessionId} with metadata`);
4530
4393
  const awaiter = pidToAwaiter.get(pid);
4531
4394
  if (awaiter) {
4532
4395
  pidToAwaiter.delete(pid);
4533
4396
  awaiter(existingSession);
4534
- types$1.logger.debug(`[DAEMON RUN] Resolved session awaiter for PID ${pid}`);
4397
+ types.logger.debug(`[DAEMON RUN] Resolved session awaiter for PID ${pid}`);
4535
4398
  }
4536
4399
  } else if (!existingSession) {
4537
4400
  const trackedSession = {
@@ -4541,12 +4404,41 @@ async function startDaemon() {
4541
4404
  pid
4542
4405
  };
4543
4406
  pidToTrackedSession.set(pid, trackedSession);
4544
- types$1.logger.debug(`[DAEMON RUN] Registered externally-started session ${sessionId}`);
4407
+ types.logger.debug(`[DAEMON RUN] Registered externally-started session ${sessionId}`);
4545
4408
  }
4546
4409
  };
4547
4410
  const spawnSession = async (directory, sessionId) => {
4411
+ let directoryCreated = false;
4412
+ if (directory.startsWith("~")) {
4413
+ directory = path.resolve(os$1.homedir(), directory.replace("~", ""));
4414
+ }
4415
+ try {
4416
+ await fs.access(directory);
4417
+ types.logger.debug(`[DAEMON RUN] Directory exists: ${directory}`);
4418
+ } catch (error) {
4419
+ types.logger.debug(`[DAEMON RUN] Directory doesn't exist, creating: ${directory}`);
4420
+ try {
4421
+ await fs.mkdir(directory, { recursive: true });
4422
+ types.logger.debug(`[DAEMON RUN] Successfully created directory: ${directory}`);
4423
+ directoryCreated = true;
4424
+ } catch (mkdirError) {
4425
+ let errorMessage = `Unable to create directory at '${directory}'. `;
4426
+ if (mkdirError.code === "EACCES") {
4427
+ errorMessage += `Permission denied. You don't have write access to create a folder at this location. Try using a different path or check your permissions.`;
4428
+ } else if (mkdirError.code === "ENOTDIR") {
4429
+ errorMessage += `A file already exists at this path or in the parent path. Cannot create a directory here. Please choose a different location.`;
4430
+ } else if (mkdirError.code === "ENOSPC") {
4431
+ errorMessage += `No space left on device. Your disk is full. Please free up some space and try again.`;
4432
+ } else if (mkdirError.code === "EROFS") {
4433
+ errorMessage += `The file system is read-only. Cannot create directories here. Please choose a writable location.`;
4434
+ } else {
4435
+ errorMessage += `System error: ${mkdirError.message || mkdirError}. Please verify the path is valid and you have the necessary permissions.`;
4436
+ }
4437
+ types.logger.debug(`[DAEMON RUN] Directory creation failed: ${errorMessage}`);
4438
+ return null;
4439
+ }
4440
+ }
4548
4441
  try {
4549
- const happyBinPath = path.join(projectPath(), "bin", "happy.mjs");
4550
4442
  const args = [
4551
4443
  "--happy-starting-mode",
4552
4444
  "remote",
@@ -4561,88 +4453,88 @@ async function startDaemon() {
4561
4453
  // Capture stdout/stderr for debugging
4562
4454
  // env is inherited automatically from parent process
4563
4455
  });
4564
- happyProcess.stdout?.on("data", (data) => {
4565
- types$1.logger.debug(`[DAEMON RUN] Child stdout: ${data.toString()}`);
4566
- });
4567
- happyProcess.stderr?.on("data", (data) => {
4568
- types$1.logger.debug(`[DAEMON RUN] Child stderr: ${data.toString()}`);
4569
- });
4456
+ if (process.env.DEBUG) {
4457
+ happyProcess.stdout?.on("data", (data) => {
4458
+ types.logger.debug(`[DAEMON RUN] Child stdout: ${data.toString()}`);
4459
+ });
4460
+ happyProcess.stderr?.on("data", (data) => {
4461
+ types.logger.debug(`[DAEMON RUN] Child stderr: ${data.toString()}`);
4462
+ });
4463
+ }
4570
4464
  if (!happyProcess.pid) {
4571
- types$1.logger.debug("[DAEMON RUN] Failed to spawn process - no PID returned");
4465
+ types.logger.debug("[DAEMON RUN] Failed to spawn process - no PID returned");
4572
4466
  return null;
4573
4467
  }
4574
- types$1.logger.debug(`[DAEMON RUN] Spawned process with PID ${happyProcess.pid}`);
4468
+ types.logger.debug(`[DAEMON RUN] Spawned process with PID ${happyProcess.pid}`);
4575
4469
  const trackedSession = {
4576
4470
  startedBy: "daemon",
4577
4471
  pid: happyProcess.pid,
4578
- childProcess: happyProcess
4472
+ childProcess: happyProcess,
4473
+ directoryCreated,
4474
+ message: directoryCreated ? `The path '${directory}' did not exist. We created a new folder and spawned a new session there.` : void 0
4579
4475
  };
4580
4476
  pidToTrackedSession.set(happyProcess.pid, trackedSession);
4581
4477
  happyProcess.on("exit", (code, signal) => {
4582
- types$1.logger.debug(`[DAEMON RUN] Child PID ${happyProcess.pid} exited with code ${code}, signal ${signal}`);
4478
+ types.logger.debug(`[DAEMON RUN] Child PID ${happyProcess.pid} exited with code ${code}, signal ${signal}`);
4583
4479
  if (happyProcess.pid) {
4584
4480
  onChildExited(happyProcess.pid);
4585
4481
  }
4586
4482
  });
4587
4483
  happyProcess.on("error", (error) => {
4588
- types$1.logger.debug(`[DAEMON RUN] Child process error:`, error);
4484
+ types.logger.debug(`[DAEMON RUN] Child process error:`, error);
4589
4485
  if (happyProcess.pid) {
4590
4486
  onChildExited(happyProcess.pid);
4591
4487
  }
4592
4488
  });
4593
- types$1.logger.debug(`[DAEMON RUN] Waiting for session webhook for PID ${happyProcess.pid}`);
4594
- return new Promise((resolve, reject) => {
4489
+ types.logger.debug(`[DAEMON RUN] Waiting for session webhook for PID ${happyProcess.pid}`);
4490
+ return new Promise((resolve2, reject) => {
4595
4491
  const timeout = setTimeout(() => {
4596
4492
  pidToAwaiter.delete(happyProcess.pid);
4597
- types$1.logger.debug(`[DAEMON RUN] Session webhook timeout for PID ${happyProcess.pid}`);
4598
- resolve(trackedSession);
4493
+ types.logger.debug(`[DAEMON RUN] Session webhook timeout for PID ${happyProcess.pid}`);
4494
+ resolve2(trackedSession);
4599
4495
  }, 1e4);
4600
4496
  pidToAwaiter.set(happyProcess.pid, (completedSession) => {
4601
4497
  clearTimeout(timeout);
4602
- types$1.logger.debug(`[DAEMON RUN] Session ${completedSession.happySessionId} fully spawned with webhook`);
4603
- resolve(completedSession);
4498
+ types.logger.debug(`[DAEMON RUN] Session ${completedSession.happySessionId} fully spawned with webhook`);
4499
+ resolve2(completedSession);
4604
4500
  });
4605
4501
  });
4606
4502
  } catch (error) {
4607
- types$1.logger.debug("[DAEMON RUN] Failed to spawn session:", error);
4503
+ types.logger.debug("[DAEMON RUN] Failed to spawn session:", error);
4608
4504
  return null;
4609
4505
  }
4610
4506
  };
4611
4507
  const stopSession = (sessionId) => {
4612
- types$1.logger.debug(`[DAEMON RUN] Attempting to stop session ${sessionId}`);
4508
+ types.logger.debug(`[DAEMON RUN] Attempting to stop session ${sessionId}`);
4613
4509
  for (const [pid, session] of pidToTrackedSession.entries()) {
4614
4510
  if (session.happySessionId === sessionId || sessionId.startsWith("PID-") && pid === parseInt(sessionId.replace("PID-", ""))) {
4615
4511
  if (session.startedBy === "daemon" && session.childProcess) {
4616
4512
  try {
4617
4513
  session.childProcess.kill("SIGTERM");
4618
- types$1.logger.debug(`[DAEMON RUN] Sent SIGTERM to daemon-spawned session ${sessionId}`);
4514
+ types.logger.debug(`[DAEMON RUN] Sent SIGTERM to daemon-spawned session ${sessionId}`);
4619
4515
  } catch (error) {
4620
- types$1.logger.debug(`[DAEMON RUN] Failed to kill session ${sessionId}:`, error);
4516
+ types.logger.debug(`[DAEMON RUN] Failed to kill session ${sessionId}:`, error);
4621
4517
  }
4622
4518
  } else {
4623
4519
  try {
4624
4520
  process.kill(pid, "SIGTERM");
4625
- types$1.logger.debug(`[DAEMON RUN] Sent SIGTERM to external session PID ${pid}`);
4521
+ types.logger.debug(`[DAEMON RUN] Sent SIGTERM to external session PID ${pid}`);
4626
4522
  } catch (error) {
4627
- types$1.logger.debug(`[DAEMON RUN] Failed to kill external session PID ${pid}:`, error);
4523
+ types.logger.debug(`[DAEMON RUN] Failed to kill external session PID ${pid}:`, error);
4628
4524
  }
4629
4525
  }
4630
4526
  pidToTrackedSession.delete(pid);
4631
- types$1.logger.debug(`[DAEMON RUN] Removed session ${sessionId} from tracking`);
4527
+ types.logger.debug(`[DAEMON RUN] Removed session ${sessionId} from tracking`);
4632
4528
  return true;
4633
4529
  }
4634
4530
  }
4635
- types$1.logger.debug(`[DAEMON RUN] Session ${sessionId} not found`);
4531
+ types.logger.debug(`[DAEMON RUN] Session ${sessionId} not found`);
4636
4532
  return false;
4637
4533
  };
4638
4534
  const onChildExited = (pid) => {
4639
- types$1.logger.debug(`[DAEMON RUN] Removing exited process PID ${pid} from tracking`);
4535
+ types.logger.debug(`[DAEMON RUN] Removing exited process PID ${pid} from tracking`);
4640
4536
  pidToTrackedSession.delete(pid);
4641
4537
  };
4642
- let requestShutdown;
4643
- let resolvesWhenShutdownRequested = new Promise((resolve) => {
4644
- requestShutdown = resolve;
4645
- });
4646
4538
  const { port: controlPort, stop: stopControlServer } = await startDaemonControlServer({
4647
4539
  getChildren: getCurrentChildren,
4648
4540
  stopSession,
@@ -4653,24 +4545,25 @@ async function startDaemon() {
4653
4545
  const fileState = {
4654
4546
  pid: process.pid,
4655
4547
  httpPort: controlPort,
4656
- startTime: (/* @__PURE__ */ new Date()).toISOString(),
4657
- startedWithCliVersion: packageJson.version
4548
+ startTime: (/* @__PURE__ */ new Date()).toLocaleString(),
4549
+ startedWithCliVersion: types.packageJson.version,
4550
+ daemonLogPath: types.logger.logFilePath
4658
4551
  };
4659
- await writeDaemonState(fileState);
4660
- types$1.logger.debug("[DAEMON RUN] Daemon state written");
4552
+ types.writeDaemonState(fileState);
4553
+ types.logger.debug("[DAEMON RUN] Daemon state written");
4661
4554
  const initialDaemonState = {
4662
4555
  status: "offline",
4663
4556
  pid: process.pid,
4664
4557
  httpPort: controlPort,
4665
4558
  startedAt: Date.now()
4666
4559
  };
4667
- const api = new types$1.ApiClient(credentials.token, credentials.secret);
4560
+ const api = new types.ApiClient(credentials.token, credentials.secret);
4668
4561
  const machine = await api.createMachineOrGetExistingAsIs({
4669
4562
  machineId,
4670
4563
  metadata: initialMachineMetadata,
4671
4564
  daemonState: initialDaemonState
4672
4565
  });
4673
- types$1.logger.debug(`[DAEMON RUN] Machine registered: ${machine.id}`);
4566
+ types.logger.debug(`[DAEMON RUN] Machine registered: ${machine.id}`);
4674
4567
  const apiMachine = api.machineSyncClient(machine);
4675
4568
  apiMachine.setRPCHandlers({
4676
4569
  spawnSession,
@@ -4678,63 +4571,96 @@ async function startDaemon() {
4678
4571
  requestShutdown: () => requestShutdown("happy-app")
4679
4572
  });
4680
4573
  apiMachine.connect();
4681
- const cleanupAndShutdown = async (source) => {
4682
- types$1.logger.debug(`[DAEMON RUN] Starting cleanup (source: ${source})...`);
4683
- if (apiMachine) {
4684
- await apiMachine.updateDaemonState((state) => ({
4685
- ...state,
4686
- status: "shutting-down",
4687
- shutdownRequestedAt: Date.now(),
4688
- shutdownSource: source === "happy-app" ? "mobile-app" : source === "happy-cli" ? "cli" : source
4689
- }));
4690
- await new Promise((resolve) => setTimeout(resolve, 100));
4574
+ const heartbeatIntervalMs = parseInt(process.env.HAPPY_DAEMON_HEARTBEAT_INTERVAL || "60000");
4575
+ let heartbeatRunning = false;
4576
+ const restartOnStaleVersionAndHeartbeat = setInterval(async () => {
4577
+ if (heartbeatRunning) {
4578
+ return;
4579
+ }
4580
+ heartbeatRunning = true;
4581
+ if (process.env.DEBUG) {
4582
+ types.logger.debug(`[DAEMON RUN] Health check started at ${(/* @__PURE__ */ new Date()).toLocaleString()}`);
4583
+ }
4584
+ for (const [pid, _] of pidToTrackedSession.entries()) {
4585
+ try {
4586
+ process.kill(pid, 0);
4587
+ } catch (error) {
4588
+ types.logger.debug(`[DAEMON RUN] Removing stale session with PID ${pid} (process no longer exists)`);
4589
+ pidToTrackedSession.delete(pid);
4590
+ }
4691
4591
  }
4692
- if (apiMachine) {
4693
- apiMachine.shutdown();
4592
+ const projectVersion = JSON.parse(fs$1.readFileSync(path.join(projectPath(), "package.json"), "utf-8")).version;
4593
+ if (projectVersion !== types.configuration.currentCliVersion) {
4594
+ types.logger.debug("[DAEMON RUN] Daemon is outdated, triggering self-restart with latest version, clearing heartbeat interval");
4595
+ clearInterval(restartOnStaleVersionAndHeartbeat);
4596
+ try {
4597
+ spawnHappyCLI(["daemon", "start"], {
4598
+ detached: true,
4599
+ stdio: "ignore"
4600
+ });
4601
+ } catch (error) {
4602
+ types.logger.debug("[DAEMON RUN] Failed to spawn new daemon, this is quite likely to happen during integration tests as we are cleaning out dist/ directory", error);
4603
+ }
4604
+ types.logger.debug("[DAEMON RUN] Hanging for a bit - waiting for CLI to kill us because we are running outdated version of the code");
4605
+ await new Promise((resolve2) => setTimeout(resolve2, 1e4));
4606
+ process.exit(0);
4694
4607
  }
4695
- types$1.logger.debug("[DAEMON RUN] Machine session shutdown");
4608
+ const daemonState = await types.readDaemonState();
4609
+ if (daemonState && daemonState.pid !== process.pid) {
4610
+ types.logger.debug("[DAEMON RUN] Somehow a different daemon was started without killing us. We should kill ourselves.");
4611
+ requestShutdown("exception", "A different daemon was started without killing us. We should kill ourselves.");
4612
+ }
4613
+ try {
4614
+ const updatedState = {
4615
+ pid: process.pid,
4616
+ httpPort: controlPort,
4617
+ startTime: fileState.startTime,
4618
+ startedWithCliVersion: types.packageJson.version,
4619
+ lastHeartbeat: (/* @__PURE__ */ new Date()).toLocaleString(),
4620
+ daemonLogPath: fileState.daemonLogPath
4621
+ };
4622
+ types.writeDaemonState(updatedState);
4623
+ if (process.env.DEBUG) {
4624
+ types.logger.debug(`[DAEMON RUN] Health check completed at ${updatedState.lastHeartbeat}`);
4625
+ }
4626
+ } catch (error) {
4627
+ types.logger.debug("[DAEMON RUN] Failed to write heartbeat", error);
4628
+ }
4629
+ heartbeatRunning = false;
4630
+ }, heartbeatIntervalMs);
4631
+ const cleanupAndShutdown = async (source, errorMessage) => {
4632
+ types.logger.debug(`[DAEMON RUN] Starting proper cleanup (source: ${source}, errorMessage: ${errorMessage})...`);
4633
+ if (restartOnStaleVersionAndHeartbeat) {
4634
+ clearInterval(restartOnStaleVersionAndHeartbeat);
4635
+ types.logger.debug("[DAEMON RUN] Health check interval cleared");
4636
+ }
4637
+ await apiMachine.updateDaemonState((state) => ({
4638
+ ...state,
4639
+ status: "shutting-down",
4640
+ shutdownRequestedAt: Date.now(),
4641
+ shutdownSource: source
4642
+ }));
4643
+ await new Promise((resolve2) => setTimeout(resolve2, 100));
4644
+ apiMachine.shutdown();
4696
4645
  await stopControlServer();
4697
- types$1.logger.debug("[DAEMON RUN] Control server stopped");
4698
4646
  await cleanupDaemonState();
4699
- types$1.logger.debug("[DAEMON RUN] State cleaned up");
4700
- stopCaffeinate();
4701
- types$1.logger.debug("[DAEMON RUN] Caffeinate stopped");
4647
+ await stopCaffeinate();
4648
+ await types.releaseDaemonLock(daemonLockHandle);
4649
+ types.logger.debug("[DAEMON RUN] Cleanup completed, exiting process");
4702
4650
  process.exit(0);
4703
4651
  };
4704
- process.on("SIGINT", () => {
4705
- types$1.logger.debug("[DAEMON RUN] Received SIGINT");
4706
- cleanupAndShutdown("os-signal");
4707
- });
4708
- process.on("SIGTERM", () => {
4709
- types$1.logger.debug("[DAEMON RUN] Received SIGTERM");
4710
- cleanupAndShutdown("os-signal");
4711
- });
4712
- process.on("uncaughtException", (error) => {
4713
- types$1.logger.debug("[DAEMON RUN] Uncaught exception - cleaning up before crash", error);
4714
- cleanupAndShutdown("unknown");
4715
- });
4716
- process.on("unhandledRejection", (reason) => {
4717
- types$1.logger.debug("[DAEMON RUN] Unhandled rejection - cleaning up before crash", reason);
4718
- cleanupAndShutdown("unknown");
4719
- });
4720
- process.on("exit", () => {
4721
- types$1.logger.debug("[DAEMON RUN] Process exit, not killing any children");
4722
- });
4723
- types$1.logger.debug("[DAEMON RUN] Daemon started successfully");
4724
- const shutdownSource = await resolvesWhenShutdownRequested;
4725
- types$1.logger.debug(`[DAEMON RUN] Shutdown requested (source: ${shutdownSource})`);
4726
- await cleanupAndShutdown(shutdownSource);
4652
+ types.logger.debug("[DAEMON RUN] Daemon started successfully, waiting for shutdown request");
4653
+ const shutdownRequest = await resolvesWhenShutdownRequested;
4654
+ await cleanupAndShutdown(shutdownRequest.source, shutdownRequest.errorMessage);
4727
4655
  } catch (error) {
4728
- types$1.logger.debug("[DAEMON RUN] Failed to start daemon", error);
4729
- await cleanupDaemonState();
4730
- stopCaffeinate();
4656
+ types.logger.debug("[DAEMON RUN][FATAL] Failed somewhere unexpectedly - exiting with code 1", error);
4731
4657
  process.exit(1);
4732
4658
  }
4733
4659
  }
4734
4660
 
4735
4661
  async function startHappyServer(client) {
4736
4662
  const handler = async (title) => {
4737
- types$1.logger.debug("[happyMCP] Changing title to:", title);
4663
+ types.logger.debug("[happyMCP] Changing title to:", title);
4738
4664
  try {
4739
4665
  client.sendClaudeSessionMessage({
4740
4666
  type: "summary",
@@ -4759,7 +4685,7 @@ async function startHappyServer(client) {
4759
4685
  }
4760
4686
  }, async (args) => {
4761
4687
  const response = await handler(args.title);
4762
- types$1.logger.debug("[happyMCP] Response:", response);
4688
+ types.logger.debug("[happyMCP] Response:", response);
4763
4689
  if (response.success) {
4764
4690
  return {
4765
4691
  content: [
@@ -4792,7 +4718,7 @@ async function startHappyServer(client) {
4792
4718
  try {
4793
4719
  await transport.handleRequest(req, res);
4794
4720
  } catch (error) {
4795
- types$1.logger.debug("Error handling request:", error);
4721
+ types.logger.debug("Error handling request:", error);
4796
4722
  if (!res.headersSent) {
4797
4723
  res.writeHead(500).end();
4798
4724
  }
@@ -4817,74 +4743,76 @@ async function startHappyServer(client) {
4817
4743
  async function start(credentials, options = {}) {
4818
4744
  const workingDirectory = process.cwd();
4819
4745
  const sessionTag = node_crypto.randomUUID();
4820
- types$1.logger.debugLargeJson("[START] Happy process started", getEnvironmentInfo());
4821
- types$1.logger.debug(`[START] Options: startedBy=${options.startedBy}, startingMode=${options.startingMode}`);
4746
+ types.logger.debugLargeJson("[START] Happy process started", getEnvironmentInfo());
4747
+ types.logger.debug(`[START] Options: startedBy=${options.startedBy}, startingMode=${options.startingMode}`);
4822
4748
  if (options.startedBy === "daemon" && options.startingMode === "local") {
4823
- types$1.logger.debug("Daemon spawn requested with local mode - forcing remote mode");
4749
+ types.logger.debug("Daemon spawn requested with local mode - forcing remote mode");
4824
4750
  options.startingMode = "remote";
4825
4751
  }
4826
- const api = new types$1.ApiClient(credentials.token, credentials.secret);
4752
+ const api = new types.ApiClient(credentials.token, credentials.secret);
4827
4753
  let state = {};
4828
- const settings = await readSettings();
4754
+ const settings = await types.readSettings();
4829
4755
  let machineId = settings?.machineId;
4830
4756
  if (!machineId) {
4831
- console.error(`[START] No machine ID found in settings, which is unexepcted since authAndSetupMachineIfNeeded should have created it, using 'unknown' id instead`);
4832
- machineId = "unknown";
4757
+ console.error(`[START] No machine ID found in settings, which is unexepcted since authAndSetupMachineIfNeeded should have created it. Please report this issue on https://github.com/slopus/happy-cli/issues`);
4758
+ process.exit(1);
4833
4759
  }
4834
- types$1.logger.debug(`Using machineId: ${machineId}`);
4760
+ types.logger.debug(`Using machineId: ${machineId}`);
4761
+ await api.createMachineOrGetExistingAsIs({
4762
+ machineId,
4763
+ metadata: initialMachineMetadata
4764
+ });
4835
4765
  let metadata = {
4836
4766
  path: workingDirectory,
4837
4767
  host: os.hostname(),
4838
- version: packageJson.version,
4768
+ version: types.packageJson.version,
4839
4769
  os: os.platform(),
4840
4770
  machineId,
4841
4771
  homeDir: os.homedir(),
4842
- happyHomeDir: types$1.configuration.happyHomeDir,
4772
+ happyHomeDir: types.configuration.happyHomeDir,
4843
4773
  startedFromDaemon: options.startedBy === "daemon",
4844
4774
  hostPid: process.pid,
4845
4775
  startedBy: options.startedBy || "terminal"
4846
4776
  };
4847
4777
  const response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
4848
- types$1.logger.debug(`Session created: ${response.id}`);
4849
- await api.createMachineOrGetExistingAsIs({
4850
- machineId,
4851
- metadata: initialMachineMetadata
4852
- });
4778
+ types.logger.debug(`Session created: ${response.id}`);
4853
4779
  try {
4854
- const daemonState = await getDaemonState();
4855
- if (daemonState?.httpPort) {
4856
- await notifyDaemonSessionStarted(response.id, metadata);
4857
- types$1.logger.debug(`[START] Reported session ${response.id} to daemon`);
4780
+ types.logger.debug(`[START] Reporting session ${response.id} to daemon`);
4781
+ const result = await notifyDaemonSessionStarted(response.id, metadata);
4782
+ if (result.error) {
4783
+ types.logger.debug(`[START] Failed to report to daemon (may not be running):`, result.error);
4784
+ } else {
4785
+ types.logger.debug(`[START] Reported session ${response.id} to daemon`);
4858
4786
  }
4859
4787
  } catch (error) {
4860
- types$1.logger.debug("[START] Failed to report to daemon (may not be running):", error);
4788
+ types.logger.debug("[START] Failed to report to daemon (may not be running):", error);
4861
4789
  }
4862
4790
  extractSDKMetadataAsync(async (sdkMetadata) => {
4863
- types$1.logger.debug("[start] SDK metadata extracted, updating session:", sdkMetadata);
4791
+ types.logger.debug("[start] SDK metadata extracted, updating session:", sdkMetadata);
4864
4792
  try {
4865
4793
  api.sessionSyncClient(response).updateMetadata((currentMetadata) => ({
4866
4794
  ...currentMetadata,
4867
4795
  tools: sdkMetadata.tools,
4868
4796
  slashCommands: sdkMetadata.slashCommands
4869
4797
  }));
4870
- types$1.logger.debug("[start] Session metadata updated with SDK capabilities");
4798
+ types.logger.debug("[start] Session metadata updated with SDK capabilities");
4871
4799
  } catch (error) {
4872
- types$1.logger.debug("[start] Failed to update session metadata:", error);
4800
+ types.logger.debug("[start] Failed to update session metadata:", error);
4873
4801
  }
4874
4802
  });
4875
4803
  const session = api.sessionSyncClient(response);
4876
4804
  const happyServer = await startHappyServer(session);
4877
- types$1.logger.debug(`[START] Happy MCP server started at ${happyServer.url}`);
4878
- const logPath = await types$1.logger.logFilePathPromise;
4879
- types$1.logger.infoDeveloper(`Session: ${response.id}`);
4880
- types$1.logger.infoDeveloper(`Logs: ${logPath}`);
4805
+ types.logger.debug(`[START] Happy MCP server started at ${happyServer.url}`);
4806
+ const logPath = types.logger.logFilePath;
4807
+ types.logger.infoDeveloper(`Session: ${response.id}`);
4808
+ types.logger.infoDeveloper(`Logs: ${logPath}`);
4881
4809
  session.updateAgentState((currentState) => ({
4882
4810
  ...currentState,
4883
4811
  controlledByUser: options.startingMode !== "remote"
4884
4812
  }));
4885
4813
  const caffeinateStarted = startCaffeinate();
4886
4814
  if (caffeinateStarted) {
4887
- types$1.logger.infoDeveloper("Sleep prevention enabled (macOS)");
4815
+ types.logger.infoDeveloper("Sleep prevention enabled (macOS)");
4888
4816
  }
4889
4817
  const messageQueue = new MessageQueue2((mode) => hashObject({
4890
4818
  isPlan: mode.permissionMode === "plan",
@@ -4910,64 +4838,64 @@ async function start(credentials, options = {}) {
4910
4838
  if (validModes.includes(message.meta.permissionMode)) {
4911
4839
  messagePermissionMode = message.meta.permissionMode;
4912
4840
  currentPermissionMode = messagePermissionMode;
4913
- types$1.logger.debug(`[loop] Permission mode updated from user message to: ${currentPermissionMode}`);
4841
+ types.logger.debug(`[loop] Permission mode updated from user message to: ${currentPermissionMode}`);
4914
4842
  } else {
4915
- types$1.logger.debug(`[loop] Invalid permission mode received: ${message.meta.permissionMode}`);
4843
+ types.logger.debug(`[loop] Invalid permission mode received: ${message.meta.permissionMode}`);
4916
4844
  }
4917
4845
  } else {
4918
- types$1.logger.debug(`[loop] User message received with no permission mode override, using current: ${currentPermissionMode}`);
4846
+ types.logger.debug(`[loop] User message received with no permission mode override, using current: ${currentPermissionMode}`);
4919
4847
  }
4920
4848
  let messageModel = currentModel;
4921
4849
  if (message.meta?.hasOwnProperty("model")) {
4922
4850
  messageModel = message.meta.model || void 0;
4923
4851
  currentModel = messageModel;
4924
- types$1.logger.debug(`[loop] Model updated from user message: ${messageModel || "reset to default"}`);
4852
+ types.logger.debug(`[loop] Model updated from user message: ${messageModel || "reset to default"}`);
4925
4853
  } else {
4926
- types$1.logger.debug(`[loop] User message received with no model override, using current: ${currentModel || "default"}`);
4854
+ types.logger.debug(`[loop] User message received with no model override, using current: ${currentModel || "default"}`);
4927
4855
  }
4928
4856
  let messageCustomSystemPrompt = currentCustomSystemPrompt;
4929
4857
  if (message.meta?.hasOwnProperty("customSystemPrompt")) {
4930
4858
  messageCustomSystemPrompt = message.meta.customSystemPrompt || void 0;
4931
4859
  currentCustomSystemPrompt = messageCustomSystemPrompt;
4932
- types$1.logger.debug(`[loop] Custom system prompt updated from user message: ${messageCustomSystemPrompt ? "set" : "reset to none"}`);
4860
+ types.logger.debug(`[loop] Custom system prompt updated from user message: ${messageCustomSystemPrompt ? "set" : "reset to none"}`);
4933
4861
  } else {
4934
- types$1.logger.debug(`[loop] User message received with no custom system prompt override, using current: ${currentCustomSystemPrompt ? "set" : "none"}`);
4862
+ types.logger.debug(`[loop] User message received with no custom system prompt override, using current: ${currentCustomSystemPrompt ? "set" : "none"}`);
4935
4863
  }
4936
4864
  let messageFallbackModel = currentFallbackModel;
4937
4865
  if (message.meta?.hasOwnProperty("fallbackModel")) {
4938
4866
  messageFallbackModel = message.meta.fallbackModel || void 0;
4939
4867
  currentFallbackModel = messageFallbackModel;
4940
- types$1.logger.debug(`[loop] Fallback model updated from user message: ${messageFallbackModel || "reset to none"}`);
4868
+ types.logger.debug(`[loop] Fallback model updated from user message: ${messageFallbackModel || "reset to none"}`);
4941
4869
  } else {
4942
- types$1.logger.debug(`[loop] User message received with no fallback model override, using current: ${currentFallbackModel || "none"}`);
4870
+ types.logger.debug(`[loop] User message received with no fallback model override, using current: ${currentFallbackModel || "none"}`);
4943
4871
  }
4944
4872
  let messageAppendSystemPrompt = currentAppendSystemPrompt;
4945
4873
  if (message.meta?.hasOwnProperty("appendSystemPrompt")) {
4946
4874
  messageAppendSystemPrompt = message.meta.appendSystemPrompt || void 0;
4947
4875
  currentAppendSystemPrompt = messageAppendSystemPrompt;
4948
- types$1.logger.debug(`[loop] Append system prompt updated from user message: ${messageAppendSystemPrompt ? "set" : "reset to none"}`);
4876
+ types.logger.debug(`[loop] Append system prompt updated from user message: ${messageAppendSystemPrompt ? "set" : "reset to none"}`);
4949
4877
  } else {
4950
- types$1.logger.debug(`[loop] User message received with no append system prompt override, using current: ${currentAppendSystemPrompt ? "set" : "none"}`);
4878
+ types.logger.debug(`[loop] User message received with no append system prompt override, using current: ${currentAppendSystemPrompt ? "set" : "none"}`);
4951
4879
  }
4952
4880
  let messageAllowedTools = currentAllowedTools;
4953
4881
  if (message.meta?.hasOwnProperty("allowedTools")) {
4954
4882
  messageAllowedTools = message.meta.allowedTools || void 0;
4955
4883
  currentAllowedTools = messageAllowedTools;
4956
- types$1.logger.debug(`[loop] Allowed tools updated from user message: ${messageAllowedTools ? messageAllowedTools.join(", ") : "reset to none"}`);
4884
+ types.logger.debug(`[loop] Allowed tools updated from user message: ${messageAllowedTools ? messageAllowedTools.join(", ") : "reset to none"}`);
4957
4885
  } else {
4958
- types$1.logger.debug(`[loop] User message received with no allowed tools override, using current: ${currentAllowedTools ? currentAllowedTools.join(", ") : "none"}`);
4886
+ types.logger.debug(`[loop] User message received with no allowed tools override, using current: ${currentAllowedTools ? currentAllowedTools.join(", ") : "none"}`);
4959
4887
  }
4960
4888
  let messageDisallowedTools = currentDisallowedTools;
4961
4889
  if (message.meta?.hasOwnProperty("disallowedTools")) {
4962
4890
  messageDisallowedTools = message.meta.disallowedTools || void 0;
4963
4891
  currentDisallowedTools = messageDisallowedTools;
4964
- types$1.logger.debug(`[loop] Disallowed tools updated from user message: ${messageDisallowedTools ? messageDisallowedTools.join(", ") : "reset to none"}`);
4892
+ types.logger.debug(`[loop] Disallowed tools updated from user message: ${messageDisallowedTools ? messageDisallowedTools.join(", ") : "reset to none"}`);
4965
4893
  } else {
4966
- types$1.logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
4894
+ types.logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
4967
4895
  }
4968
4896
  const specialCommand = parseSpecialCommand(message.content.text);
4969
4897
  if (specialCommand.type === "compact") {
4970
- types$1.logger.debug("[start] Detected /compact command");
4898
+ types.logger.debug("[start] Detected /compact command");
4971
4899
  const enhancedMode2 = {
4972
4900
  permissionMode: messagePermissionMode || "default",
4973
4901
  model: messageModel,
@@ -4978,11 +4906,11 @@ async function start(credentials, options = {}) {
4978
4906
  disallowedTools: messageDisallowedTools
4979
4907
  };
4980
4908
  messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
4981
- types$1.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
4909
+ types.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
4982
4910
  return;
4983
4911
  }
4984
4912
  if (specialCommand.type === "clear") {
4985
- types$1.logger.debug("[start] Detected /clear command");
4913
+ types.logger.debug("[start] Detected /clear command");
4986
4914
  const enhancedMode2 = {
4987
4915
  permissionMode: messagePermissionMode || "default",
4988
4916
  model: messageModel,
@@ -4993,7 +4921,7 @@ async function start(credentials, options = {}) {
4993
4921
  disallowedTools: messageDisallowedTools
4994
4922
  };
4995
4923
  messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
4996
- types$1.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
4924
+ types.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
4997
4925
  return;
4998
4926
  }
4999
4927
  const enhancedMode = {
@@ -5006,10 +4934,10 @@ async function start(credentials, options = {}) {
5006
4934
  disallowedTools: messageDisallowedTools
5007
4935
  };
5008
4936
  messageQueue.push(message.content.text, enhancedMode);
5009
- types$1.logger.debugLargeJson("User message pushed to queue:", message);
4937
+ types.logger.debugLargeJson("User message pushed to queue:", message);
5010
4938
  });
5011
4939
  const cleanup = async () => {
5012
- types$1.logger.debug("[START] Received termination signal, cleaning up...");
4940
+ types.logger.debug("[START] Received termination signal, cleaning up...");
5013
4941
  try {
5014
4942
  if (session) {
5015
4943
  session.sendSessionDeath();
@@ -5018,21 +4946,21 @@ async function start(credentials, options = {}) {
5018
4946
  }
5019
4947
  stopCaffeinate();
5020
4948
  happyServer.stop();
5021
- types$1.logger.debug("[START] Cleanup complete, exiting");
4949
+ types.logger.debug("[START] Cleanup complete, exiting");
5022
4950
  process.exit(0);
5023
4951
  } catch (error) {
5024
- types$1.logger.debug("[START] Error during cleanup:", error);
4952
+ types.logger.debug("[START] Error during cleanup:", error);
5025
4953
  process.exit(1);
5026
4954
  }
5027
4955
  };
5028
4956
  process.on("SIGTERM", cleanup);
5029
4957
  process.on("SIGINT", cleanup);
5030
4958
  process.on("uncaughtException", (error) => {
5031
- types$1.logger.debug("[START] Uncaught exception:", error);
4959
+ types.logger.debug("[START] Uncaught exception:", error);
5032
4960
  cleanup();
5033
4961
  });
5034
4962
  process.on("unhandledRejection", (reason) => {
5035
- types$1.logger.debug("[START] Unhandled rejection:", reason);
4963
+ types.logger.debug("[START] Unhandled rejection:", reason);
5036
4964
  cleanup();
5037
4965
  });
5038
4966
  await loop({
@@ -5063,14 +4991,14 @@ async function start(credentials, options = {}) {
5063
4991
  claudeArgs: options.claudeArgs
5064
4992
  });
5065
4993
  session.sendSessionDeath();
5066
- types$1.logger.debug("Waiting for socket to flush...");
4994
+ types.logger.debug("Waiting for socket to flush...");
5067
4995
  await session.flush();
5068
- types$1.logger.debug("Closing session...");
4996
+ types.logger.debug("Closing session...");
5069
4997
  await session.close();
5070
4998
  stopCaffeinate();
5071
- types$1.logger.debug("Stopped sleep prevention");
4999
+ types.logger.debug("Stopped sleep prevention");
5072
5000
  happyServer.stop();
5073
- types$1.logger.debug("Stopped Happy MCP server");
5001
+ types.logger.debug("Stopped Happy MCP server");
5074
5002
  process.exit(0);
5075
5003
  }
5076
5004
 
@@ -5078,8 +5006,8 @@ const PLIST_LABEL$1 = "com.happy-cli.daemon";
5078
5006
  const PLIST_FILE$1 = `/Library/LaunchDaemons/${PLIST_LABEL$1}.plist`;
5079
5007
  async function install$1() {
5080
5008
  try {
5081
- if (fs.existsSync(PLIST_FILE$1)) {
5082
- types$1.logger.info("Daemon plist already exists. Uninstalling first...");
5009
+ if (fs$1.existsSync(PLIST_FILE$1)) {
5010
+ types.logger.info("Daemon plist already exists. Uninstalling first...");
5083
5011
  child_process.execSync(`launchctl unload ${PLIST_FILE$1}`, { stdio: "inherit" });
5084
5012
  }
5085
5013
  const happyPath = process.argv[0];
@@ -5122,14 +5050,14 @@ async function install$1() {
5122
5050
  </dict>
5123
5051
  </plist>
5124
5052
  `);
5125
- fs.writeFileSync(PLIST_FILE$1, plistContent);
5126
- fs.chmodSync(PLIST_FILE$1, 420);
5127
- types$1.logger.info(`Created daemon plist at ${PLIST_FILE$1}`);
5053
+ fs$1.writeFileSync(PLIST_FILE$1, plistContent);
5054
+ fs$1.chmodSync(PLIST_FILE$1, 420);
5055
+ types.logger.info(`Created daemon plist at ${PLIST_FILE$1}`);
5128
5056
  child_process.execSync(`launchctl load ${PLIST_FILE$1}`, { stdio: "inherit" });
5129
- types$1.logger.info("Daemon installed and started successfully");
5130
- types$1.logger.info("Check logs at ~/.happy/daemon.log");
5057
+ types.logger.info("Daemon installed and started successfully");
5058
+ types.logger.info("Check logs at ~/.happy/daemon.log");
5131
5059
  } catch (error) {
5132
- types$1.logger.debug("Failed to install daemon:", error);
5060
+ types.logger.debug("Failed to install daemon:", error);
5133
5061
  throw error;
5134
5062
  }
5135
5063
  }
@@ -5141,7 +5069,7 @@ async function install() {
5141
5069
  if (process.getuid && process.getuid() !== 0) {
5142
5070
  throw new Error("Daemon installation requires sudo privileges. Please run with sudo.");
5143
5071
  }
5144
- types$1.logger.info("Installing Happy CLI daemon for macOS...");
5072
+ types.logger.info("Installing Happy CLI daemon for macOS...");
5145
5073
  await install$1();
5146
5074
  }
5147
5075
 
@@ -5149,21 +5077,21 @@ const PLIST_LABEL = "com.happy-cli.daemon";
5149
5077
  const PLIST_FILE = `/Library/LaunchDaemons/${PLIST_LABEL}.plist`;
5150
5078
  async function uninstall$1() {
5151
5079
  try {
5152
- if (!fs.existsSync(PLIST_FILE)) {
5153
- types$1.logger.info("Daemon plist not found. Nothing to uninstall.");
5080
+ if (!fs$1.existsSync(PLIST_FILE)) {
5081
+ types.logger.info("Daemon plist not found. Nothing to uninstall.");
5154
5082
  return;
5155
5083
  }
5156
5084
  try {
5157
5085
  child_process.execSync(`launchctl unload ${PLIST_FILE}`, { stdio: "inherit" });
5158
- types$1.logger.info("Daemon stopped successfully");
5086
+ types.logger.info("Daemon stopped successfully");
5159
5087
  } catch (error) {
5160
- types$1.logger.info("Failed to unload daemon (it might not be running)");
5088
+ types.logger.info("Failed to unload daemon (it might not be running)");
5161
5089
  }
5162
- fs.unlinkSync(PLIST_FILE);
5163
- types$1.logger.info(`Removed daemon plist from ${PLIST_FILE}`);
5164
- types$1.logger.info("Daemon uninstalled successfully");
5090
+ fs$1.unlinkSync(PLIST_FILE);
5091
+ types.logger.info(`Removed daemon plist from ${PLIST_FILE}`);
5092
+ types.logger.info("Daemon uninstalled successfully");
5165
5093
  } catch (error) {
5166
- types$1.logger.debug("Failed to uninstall daemon:", error);
5094
+ types.logger.debug("Failed to uninstall daemon:", error);
5167
5095
  throw error;
5168
5096
  }
5169
5097
  }
@@ -5175,7 +5103,7 @@ async function uninstall() {
5175
5103
  if (process.getuid && process.getuid() !== 0) {
5176
5104
  throw new Error("Daemon uninstallation requires sudo privileges. Please run with sudo.");
5177
5105
  }
5178
- types$1.logger.info("Uninstalling Happy CLI daemon for macOS...");
5106
+ types.logger.info("Uninstalling Happy CLI daemon for macOS...");
5179
5107
  await uninstall$1();
5180
5108
  }
5181
5109
 
@@ -5268,21 +5196,21 @@ async function handleAuthLogin(args) {
5268
5196
  console.log(chalk.gray(" \u2022 Stop daemon if running"));
5269
5197
  console.log(chalk.gray(" \u2022 Re-authenticate and register machine\n"));
5270
5198
  try {
5271
- types$1.logger.debug("Stopping daemon for force auth...");
5199
+ types.logger.debug("Stopping daemon for force auth...");
5272
5200
  await stopDaemon();
5273
5201
  console.log(chalk.gray("\u2713 Stopped daemon"));
5274
5202
  } catch (error) {
5275
- types$1.logger.debug("Daemon was not running or failed to stop:", error);
5203
+ types.logger.debug("Daemon was not running or failed to stop:", error);
5276
5204
  }
5277
- await clearCredentials();
5205
+ await types.clearCredentials();
5278
5206
  console.log(chalk.gray("\u2713 Cleared credentials"));
5279
- await clearMachineId();
5207
+ await types.clearMachineId();
5280
5208
  console.log(chalk.gray("\u2713 Cleared machine ID"));
5281
5209
  console.log("");
5282
5210
  }
5283
5211
  if (!forceAuth) {
5284
- const existingCreds = await readCredentials();
5285
- const settings = await readSettings();
5212
+ const existingCreds = await types.readCredentials();
5213
+ const settings = await types.readSettings();
5286
5214
  if (existingCreds && settings?.machineId) {
5287
5215
  console.log(chalk.green("\u2713 Already authenticated"));
5288
5216
  console.log(chalk.gray(` Machine ID: ${settings.machineId}`));
@@ -5305,8 +5233,8 @@ async function handleAuthLogin(args) {
5305
5233
  }
5306
5234
  }
5307
5235
  async function handleAuthLogout() {
5308
- const happyDir = types$1.configuration.happyHomeDir;
5309
- const credentials = await readCredentials();
5236
+ const happyDir = types.configuration.happyHomeDir;
5237
+ const credentials = await types.readCredentials();
5310
5238
  if (!credentials) {
5311
5239
  console.log(chalk.yellow("Not currently authenticated"));
5312
5240
  return;
@@ -5341,8 +5269,8 @@ async function handleAuthLogout() {
5341
5269
  }
5342
5270
  }
5343
5271
  async function handleAuthShowBackup() {
5344
- const credentials = await readCredentials();
5345
- const settings = await readSettings();
5272
+ const credentials = await types.readCredentials();
5273
+ const settings = await types.readSettings();
5346
5274
  if (!credentials) {
5347
5275
  console.log(chalk.yellow("Not authenticated"));
5348
5276
  console.log(chalk.gray('Run "happy auth login" to authenticate first'));
@@ -5366,8 +5294,8 @@ async function handleAuthShowBackup() {
5366
5294
  console.log(chalk.yellow("\u26A0\uFE0F Keep this key secure - it provides full access to your account"));
5367
5295
  }
5368
5296
  async function handleAuthStatus() {
5369
- const credentials = await readCredentials();
5370
- const settings = await readSettings();
5297
+ const credentials = await types.readCredentials();
5298
+ const settings = await types.readSettings();
5371
5299
  console.log(chalk.bold("\nAuthentication Status\n"));
5372
5300
  if (!credentials) {
5373
5301
  console.log(chalk.red("\u2717 Not authenticated"));
@@ -5386,10 +5314,9 @@ async function handleAuthStatus() {
5386
5314
  console.log(chalk.gray(' Run "happy auth login --force" to fix this'));
5387
5315
  }
5388
5316
  console.log(chalk.gray(`
5389
- Data directory: ${types$1.configuration.happyHomeDir}`));
5317
+ Data directory: ${types.configuration.happyHomeDir}`));
5390
5318
  try {
5391
- const { isDaemonRunning } = await Promise.resolve().then(function () { return utils; });
5392
- const running = await isDaemonRunning();
5319
+ const running = await checkIfDaemonRunningAndCleanupStaleState();
5393
5320
  if (running) {
5394
5321
  console.log(chalk.green("\u2713 Daemon running"));
5395
5322
  } else {
@@ -5430,9 +5357,19 @@ const DaemonPrompt = ({ onSelect }) => {
5430
5357
 
5431
5358
  (async () => {
5432
5359
  const args = process.argv.slice(2);
5433
- types$1.logger.debug("Starting happy CLI with args: ", process.argv);
5360
+ if (!args.includes("--version")) {
5361
+ types.logger.debug("Starting happy CLI with args: ", process.argv);
5362
+ }
5434
5363
  const subcommand = args[0];
5435
5364
  if (subcommand === "doctor") {
5365
+ if (args[1] === "clean") {
5366
+ const result = await killRunawayHappyProcesses();
5367
+ console.log(`Cleaned up ${result.killed} runaway processes`);
5368
+ if (result.errors.length > 0) {
5369
+ console.log("Errors:", result.errors);
5370
+ }
5371
+ process.exit(0);
5372
+ }
5436
5373
  await runDoctorCommand();
5437
5374
  return;
5438
5375
  } else if (subcommand === "auth") {
@@ -5475,16 +5412,10 @@ const DaemonPrompt = ({ onSelect }) => {
5475
5412
  try {
5476
5413
  const sessions = await listDaemonSessions();
5477
5414
  if (sessions.length === 0) {
5478
- console.log("No active sessions");
5415
+ console.log("No active sessions this daemon is aware of (they might have been started by a previous version of the daemon)");
5479
5416
  } else {
5480
5417
  console.log("Active sessions:");
5481
- const cleanSessions = sessions.map((s) => ({
5482
- pid: s.pid,
5483
- sessionId: s.happySessionId || `PID-${s.pid}`,
5484
- startedBy: s.startedBy,
5485
- directory: s.happySessionMetadataFromLocalWebhook?.directory || "unknown"
5486
- }));
5487
- console.log(JSON.stringify(cleanSessions, null, 2));
5418
+ console.log(JSON.stringify(sessions, null, 2));
5488
5419
  }
5489
5420
  } catch (error) {
5490
5421
  console.log("No daemon running");
@@ -5512,7 +5443,7 @@ const DaemonPrompt = ({ onSelect }) => {
5512
5443
  child.unref();
5513
5444
  let started = false;
5514
5445
  for (let i = 0; i < 50; i++) {
5515
- if (await isDaemonRunning()) {
5446
+ if (await checkIfDaemonRunningAndCleanupStaleState()) {
5516
5447
  started = true;
5517
5448
  break;
5518
5449
  }
@@ -5532,28 +5463,14 @@ const DaemonPrompt = ({ onSelect }) => {
5532
5463
  await stopDaemon();
5533
5464
  process.exit(0);
5534
5465
  } else if (daemonSubcommand === "status") {
5535
- const state = await getDaemonState();
5536
- if (!state) {
5537
- console.log("Daemon is not running");
5538
- } else {
5539
- const isRunning = await isDaemonRunning();
5540
- if (isRunning) {
5541
- console.log("Daemon is running");
5542
- console.log(` PID: ${state.pid}`);
5543
- console.log(` Port: ${state.httpPort}`);
5544
- console.log(` Started: ${new Date(state.startTime).toLocaleString()}`);
5545
- console.log(` CLI Version: ${state.startedWithCliVersion}`);
5546
- } else {
5547
- console.log("Daemon state file exists but daemon is not running (stale)");
5548
- }
5549
- }
5466
+ await runDoctorCommand("daemon");
5550
5467
  process.exit(0);
5551
- } else if (daemonSubcommand === "kill-runaway") {
5552
- const { killRunawayHappyProcesses } = await Promise.resolve().then(function () { return utils; });
5553
- const result = await killRunawayHappyProcesses();
5554
- console.log(`Killed ${result.killed} runaway processes`);
5555
- if (result.errors.length > 0) {
5556
- console.log("Errors:", result.errors);
5468
+ } else if (daemonSubcommand === "logs") {
5469
+ const latest = await types.getLatestDaemonLog();
5470
+ if (!latest) {
5471
+ console.log("No daemon logs found");
5472
+ } else {
5473
+ console.log(latest.path);
5557
5474
  }
5558
5475
  process.exit(0);
5559
5476
  } else if (daemonSubcommand === "install") {
@@ -5577,14 +5494,15 @@ ${chalk.bold("happy daemon")} - Daemon management
5577
5494
  ${chalk.bold("Usage:")}
5578
5495
  happy daemon start Start the daemon (detached)
5579
5496
  happy daemon stop Stop the daemon (sessions stay alive)
5580
- happy daemon stop --kill-managed Stop daemon and kill managed sessions
5581
5497
  happy daemon status Show daemon status
5582
5498
  happy daemon list List active sessions
5583
- happy daemon stop-session <id> Stop a specific session
5584
- happy daemon kill-runaway Kill all runaway Happy processes
5499
+
5500
+ If you want to kill all happy related processes run
5501
+ ${chalk.cyan("happy doctor clean")}
5585
5502
 
5586
5503
  ${chalk.bold("Note:")} The daemon runs in the background and manages Claude sessions.
5587
- Sessions spawned by the daemon will continue running after daemon stops unless --kill-managed is used.
5504
+
5505
+ ${chalk.bold("To clean up runaway processes:")} Use ${chalk.cyan("happy doctor clean")}
5588
5506
  `);
5589
5507
  }
5590
5508
  return;
@@ -5592,8 +5510,6 @@ Sessions spawned by the daemon will continue running after daemon stops unless -
5592
5510
  const options = {};
5593
5511
  let showHelp = false;
5594
5512
  let showVersion = false;
5595
- let forceAuth = false;
5596
- let forceAuthNew = false;
5597
5513
  const unknownArgs = [];
5598
5514
  for (let i = 0; i < args.length; i++) {
5599
5515
  const arg = args[i];
@@ -5603,11 +5519,7 @@ Sessions spawned by the daemon will continue running after daemon stops unless -
5603
5519
  } else if (arg === "-v" || arg === "--version") {
5604
5520
  showVersion = true;
5605
5521
  unknownArgs.push(arg);
5606
- } else if (arg === "--auth" || arg === "--login") {
5607
- forceAuth = true;
5608
- } else if (arg === "--force-auth") {
5609
- forceAuthNew = true;
5610
- } else if (arg === "--happy-starting-mode") {
5522
+ } else if (arg === "--auth" || arg === "--login") ; else if (arg === "--force-auth") ; else if (arg === "--happy-starting-mode") {
5611
5523
  options.startingMode = z.z.enum(["local", "remote"]).parse(args[++i]);
5612
5524
  } else if (arg === "--yolo") {
5613
5525
  unknownArgs.push("--dangerously-skip-permissions");
@@ -5628,29 +5540,23 @@ Sessions spawned by the daemon will continue running after daemon stops unless -
5628
5540
  ${chalk.bold("happy")} - Claude Code On the Go
5629
5541
 
5630
5542
  ${chalk.bold("Usage:")}
5631
- happy [options] Start Claude with mobile control
5632
- happy auth Manage authentication
5633
- happy notify Send push notification
5634
- happy daemon Manage background service
5635
-
5636
- ${chalk.bold("Happy Options:")}
5637
- --help Show this help message
5638
- --yolo Skip all permissions (--dangerously-skip-permissions)
5639
- --force-auth Force re-authentication
5640
-
5641
- ${chalk.bold("\u{1F3AF} Happy supports ALL Claude options!")}
5642
- Use any claude flag exactly as you normally would.
5543
+ happy [options] Start Claude with mobile control
5544
+ happy auth Manage authentication
5545
+ happy daemon Manage background service that allows
5546
+ to spawn new sessions away from your computer
5547
+ happy doctor System diagnostics & troubleshooting
5643
5548
 
5644
5549
  ${chalk.bold("Examples:")}
5645
- happy Start session
5646
- happy --yolo Start without permissions
5647
- happy --verbose Enable verbose mode
5648
- happy -c Continue last conversation
5649
- happy auth login Authenticate
5650
- happy notify -p "Done!" Send notification
5550
+ happy Start session
5551
+ happy --yolo Bypass permissions
5552
+ happy --verbose Enable verbose mode
5553
+ happy auth login --force Authenticate
5554
+ happy doctor Run diagnostics
5651
5555
 
5652
5556
  ${chalk.bold("Happy is a wrapper around Claude Code that enables remote control via mobile app.")}
5653
- ${chalk.bold('Use "happy daemon" for background service management.')}
5557
+
5558
+ ${chalk.bold("Happy supports ALL Claude options!")}
5559
+ Use any claude flag exactly as you normally would.
5654
5560
 
5655
5561
  ${chalk.gray("\u2500".repeat(60))}
5656
5562
  ${chalk.bold.cyan("Claude Code Options (from `claude --help`):")}
@@ -5665,34 +5571,13 @@ ${chalk.bold.cyan("Claude Code Options (from `claude --help`):")}
5665
5571
  process.exit(0);
5666
5572
  }
5667
5573
  if (showVersion) {
5668
- console.log(packageJson.version);
5574
+ console.log(types.packageJson.version);
5669
5575
  process.exit(0);
5670
5576
  }
5671
- let credentials;
5672
- if (forceAuthNew) {
5673
- console.log(chalk.yellow("Force authentication requested..."));
5674
- try {
5675
- await stopDaemon();
5676
- } catch {
5677
- }
5678
- await clearCredentials();
5679
- await clearMachineId();
5680
- const result = await authAndSetupMachineIfNeeded();
5681
- credentials = result.credentials;
5682
- } else if (forceAuth) {
5683
- console.log(chalk.yellow('Note: --auth is deprecated. Use "happy auth login" or --force-auth instead.\n'));
5684
- const res = await doAuth();
5685
- if (!res) {
5686
- process.exit(1);
5687
- }
5688
- await writeCredentials(res);
5689
- const result = await authAndSetupMachineIfNeeded();
5690
- credentials = result.credentials;
5691
- } else {
5692
- const result = await authAndSetupMachineIfNeeded();
5693
- credentials = result.credentials;
5694
- }
5695
- let settings = await readSettings();
5577
+ const {
5578
+ credentials
5579
+ } = await authAndSetupMachineIfNeeded();
5580
+ let settings = await types.readSettings();
5696
5581
  if (settings && settings.daemonAutoStartWhenRunningHappy === void 0) {
5697
5582
  const shouldAutoStart = await new Promise((resolve) => {
5698
5583
  let hasResolved = false;
@@ -5708,7 +5593,7 @@ ${chalk.bold.cyan("Claude Code Options (from `claude --help`):")}
5708
5593
  patchConsole: false
5709
5594
  });
5710
5595
  });
5711
- settings = await updateSettings((settings2) => ({
5596
+ settings = await types.updateSettings((settings2) => ({
5712
5597
  ...settings2,
5713
5598
  daemonAutoStartWhenRunningHappy: shouldAutoStart
5714
5599
  }));
@@ -5720,15 +5605,18 @@ ${chalk.bold.cyan("Claude Code Options (from `claude --help`):")}
5720
5605
  }
5721
5606
  }
5722
5607
  if (settings && settings.daemonAutoStartWhenRunningHappy) {
5723
- types$1.logger.debug("Starting Happy background service...");
5724
- if (!await isDaemonRunning()) {
5608
+ types.logger.debug("Ensuring Happy background service is running & matches our version...");
5609
+ if (!await isDaemonRunningSameVersion()) {
5610
+ types.logger.debug("Starting Happy background service...");
5725
5611
  const daemonProcess = spawnHappyCLI(["daemon", "start-sync"], {
5726
5612
  detached: true,
5727
5613
  stdio: "ignore",
5728
5614
  env: process.env
5729
5615
  });
5730
5616
  daemonProcess.unref();
5731
- await new Promise((resolve) => setTimeout(resolve, 500));
5617
+ await new Promise((resolve) => setTimeout(resolve, 100));
5618
+ } else {
5619
+ types.logger.debug("Happy background service is running & matches our version");
5732
5620
  }
5733
5621
  }
5734
5622
  try {
@@ -5783,14 +5671,14 @@ ${chalk.bold("Examples:")}
5783
5671
  console.log(chalk.gray('Run "happy notify --help" for usage information.'));
5784
5672
  process.exit(1);
5785
5673
  }
5786
- let credentials = await readCredentials();
5674
+ let credentials = await types.readCredentials();
5787
5675
  if (!credentials) {
5788
5676
  console.error(chalk.red('Error: Not authenticated. Please run "happy --auth" first.'));
5789
5677
  process.exit(1);
5790
5678
  }
5791
5679
  console.log(chalk.blue("\u{1F4F1} Sending push notification..."));
5792
5680
  try {
5793
- const api = new types$1.ApiClient(credentials.token, credentials.secret);
5681
+ const api = new types.ApiClient(credentials.token, credentials.secret);
5794
5682
  const notificationTitle = title || "Happy";
5795
5683
  api.push().sendToAllDevices(
5796
5684
  notificationTitle,