happy-coder 0.1.9 → 0.1.10

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,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chalk = require('chalk');
4
- var types = require('./types-mykDX2xe.cjs');
4
+ var types = require('./types-B2JzqUiU.cjs');
5
5
  var node_crypto = require('node:crypto');
6
6
  var claudeCode = require('@anthropic-ai/claude-code');
7
7
  var node_fs = require('node:fs');
@@ -17,12 +17,18 @@ var streamableHttp_js = require('@modelcontextprotocol/sdk/server/streamableHttp
17
17
  var z = require('zod');
18
18
  var node_https = require('node:https');
19
19
  var net = require('node:net');
20
+ var child_process = require('child_process');
21
+ var util = require('util');
22
+ var promises$1 = require('fs/promises');
23
+ var crypto = require('crypto');
24
+ var path = require('path');
20
25
  var tweetnacl = require('tweetnacl');
21
26
  var axios = require('axios');
22
27
  var qrcode = require('qrcode-terminal');
23
- require('fs');
24
- require('node:events');
25
- require('socket.io-client');
28
+ var node_events = require('node:events');
29
+ var socket_ioClient = require('socket.io-client');
30
+ var os$1 = require('os');
31
+ var fs = require('fs');
26
32
  require('expo-server-sdk');
27
33
 
28
34
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -573,14 +579,14 @@ function createSessionScanner(opts) {
573
579
  processedMessages.add(key);
574
580
  types.logger.debugLargeJson(`[SESSION_SCANNER] Processing message`, parsed.data);
575
581
  types.logger.debug(`[SESSION_SCANNER] Message key (new): ${key}`);
576
- if (parsed.data.type === "user" && typeof parsed.data.message.content === "string") {
582
+ if (parsed.data.type === "user" && typeof parsed.data.message.content === "string" && parsed.data.isSidechain !== true && parsed.data.isMeta !== true) {
577
583
  const currentCounter = seenRemoteUserMessageCounters.get(parsed.data.message.content);
578
584
  if (currentCounter && currentCounter > 0) {
579
585
  seenRemoteUserMessageCounters.set(parsed.data.message.content, currentCounter - 1);
580
586
  continue;
581
587
  }
582
588
  }
583
- opts.onMessage(parsed.data);
589
+ opts.onMessage(message);
584
590
  } catch (e) {
585
591
  continue;
586
592
  }
@@ -883,7 +889,7 @@ class InterruptController {
883
889
  }
884
890
  }
885
891
 
886
- var version = "0.1.9";
892
+ var version = "0.1.10";
887
893
  var packageJson = {
888
894
  version: version};
889
895
 
@@ -1034,12 +1040,235 @@ async function startAnthropicActivityProxy(onClaudeActivity) {
1034
1040
  };
1035
1041
  }
1036
1042
 
1043
+ const execAsync = util.promisify(child_process.exec);
1044
+ function registerHandlers(session, interruptController, permissionCallbacks) {
1045
+ session.setHandler("abort", async () => {
1046
+ types.logger.info("Abort request - interrupting Claude");
1047
+ await interruptController.interrupt();
1048
+ });
1049
+ if (permissionCallbacks) {
1050
+ session.setHandler("permission", async (message) => {
1051
+ types.logger.info("Permission response" + JSON.stringify(message));
1052
+ const id = message.id;
1053
+ const resolve = permissionCallbacks.requests.get(id);
1054
+ if (resolve) {
1055
+ if (!message.approved) {
1056
+ types.logger.debug("Permission denied, interrupting Claude");
1057
+ await interruptController.interrupt();
1058
+ }
1059
+ resolve({ approved: message.approved, reason: message.reason });
1060
+ permissionCallbacks.requests.delete(id);
1061
+ } else {
1062
+ types.logger.info("Permission request stale, likely timed out");
1063
+ return;
1064
+ }
1065
+ session.updateAgentState((currentState) => {
1066
+ let r = { ...currentState.requests };
1067
+ delete r[id];
1068
+ return {
1069
+ ...currentState,
1070
+ requests: r
1071
+ };
1072
+ });
1073
+ });
1074
+ }
1075
+ session.setHandler("bash", async (data) => {
1076
+ types.logger.info("Shell command request:", data.command);
1077
+ try {
1078
+ const options = {
1079
+ cwd: data.cwd,
1080
+ timeout: data.timeout || 3e4
1081
+ // Default 30 seconds timeout
1082
+ };
1083
+ const { stdout, stderr } = await execAsync(data.command, options);
1084
+ return {
1085
+ success: true,
1086
+ stdout: stdout || "",
1087
+ stderr: stderr || "",
1088
+ exitCode: 0
1089
+ };
1090
+ } catch (error) {
1091
+ const execError = error;
1092
+ if (execError.code === "ETIMEDOUT" || execError.killed) {
1093
+ return {
1094
+ success: false,
1095
+ stdout: execError.stdout || "",
1096
+ stderr: execError.stderr || "",
1097
+ exitCode: typeof execError.code === "number" ? execError.code : -1,
1098
+ error: "Command timed out"
1099
+ };
1100
+ }
1101
+ return {
1102
+ success: false,
1103
+ stdout: execError.stdout || "",
1104
+ stderr: execError.stderr || execError.message || "Command failed",
1105
+ exitCode: typeof execError.code === "number" ? execError.code : 1,
1106
+ error: execError.message || "Command failed"
1107
+ };
1108
+ }
1109
+ });
1110
+ session.setHandler("readFile", async (data) => {
1111
+ types.logger.info("Read file request:", data.path);
1112
+ try {
1113
+ const buffer = await promises$1.readFile(data.path);
1114
+ const content = buffer.toString("base64");
1115
+ return { success: true, content };
1116
+ } catch (error) {
1117
+ types.logger.debug("Failed to read file:", error);
1118
+ return { success: false, error: error instanceof Error ? error.message : "Failed to read file" };
1119
+ }
1120
+ });
1121
+ session.setHandler("writeFile", async (data) => {
1122
+ types.logger.info("Write file request:", data.path);
1123
+ try {
1124
+ if (data.expectedHash !== null && data.expectedHash !== void 0) {
1125
+ try {
1126
+ const existingBuffer = await promises$1.readFile(data.path);
1127
+ const existingHash = crypto.createHash("sha256").update(existingBuffer).digest("hex");
1128
+ if (existingHash !== data.expectedHash) {
1129
+ return {
1130
+ success: false,
1131
+ error: `File hash mismatch. Expected: ${data.expectedHash}, Actual: ${existingHash}`
1132
+ };
1133
+ }
1134
+ } catch (error) {
1135
+ const nodeError = error;
1136
+ if (nodeError.code !== "ENOENT") {
1137
+ throw error;
1138
+ }
1139
+ return {
1140
+ success: false,
1141
+ error: "File does not exist but hash was provided"
1142
+ };
1143
+ }
1144
+ } else {
1145
+ try {
1146
+ await promises$1.stat(data.path);
1147
+ return {
1148
+ success: false,
1149
+ error: "File already exists but was expected to be new"
1150
+ };
1151
+ } catch (error) {
1152
+ const nodeError = error;
1153
+ if (nodeError.code !== "ENOENT") {
1154
+ throw error;
1155
+ }
1156
+ }
1157
+ }
1158
+ const buffer = Buffer.from(data.content, "base64");
1159
+ await promises$1.writeFile(data.path, buffer);
1160
+ const hash = crypto.createHash("sha256").update(buffer).digest("hex");
1161
+ return { success: true, hash };
1162
+ } catch (error) {
1163
+ types.logger.debug("Failed to write file:", error);
1164
+ return { success: false, error: error instanceof Error ? error.message : "Failed to write file" };
1165
+ }
1166
+ });
1167
+ session.setHandler("listDirectory", async (data) => {
1168
+ types.logger.info("List directory request:", data.path);
1169
+ try {
1170
+ const entries = await promises$1.readdir(data.path, { withFileTypes: true });
1171
+ const directoryEntries = await Promise.all(
1172
+ entries.map(async (entry) => {
1173
+ const fullPath = path.join(data.path, entry.name);
1174
+ let type = "other";
1175
+ let size;
1176
+ let modified;
1177
+ if (entry.isDirectory()) {
1178
+ type = "directory";
1179
+ } else if (entry.isFile()) {
1180
+ type = "file";
1181
+ }
1182
+ try {
1183
+ const stats = await promises$1.stat(fullPath);
1184
+ size = stats.size;
1185
+ modified = stats.mtime.getTime();
1186
+ } catch (error) {
1187
+ types.logger.debug(`Failed to stat ${fullPath}:`, error);
1188
+ }
1189
+ return {
1190
+ name: entry.name,
1191
+ type,
1192
+ size,
1193
+ modified
1194
+ };
1195
+ })
1196
+ );
1197
+ directoryEntries.sort((a, b) => {
1198
+ if (a.type === "directory" && b.type !== "directory") return -1;
1199
+ if (a.type !== "directory" && b.type === "directory") return 1;
1200
+ return a.name.localeCompare(b.name);
1201
+ });
1202
+ return { success: true, entries: directoryEntries };
1203
+ } catch (error) {
1204
+ types.logger.debug("Failed to list directory:", error);
1205
+ return { success: false, error: error instanceof Error ? error.message : "Failed to list directory" };
1206
+ }
1207
+ });
1208
+ session.setHandler("getDirectoryTree", async (data) => {
1209
+ types.logger.info("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
1210
+ async function buildTree(path$1, name, currentDepth) {
1211
+ try {
1212
+ const stats = await promises$1.stat(path$1);
1213
+ const node = {
1214
+ name,
1215
+ path: path$1,
1216
+ type: stats.isDirectory() ? "directory" : "file",
1217
+ size: stats.size,
1218
+ modified: stats.mtime.getTime()
1219
+ };
1220
+ if (stats.isDirectory() && currentDepth < data.maxDepth) {
1221
+ const entries = await promises$1.readdir(path$1, { withFileTypes: true });
1222
+ const children = [];
1223
+ await Promise.all(
1224
+ entries.map(async (entry) => {
1225
+ if (entry.isSymbolicLink()) {
1226
+ types.logger.debug(`Skipping symlink: ${path.join(path$1, entry.name)}`);
1227
+ return;
1228
+ }
1229
+ const childPath = path.join(path$1, entry.name);
1230
+ const childNode = await buildTree(childPath, entry.name, currentDepth + 1);
1231
+ if (childNode) {
1232
+ children.push(childNode);
1233
+ }
1234
+ })
1235
+ );
1236
+ children.sort((a, b) => {
1237
+ if (a.type === "directory" && b.type !== "directory") return -1;
1238
+ if (a.type !== "directory" && b.type === "directory") return 1;
1239
+ return a.name.localeCompare(b.name);
1240
+ });
1241
+ node.children = children;
1242
+ }
1243
+ return node;
1244
+ } catch (error) {
1245
+ types.logger.debug(`Failed to process ${path$1}:`, error instanceof Error ? error.message : String(error));
1246
+ return null;
1247
+ }
1248
+ }
1249
+ try {
1250
+ if (data.maxDepth < 0) {
1251
+ return { success: false, error: "maxDepth must be non-negative" };
1252
+ }
1253
+ const baseName = data.path === "/" ? "/" : data.path.split("/").pop() || data.path;
1254
+ const tree = await buildTree(data.path, baseName, 0);
1255
+ if (!tree) {
1256
+ return { success: false, error: "Failed to access the specified path" };
1257
+ }
1258
+ return { success: true, tree };
1259
+ } catch (error) {
1260
+ types.logger.debug("Failed to get directory tree:", error);
1261
+ return { success: false, error: error instanceof Error ? error.message : "Failed to get directory tree" };
1262
+ }
1263
+ });
1264
+ }
1265
+
1037
1266
  async function start(credentials, options = {}) {
1038
1267
  const workingDirectory = process.cwd();
1039
1268
  const sessionTag = node_crypto.randomUUID();
1040
1269
  const api = new types.ApiClient(credentials.token, credentials.secret);
1041
1270
  let state = {};
1042
- let metadata = { path: workingDirectory, host: os.hostname(), version: packageJson.version };
1271
+ let metadata = { path: workingDirectory, host: os.hostname(), version: packageJson.version, os: os.platform() };
1043
1272
  const response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
1044
1273
  types.logger.debug(`Session created: ${response.id}`);
1045
1274
  const session = api.session(response);
@@ -1062,7 +1291,7 @@ async function start(credentials, options = {}) {
1062
1291
  process.env.HTTPS_PROXY = antropicActivityProxy.url;
1063
1292
  types.logger.debug(`[AnthropicProxy] Set HTTP_PROXY and HTTPS_PROXY to ${antropicActivityProxy.url}`);
1064
1293
  const logPath = await types.logger.logFilePathPromise;
1065
- types.logger.info(`Session: ${response.id}`);
1294
+ types.logger.infoDeveloper(`Session: ${response.id}`);
1066
1295
  types.logger.infoDeveloper(`Logs: ${logPath}`);
1067
1296
  const interruptController = new InterruptController();
1068
1297
  let requests = /* @__PURE__ */ new Map();
@@ -1116,29 +1345,7 @@ async function start(credentials, options = {}) {
1116
1345
  promise.then(() => clearTimeout(timeout)).catch(() => clearTimeout(timeout));
1117
1346
  return promise;
1118
1347
  });
1119
- session.setHandler("permission", (message) => {
1120
- types.logger.info("Permission response" + JSON.stringify(message));
1121
- const id = message.id;
1122
- const resolve = requests.get(id);
1123
- if (resolve) {
1124
- resolve({ approved: message.approved, reason: message.reason });
1125
- } else {
1126
- types.logger.info("Permission request stale, likely timed out");
1127
- return;
1128
- }
1129
- session.updateAgentState((currentState) => {
1130
- let r = { ...currentState.requests };
1131
- delete r[id];
1132
- return {
1133
- ...currentState,
1134
- requests: r
1135
- };
1136
- });
1137
- });
1138
- session.setHandler("abort", async () => {
1139
- types.logger.info("Abort request - interrupting Claude");
1140
- await interruptController.interrupt();
1141
- });
1348
+ registerHandlers(session, interruptController, { requests });
1142
1349
  const onAssistantResult = async (result) => {
1143
1350
  try {
1144
1351
  const summary = "result" in result && result.result ? result.result.substring(0, 100) + (result.result.length > 100 ? "..." : "") : "";
@@ -1176,12 +1383,32 @@ async function start(credentials, options = {}) {
1176
1383
  });
1177
1384
  clearInterval(pingInterval);
1178
1385
  if (antropicActivityProxy) {
1179
- types.logger.info("[AnthropicProxy] Shutting down activity monitoring proxy");
1386
+ types.logger.debug("[AnthropicProxy] Shutting down thinking activity monitoring proxy");
1180
1387
  antropicActivityProxy.cleanup();
1181
1388
  }
1182
1389
  process.exit(0);
1183
1390
  }
1184
1391
 
1392
+ const defaultSettings = {
1393
+ onboardingCompleted: false
1394
+ };
1395
+ async function readSettings() {
1396
+ if (!node_fs.existsSync(types.configuration.settingsFile)) {
1397
+ return { ...defaultSettings };
1398
+ }
1399
+ try {
1400
+ const content = await promises.readFile(types.configuration.settingsFile, "utf8");
1401
+ return JSON.parse(content);
1402
+ } catch {
1403
+ return { ...defaultSettings };
1404
+ }
1405
+ }
1406
+ async function writeSettings(settings) {
1407
+ if (!node_fs.existsSync(types.configuration.happyDir)) {
1408
+ await promises.mkdir(types.configuration.happyDir, { recursive: true });
1409
+ }
1410
+ await promises.writeFile(types.configuration.settingsFile, JSON.stringify(settings, null, 2));
1411
+ }
1185
1412
  const credentialsSchema = z__namespace.object({
1186
1413
  secret: z__namespace.string().base64(),
1187
1414
  token: z__namespace.string()
@@ -1283,6 +1510,355 @@ function decryptWithEphemeralKey(encryptedBundle, recipientSecretKey) {
1283
1510
  return decrypted;
1284
1511
  }
1285
1512
 
1513
+ class ApiDaemonSession extends node_events.EventEmitter {
1514
+ socket;
1515
+ machineIdentity;
1516
+ keepAliveInterval = null;
1517
+ token;
1518
+ secret;
1519
+ constructor(token, secret, machineIdentity) {
1520
+ super();
1521
+ this.token = token;
1522
+ this.secret = secret;
1523
+ this.machineIdentity = machineIdentity;
1524
+ const socket = socket_ioClient.io(types.configuration.serverUrl, {
1525
+ auth: {
1526
+ token: this.token,
1527
+ clientType: "machine-scoped",
1528
+ machineId: this.machineIdentity.machineId
1529
+ },
1530
+ path: "/v1/user-machine-daemon",
1531
+ reconnection: true,
1532
+ reconnectionAttempts: Infinity,
1533
+ reconnectionDelay: 1e3,
1534
+ reconnectionDelayMax: 5e3,
1535
+ transports: ["websocket"],
1536
+ withCredentials: true,
1537
+ autoConnect: false
1538
+ });
1539
+ socket.on("connect", () => {
1540
+ types.logger.debug("[DAEMON] Connected to server");
1541
+ this.emit("connected");
1542
+ socket.emit("machine-connect", {
1543
+ token: this.token,
1544
+ machineIdentity: types.encodeBase64(types.encrypt(this.machineIdentity, this.secret))
1545
+ });
1546
+ this.startKeepAlive();
1547
+ });
1548
+ socket.on("disconnect", () => {
1549
+ types.logger.debug("[DAEMON] Disconnected from server");
1550
+ this.emit("disconnected");
1551
+ this.stopKeepAlive();
1552
+ });
1553
+ socket.on("spawn-session", async (encryptedData, callback) => {
1554
+ let requestData;
1555
+ try {
1556
+ requestData = types.decrypt(types.decodeBase64(encryptedData), this.secret);
1557
+ types.logger.debug("[DAEMON] Received spawn-session request", requestData);
1558
+ const args = [
1559
+ "--directory",
1560
+ requestData.directory,
1561
+ "--happy-starting-mode",
1562
+ requestData.startingMode
1563
+ ];
1564
+ if (requestData.metadata) {
1565
+ args.push("--metadata", requestData.metadata);
1566
+ }
1567
+ if (requestData.startingMode === "interactive" && process.platform === "darwin") {
1568
+ const script = `
1569
+ tell application "Terminal"
1570
+ activate
1571
+ do script "cd ${requestData.directory} && happy ${args.join(" ")}"
1572
+ end tell
1573
+ `;
1574
+ child_process.spawn("osascript", ["-e", script], { detached: true });
1575
+ } else {
1576
+ const child = child_process.spawn("happy", args, {
1577
+ detached: true,
1578
+ stdio: "ignore",
1579
+ cwd: requestData.directory
1580
+ });
1581
+ child.unref();
1582
+ }
1583
+ const result = { success: true };
1584
+ socket.emit("session-spawn-result", {
1585
+ requestId: requestData.requestId,
1586
+ result: types.encodeBase64(types.encrypt(result, this.secret))
1587
+ });
1588
+ callback(types.encodeBase64(types.encrypt({ success: true }, this.secret)));
1589
+ } catch (error) {
1590
+ types.logger.debug("[DAEMON] Failed to spawn session", error);
1591
+ const errorResult = {
1592
+ success: false,
1593
+ error: error instanceof Error ? error.message : "Unknown error"
1594
+ };
1595
+ socket.emit("session-spawn-result", {
1596
+ requestId: requestData?.requestId || "",
1597
+ result: types.encodeBase64(types.encrypt(errorResult, this.secret))
1598
+ });
1599
+ callback(types.encodeBase64(types.encrypt(errorResult, this.secret)));
1600
+ }
1601
+ });
1602
+ socket.on("daemon-command", (data) => {
1603
+ switch (data.command) {
1604
+ case "shutdown":
1605
+ this.shutdown();
1606
+ break;
1607
+ case "status":
1608
+ this.emit("status-request");
1609
+ break;
1610
+ }
1611
+ });
1612
+ this.socket = socket;
1613
+ }
1614
+ startKeepAlive() {
1615
+ this.stopKeepAlive();
1616
+ this.keepAliveInterval = setInterval(() => {
1617
+ this.socket.volatile.emit("machine-alive", {
1618
+ time: Date.now()
1619
+ });
1620
+ }, 2e4);
1621
+ }
1622
+ stopKeepAlive() {
1623
+ if (this.keepAliveInterval) {
1624
+ clearInterval(this.keepAliveInterval);
1625
+ this.keepAliveInterval = null;
1626
+ }
1627
+ }
1628
+ connect() {
1629
+ this.socket.connect();
1630
+ }
1631
+ shutdown() {
1632
+ this.stopKeepAlive();
1633
+ this.socket.close();
1634
+ this.emit("shutdown");
1635
+ }
1636
+ }
1637
+
1638
+ const DAEMON_PID_FILE = path.join(os$1.homedir(), ".happy", "daemon-pid");
1639
+ async function startDaemon() {
1640
+ if (isDaemonRunning()) {
1641
+ console.log("Happy daemon is already running");
1642
+ process.exit(0);
1643
+ }
1644
+ types.logger.info("Happy CLI daemon started successfully");
1645
+ writePidFile();
1646
+ process.on("SIGINT", stopDaemon);
1647
+ process.on("SIGTERM", stopDaemon);
1648
+ process.on("exit", stopDaemon);
1649
+ try {
1650
+ const settings = await readSettings() || { onboardingCompleted: false };
1651
+ if (!settings.machineId) {
1652
+ settings.machineId = crypto.randomUUID();
1653
+ settings.machineHost = os$1.hostname();
1654
+ await writeSettings(settings);
1655
+ }
1656
+ const machineIdentity = {
1657
+ machineId: settings.machineId,
1658
+ machineHost: settings.machineHost || os$1.hostname(),
1659
+ platform: process.platform,
1660
+ version: process.env.npm_package_version || "unknown"
1661
+ };
1662
+ let credentials = await readCredentials();
1663
+ if (!credentials) {
1664
+ types.logger.debug("[DAEMON] No credentials found, running auth");
1665
+ await doAuth();
1666
+ credentials = await readCredentials();
1667
+ if (!credentials) {
1668
+ throw new Error("Failed to authenticate");
1669
+ }
1670
+ }
1671
+ const { token, secret } = credentials;
1672
+ const daemon = new ApiDaemonSession(token, secret, machineIdentity);
1673
+ daemon.on("connected", () => {
1674
+ types.logger.debug("[DAEMON] Successfully connected to server");
1675
+ });
1676
+ daemon.on("disconnected", () => {
1677
+ types.logger.debug("[DAEMON] Disconnected from server");
1678
+ });
1679
+ daemon.on("shutdown", () => {
1680
+ types.logger.debug("[DAEMON] Shutdown requested");
1681
+ stopDaemon();
1682
+ process.exit(0);
1683
+ });
1684
+ daemon.connect();
1685
+ setInterval(() => {
1686
+ }, 1e3);
1687
+ } catch (error) {
1688
+ types.logger.debug("[DAEMON] Failed to start daemon", error);
1689
+ stopDaemon();
1690
+ process.exit(1);
1691
+ }
1692
+ process.on("SIGINT", () => process.exit(0));
1693
+ process.on("SIGTERM", () => process.exit(0));
1694
+ process.on("exit", () => process.exit(0));
1695
+ while (true) {
1696
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
1697
+ }
1698
+ }
1699
+ function isDaemonRunning() {
1700
+ try {
1701
+ if (!fs.existsSync(DAEMON_PID_FILE)) {
1702
+ console.log("No PID file found");
1703
+ return false;
1704
+ }
1705
+ const pid = parseInt(fs.readFileSync(DAEMON_PID_FILE, "utf-8"));
1706
+ try {
1707
+ process.kill(pid, 0);
1708
+ return true;
1709
+ } catch (error) {
1710
+ console.log("Process not running", error);
1711
+ fs.unlinkSync(DAEMON_PID_FILE);
1712
+ return false;
1713
+ }
1714
+ } catch {
1715
+ return false;
1716
+ }
1717
+ }
1718
+ function writePidFile() {
1719
+ const happyDir = path.join(os$1.homedir(), ".happy");
1720
+ if (!fs.existsSync(happyDir)) {
1721
+ fs.mkdirSync(happyDir, { recursive: true });
1722
+ }
1723
+ fs.writeFileSync(DAEMON_PID_FILE, process.pid.toString());
1724
+ }
1725
+ function stopDaemon() {
1726
+ try {
1727
+ if (fs.existsSync(DAEMON_PID_FILE)) {
1728
+ types.logger.debug("[DAEMON] Stopping daemon");
1729
+ process.kill(parseInt(fs.readFileSync(DAEMON_PID_FILE, "utf-8")), "SIGTERM");
1730
+ fs.unlinkSync(DAEMON_PID_FILE);
1731
+ }
1732
+ } catch (error) {
1733
+ types.logger.debug("[DAEMON] Error cleaning up PID file", error);
1734
+ }
1735
+ }
1736
+
1737
+ function trimIdent(text) {
1738
+ const lines = text.split("\n");
1739
+ while (lines.length > 0 && lines[0].trim() === "") {
1740
+ lines.shift();
1741
+ }
1742
+ while (lines.length > 0 && lines[lines.length - 1].trim() === "") {
1743
+ lines.pop();
1744
+ }
1745
+ const minSpaces = lines.reduce((min, line) => {
1746
+ if (line.trim() === "") {
1747
+ return min;
1748
+ }
1749
+ const leadingSpaces = line.match(/^\s*/)[0].length;
1750
+ return Math.min(min, leadingSpaces);
1751
+ }, Infinity);
1752
+ const trimmedLines = lines.map((line) => line.slice(minSpaces));
1753
+ return trimmedLines.join("\n");
1754
+ }
1755
+
1756
+ const PLIST_LABEL$1 = "com.happy-cli.daemon";
1757
+ const PLIST_FILE$1 = `/Library/LaunchDaemons/${PLIST_LABEL$1}.plist`;
1758
+ const USER_HOME = process.env.HOME || process.env.USERPROFILE;
1759
+ async function install$1() {
1760
+ try {
1761
+ if (fs.existsSync(PLIST_FILE$1)) {
1762
+ types.logger.info("Daemon plist already exists. Uninstalling first...");
1763
+ child_process.execSync(`launchctl unload ${PLIST_FILE$1}`, { stdio: "inherit" });
1764
+ }
1765
+ const happyPath = process.argv[0];
1766
+ const scriptPath = process.argv[1];
1767
+ const plistContent = trimIdent(`
1768
+ <?xml version="1.0" encoding="UTF-8"?>
1769
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1770
+ <plist version="1.0">
1771
+ <dict>
1772
+ <key>Label</key>
1773
+ <string>${PLIST_LABEL$1}</string>
1774
+
1775
+ <key>ProgramArguments</key>
1776
+ <array>
1777
+ <string>${happyPath}</string>
1778
+ <string>${scriptPath}</string>
1779
+ <string>happy-daemon</string>
1780
+ </array>
1781
+
1782
+ <key>EnvironmentVariables</key>
1783
+ <dict>
1784
+ <key>HAPPY_DAEMON_MODE</key>
1785
+ <string>true</string>
1786
+ </dict>
1787
+
1788
+ <key>RunAtLoad</key>
1789
+ <true/>
1790
+
1791
+ <key>KeepAlive</key>
1792
+ <true/>
1793
+
1794
+ <key>StandardErrorPath</key>
1795
+ <string>${USER_HOME}/.happy/daemon.err</string>
1796
+
1797
+ <key>StandardOutPath</key>
1798
+ <string>${USER_HOME}/.happy/daemon.log</string>
1799
+
1800
+ <key>WorkingDirectory</key>
1801
+ <string>/tmp</string>
1802
+ </dict>
1803
+ </plist>
1804
+ `);
1805
+ fs.writeFileSync(PLIST_FILE$1, plistContent);
1806
+ fs.chmodSync(PLIST_FILE$1, 420);
1807
+ types.logger.info(`Created daemon plist at ${PLIST_FILE$1}`);
1808
+ child_process.execSync(`launchctl load ${PLIST_FILE$1}`, { stdio: "inherit" });
1809
+ types.logger.info("Daemon installed and started successfully");
1810
+ types.logger.info("Check logs at ~/.happy/daemon.log");
1811
+ } catch (error) {
1812
+ types.logger.debug("Failed to install daemon:", error);
1813
+ throw error;
1814
+ }
1815
+ }
1816
+
1817
+ async function install() {
1818
+ if (process.platform !== "darwin") {
1819
+ throw new Error("Daemon installation is currently only supported on macOS");
1820
+ }
1821
+ if (process.getuid && process.getuid() !== 0) {
1822
+ throw new Error("Daemon installation requires sudo privileges. Please run with sudo.");
1823
+ }
1824
+ types.logger.info("Installing Happy CLI daemon for macOS...");
1825
+ await install$1();
1826
+ }
1827
+
1828
+ const PLIST_LABEL = "com.happy-cli.daemon";
1829
+ const PLIST_FILE = `/Library/LaunchDaemons/${PLIST_LABEL}.plist`;
1830
+ async function uninstall$1() {
1831
+ try {
1832
+ if (!fs.existsSync(PLIST_FILE)) {
1833
+ types.logger.info("Daemon plist not found. Nothing to uninstall.");
1834
+ return;
1835
+ }
1836
+ try {
1837
+ child_process.execSync(`launchctl unload ${PLIST_FILE}`, { stdio: "inherit" });
1838
+ types.logger.info("Daemon stopped successfully");
1839
+ } catch (error) {
1840
+ types.logger.info("Failed to unload daemon (it might not be running)");
1841
+ }
1842
+ fs.unlinkSync(PLIST_FILE);
1843
+ types.logger.info(`Removed daemon plist from ${PLIST_FILE}`);
1844
+ types.logger.info("Daemon uninstalled successfully");
1845
+ } catch (error) {
1846
+ types.logger.debug("Failed to uninstall daemon:", error);
1847
+ throw error;
1848
+ }
1849
+ }
1850
+
1851
+ async function uninstall() {
1852
+ if (process.platform !== "darwin") {
1853
+ throw new Error("Daemon uninstallation is currently only supported on macOS");
1854
+ }
1855
+ if (process.getuid && process.getuid() !== 0) {
1856
+ throw new Error("Daemon uninstallation requires sudo privileges. Please run with sudo.");
1857
+ }
1858
+ types.logger.info("Uninstalling Happy CLI daemon for macOS...");
1859
+ await uninstall$1();
1860
+ }
1861
+
1286
1862
  (async () => {
1287
1863
  const args = process.argv.slice(2);
1288
1864
  let installationLocation = args.includes("--local") || process.env.HANDY_LOCAL ? "local" : "global";
@@ -1302,39 +1878,40 @@ function decryptWithEphemeralKey(encryptedBundle, recipientSecretKey) {
1302
1878
  }
1303
1879
  return;
1304
1880
  } else if (subcommand === "daemon") {
1305
- if (process.env.HAPPY_DAEMON_MODE) {
1306
- const { run } = await Promise.resolve().then(function () { return require('./run-q2To6b-c.cjs'); });
1307
- await run();
1881
+ const daemonSubcommand = args[1];
1882
+ if (daemonSubcommand === "start") {
1883
+ await startDaemon();
1884
+ process.exit(0);
1885
+ } else if (daemonSubcommand === "stop") {
1886
+ await stopDaemon();
1887
+ process.exit(0);
1888
+ } else if (daemonSubcommand === "install") {
1889
+ try {
1890
+ await install();
1891
+ } catch (error) {
1892
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
1893
+ process.exit(1);
1894
+ }
1895
+ } else if (daemonSubcommand === "uninstall") {
1896
+ try {
1897
+ await uninstall();
1898
+ } catch (error) {
1899
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
1900
+ process.exit(1);
1901
+ }
1308
1902
  } else {
1309
- const daemonSubcommand = args[1];
1310
- if (daemonSubcommand === "install") {
1311
- const { install } = await Promise.resolve().then(function () { return require('./install-B2r_gX72.cjs'); });
1312
- try {
1313
- await install();
1314
- } catch (error) {
1315
- console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
1316
- process.exit(1);
1317
- }
1318
- } else if (daemonSubcommand === "uninstall") {
1319
- const { uninstall } = await Promise.resolve().then(function () { return require('./uninstall-C42CoSCI.cjs'); });
1320
- try {
1321
- await uninstall();
1322
- } catch (error) {
1323
- console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
1324
- process.exit(1);
1325
- }
1326
- } else {
1327
- console.log(`
1903
+ console.log(`
1328
1904
  ${chalk.bold("happy daemon")} - Daemon management
1329
1905
 
1330
1906
  ${chalk.bold("Usage:")}
1907
+ happy daemon start Start the daemon
1908
+ happy daemon stop Stop the daemon
1331
1909
  sudo happy daemon install Install the daemon (requires sudo)
1332
1910
  sudo happy daemon uninstall Uninstall the daemon (requires sudo)
1333
1911
 
1334
1912
  ${chalk.bold("Note:")} The daemon runs in the background and provides persistent services.
1335
1913
  Currently only supported on macOS.
1336
1914
  `);
1337
- }
1338
1915
  }
1339
1916
  return;
1340
1917
  } else {
@@ -1370,7 +1947,6 @@ ${chalk.bold("happy")} - Claude Code session sharing
1370
1947
  ${chalk.bold("Usage:")}
1371
1948
  happy [options]
1372
1949
  happy logout Logs out of your account and removes data directory
1373
- happy daemon Manage the background daemon (macOS only)
1374
1950
 
1375
1951
  ${chalk.bold("Options:")}
1376
1952
  -h, --help Show this help message
@@ -1383,9 +1959,8 @@ ${chalk.bold("Options:")}
1383
1959
  --local < global | local >
1384
1960
  Will use .happy folder in the current directory for storing your private key and debug logs.
1385
1961
  You will require re-login each time you run this in a new directory.
1386
-
1387
- --happy-starting-mode <mode> Start in specified mode (interactive or remote)
1388
- Default: interactive
1962
+ --happy-starting-mode <interactive|remote>
1963
+ Set the starting mode for new sessions (default: remote)
1389
1964
 
1390
1965
  ${chalk.bold("Examples:")}
1391
1966
  happy Start a session with default settings