happy-coder 0.10.0-0 → 0.10.0-2

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.
@@ -11,10 +11,16 @@ import { randomBytes, randomUUID } from 'node:crypto';
11
11
  import tweetnacl from 'tweetnacl';
12
12
  import { EventEmitter } from 'node:events';
13
13
  import { io } from 'socket.io-client';
14
+ import { spawn, exec } from 'child_process';
15
+ import { promisify } from 'util';
16
+ import { readFile as readFile$1, stat as stat$1, writeFile as writeFile$1, readdir } from 'fs/promises';
17
+ import { createHash } from 'crypto';
18
+ import { dirname, resolve, join as join$1 } from 'path';
19
+ import { fileURLToPath } from 'url';
14
20
  import { Expo } from 'expo-server-sdk';
15
21
 
16
22
  var name = "happy-coder";
17
- var version = "0.10.0-0";
23
+ var version = "0.10.0-2";
18
24
  var description = "Claude Code session sharing CLI";
19
25
  var author = "Kirill Dubovitskiy";
20
26
  var license = "MIT";
@@ -70,11 +76,13 @@ var scripts = {
70
76
  release: "release-it"
71
77
  };
72
78
  var dependencies = {
73
- "@anthropic-ai/claude-code": "^1.0.89",
79
+ "@anthropic-ai/claude-code": "^1.0.102",
74
80
  "@anthropic-ai/sdk": "^0.56.0",
75
81
  "@modelcontextprotocol/sdk": "^1.15.1",
76
82
  "@stablelib/base64": "^2.0.1",
83
+ "@types/cross-spawn": "^6.0.6",
77
84
  "@types/http-proxy": "^1.17.16",
85
+ "@types/ps-list": "^6.2.1",
78
86
  "@types/qrcode-terminal": "^0.12.2",
79
87
  "@types/react": "^19.1.9",
80
88
  axios: "^1.10.0",
@@ -96,9 +104,7 @@ var dependencies = {
96
104
  };
97
105
  var devDependencies = {
98
106
  "@eslint/compat": "^1",
99
- "@types/cross-spawn": "^6.0.6",
100
107
  "@types/node": ">=20",
101
- "@types/ps-list": "^6.2.1",
102
108
  "cross-env": "^10.0.0",
103
109
  dotenv: "^16.6.1",
104
110
  eslint: "^9",
@@ -836,6 +842,340 @@ class AsyncLock {
836
842
  }
837
843
  }
838
844
 
845
+ class RpcHandlerManager {
846
+ handlers = /* @__PURE__ */ new Map();
847
+ scopePrefix;
848
+ secret;
849
+ logger;
850
+ socket = null;
851
+ constructor(config) {
852
+ this.scopePrefix = config.scopePrefix;
853
+ this.secret = config.secret;
854
+ this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
855
+ }
856
+ /**
857
+ * Register an RPC handler for a specific method
858
+ * @param method - The method name (without prefix)
859
+ * @param handler - The handler function
860
+ */
861
+ registerHandler(method, handler) {
862
+ const prefixedMethod = this.getPrefixedMethod(method);
863
+ this.handlers.set(prefixedMethod, handler);
864
+ if (this.socket) {
865
+ this.socket.emit("rpc-register", { method: prefixedMethod });
866
+ }
867
+ }
868
+ /**
869
+ * Handle an incoming RPC request
870
+ * @param request - The RPC request data
871
+ * @param callback - The response callback
872
+ */
873
+ async handleRequest(request) {
874
+ try {
875
+ const handler = this.handlers.get(request.method);
876
+ if (!handler) {
877
+ this.logger("[RPC] [ERROR] Method not found", { method: request.method });
878
+ const errorResponse = { error: "Method not found" };
879
+ const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
880
+ return encryptedError;
881
+ }
882
+ const decryptedParams = decrypt(decodeBase64(request.params), this.secret);
883
+ const result = await handler(decryptedParams);
884
+ const encryptedResponse = encodeBase64(encrypt(result, this.secret));
885
+ return encryptedResponse;
886
+ } catch (error) {
887
+ this.logger("[RPC] [ERROR] Error handling request", { error });
888
+ const errorResponse = {
889
+ error: error instanceof Error ? error.message : "Unknown error"
890
+ };
891
+ const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
892
+ return encryptedError;
893
+ }
894
+ }
895
+ onSocketConnect(socket) {
896
+ this.socket = socket;
897
+ for (const [prefixedMethod] of this.handlers) {
898
+ socket.emit("rpc-register", { method: prefixedMethod });
899
+ }
900
+ }
901
+ onSocketDisconnect() {
902
+ this.socket = null;
903
+ }
904
+ /**
905
+ * Get the number of registered handlers
906
+ */
907
+ getHandlerCount() {
908
+ return this.handlers.size;
909
+ }
910
+ /**
911
+ * Check if a handler is registered
912
+ * @param method - The method name (without prefix)
913
+ */
914
+ hasHandler(method) {
915
+ const prefixedMethod = this.getPrefixedMethod(method);
916
+ return this.handlers.has(prefixedMethod);
917
+ }
918
+ /**
919
+ * Clear all handlers
920
+ */
921
+ clearHandlers() {
922
+ this.handlers.clear();
923
+ this.logger("Cleared all RPC handlers");
924
+ }
925
+ /**
926
+ * Get the prefixed method name
927
+ * @param method - The method name
928
+ */
929
+ getPrefixedMethod(method) {
930
+ return `${this.scopePrefix}:${method}`;
931
+ }
932
+ }
933
+
934
+ const __dirname = dirname(fileURLToPath(import.meta.url));
935
+ function projectPath() {
936
+ const path = resolve(__dirname, "..");
937
+ return path;
938
+ }
939
+
940
+ function run(args, options) {
941
+ const RUNNER_PATH = resolve(join$1(projectPath(), "scripts", "ripgrep_launcher.cjs"));
942
+ return new Promise((resolve2, reject) => {
943
+ const child = spawn("node", [RUNNER_PATH, JSON.stringify(args)], {
944
+ stdio: ["pipe", "pipe", "pipe"],
945
+ cwd: options?.cwd
946
+ });
947
+ let stdout = "";
948
+ let stderr = "";
949
+ child.stdout.on("data", (data) => {
950
+ stdout += data.toString();
951
+ });
952
+ child.stderr.on("data", (data) => {
953
+ stderr += data.toString();
954
+ });
955
+ child.on("close", (code) => {
956
+ resolve2({
957
+ exitCode: code || 0,
958
+ stdout,
959
+ stderr
960
+ });
961
+ });
962
+ child.on("error", (err) => {
963
+ reject(err);
964
+ });
965
+ });
966
+ }
967
+
968
+ const execAsync = promisify(exec);
969
+ function registerCommonHandlers(rpcHandlerManager) {
970
+ rpcHandlerManager.registerHandler("bash", async (data) => {
971
+ logger.debug("Shell command request:", data.command);
972
+ try {
973
+ const options = {
974
+ cwd: data.cwd,
975
+ timeout: data.timeout || 3e4
976
+ // Default 30 seconds timeout
977
+ };
978
+ const { stdout, stderr } = await execAsync(data.command, options);
979
+ return {
980
+ success: true,
981
+ stdout: stdout ? stdout.toString() : "",
982
+ stderr: stderr ? stderr.toString() : "",
983
+ exitCode: 0
984
+ };
985
+ } catch (error) {
986
+ const execError = error;
987
+ if (execError.code === "ETIMEDOUT" || execError.killed) {
988
+ return {
989
+ success: false,
990
+ stdout: execError.stdout || "",
991
+ stderr: execError.stderr || "",
992
+ exitCode: typeof execError.code === "number" ? execError.code : -1,
993
+ error: "Command timed out"
994
+ };
995
+ }
996
+ return {
997
+ success: false,
998
+ stdout: execError.stdout ? execError.stdout.toString() : "",
999
+ stderr: execError.stderr ? execError.stderr.toString() : execError.message || "Command failed",
1000
+ exitCode: typeof execError.code === "number" ? execError.code : 1,
1001
+ error: execError.message || "Command failed"
1002
+ };
1003
+ }
1004
+ });
1005
+ rpcHandlerManager.registerHandler("readFile", async (data) => {
1006
+ logger.debug("Read file request:", data.path);
1007
+ try {
1008
+ const buffer = await readFile$1(data.path);
1009
+ const content = buffer.toString("base64");
1010
+ return { success: true, content };
1011
+ } catch (error) {
1012
+ logger.debug("Failed to read file:", error);
1013
+ return { success: false, error: error instanceof Error ? error.message : "Failed to read file" };
1014
+ }
1015
+ });
1016
+ rpcHandlerManager.registerHandler("writeFile", async (data) => {
1017
+ logger.debug("Write file request:", data.path);
1018
+ try {
1019
+ if (data.expectedHash !== null && data.expectedHash !== void 0) {
1020
+ try {
1021
+ const existingBuffer = await readFile$1(data.path);
1022
+ const existingHash = createHash("sha256").update(existingBuffer).digest("hex");
1023
+ if (existingHash !== data.expectedHash) {
1024
+ return {
1025
+ success: false,
1026
+ error: `File hash mismatch. Expected: ${data.expectedHash}, Actual: ${existingHash}`
1027
+ };
1028
+ }
1029
+ } catch (error) {
1030
+ const nodeError = error;
1031
+ if (nodeError.code !== "ENOENT") {
1032
+ throw error;
1033
+ }
1034
+ return {
1035
+ success: false,
1036
+ error: "File does not exist but hash was provided"
1037
+ };
1038
+ }
1039
+ } else {
1040
+ try {
1041
+ await stat$1(data.path);
1042
+ return {
1043
+ success: false,
1044
+ error: "File already exists but was expected to be new"
1045
+ };
1046
+ } catch (error) {
1047
+ const nodeError = error;
1048
+ if (nodeError.code !== "ENOENT") {
1049
+ throw error;
1050
+ }
1051
+ }
1052
+ }
1053
+ const buffer = Buffer.from(data.content, "base64");
1054
+ await writeFile$1(data.path, buffer);
1055
+ const hash = createHash("sha256").update(buffer).digest("hex");
1056
+ return { success: true, hash };
1057
+ } catch (error) {
1058
+ logger.debug("Failed to write file:", error);
1059
+ return { success: false, error: error instanceof Error ? error.message : "Failed to write file" };
1060
+ }
1061
+ });
1062
+ rpcHandlerManager.registerHandler("listDirectory", async (data) => {
1063
+ logger.debug("List directory request:", data.path);
1064
+ try {
1065
+ const entries = await readdir(data.path, { withFileTypes: true });
1066
+ const directoryEntries = await Promise.all(
1067
+ entries.map(async (entry) => {
1068
+ const fullPath = join$1(data.path, entry.name);
1069
+ let type = "other";
1070
+ let size;
1071
+ let modified;
1072
+ if (entry.isDirectory()) {
1073
+ type = "directory";
1074
+ } else if (entry.isFile()) {
1075
+ type = "file";
1076
+ }
1077
+ try {
1078
+ const stats = await stat$1(fullPath);
1079
+ size = stats.size;
1080
+ modified = stats.mtime.getTime();
1081
+ } catch (error) {
1082
+ logger.debug(`Failed to stat ${fullPath}:`, error);
1083
+ }
1084
+ return {
1085
+ name: entry.name,
1086
+ type,
1087
+ size,
1088
+ modified
1089
+ };
1090
+ })
1091
+ );
1092
+ directoryEntries.sort((a, b) => {
1093
+ if (a.type === "directory" && b.type !== "directory") return -1;
1094
+ if (a.type !== "directory" && b.type === "directory") return 1;
1095
+ return a.name.localeCompare(b.name);
1096
+ });
1097
+ return { success: true, entries: directoryEntries };
1098
+ } catch (error) {
1099
+ logger.debug("Failed to list directory:", error);
1100
+ return { success: false, error: error instanceof Error ? error.message : "Failed to list directory" };
1101
+ }
1102
+ });
1103
+ rpcHandlerManager.registerHandler("getDirectoryTree", async (data) => {
1104
+ logger.debug("Get directory tree request:", data.path, "maxDepth:", data.maxDepth);
1105
+ async function buildTree(path, name, currentDepth) {
1106
+ try {
1107
+ const stats = await stat$1(path);
1108
+ const node = {
1109
+ name,
1110
+ path,
1111
+ type: stats.isDirectory() ? "directory" : "file",
1112
+ size: stats.size,
1113
+ modified: stats.mtime.getTime()
1114
+ };
1115
+ if (stats.isDirectory() && currentDepth < data.maxDepth) {
1116
+ const entries = await readdir(path, { withFileTypes: true });
1117
+ const children = [];
1118
+ await Promise.all(
1119
+ entries.map(async (entry) => {
1120
+ if (entry.isSymbolicLink()) {
1121
+ logger.debug(`Skipping symlink: ${join$1(path, entry.name)}`);
1122
+ return;
1123
+ }
1124
+ const childPath = join$1(path, entry.name);
1125
+ const childNode = await buildTree(childPath, entry.name, currentDepth + 1);
1126
+ if (childNode) {
1127
+ children.push(childNode);
1128
+ }
1129
+ })
1130
+ );
1131
+ children.sort((a, b) => {
1132
+ if (a.type === "directory" && b.type !== "directory") return -1;
1133
+ if (a.type !== "directory" && b.type === "directory") return 1;
1134
+ return a.name.localeCompare(b.name);
1135
+ });
1136
+ node.children = children;
1137
+ }
1138
+ return node;
1139
+ } catch (error) {
1140
+ logger.debug(`Failed to process ${path}:`, error instanceof Error ? error.message : String(error));
1141
+ return null;
1142
+ }
1143
+ }
1144
+ try {
1145
+ if (data.maxDepth < 0) {
1146
+ return { success: false, error: "maxDepth must be non-negative" };
1147
+ }
1148
+ const baseName = data.path === "/" ? "/" : data.path.split("/").pop() || data.path;
1149
+ const tree = await buildTree(data.path, baseName, 0);
1150
+ if (!tree) {
1151
+ return { success: false, error: "Failed to access the specified path" };
1152
+ }
1153
+ return { success: true, tree };
1154
+ } catch (error) {
1155
+ logger.debug("Failed to get directory tree:", error);
1156
+ return { success: false, error: error instanceof Error ? error.message : "Failed to get directory tree" };
1157
+ }
1158
+ });
1159
+ rpcHandlerManager.registerHandler("ripgrep", async (data) => {
1160
+ logger.debug("Ripgrep request with args:", data.args, "cwd:", data.cwd);
1161
+ try {
1162
+ const result = await run(data.args, { cwd: data.cwd });
1163
+ return {
1164
+ success: true,
1165
+ exitCode: result.exitCode,
1166
+ stdout: result.stdout.toString(),
1167
+ stderr: result.stderr.toString()
1168
+ };
1169
+ } catch (error) {
1170
+ logger.debug("Failed to run ripgrep:", error);
1171
+ return {
1172
+ success: false,
1173
+ error: error instanceof Error ? error.message : "Failed to run ripgrep"
1174
+ };
1175
+ }
1176
+ });
1177
+ }
1178
+
839
1179
  class ApiSessionClient extends EventEmitter {
840
1180
  token;
841
1181
  secret;
@@ -847,7 +1187,7 @@ class ApiSessionClient extends EventEmitter {
847
1187
  socket;
848
1188
  pendingMessages = [];
849
1189
  pendingMessageCallback = null;
850
- rpcHandlers = /* @__PURE__ */ new Map();
1190
+ rpcHandlerManager;
851
1191
  agentStateLock = new AsyncLock();
852
1192
  metadataLock = new AsyncLock();
853
1193
  constructor(token, secret, session) {
@@ -859,6 +1199,12 @@ class ApiSessionClient extends EventEmitter {
859
1199
  this.metadataVersion = session.metadataVersion;
860
1200
  this.agentState = session.agentState;
861
1201
  this.agentStateVersion = session.agentStateVersion;
1202
+ this.rpcHandlerManager = new RpcHandlerManager({
1203
+ scopePrefix: this.sessionId,
1204
+ secret: this.secret,
1205
+ logger: (msg, data) => logger.debug(msg, data)
1206
+ });
1207
+ registerCommonHandlers(this.rpcHandlerManager);
862
1208
  this.socket = io(configuration.serverUrl, {
863
1209
  auth: {
864
1210
  token: this.token,
@@ -876,35 +1222,18 @@ class ApiSessionClient extends EventEmitter {
876
1222
  });
877
1223
  this.socket.on("connect", () => {
878
1224
  logger.debug("Socket connected successfully");
879
- this.reregisterHandlers();
1225
+ this.rpcHandlerManager.onSocketConnect(this.socket);
880
1226
  });
881
1227
  this.socket.on("rpc-request", async (data, callback) => {
882
- try {
883
- const method = data.method;
884
- const handler = this.rpcHandlers.get(method);
885
- if (!handler) {
886
- logger.debug("[SOCKET] [RPC] [ERROR] method not found", { method });
887
- const errorResponse = { error: "Method not found" };
888
- const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
889
- callback(encryptedError);
890
- return;
891
- }
892
- const decryptedParams = decrypt(decodeBase64(data.params), this.secret);
893
- const result = await handler(decryptedParams);
894
- const encryptedResponse = encodeBase64(encrypt(result, this.secret));
895
- callback(encryptedResponse);
896
- } catch (error) {
897
- logger.debug("[SOCKET] [RPC] [ERROR] Error handling RPC request", { error });
898
- const errorResponse = { error: error instanceof Error ? error.message : "Unknown error" };
899
- const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
900
- callback(encryptedError);
901
- }
1228
+ callback(await this.rpcHandlerManager.handleRequest(data));
902
1229
  });
903
1230
  this.socket.on("disconnect", (reason) => {
904
1231
  logger.debug("[API] Socket disconnected:", reason);
1232
+ this.rpcHandlerManager.onSocketDisconnect();
905
1233
  });
906
1234
  this.socket.on("connect_error", (error) => {
907
1235
  logger.debug("[API] Socket connection error:", error);
1236
+ this.rpcHandlerManager.onSocketDisconnect();
908
1237
  });
909
1238
  this.socket.on("update", (data) => {
910
1239
  try {
@@ -1115,29 +1444,6 @@ class ApiSessionClient extends EventEmitter {
1115
1444
  });
1116
1445
  });
1117
1446
  }
1118
- /**
1119
- * Set a custom RPC handler for a specific method with encrypted arguments and responses
1120
- * @param method - The method name to handle
1121
- * @param handler - The handler function to call when the method is invoked
1122
- */
1123
- setHandler(method, handler) {
1124
- const prefixedMethod = `${this.sessionId}:${method}`;
1125
- this.rpcHandlers.set(prefixedMethod, handler);
1126
- this.socket.emit("rpc-register", { method: prefixedMethod });
1127
- logger.debug("Registered RPC handler", { method, prefixedMethod });
1128
- }
1129
- /**
1130
- * Re-register all RPC handlers after reconnection
1131
- */
1132
- reregisterHandlers() {
1133
- logger.debug("Re-registering RPC handlers after reconnection", {
1134
- totalMethods: this.rpcHandlers.size
1135
- });
1136
- for (const [prefixedMethod] of this.rpcHandlers) {
1137
- this.socket.emit("rpc-register", { method: prefixedMethod });
1138
- logger.debug("Re-registered method", { prefixedMethod });
1139
- }
1140
- }
1141
1447
  /**
1142
1448
  * Wait for socket buffer to flush
1143
1449
  */
@@ -1164,21 +1470,58 @@ class ApiMachineClient {
1164
1470
  this.token = token;
1165
1471
  this.secret = secret;
1166
1472
  this.machine = machine;
1473
+ this.rpcHandlerManager = new RpcHandlerManager({
1474
+ scopePrefix: this.machine.id,
1475
+ secret: this.secret,
1476
+ logger: (msg, data) => logger.debug(msg, data)
1477
+ });
1478
+ registerCommonHandlers(this.rpcHandlerManager);
1167
1479
  }
1168
1480
  socket;
1169
1481
  keepAliveInterval = null;
1170
- // RPC handlers
1171
- spawnSession;
1172
- stopSession;
1173
- requestShutdown;
1482
+ rpcHandlerManager;
1174
1483
  setRPCHandlers({
1175
1484
  spawnSession,
1176
1485
  stopSession,
1177
1486
  requestShutdown
1178
1487
  }) {
1179
- this.spawnSession = spawnSession;
1180
- this.stopSession = stopSession;
1181
- this.requestShutdown = requestShutdown;
1488
+ this.rpcHandlerManager.registerHandler("spawn-happy-session", async (params) => {
1489
+ const { directory, sessionId, machineId, approvedNewDirectoryCreation } = params || {};
1490
+ if (!directory) {
1491
+ throw new Error("Directory is required");
1492
+ }
1493
+ const result = await spawnSession({ directory, sessionId, machineId, approvedNewDirectoryCreation });
1494
+ switch (result.type) {
1495
+ case "success":
1496
+ logger.debug(`[API MACHINE] Spawned session ${result.sessionId}`);
1497
+ return { type: "success", sessionId: result.sessionId };
1498
+ case "requestToApproveDirectoryCreation":
1499
+ logger.debug(`[API MACHINE] Requesting directory creation approval for: ${result.directory}`);
1500
+ return { type: "requestToApproveDirectoryCreation", directory: result.directory };
1501
+ case "error":
1502
+ throw new Error(result.errorMessage);
1503
+ }
1504
+ });
1505
+ this.rpcHandlerManager.registerHandler("stop-session", (params) => {
1506
+ const { sessionId } = params || {};
1507
+ if (!sessionId) {
1508
+ throw new Error("Session ID is required");
1509
+ }
1510
+ const success = stopSession(sessionId);
1511
+ if (!success) {
1512
+ throw new Error("Session not found or failed to stop");
1513
+ }
1514
+ logger.debug(`[API MACHINE] Stopped session ${sessionId}`);
1515
+ return { message: "Session stopped" };
1516
+ });
1517
+ this.rpcHandlerManager.registerHandler("stop-daemon", () => {
1518
+ logger.debug("[API MACHINE] Received stop-daemon RPC request");
1519
+ setTimeout(() => {
1520
+ logger.debug("[API MACHINE] Initiating daemon shutdown from RPC");
1521
+ requestShutdown();
1522
+ }, 100);
1523
+ return { message: "Daemon stop request acknowledged, starting shutdown sequence..." };
1524
+ });
1182
1525
  }
1183
1526
  /**
1184
1527
  * Update machine metadata
@@ -1246,9 +1589,6 @@ class ApiMachineClient {
1246
1589
  reconnectionDelay: 1e3,
1247
1590
  reconnectionDelayMax: 5e3
1248
1591
  });
1249
- const spawnMethod = `${this.machine.id}:spawn-happy-session`;
1250
- const stopMethod = `${this.machine.id}:stop-session`;
1251
- const stopDaemonMethod = `${this.machine.id}:stop-daemon`;
1252
1592
  this.socket.on("connect", () => {
1253
1593
  logger.debug("[API MACHINE] Connected to server");
1254
1594
  this.updateDaemonState((state) => ({
@@ -1258,86 +1598,17 @@ class ApiMachineClient {
1258
1598
  httpPort: this.machine.daemonState?.httpPort,
1259
1599
  startedAt: Date.now()
1260
1600
  }));
1261
- this.socket.emit("rpc-register", { method: spawnMethod });
1262
- this.socket.emit("rpc-register", { method: stopMethod });
1263
- this.socket.emit("rpc-register", { method: stopDaemonMethod });
1264
- logger.debug(`[API MACHINE] Registered RPC methods: ${spawnMethod}, ${stopMethod}, ${stopDaemonMethod}`);
1601
+ this.rpcHandlerManager.onSocketConnect(this.socket);
1265
1602
  this.startKeepAlive();
1266
1603
  });
1604
+ this.socket.on("disconnect", () => {
1605
+ logger.debug("[API MACHINE] Disconnected from server");
1606
+ this.rpcHandlerManager.onSocketDisconnect();
1607
+ this.stopKeepAlive();
1608
+ });
1267
1609
  this.socket.on("rpc-request", async (data, callback) => {
1268
1610
  logger.debugLargeJson(`[API MACHINE] Received RPC request:`, data);
1269
- try {
1270
- if (data.method === spawnMethod) {
1271
- if (!this.spawnSession) {
1272
- throw new Error("Spawn session handler not set");
1273
- }
1274
- const { directory, sessionId, machineId, approvedNewDirectoryCreation } = decrypt(decodeBase64(data.params), this.secret) || {};
1275
- if (!directory) {
1276
- throw new Error("Directory is required");
1277
- }
1278
- const result = await this.spawnSession({ directory, sessionId, machineId, approvedNewDirectoryCreation });
1279
- switch (result.type) {
1280
- case "success": {
1281
- logger.debug(`[API MACHINE] Spawned session ${result.sessionId}`);
1282
- const response = {
1283
- type: "success",
1284
- sessionId: result.sessionId
1285
- };
1286
- logger.debug(`[API MACHINE] Sending RPC response:`, response);
1287
- callback(encodeBase64(encrypt(response, this.secret)));
1288
- return;
1289
- }
1290
- case "requestToApproveDirectoryCreation":
1291
- const promptResponse = {
1292
- type: "requestToApproveDirectoryCreation",
1293
- directory: result.directory
1294
- };
1295
- logger.debug(`[API MACHINE] Requesting directory creation approval for: ${result.directory}`);
1296
- callback(encodeBase64(encrypt(promptResponse, this.secret)));
1297
- return;
1298
- case "error":
1299
- throw new Error(result.errorMessage);
1300
- }
1301
- }
1302
- if (data.method === stopMethod) {
1303
- logger.debug("[API MACHINE] Received stop-session RPC request");
1304
- const decryptedParams = decrypt(decodeBase64(data.params), this.secret);
1305
- const { sessionId } = decryptedParams || {};
1306
- if (!this.stopSession) {
1307
- throw new Error("Stop session handler not set");
1308
- }
1309
- if (!sessionId) {
1310
- throw new Error("Session ID is required");
1311
- }
1312
- const success = this.stopSession(sessionId);
1313
- if (!success) {
1314
- throw new Error("Session not found or failed to stop");
1315
- }
1316
- logger.debug(`[API MACHINE] Stopped session ${sessionId}`);
1317
- const response = { message: "Session stopped" };
1318
- const encryptedResponse = encodeBase64(encrypt(response, this.secret));
1319
- callback(encryptedResponse);
1320
- return;
1321
- }
1322
- if (data.method === stopDaemonMethod) {
1323
- logger.debug("[API MACHINE] Received stop-daemon RPC request");
1324
- callback(encodeBase64(encrypt({
1325
- message: "Daemon stop request acknowledged, starting shutdown sequence..."
1326
- }, this.secret)));
1327
- setTimeout(() => {
1328
- logger.debug("[API MACHINE] Initiating daemon shutdown from RPC");
1329
- if (this.requestShutdown) {
1330
- this.requestShutdown();
1331
- }
1332
- }, 100);
1333
- return;
1334
- }
1335
- throw new Error(`Unknown RPC method: ${data.method}`);
1336
- } catch (error) {
1337
- logger.debug(`[API MACHINE] RPC handler failed:`, error.message || error);
1338
- logger.debug(`[API MACHINE] Error stack:`, error.stack);
1339
- callback(encodeBase64(encrypt({ error: error.message || String(error) }, this.secret)));
1340
- }
1611
+ callback(await this.rpcHandlerManager.handleRequest(data));
1341
1612
  });
1342
1613
  this.socket.on("update", (data) => {
1343
1614
  if (data.body.t === "update-machine" && data.body.machineId === this.machine.id) {
@@ -1356,16 +1627,6 @@ class ApiMachineClient {
1356
1627
  logger.debug(`[API MACHINE] Received unknown update type: ${data.body.t}`);
1357
1628
  }
1358
1629
  });
1359
- this.socket.on("disconnect", () => {
1360
- logger.debug("[API MACHINE] Disconnected from server");
1361
- this.stopKeepAlive();
1362
- });
1363
- this.socket.io.on("reconnect", () => {
1364
- logger.debug("[API MACHINE] Reconnected to server");
1365
- this.socket.emit("rpc-register", { method: spawnMethod });
1366
- this.socket.emit("rpc-register", { method: stopMethod });
1367
- this.socket.emit("rpc-register", { method: stopDaemonMethod });
1368
- });
1369
1630
  this.socket.on("connect_error", (error) => {
1370
1631
  logger.debug(`[API MACHINE] Connection error: ${error.message}`);
1371
1632
  });
@@ -1612,7 +1873,7 @@ class ApiClient {
1612
1873
  * Register or update machine with the server
1613
1874
  * Returns the current machine state from the server with decrypted metadata and daemonState
1614
1875
  */
1615
- async createMachineOrGetExistingAsIs(opts) {
1876
+ async getOrCreateMachine(opts) {
1616
1877
  const response = await axios.post(
1617
1878
  `${configuration.serverUrl}/v1/machines`,
1618
1879
  {
@@ -1735,4 +1996,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
1735
1996
  }).passthrough()
1736
1997
  ]);
1737
1998
 
1738
- export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, backoff as b, configuration as c, delay as d, AsyncLock as e, clearDaemonState as f, readSettings as g, readCredentials as h, encodeBase64 as i, encodeBase64Url as j, decodeBase64 as k, logger as l, acquireDaemonLock as m, writeDaemonState as n, releaseDaemonLock as o, packageJson as p, clearCredentials as q, readDaemonState as r, clearMachineId as s, getLatestDaemonLog as t, updateSettings as u, writeCredentials as w };
1999
+ export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, backoff as b, configuration as c, delay as d, AsyncLock as e, clearDaemonState as f, packageJson as g, readSettings as h, readCredentials as i, encodeBase64 as j, encodeBase64Url as k, logger as l, decodeBase64 as m, acquireDaemonLock as n, writeDaemonState as o, projectPath as p, releaseDaemonLock as q, readDaemonState as r, clearCredentials as s, clearMachineId as t, updateSettings as u, getLatestDaemonLog as v, writeCredentials as w };