happy-coder 0.10.0-0 → 0.10.0-1

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.
@@ -12,8 +12,15 @@ var node_crypto = require('node:crypto');
12
12
  var tweetnacl = require('tweetnacl');
13
13
  var node_events = require('node:events');
14
14
  var socket_ioClient = require('socket.io-client');
15
+ var child_process = require('child_process');
16
+ var util = require('util');
17
+ var fs$1 = require('fs/promises');
18
+ var crypto = require('crypto');
19
+ var path = require('path');
20
+ var url = require('url');
15
21
  var expoServerSdk = require('expo-server-sdk');
16
22
 
23
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
17
24
  function _interopNamespaceDefault(e) {
18
25
  var n = Object.create(null);
19
26
  if (e) {
@@ -34,7 +41,7 @@ function _interopNamespaceDefault(e) {
34
41
  var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
35
42
 
36
43
  var name = "happy-coder";
37
- var version = "0.10.0-0";
44
+ var version = "0.10.0-1";
38
45
  var description = "Claude Code session sharing CLI";
39
46
  var author = "Kirill Dubovitskiy";
40
47
  var license = "MIT";
@@ -97,6 +104,8 @@ var dependencies = {
97
104
  "@types/http-proxy": "^1.17.16",
98
105
  "@types/qrcode-terminal": "^0.12.2",
99
106
  "@types/react": "^19.1.9",
107
+ "@types/ps-list": "^6.2.1",
108
+ "@types/cross-spawn": "^6.0.6",
100
109
  axios: "^1.10.0",
101
110
  chalk: "^5.4.1",
102
111
  "cross-spawn": "^7.0.6",
@@ -116,9 +125,7 @@ var dependencies = {
116
125
  };
117
126
  var devDependencies = {
118
127
  "@eslint/compat": "^1",
119
- "@types/cross-spawn": "^6.0.6",
120
128
  "@types/node": ">=20",
121
- "@types/ps-list": "^6.2.1",
122
129
  "cross-env": "^10.0.0",
123
130
  dotenv: "^16.6.1",
124
131
  eslint: "^9",
@@ -856,6 +863,340 @@ class AsyncLock {
856
863
  }
857
864
  }
858
865
 
866
+ class RpcHandlerManager {
867
+ handlers = /* @__PURE__ */ new Map();
868
+ scopePrefix;
869
+ secret;
870
+ logger;
871
+ socket = null;
872
+ constructor(config) {
873
+ this.scopePrefix = config.scopePrefix;
874
+ this.secret = config.secret;
875
+ this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
876
+ }
877
+ /**
878
+ * Register an RPC handler for a specific method
879
+ * @param method - The method name (without prefix)
880
+ * @param handler - The handler function
881
+ */
882
+ registerHandler(method, handler) {
883
+ const prefixedMethod = this.getPrefixedMethod(method);
884
+ this.handlers.set(prefixedMethod, handler);
885
+ if (this.socket) {
886
+ this.socket.emit("rpc-register", { method: prefixedMethod });
887
+ }
888
+ }
889
+ /**
890
+ * Handle an incoming RPC request
891
+ * @param request - The RPC request data
892
+ * @param callback - The response callback
893
+ */
894
+ async handleRequest(request) {
895
+ try {
896
+ const handler = this.handlers.get(request.method);
897
+ if (!handler) {
898
+ this.logger("[RPC] [ERROR] Method not found", { method: request.method });
899
+ const errorResponse = { error: "Method not found" };
900
+ const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
901
+ return encryptedError;
902
+ }
903
+ const decryptedParams = decrypt(decodeBase64(request.params), this.secret);
904
+ const result = await handler(decryptedParams);
905
+ const encryptedResponse = encodeBase64(encrypt(result, this.secret));
906
+ return encryptedResponse;
907
+ } catch (error) {
908
+ this.logger("[RPC] [ERROR] Error handling request", { error });
909
+ const errorResponse = {
910
+ error: error instanceof Error ? error.message : "Unknown error"
911
+ };
912
+ const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
913
+ return encryptedError;
914
+ }
915
+ }
916
+ onSocketConnect(socket) {
917
+ this.socket = socket;
918
+ for (const [prefixedMethod] of this.handlers) {
919
+ socket.emit("rpc-register", { method: prefixedMethod });
920
+ }
921
+ }
922
+ onSocketDisconnect() {
923
+ this.socket = null;
924
+ }
925
+ /**
926
+ * Get the number of registered handlers
927
+ */
928
+ getHandlerCount() {
929
+ return this.handlers.size;
930
+ }
931
+ /**
932
+ * Check if a handler is registered
933
+ * @param method - The method name (without prefix)
934
+ */
935
+ hasHandler(method) {
936
+ const prefixedMethod = this.getPrefixedMethod(method);
937
+ return this.handlers.has(prefixedMethod);
938
+ }
939
+ /**
940
+ * Clear all handlers
941
+ */
942
+ clearHandlers() {
943
+ this.handlers.clear();
944
+ this.logger("Cleared all RPC handlers");
945
+ }
946
+ /**
947
+ * Get the prefixed method name
948
+ * @param method - The method name
949
+ */
950
+ getPrefixedMethod(method) {
951
+ return `${this.scopePrefix}:${method}`;
952
+ }
953
+ }
954
+
955
+ const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-D9P2bndj.cjs', document.baseURI).href))));
956
+ function projectPath() {
957
+ const path$1 = path.resolve(__dirname$1, "..");
958
+ return path$1;
959
+ }
960
+
961
+ function run(args, options) {
962
+ const RUNNER_PATH = path.resolve(path.join(projectPath(), "scripts", "ripgrep_launcher.cjs"));
963
+ return new Promise((resolve2, reject) => {
964
+ const child = child_process.spawn("node", [RUNNER_PATH, JSON.stringify(args)], {
965
+ stdio: ["pipe", "pipe", "pipe"],
966
+ cwd: options?.cwd
967
+ });
968
+ let stdout = "";
969
+ let stderr = "";
970
+ child.stdout.on("data", (data) => {
971
+ stdout += data.toString();
972
+ });
973
+ child.stderr.on("data", (data) => {
974
+ stderr += data.toString();
975
+ });
976
+ child.on("close", (code) => {
977
+ resolve2({
978
+ exitCode: code || 0,
979
+ stdout,
980
+ stderr
981
+ });
982
+ });
983
+ child.on("error", (err) => {
984
+ reject(err);
985
+ });
986
+ });
987
+ }
988
+
989
+ const execAsync = util.promisify(child_process.exec);
990
+ function registerCommonHandlers(rpcHandlerManager) {
991
+ rpcHandlerManager.registerHandler("bash", async (data) => {
992
+ logger.debug("Shell command request:", data.command);
993
+ try {
994
+ const options = {
995
+ cwd: data.cwd,
996
+ timeout: data.timeout || 3e4
997
+ // Default 30 seconds timeout
998
+ };
999
+ const { stdout, stderr } = await execAsync(data.command, options);
1000
+ return {
1001
+ success: true,
1002
+ stdout: stdout ? stdout.toString() : "",
1003
+ stderr: stderr ? stderr.toString() : "",
1004
+ exitCode: 0
1005
+ };
1006
+ } catch (error) {
1007
+ const execError = error;
1008
+ if (execError.code === "ETIMEDOUT" || execError.killed) {
1009
+ return {
1010
+ success: false,
1011
+ stdout: execError.stdout || "",
1012
+ stderr: execError.stderr || "",
1013
+ exitCode: typeof execError.code === "number" ? execError.code : -1,
1014
+ error: "Command timed out"
1015
+ };
1016
+ }
1017
+ return {
1018
+ success: false,
1019
+ stdout: execError.stdout ? execError.stdout.toString() : "",
1020
+ stderr: execError.stderr ? execError.stderr.toString() : execError.message || "Command failed",
1021
+ exitCode: typeof execError.code === "number" ? execError.code : 1,
1022
+ error: execError.message || "Command failed"
1023
+ };
1024
+ }
1025
+ });
1026
+ rpcHandlerManager.registerHandler("readFile", async (data) => {
1027
+ logger.debug("Read file request:", data.path);
1028
+ try {
1029
+ const buffer = await fs$1.readFile(data.path);
1030
+ const content = buffer.toString("base64");
1031
+ return { success: true, content };
1032
+ } catch (error) {
1033
+ logger.debug("Failed to read file:", error);
1034
+ return { success: false, error: error instanceof Error ? error.message : "Failed to read file" };
1035
+ }
1036
+ });
1037
+ rpcHandlerManager.registerHandler("writeFile", async (data) => {
1038
+ logger.debug("Write file request:", data.path);
1039
+ try {
1040
+ if (data.expectedHash !== null && data.expectedHash !== void 0) {
1041
+ try {
1042
+ const existingBuffer = await fs$1.readFile(data.path);
1043
+ const existingHash = crypto.createHash("sha256").update(existingBuffer).digest("hex");
1044
+ if (existingHash !== data.expectedHash) {
1045
+ return {
1046
+ success: false,
1047
+ error: `File hash mismatch. Expected: ${data.expectedHash}, Actual: ${existingHash}`
1048
+ };
1049
+ }
1050
+ } catch (error) {
1051
+ const nodeError = error;
1052
+ if (nodeError.code !== "ENOENT") {
1053
+ throw error;
1054
+ }
1055
+ return {
1056
+ success: false,
1057
+ error: "File does not exist but hash was provided"
1058
+ };
1059
+ }
1060
+ } else {
1061
+ try {
1062
+ await fs$1.stat(data.path);
1063
+ return {
1064
+ success: false,
1065
+ error: "File already exists but was expected to be new"
1066
+ };
1067
+ } catch (error) {
1068
+ const nodeError = error;
1069
+ if (nodeError.code !== "ENOENT") {
1070
+ throw error;
1071
+ }
1072
+ }
1073
+ }
1074
+ const buffer = Buffer.from(data.content, "base64");
1075
+ await fs$1.writeFile(data.path, buffer);
1076
+ const hash = crypto.createHash("sha256").update(buffer).digest("hex");
1077
+ return { success: true, hash };
1078
+ } catch (error) {
1079
+ logger.debug("Failed to write file:", error);
1080
+ return { success: false, error: error instanceof Error ? error.message : "Failed to write file" };
1081
+ }
1082
+ });
1083
+ rpcHandlerManager.registerHandler("listDirectory", async (data) => {
1084
+ logger.debug("List directory request:", data.path);
1085
+ try {
1086
+ const entries = await fs$1.readdir(data.path, { withFileTypes: true });
1087
+ const directoryEntries = await Promise.all(
1088
+ entries.map(async (entry) => {
1089
+ const fullPath = path.join(data.path, entry.name);
1090
+ let type = "other";
1091
+ let size;
1092
+ let modified;
1093
+ if (entry.isDirectory()) {
1094
+ type = "directory";
1095
+ } else if (entry.isFile()) {
1096
+ type = "file";
1097
+ }
1098
+ try {
1099
+ const stats = await fs$1.stat(fullPath);
1100
+ size = stats.size;
1101
+ modified = stats.mtime.getTime();
1102
+ } catch (error) {
1103
+ logger.debug(`Failed to stat ${fullPath}:`, error);
1104
+ }
1105
+ return {
1106
+ name: entry.name,
1107
+ type,
1108
+ size,
1109
+ modified
1110
+ };
1111
+ })
1112
+ );
1113
+ directoryEntries.sort((a, b) => {
1114
+ if (a.type === "directory" && b.type !== "directory") return -1;
1115
+ if (a.type !== "directory" && b.type === "directory") return 1;
1116
+ return a.name.localeCompare(b.name);
1117
+ });
1118
+ return { success: true, entries: directoryEntries };
1119
+ } catch (error) {
1120
+ logger.debug("Failed to list directory:", error);
1121
+ return { success: false, error: error instanceof Error ? error.message : "Failed to list directory" };
1122
+ }
1123
+ });
1124
+ rpcHandlerManager.registerHandler("getDirectoryTree", async (data) => {
1125
+ logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
1126
+ async function buildTree(path$1, name, currentDepth) {
1127
+ try {
1128
+ const stats = await fs$1.stat(path$1);
1129
+ const node = {
1130
+ name,
1131
+ path: path$1,
1132
+ type: stats.isDirectory() ? "directory" : "file",
1133
+ size: stats.size,
1134
+ modified: stats.mtime.getTime()
1135
+ };
1136
+ if (stats.isDirectory() && currentDepth < data.maxDepth) {
1137
+ const entries = await fs$1.readdir(path$1, { withFileTypes: true });
1138
+ const children = [];
1139
+ await Promise.all(
1140
+ entries.map(async (entry) => {
1141
+ if (entry.isSymbolicLink()) {
1142
+ logger.debug(`Skipping symlink: ${path.join(path$1, entry.name)}`);
1143
+ return;
1144
+ }
1145
+ const childPath = path.join(path$1, entry.name);
1146
+ const childNode = await buildTree(childPath, entry.name, currentDepth + 1);
1147
+ if (childNode) {
1148
+ children.push(childNode);
1149
+ }
1150
+ })
1151
+ );
1152
+ children.sort((a, b) => {
1153
+ if (a.type === "directory" && b.type !== "directory") return -1;
1154
+ if (a.type !== "directory" && b.type === "directory") return 1;
1155
+ return a.name.localeCompare(b.name);
1156
+ });
1157
+ node.children = children;
1158
+ }
1159
+ return node;
1160
+ } catch (error) {
1161
+ logger.debug(`Failed to process ${path$1}:`, error instanceof Error ? error.message : String(error));
1162
+ return null;
1163
+ }
1164
+ }
1165
+ try {
1166
+ if (data.maxDepth < 0) {
1167
+ return { success: false, error: "maxDepth must be non-negative" };
1168
+ }
1169
+ const baseName = data.path === "/" ? "/" : data.path.split("/").pop() || data.path;
1170
+ const tree = await buildTree(data.path, baseName, 0);
1171
+ if (!tree) {
1172
+ return { success: false, error: "Failed to access the specified path" };
1173
+ }
1174
+ return { success: true, tree };
1175
+ } catch (error) {
1176
+ logger.debug("Failed to get directory tree:", error);
1177
+ return { success: false, error: error instanceof Error ? error.message : "Failed to get directory tree" };
1178
+ }
1179
+ });
1180
+ rpcHandlerManager.registerHandler("ripgrep", async (data) => {
1181
+ logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
1182
+ try {
1183
+ const result = await run(data.args, { cwd: data.cwd });
1184
+ return {
1185
+ success: true,
1186
+ exitCode: result.exitCode,
1187
+ stdout: result.stdout.toString(),
1188
+ stderr: result.stderr.toString()
1189
+ };
1190
+ } catch (error) {
1191
+ logger.debug("Failed to run ripgrep:", error);
1192
+ return {
1193
+ success: false,
1194
+ error: error instanceof Error ? error.message : "Failed to run ripgrep"
1195
+ };
1196
+ }
1197
+ });
1198
+ }
1199
+
859
1200
  class ApiSessionClient extends node_events.EventEmitter {
860
1201
  token;
861
1202
  secret;
@@ -867,7 +1208,7 @@ class ApiSessionClient extends node_events.EventEmitter {
867
1208
  socket;
868
1209
  pendingMessages = [];
869
1210
  pendingMessageCallback = null;
870
- rpcHandlers = /* @__PURE__ */ new Map();
1211
+ rpcHandlerManager;
871
1212
  agentStateLock = new AsyncLock();
872
1213
  metadataLock = new AsyncLock();
873
1214
  constructor(token, secret, session) {
@@ -879,6 +1220,12 @@ class ApiSessionClient extends node_events.EventEmitter {
879
1220
  this.metadataVersion = session.metadataVersion;
880
1221
  this.agentState = session.agentState;
881
1222
  this.agentStateVersion = session.agentStateVersion;
1223
+ this.rpcHandlerManager = new RpcHandlerManager({
1224
+ scopePrefix: this.sessionId,
1225
+ secret: this.secret,
1226
+ logger: (msg, data) => logger.debug(msg, data)
1227
+ });
1228
+ registerCommonHandlers(this.rpcHandlerManager);
882
1229
  this.socket = socket_ioClient.io(configuration.serverUrl, {
883
1230
  auth: {
884
1231
  token: this.token,
@@ -896,35 +1243,18 @@ class ApiSessionClient extends node_events.EventEmitter {
896
1243
  });
897
1244
  this.socket.on("connect", () => {
898
1245
  logger.debug("Socket connected successfully");
899
- this.reregisterHandlers();
1246
+ this.rpcHandlerManager.onSocketConnect(this.socket);
900
1247
  });
901
1248
  this.socket.on("rpc-request", async (data, callback) => {
902
- try {
903
- const method = data.method;
904
- const handler = this.rpcHandlers.get(method);
905
- if (!handler) {
906
- logger.debug("[SOCKET] [RPC] [ERROR] method not found", { method });
907
- const errorResponse = { error: "Method not found" };
908
- const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
909
- callback(encryptedError);
910
- return;
911
- }
912
- const decryptedParams = decrypt(decodeBase64(data.params), this.secret);
913
- const result = await handler(decryptedParams);
914
- const encryptedResponse = encodeBase64(encrypt(result, this.secret));
915
- callback(encryptedResponse);
916
- } catch (error) {
917
- logger.debug("[SOCKET] [RPC] [ERROR] Error handling RPC request", { error });
918
- const errorResponse = { error: error instanceof Error ? error.message : "Unknown error" };
919
- const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
920
- callback(encryptedError);
921
- }
1249
+ callback(await this.rpcHandlerManager.handleRequest(data));
922
1250
  });
923
1251
  this.socket.on("disconnect", (reason) => {
924
1252
  logger.debug("[API] Socket disconnected:", reason);
1253
+ this.rpcHandlerManager.onSocketDisconnect();
925
1254
  });
926
1255
  this.socket.on("connect_error", (error) => {
927
1256
  logger.debug("[API] Socket connection error:", error);
1257
+ this.rpcHandlerManager.onSocketDisconnect();
928
1258
  });
929
1259
  this.socket.on("update", (data) => {
930
1260
  try {
@@ -1135,29 +1465,6 @@ class ApiSessionClient extends node_events.EventEmitter {
1135
1465
  });
1136
1466
  });
1137
1467
  }
1138
- /**
1139
- * Set a custom RPC handler for a specific method with encrypted arguments and responses
1140
- * @param method - The method name to handle
1141
- * @param handler - The handler function to call when the method is invoked
1142
- */
1143
- setHandler(method, handler) {
1144
- const prefixedMethod = `${this.sessionId}:${method}`;
1145
- this.rpcHandlers.set(prefixedMethod, handler);
1146
- this.socket.emit("rpc-register", { method: prefixedMethod });
1147
- logger.debug("Registered RPC handler", { method, prefixedMethod });
1148
- }
1149
- /**
1150
- * Re-register all RPC handlers after reconnection
1151
- */
1152
- reregisterHandlers() {
1153
- logger.debug("Re-registering RPC handlers after reconnection", {
1154
- totalMethods: this.rpcHandlers.size
1155
- });
1156
- for (const [prefixedMethod] of this.rpcHandlers) {
1157
- this.socket.emit("rpc-register", { method: prefixedMethod });
1158
- logger.debug("Re-registered method", { prefixedMethod });
1159
- }
1160
- }
1161
1468
  /**
1162
1469
  * Wait for socket buffer to flush
1163
1470
  */
@@ -1184,21 +1491,58 @@ class ApiMachineClient {
1184
1491
  this.token = token;
1185
1492
  this.secret = secret;
1186
1493
  this.machine = machine;
1494
+ this.rpcHandlerManager = new RpcHandlerManager({
1495
+ scopePrefix: this.machine.id,
1496
+ secret: this.secret,
1497
+ logger: (msg, data) => logger.debug(msg, data)
1498
+ });
1499
+ registerCommonHandlers(this.rpcHandlerManager);
1187
1500
  }
1188
1501
  socket;
1189
1502
  keepAliveInterval = null;
1190
- // RPC handlers
1191
- spawnSession;
1192
- stopSession;
1193
- requestShutdown;
1503
+ rpcHandlerManager;
1194
1504
  setRPCHandlers({
1195
1505
  spawnSession,
1196
1506
  stopSession,
1197
1507
  requestShutdown
1198
1508
  }) {
1199
- this.spawnSession = spawnSession;
1200
- this.stopSession = stopSession;
1201
- this.requestShutdown = requestShutdown;
1509
+ this.rpcHandlerManager.registerHandler("spawn-happy-session", async (params) => {
1510
+ const { directory, sessionId, machineId, approvedNewDirectoryCreation } = params || {};
1511
+ if (!directory) {
1512
+ throw new Error("Directory is required");
1513
+ }
1514
+ const result = await spawnSession({ directory, sessionId, machineId, approvedNewDirectoryCreation });
1515
+ switch (result.type) {
1516
+ case "success":
1517
+ logger.debug(`[API MACHINE] Spawned session ${result.sessionId}`);
1518
+ return { type: "success", sessionId: result.sessionId };
1519
+ case "requestToApproveDirectoryCreation":
1520
+ logger.debug(`[API MACHINE] Requesting directory creation approval for: ${result.directory}`);
1521
+ return { type: "requestToApproveDirectoryCreation", directory: result.directory };
1522
+ case "error":
1523
+ throw new Error(result.errorMessage);
1524
+ }
1525
+ });
1526
+ this.rpcHandlerManager.registerHandler("stop-session", (params) => {
1527
+ const { sessionId } = params || {};
1528
+ if (!sessionId) {
1529
+ throw new Error("Session ID is required");
1530
+ }
1531
+ const success = stopSession(sessionId);
1532
+ if (!success) {
1533
+ throw new Error("Session not found or failed to stop");
1534
+ }
1535
+ logger.debug(`[API MACHINE] Stopped session ${sessionId}`);
1536
+ return { message: "Session stopped" };
1537
+ });
1538
+ this.rpcHandlerManager.registerHandler("stop-daemon", () => {
1539
+ logger.debug("[API MACHINE] Received stop-daemon RPC request");
1540
+ setTimeout(() => {
1541
+ logger.debug("[API MACHINE] Initiating daemon shutdown from RPC");
1542
+ requestShutdown();
1543
+ }, 100);
1544
+ return { message: "Daemon stop request acknowledged, starting shutdown sequence..." };
1545
+ });
1202
1546
  }
1203
1547
  /**
1204
1548
  * Update machine metadata
@@ -1266,9 +1610,6 @@ class ApiMachineClient {
1266
1610
  reconnectionDelay: 1e3,
1267
1611
  reconnectionDelayMax: 5e3
1268
1612
  });
1269
- const spawnMethod = `${this.machine.id}:spawn-happy-session`;
1270
- const stopMethod = `${this.machine.id}:stop-session`;
1271
- const stopDaemonMethod = `${this.machine.id}:stop-daemon`;
1272
1613
  this.socket.on("connect", () => {
1273
1614
  logger.debug("[API MACHINE] Connected to server");
1274
1615
  this.updateDaemonState((state) => ({
@@ -1278,86 +1619,17 @@ class ApiMachineClient {
1278
1619
  httpPort: this.machine.daemonState?.httpPort,
1279
1620
  startedAt: Date.now()
1280
1621
  }));
1281
- this.socket.emit("rpc-register", { method: spawnMethod });
1282
- this.socket.emit("rpc-register", { method: stopMethod });
1283
- this.socket.emit("rpc-register", { method: stopDaemonMethod });
1284
- logger.debug(`[API MACHINE] Registered RPC methods: ${spawnMethod}, ${stopMethod}, ${stopDaemonMethod}`);
1622
+ this.rpcHandlerManager.onSocketConnect(this.socket);
1285
1623
  this.startKeepAlive();
1286
1624
  });
1625
+ this.socket.on("disconnect", () => {
1626
+ logger.debug("[API MACHINE] Disconnected from server");
1627
+ this.rpcHandlerManager.onSocketDisconnect();
1628
+ this.stopKeepAlive();
1629
+ });
1287
1630
  this.socket.on("rpc-request", async (data, callback) => {
1288
1631
  logger.debugLargeJson(`[API MACHINE] Received RPC request:`, data);
1289
- try {
1290
- if (data.method === spawnMethod) {
1291
- if (!this.spawnSession) {
1292
- throw new Error("Spawn session handler not set");
1293
- }
1294
- const { directory, sessionId, machineId, approvedNewDirectoryCreation } = decrypt(decodeBase64(data.params), this.secret) || {};
1295
- if (!directory) {
1296
- throw new Error("Directory is required");
1297
- }
1298
- const result = await this.spawnSession({ directory, sessionId, machineId, approvedNewDirectoryCreation });
1299
- switch (result.type) {
1300
- case "success": {
1301
- logger.debug(`[API MACHINE] Spawned session ${result.sessionId}`);
1302
- const response = {
1303
- type: "success",
1304
- sessionId: result.sessionId
1305
- };
1306
- logger.debug(`[API MACHINE] Sending RPC response:`, response);
1307
- callback(encodeBase64(encrypt(response, this.secret)));
1308
- return;
1309
- }
1310
- case "requestToApproveDirectoryCreation":
1311
- const promptResponse = {
1312
- type: "requestToApproveDirectoryCreation",
1313
- directory: result.directory
1314
- };
1315
- logger.debug(`[API MACHINE] Requesting directory creation approval for: ${result.directory}`);
1316
- callback(encodeBase64(encrypt(promptResponse, this.secret)));
1317
- return;
1318
- case "error":
1319
- throw new Error(result.errorMessage);
1320
- }
1321
- }
1322
- if (data.method === stopMethod) {
1323
- logger.debug("[API MACHINE] Received stop-session RPC request");
1324
- const decryptedParams = decrypt(decodeBase64(data.params), this.secret);
1325
- const { sessionId } = decryptedParams || {};
1326
- if (!this.stopSession) {
1327
- throw new Error("Stop session handler not set");
1328
- }
1329
- if (!sessionId) {
1330
- throw new Error("Session ID is required");
1331
- }
1332
- const success = this.stopSession(sessionId);
1333
- if (!success) {
1334
- throw new Error("Session not found or failed to stop");
1335
- }
1336
- logger.debug(`[API MACHINE] Stopped session ${sessionId}`);
1337
- const response = { message: "Session stopped" };
1338
- const encryptedResponse = encodeBase64(encrypt(response, this.secret));
1339
- callback(encryptedResponse);
1340
- return;
1341
- }
1342
- if (data.method === stopDaemonMethod) {
1343
- logger.debug("[API MACHINE] Received stop-daemon RPC request");
1344
- callback(encodeBase64(encrypt({
1345
- message: "Daemon stop request acknowledged, starting shutdown sequence..."
1346
- }, this.secret)));
1347
- setTimeout(() => {
1348
- logger.debug("[API MACHINE] Initiating daemon shutdown from RPC");
1349
- if (this.requestShutdown) {
1350
- this.requestShutdown();
1351
- }
1352
- }, 100);
1353
- return;
1354
- }
1355
- throw new Error(`Unknown RPC method: ${data.method}`);
1356
- } catch (error) {
1357
- logger.debug(`[API MACHINE] RPC handler failed:`, error.message || error);
1358
- logger.debug(`[API MACHINE] Error stack:`, error.stack);
1359
- callback(encodeBase64(encrypt({ error: error.message || String(error) }, this.secret)));
1360
- }
1632
+ callback(await this.rpcHandlerManager.handleRequest(data));
1361
1633
  });
1362
1634
  this.socket.on("update", (data) => {
1363
1635
  if (data.body.t === "update-machine" && data.body.machineId === this.machine.id) {
@@ -1376,16 +1648,6 @@ class ApiMachineClient {
1376
1648
  logger.debug(`[API MACHINE] Received unknown update type: ${data.body.t}`);
1377
1649
  }
1378
1650
  });
1379
- this.socket.on("disconnect", () => {
1380
- logger.debug("[API MACHINE] Disconnected from server");
1381
- this.stopKeepAlive();
1382
- });
1383
- this.socket.io.on("reconnect", () => {
1384
- logger.debug("[API MACHINE] Reconnected to server");
1385
- this.socket.emit("rpc-register", { method: spawnMethod });
1386
- this.socket.emit("rpc-register", { method: stopMethod });
1387
- this.socket.emit("rpc-register", { method: stopDaemonMethod });
1388
- });
1389
1651
  this.socket.on("connect_error", (error) => {
1390
1652
  logger.debug(`[API MACHINE] Connection error: ${error.message}`);
1391
1653
  });
@@ -1632,7 +1894,7 @@ class ApiClient {
1632
1894
  * Register or update machine with the server
1633
1895
  * Returns the current machine state from the server with decrypted metadata and daemonState
1634
1896
  */
1635
- async createMachineOrGetExistingAsIs(opts) {
1897
+ async getOrCreateMachine(opts) {
1636
1898
  const response = await axios.post(
1637
1899
  `${configuration.serverUrl}/v1/machines`,
1638
1900
  {
@@ -1772,6 +2034,7 @@ exports.encodeBase64Url = encodeBase64Url;
1772
2034
  exports.getLatestDaemonLog = getLatestDaemonLog;
1773
2035
  exports.logger = logger;
1774
2036
  exports.packageJson = packageJson;
2037
+ exports.projectPath = projectPath;
1775
2038
  exports.readCredentials = readCredentials;
1776
2039
  exports.readDaemonState = readDaemonState;
1777
2040
  exports.readSettings = readSettings;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-coder",
3
- "version": "0.10.0-0",
3
+ "version": "0.10.0-1",
4
4
  "description": "Claude Code session sharing CLI",
5
5
  "author": "Kirill Dubovitskiy",
6
6
  "license": "MIT",
@@ -63,6 +63,8 @@
63
63
  "@types/http-proxy": "^1.17.16",
64
64
  "@types/qrcode-terminal": "^0.12.2",
65
65
  "@types/react": "^19.1.9",
66
+ "@types/ps-list": "^6.2.1",
67
+ "@types/cross-spawn": "^6.0.6",
66
68
  "axios": "^1.10.0",
67
69
  "chalk": "^5.4.1",
68
70
  "cross-spawn": "^7.0.6",
@@ -82,9 +84,7 @@
82
84
  },
83
85
  "devDependencies": {
84
86
  "@eslint/compat": "^1",
85
- "@types/cross-spawn": "^6.0.6",
86
87
  "@types/node": ">=20",
87
- "@types/ps-list": "^6.2.1",
88
88
  "cross-env": "^10.0.0",
89
89
  "dotenv": "^16.6.1",
90
90
  "eslint": "^9",