happy-coder 0.9.1 → 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.
package/dist/lib.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-DNUk09Np.cjs');
3
+ var types = require('./types-fU2E-jQl.cjs');
4
4
  require('axios');
5
5
  require('chalk');
6
6
  require('fs');
package/dist/lib.d.cts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { z } from 'zod';
2
2
  import { EventEmitter } from 'node:events';
3
- import { ChildProcess } from 'child_process';
4
3
  import { ExpoPushMessage } from 'expo-server-sdk';
5
4
 
6
5
  /**
@@ -644,22 +643,26 @@ type AgentState = {
644
643
  };
645
644
 
646
645
  /**
647
- * Daemon-specific types (not related to API/server communication)
646
+ * RPC handlers for API session communication
647
+ * Handles remote procedure calls from mobile clients
648
648
  */
649
649
 
650
- /**
651
- * Session tracking for daemon
652
- */
653
- interface TrackedSession {
654
- startedBy: 'daemon' | string;
655
- happySessionId?: string;
656
- happySessionMetadataFromLocalWebhook?: Metadata;
657
- pid: number;
658
- childProcess?: ChildProcess;
659
- error?: string;
660
- directoryCreated?: boolean;
661
- message?: string;
650
+ interface SpawnSessionOptions {
651
+ machineId?: string;
652
+ directory: string;
653
+ sessionId?: string;
654
+ approvedNewDirectoryCreation?: boolean;
662
655
  }
656
+ type SpawnSessionResult = {
657
+ type: 'success';
658
+ sessionId: string;
659
+ } | {
660
+ type: 'requestToApproveDirectoryCreation';
661
+ directory: string;
662
+ } | {
663
+ type: 'error';
664
+ errorMessage: string;
665
+ };
663
666
 
664
667
  /**
665
668
  * WebSocket client for machine/daemon communication with Happy server
@@ -667,7 +670,7 @@ interface TrackedSession {
667
670
  */
668
671
 
669
672
  type MachineRpcHandlers = {
670
- spawnSession: (directory: string, sessionId?: string) => Promise<TrackedSession | null>;
673
+ spawnSession: (options: SpawnSessionOptions) => Promise<SpawnSessionResult>;
671
674
  stopSession: (sessionId: string) => boolean;
672
675
  requestShutdown: () => void;
673
676
  };
@@ -758,6 +761,11 @@ declare class ApiClient {
758
761
  sessionSyncClient(session: Session): ApiSessionClient;
759
762
  machineSyncClient(machine: Machine): ApiMachineClient;
760
763
  push(): PushNotificationClient;
764
+ /**
765
+ * Register a vendor API token with the server
766
+ * The token is sent as a JSON string - server handles encryption
767
+ */
768
+ registerVendorToken(vendor: 'openai' | 'anthropic' | 'gemini', apiKey: any): Promise<void>;
761
769
  }
762
770
 
763
771
  /**
package/dist/lib.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { z } from 'zod';
2
2
  import { EventEmitter } from 'node:events';
3
- import { ChildProcess } from 'child_process';
4
3
  import { ExpoPushMessage } from 'expo-server-sdk';
5
4
 
6
5
  /**
@@ -644,22 +643,26 @@ type AgentState = {
644
643
  };
645
644
 
646
645
  /**
647
- * Daemon-specific types (not related to API/server communication)
646
+ * RPC handlers for API session communication
647
+ * Handles remote procedure calls from mobile clients
648
648
  */
649
649
 
650
- /**
651
- * Session tracking for daemon
652
- */
653
- interface TrackedSession {
654
- startedBy: 'daemon' | string;
655
- happySessionId?: string;
656
- happySessionMetadataFromLocalWebhook?: Metadata;
657
- pid: number;
658
- childProcess?: ChildProcess;
659
- error?: string;
660
- directoryCreated?: boolean;
661
- message?: string;
650
+ interface SpawnSessionOptions {
651
+ machineId?: string;
652
+ directory: string;
653
+ sessionId?: string;
654
+ approvedNewDirectoryCreation?: boolean;
662
655
  }
656
+ type SpawnSessionResult = {
657
+ type: 'success';
658
+ sessionId: string;
659
+ } | {
660
+ type: 'requestToApproveDirectoryCreation';
661
+ directory: string;
662
+ } | {
663
+ type: 'error';
664
+ errorMessage: string;
665
+ };
663
666
 
664
667
  /**
665
668
  * WebSocket client for machine/daemon communication with Happy server
@@ -667,7 +670,7 @@ interface TrackedSession {
667
670
  */
668
671
 
669
672
  type MachineRpcHandlers = {
670
- spawnSession: (directory: string, sessionId?: string) => Promise<TrackedSession | null>;
673
+ spawnSession: (options: SpawnSessionOptions) => Promise<SpawnSessionResult>;
671
674
  stopSession: (sessionId: string) => boolean;
672
675
  requestShutdown: () => void;
673
676
  };
@@ -758,6 +761,11 @@ declare class ApiClient {
758
761
  sessionSyncClient(session: Session): ApiSessionClient;
759
762
  machineSyncClient(machine: Machine): ApiMachineClient;
760
763
  push(): PushNotificationClient;
764
+ /**
765
+ * Register a vendor API token with the server
766
+ * The token is sent as a JSON string - server handles encryption
767
+ */
768
+ registerVendorToken(vendor: 'openai' | 'anthropic' | 'gemini', apiKey: any): Promise<void>;
761
769
  }
762
770
 
763
771
  /**
package/dist/lib.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-BS8Pr3Im.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-CGbH1LGX.mjs';
2
2
  import 'axios';
3
3
  import 'chalk';
4
4
  import 'fs';
@@ -14,7 +14,7 @@ import { io } from 'socket.io-client';
14
14
  import { Expo } from 'expo-server-sdk';
15
15
 
16
16
  var name = "happy-coder";
17
- var version = "0.9.1";
17
+ var version = "0.10.0-0";
18
18
  var description = "Claude Code session sharing CLI";
19
19
  var author = "Kirill Dubovitskiy";
20
20
  var license = "MIT";
@@ -79,6 +79,7 @@ var dependencies = {
79
79
  "@types/react": "^19.1.9",
80
80
  axios: "^1.10.0",
81
81
  chalk: "^5.4.1",
82
+ "cross-spawn": "^7.0.6",
82
83
  "expo-server-sdk": "^3.15.0",
83
84
  fastify: "^5.5.0",
84
85
  "fastify-type-provider-zod": "4.0.2",
@@ -86,6 +87,7 @@ var dependencies = {
86
87
  "http-proxy-middleware": "^3.0.5",
87
88
  ink: "^6.1.0",
88
89
  open: "^10.2.0",
90
+ "ps-list": "^8.1.1",
89
91
  "qrcode-terminal": "^0.12.0",
90
92
  react: "^19.1.1",
91
93
  "socket.io-client": "^4.8.1",
@@ -94,7 +96,9 @@ var dependencies = {
94
96
  };
95
97
  var devDependencies = {
96
98
  "@eslint/compat": "^1",
99
+ "@types/cross-spawn": "^6.0.6",
97
100
  "@types/node": ">=20",
101
+ "@types/ps-list": "^6.2.1",
98
102
  "cross-env": "^10.0.0",
99
103
  dotenv: "^16.6.1",
100
104
  eslint: "^9",
@@ -1263,37 +1267,39 @@ class ApiMachineClient {
1263
1267
  this.socket.on("rpc-request", async (data, callback) => {
1264
1268
  logger.debugLargeJson(`[API MACHINE] Received RPC request:`, data);
1265
1269
  try {
1266
- const spawnMethod2 = `${this.machine.id}:spawn-happy-session`;
1267
- const stopMethod2 = `${this.machine.id}:stop-session`;
1268
- const stopDaemonMethod2 = `${this.machine.id}:stop-daemon`;
1269
- if (data.method === spawnMethod2) {
1270
+ if (data.method === spawnMethod) {
1270
1271
  if (!this.spawnSession) {
1271
1272
  throw new Error("Spawn session handler not set");
1272
1273
  }
1273
- const { directory, sessionId } = decrypt(decodeBase64(data.params), this.secret) || {};
1274
+ const { directory, sessionId, machineId, approvedNewDirectoryCreation } = decrypt(decodeBase64(data.params), this.secret) || {};
1274
1275
  if (!directory) {
1275
1276
  throw new Error("Directory is required");
1276
1277
  }
1277
- const session = await this.spawnSession(directory, sessionId);
1278
- if (!session) {
1279
- throw new Error("Failed to spawn session");
1280
- }
1281
- if (session.error) {
1282
- throw new Error(session.error);
1283
- }
1284
- logger.debug(`[API MACHINE] Spawned session ${session.happySessionId || "WARNING - not session Id recieved in webhook"} with PID ${session.pid}`);
1285
- if (!session.happySessionId) {
1286
- throw new Error(`Session spawned (PID ${session.pid}) but no sessionId received from webhook. The session process may still be initializing.`);
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);
1287
1300
  }
1288
- const response = {
1289
- sessionId: session.happySessionId,
1290
- message: session.message
1291
- };
1292
- logger.debug(`[API MACHINE] Sending RPC response:`, response);
1293
- callback(encodeBase64(encrypt(response, this.secret)));
1294
- return;
1295
1301
  }
1296
- if (data.method === stopMethod2) {
1302
+ if (data.method === stopMethod) {
1297
1303
  logger.debug("[API MACHINE] Received stop-session RPC request");
1298
1304
  const decryptedParams = decrypt(decodeBase64(data.params), this.secret);
1299
1305
  const { sessionId } = decryptedParams || {};
@@ -1313,7 +1319,7 @@ class ApiMachineClient {
1313
1319
  callback(encryptedResponse);
1314
1320
  return;
1315
1321
  }
1316
- if (data.method === stopDaemonMethod2) {
1322
+ if (data.method === stopDaemonMethod) {
1317
1323
  logger.debug("[API MACHINE] Received stop-daemon RPC request");
1318
1324
  callback(encodeBase64(encrypt({
1319
1325
  message: "Daemon stop request acknowledged, starting shutdown sequence..."
@@ -1651,6 +1657,34 @@ class ApiClient {
1651
1657
  push() {
1652
1658
  return this.pushClient;
1653
1659
  }
1660
+ /**
1661
+ * Register a vendor API token with the server
1662
+ * The token is sent as a JSON string - server handles encryption
1663
+ */
1664
+ async registerVendorToken(vendor, apiKey) {
1665
+ try {
1666
+ const response = await axios.post(
1667
+ `${configuration.serverUrl}/v1/connect/${vendor}/register`,
1668
+ {
1669
+ token: JSON.stringify(apiKey)
1670
+ },
1671
+ {
1672
+ headers: {
1673
+ "Authorization": `Bearer ${this.token}`,
1674
+ "Content-Type": "application/json"
1675
+ },
1676
+ timeout: 5e3
1677
+ }
1678
+ );
1679
+ if (response.status !== 200 && response.status !== 201) {
1680
+ throw new Error(`Server returned status ${response.status}`);
1681
+ }
1682
+ logger.debug(`[API] Vendor token for ${vendor} registered successfully`);
1683
+ } catch (error) {
1684
+ logger.debug(`[API] [ERROR] Failed to register vendor token:`, error);
1685
+ throw new Error(`Failed to register vendor token: ${error instanceof Error ? error.message : "Unknown error"}`);
1686
+ }
1687
+ }
1654
1688
  }
1655
1689
 
1656
1690
  const UsageSchema = z$1.object({
@@ -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.1";
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",
@@ -1283,37 +1287,39 @@ class ApiMachineClient {
1283
1287
  this.socket.on("rpc-request", async (data, callback) => {
1284
1288
  logger.debugLargeJson(`[API MACHINE] Received RPC request:`, data);
1285
1289
  try {
1286
- const spawnMethod2 = `${this.machine.id}:spawn-happy-session`;
1287
- const stopMethod2 = `${this.machine.id}:stop-session`;
1288
- const stopDaemonMethod2 = `${this.machine.id}:stop-daemon`;
1289
- if (data.method === spawnMethod2) {
1290
+ if (data.method === spawnMethod) {
1290
1291
  if (!this.spawnSession) {
1291
1292
  throw new Error("Spawn session handler not set");
1292
1293
  }
1293
- const { directory, sessionId } = decrypt(decodeBase64(data.params), this.secret) || {};
1294
+ const { directory, sessionId, machineId, approvedNewDirectoryCreation } = decrypt(decodeBase64(data.params), this.secret) || {};
1294
1295
  if (!directory) {
1295
1296
  throw new Error("Directory is required");
1296
1297
  }
1297
- const session = await this.spawnSession(directory, sessionId);
1298
- if (!session) {
1299
- throw new Error("Failed to spawn session");
1300
- }
1301
- if (session.error) {
1302
- throw new Error(session.error);
1303
- }
1304
- logger.debug(`[API MACHINE] Spawned session ${session.happySessionId || "WARNING - not session Id recieved in webhook"} with PID ${session.pid}`);
1305
- if (!session.happySessionId) {
1306
- 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);
1307
1320
  }
1308
- const response = {
1309
- sessionId: session.happySessionId,
1310
- message: session.message
1311
- };
1312
- logger.debug(`[API MACHINE] Sending RPC response:`, response);
1313
- callback(encodeBase64(encrypt(response, this.secret)));
1314
- return;
1315
1321
  }
1316
- if (data.method === stopMethod2) {
1322
+ if (data.method === stopMethod) {
1317
1323
  logger.debug("[API MACHINE] Received stop-session RPC request");
1318
1324
  const decryptedParams = decrypt(decodeBase64(data.params), this.secret);
1319
1325
  const { sessionId } = decryptedParams || {};
@@ -1333,7 +1339,7 @@ class ApiMachineClient {
1333
1339
  callback(encryptedResponse);
1334
1340
  return;
1335
1341
  }
1336
- if (data.method === stopDaemonMethod2) {
1342
+ if (data.method === stopDaemonMethod) {
1337
1343
  logger.debug("[API MACHINE] Received stop-daemon RPC request");
1338
1344
  callback(encodeBase64(encrypt({
1339
1345
  message: "Daemon stop request acknowledged, starting shutdown sequence..."
@@ -1671,6 +1677,34 @@ class ApiClient {
1671
1677
  push() {
1672
1678
  return this.pushClient;
1673
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
+ }
1674
1708
  }
1675
1709
 
1676
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.1",
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",