happy-coder 0.9.0 → 0.10.0-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.
@@ -34,7 +34,7 @@ function _interopNamespaceDefault(e) {
34
34
  var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
35
35
 
36
36
  var name = "happy-coder";
37
- var version = "0.9.0";
37
+ var version = "0.10.0-0";
38
38
  var description = "Claude Code session sharing CLI";
39
39
  var author = "Kirill Dubovitskiy";
40
40
  var license = "MIT";
@@ -99,6 +99,7 @@ var dependencies = {
99
99
  "@types/react": "^19.1.9",
100
100
  axios: "^1.10.0",
101
101
  chalk: "^5.4.1",
102
+ "cross-spawn": "^7.0.6",
102
103
  "expo-server-sdk": "^3.15.0",
103
104
  fastify: "^5.5.0",
104
105
  "fastify-type-provider-zod": "4.0.2",
@@ -106,6 +107,7 @@ var dependencies = {
106
107
  "http-proxy-middleware": "^3.0.5",
107
108
  ink: "^6.1.0",
108
109
  open: "^10.2.0",
110
+ "ps-list": "^8.1.1",
109
111
  "qrcode-terminal": "^0.12.0",
110
112
  react: "^19.1.1",
111
113
  "socket.io-client": "^4.8.1",
@@ -114,7 +116,9 @@ var dependencies = {
114
116
  };
115
117
  var devDependencies = {
116
118
  "@eslint/compat": "^1",
119
+ "@types/cross-spawn": "^6.0.6",
117
120
  "@types/node": ">=20",
121
+ "@types/ps-list": "^6.2.1",
118
122
  "cross-env": "^10.0.0",
119
123
  dotenv: "^16.6.1",
120
124
  eslint: "^9",
@@ -173,7 +177,7 @@ class Configuration {
173
177
  currentCliVersion;
174
178
  isExperimentalEnabled;
175
179
  constructor() {
176
- this.serverUrl = process.env.HAPPY_SERVER_URL || "https://handy-api.korshakov.org";
180
+ this.serverUrl = process.env.HAPPY_SERVER_URL || "https://api.cluster-fluster.com";
177
181
  const args = process.argv.slice(2);
178
182
  this.isDaemonProcess = args.length >= 2 && args[0] === "daemon" && args[1] === "start-sync";
179
183
  if (process.env.HAPPY_HOME_DIR) {
@@ -553,7 +557,7 @@ class Logger {
553
557
  fs.appendFileSync(this.logFilePath, logLine);
554
558
  } catch (appendError) {
555
559
  if (process.env.DEBUG) {
556
- console.error("Failed to append to log file:", appendError);
560
+ console.error("[DEV MODE ONLY THROWING] Failed to append to log file:", appendError);
557
561
  throw appendError;
558
562
  }
559
563
  }
@@ -660,7 +664,23 @@ z.z.object({
660
664
  metadata: z.z.any(),
661
665
  metadataVersion: z.z.number(),
662
666
  agentState: z.z.any().nullable(),
663
- agentStateVersion: z.z.number()
667
+ agentStateVersion: z.z.number(),
668
+ // Connectivity tracking (from server)
669
+ connectivityStatus: z.z.union([
670
+ z.z.enum(["neverConnected", "online", "offline"]),
671
+ z.z.string()
672
+ // Forward compatibility
673
+ ]).optional(),
674
+ connectivityStatusSince: z.z.number().optional(),
675
+ connectivityStatusReason: z.z.string().optional(),
676
+ // State tracking (from server)
677
+ state: z.z.union([
678
+ z.z.enum(["running", "archiveRequested", "archived"]),
679
+ z.z.string()
680
+ // Forward compatibility
681
+ ]).optional(),
682
+ stateSince: z.z.number().optional(),
683
+ stateReason: z.z.string().optional()
664
684
  });
665
685
  z.z.object({
666
686
  host: z.z.string(),
@@ -698,7 +718,23 @@ z.z.object({
698
718
  active: z.z.boolean(),
699
719
  activeAt: z.z.number(),
700
720
  createdAt: z.z.number(),
701
- updatedAt: z.z.number()
721
+ updatedAt: z.z.number(),
722
+ // Connectivity tracking (from server)
723
+ connectivityStatus: z.z.union([
724
+ z.z.enum(["neverConnected", "online", "offline"]),
725
+ z.z.string()
726
+ // Forward compatibility
727
+ ]).optional(),
728
+ connectivityStatusSince: z.z.number().optional(),
729
+ connectivityStatusReason: z.z.string().optional(),
730
+ // State tracking (from server)
731
+ state: z.z.union([
732
+ z.z.enum(["running", "archiveRequested", "archived"]),
733
+ z.z.string()
734
+ // Forward compatibility
735
+ ]).optional(),
736
+ stateSince: z.z.number().optional(),
737
+ stateReason: z.z.string().optional()
702
738
  });
703
739
  z.z.object({
704
740
  content: SessionMessageContentSchema,
@@ -1011,7 +1047,9 @@ class ApiSessionClient extends node_events.EventEmitter {
1011
1047
  * Send a ping message to keep the connection alive
1012
1048
  */
1013
1049
  keepAlive(thinking, mode) {
1014
- logger.debug(`[API] Sending keep alive message: ${thinking}`);
1050
+ if (process.env.DEBUG) {
1051
+ logger.debug(`[API] Sending keep alive message: ${thinking}`);
1052
+ }
1015
1053
  this.socket.volatile.emit("session-alive", {
1016
1054
  sid: this.sessionId,
1017
1055
  time: Date.now(),
@@ -1249,37 +1287,39 @@ class ApiMachineClient {
1249
1287
  this.socket.on("rpc-request", async (data, callback) => {
1250
1288
  logger.debugLargeJson(`[API MACHINE] Received RPC request:`, data);
1251
1289
  try {
1252
- const spawnMethod2 = `${this.machine.id}:spawn-happy-session`;
1253
- const stopMethod2 = `${this.machine.id}:stop-session`;
1254
- const stopDaemonMethod2 = `${this.machine.id}:stop-daemon`;
1255
- if (data.method === spawnMethod2) {
1290
+ if (data.method === spawnMethod) {
1256
1291
  if (!this.spawnSession) {
1257
1292
  throw new Error("Spawn session handler not set");
1258
1293
  }
1259
- const { directory, sessionId } = decrypt(decodeBase64(data.params), this.secret) || {};
1294
+ const { directory, sessionId, machineId, approvedNewDirectoryCreation } = decrypt(decodeBase64(data.params), this.secret) || {};
1260
1295
  if (!directory) {
1261
1296
  throw new Error("Directory is required");
1262
1297
  }
1263
- const session = await this.spawnSession(directory, sessionId);
1264
- if (!session) {
1265
- throw new Error("Failed to spawn session");
1266
- }
1267
- if (session.error) {
1268
- throw new Error(session.error);
1269
- }
1270
- logger.debug(`[API MACHINE] Spawned session ${session.happySessionId || "WARNING - not session Id recieved in webhook"} with PID ${session.pid}`);
1271
- if (!session.happySessionId) {
1272
- throw new Error(`Session spawned (PID ${session.pid}) but no sessionId received from webhook. The session process may still be initializing.`);
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);
1273
1320
  }
1274
- const response = {
1275
- sessionId: session.happySessionId,
1276
- message: session.message
1277
- };
1278
- logger.debug(`[API MACHINE] Sending RPC response:`, response);
1279
- callback(encodeBase64(encrypt(response, this.secret)));
1280
- return;
1281
1321
  }
1282
- if (data.method === stopMethod2) {
1322
+ if (data.method === stopMethod) {
1283
1323
  logger.debug("[API MACHINE] Received stop-session RPC request");
1284
1324
  const decryptedParams = decrypt(decodeBase64(data.params), this.secret);
1285
1325
  const { sessionId } = decryptedParams || {};
@@ -1299,7 +1339,7 @@ class ApiMachineClient {
1299
1339
  callback(encryptedResponse);
1300
1340
  return;
1301
1341
  }
1302
- if (data.method === stopDaemonMethod2) {
1342
+ if (data.method === stopDaemonMethod) {
1303
1343
  logger.debug("[API MACHINE] Received stop-daemon RPC request");
1304
1344
  callback(encodeBase64(encrypt({
1305
1345
  message: "Daemon stop request acknowledged, starting shutdown sequence..."
@@ -1360,7 +1400,9 @@ class ApiMachineClient {
1360
1400
  machineId: this.machine.id,
1361
1401
  time: Date.now()
1362
1402
  };
1363
- logger.debugLargeJson(`[API MACHINE] Emitting machine-alive`, payload);
1403
+ if (process.env.DEBUG) {
1404
+ logger.debugLargeJson(`[API MACHINE] Emitting machine-alive`, payload);
1405
+ }
1364
1406
  this.socket.emit("machine-alive", payload);
1365
1407
  }, 2e4);
1366
1408
  logger.debug("[API MACHINE] Keep-alive started (20s interval)");
@@ -1386,7 +1428,7 @@ class PushNotificationClient {
1386
1428
  token;
1387
1429
  baseUrl;
1388
1430
  expo;
1389
- constructor(token, baseUrl = "https://handy-api.korshakov.org") {
1431
+ constructor(token, baseUrl = "https://api.cluster-fluster.com") {
1390
1432
  this.token = token;
1391
1433
  this.baseUrl = baseUrl;
1392
1434
  this.expo = new expoServerSdk.Expo();
@@ -1635,6 +1677,34 @@ class ApiClient {
1635
1677
  push() {
1636
1678
  return this.pushClient;
1637
1679
  }
1680
+ /**
1681
+ * Register a vendor API token with the server
1682
+ * The token is sent as a JSON string - server handles encryption
1683
+ */
1684
+ async registerVendorToken(vendor, apiKey) {
1685
+ try {
1686
+ const response = await axios.post(
1687
+ `${configuration.serverUrl}/v1/connect/${vendor}/register`,
1688
+ {
1689
+ token: JSON.stringify(apiKey)
1690
+ },
1691
+ {
1692
+ headers: {
1693
+ "Authorization": `Bearer ${this.token}`,
1694
+ "Content-Type": "application/json"
1695
+ },
1696
+ timeout: 5e3
1697
+ }
1698
+ );
1699
+ if (response.status !== 200 && response.status !== 201) {
1700
+ throw new Error(`Server returned status ${response.status}`);
1701
+ }
1702
+ logger.debug(`[API] Vendor token for ${vendor} registered successfully`);
1703
+ } catch (error) {
1704
+ logger.debug(`[API] [ERROR] Failed to register vendor token:`, error);
1705
+ throw new Error(`Failed to register vendor token: ${error instanceof Error ? error.message : "Unknown error"}`);
1706
+ }
1707
+ }
1638
1708
  }
1639
1709
 
1640
1710
  const UsageSchema = z.z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-coder",
3
- "version": "0.9.0",
3
+ "version": "0.10.0-0",
4
4
  "description": "Claude Code session sharing CLI",
5
5
  "author": "Kirill Dubovitskiy",
6
6
  "license": "MIT",
@@ -65,6 +65,7 @@
65
65
  "@types/react": "^19.1.9",
66
66
  "axios": "^1.10.0",
67
67
  "chalk": "^5.4.1",
68
+ "cross-spawn": "^7.0.6",
68
69
  "expo-server-sdk": "^3.15.0",
69
70
  "fastify": "^5.5.0",
70
71
  "fastify-type-provider-zod": "4.0.2",
@@ -72,6 +73,7 @@
72
73
  "http-proxy-middleware": "^3.0.5",
73
74
  "ink": "^6.1.0",
74
75
  "open": "^10.2.0",
76
+ "ps-list": "^8.1.1",
75
77
  "qrcode-terminal": "^0.12.0",
76
78
  "react": "^19.1.1",
77
79
  "socket.io-client": "^4.8.1",
@@ -80,7 +82,9 @@
80
82
  },
81
83
  "devDependencies": {
82
84
  "@eslint/compat": "^1",
85
+ "@types/cross-spawn": "^6.0.6",
83
86
  "@types/node": ">=20",
87
+ "@types/ps-list": "^6.2.1",
84
88
  "cross-env": "^10.0.0",
85
89
  "dotenv": "^16.6.1",
86
90
  "eslint": "^9",
@@ -102,4 +106,4 @@
102
106
  "registry": "https://registry.npmjs.org"
103
107
  },
104
108
  "packageManager": "yarn@1.22.22"
105
- }
109
+ }