forkoff 1.0.8 → 1.0.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.
Files changed (78) hide show
  1. package/E2EE-COMPLETE.md +290 -0
  2. package/README.md +11 -0
  3. package/dist/__tests__/crypto/e2e-integration.test.d.ts +17 -0
  4. package/dist/__tests__/crypto/e2e-integration.test.d.ts.map +1 -0
  5. package/dist/__tests__/crypto/e2e-integration.test.js +338 -0
  6. package/dist/__tests__/crypto/e2e-integration.test.js.map +1 -0
  7. package/dist/__tests__/crypto/e2eeManager.test.d.ts +2 -0
  8. package/dist/__tests__/crypto/e2eeManager.test.d.ts.map +1 -0
  9. package/dist/__tests__/crypto/e2eeManager.test.js +242 -0
  10. package/dist/__tests__/crypto/e2eeManager.test.js.map +1 -0
  11. package/dist/__tests__/crypto/encryption.test.d.ts +2 -0
  12. package/dist/__tests__/crypto/encryption.test.d.ts.map +1 -0
  13. package/dist/__tests__/crypto/encryption.test.js +116 -0
  14. package/dist/__tests__/crypto/encryption.test.js.map +1 -0
  15. package/dist/__tests__/crypto/keyExchange.test.d.ts +2 -0
  16. package/dist/__tests__/crypto/keyExchange.test.d.ts.map +1 -0
  17. package/dist/__tests__/crypto/keyExchange.test.js +84 -0
  18. package/dist/__tests__/crypto/keyExchange.test.js.map +1 -0
  19. package/dist/__tests__/crypto/keyGeneration.test.d.ts +2 -0
  20. package/dist/__tests__/crypto/keyGeneration.test.d.ts.map +1 -0
  21. package/dist/__tests__/crypto/keyGeneration.test.js +61 -0
  22. package/dist/__tests__/crypto/keyGeneration.test.js.map +1 -0
  23. package/dist/__tests__/crypto/keyStorage.test.d.ts +2 -0
  24. package/dist/__tests__/crypto/keyStorage.test.d.ts.map +1 -0
  25. package/dist/__tests__/crypto/keyStorage.test.js +133 -0
  26. package/dist/__tests__/crypto/keyStorage.test.js.map +1 -0
  27. package/dist/__tests__/crypto/websocketIntegration.test.d.ts +2 -0
  28. package/dist/__tests__/crypto/websocketIntegration.test.d.ts.map +1 -0
  29. package/dist/__tests__/crypto/websocketIntegration.test.js +259 -0
  30. package/dist/__tests__/crypto/websocketIntegration.test.js.map +1 -0
  31. package/dist/__tests__/tools/claude-process.test.d.ts +8 -0
  32. package/dist/__tests__/tools/claude-process.test.d.ts.map +1 -0
  33. package/dist/__tests__/tools/claude-process.test.js +224 -0
  34. package/dist/__tests__/tools/claude-process.test.js.map +1 -0
  35. package/dist/crypto/e2eeManager.d.ts +82 -0
  36. package/dist/crypto/e2eeManager.d.ts.map +1 -0
  37. package/dist/crypto/e2eeManager.js +270 -0
  38. package/dist/crypto/e2eeManager.js.map +1 -0
  39. package/dist/crypto/encryption.d.ts +19 -0
  40. package/dist/crypto/encryption.d.ts.map +1 -0
  41. package/dist/crypto/encryption.js +111 -0
  42. package/dist/crypto/encryption.js.map +1 -0
  43. package/dist/crypto/keyExchange.d.ts +24 -0
  44. package/dist/crypto/keyExchange.d.ts.map +1 -0
  45. package/dist/crypto/keyExchange.js +119 -0
  46. package/dist/crypto/keyExchange.js.map +1 -0
  47. package/dist/crypto/keyGeneration.d.ts +18 -0
  48. package/dist/crypto/keyGeneration.d.ts.map +1 -0
  49. package/dist/crypto/keyGeneration.js +99 -0
  50. package/dist/crypto/keyGeneration.js.map +1 -0
  51. package/dist/crypto/keyStorage.d.ts +39 -0
  52. package/dist/crypto/keyStorage.d.ts.map +1 -0
  53. package/dist/crypto/keyStorage.js +117 -0
  54. package/dist/crypto/keyStorage.js.map +1 -0
  55. package/dist/crypto/sessionPersistence.d.ts +33 -0
  56. package/dist/crypto/sessionPersistence.d.ts.map +1 -0
  57. package/dist/crypto/sessionPersistence.js +173 -0
  58. package/dist/crypto/sessionPersistence.js.map +1 -0
  59. package/dist/crypto/types.d.ts +35 -0
  60. package/dist/crypto/types.d.ts.map +1 -0
  61. package/dist/crypto/types.js +8 -0
  62. package/dist/crypto/types.js.map +1 -0
  63. package/dist/crypto/websocketE2EE.d.ts +47 -0
  64. package/dist/crypto/websocketE2EE.d.ts.map +1 -0
  65. package/dist/crypto/websocketE2EE.js +144 -0
  66. package/dist/crypto/websocketE2EE.js.map +1 -0
  67. package/dist/index.js +103 -2
  68. package/dist/index.js.map +1 -1
  69. package/dist/tools/claude-process.d.ts +19 -3
  70. package/dist/tools/claude-process.d.ts.map +1 -1
  71. package/dist/tools/claude-process.js +107 -164
  72. package/dist/tools/claude-process.js.map +1 -1
  73. package/dist/websocket.d.ts +33 -1
  74. package/dist/websocket.d.ts.map +1 -1
  75. package/dist/websocket.js +30 -1
  76. package/dist/websocket.js.map +1 -1
  77. package/jest.config.js +15 -0
  78. package/package.json +9 -2
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.persistSessionKey = persistSessionKey;
37
+ exports.loadPersistedSessionKey = loadPersistedSessionKey;
38
+ exports.deletePersistedSession = deletePersistedSession;
39
+ exports.deleteAllPersistedSessions = deleteAllPersistedSessions;
40
+ exports.listPersistedSessions = listPersistedSessions;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const os = __importStar(require("os"));
44
+ /**
45
+ * Session Persistence
46
+ * Stores session keys to disk so they survive reconnections after IP changes
47
+ */
48
+ const SESSION_STORE_DIR = path.join(os.homedir(), '.forkoff-cli', 'sessions');
49
+ /**
50
+ * Ensures the session store directory exists
51
+ */
52
+ function ensureSessionStoreExists() {
53
+ if (!fs.existsSync(SESSION_STORE_DIR)) {
54
+ fs.mkdirSync(SESSION_STORE_DIR, { recursive: true });
55
+ }
56
+ }
57
+ /**
58
+ * Gets the file path for a device's session
59
+ */
60
+ function getSessionFilePath(deviceId, targetDeviceId) {
61
+ return path.join(SESSION_STORE_DIR, `${deviceId}-${targetDeviceId}.json`);
62
+ }
63
+ /**
64
+ * Persists a session key to disk
65
+ * @param deviceId - Current device ID
66
+ * @param targetDeviceId - Target device ID
67
+ * @param sessionKeys - Session encryption keys
68
+ */
69
+ function persistSessionKey(deviceId, targetDeviceId, sessionKeys) {
70
+ try {
71
+ ensureSessionStoreExists();
72
+ const data = {
73
+ encryptionKey: Array.from(sessionKeys.encryptionKey), // Convert Uint8Array to Array for JSON
74
+ sessionId: sessionKeys.sessionId,
75
+ timestamp: new Date().toISOString(),
76
+ };
77
+ const filePath = getSessionFilePath(deviceId, targetDeviceId);
78
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
79
+ }
80
+ catch (error) {
81
+ console.error('Failed to persist session key:', error);
82
+ }
83
+ }
84
+ /**
85
+ * Loads a persisted session key from disk
86
+ * @param deviceId - Current device ID
87
+ * @param targetDeviceId - Target device ID
88
+ * @returns SessionKeys or null if not found
89
+ */
90
+ function loadPersistedSessionKey(deviceId, targetDeviceId) {
91
+ try {
92
+ const filePath = getSessionFilePath(deviceId, targetDeviceId);
93
+ if (!fs.existsSync(filePath)) {
94
+ return null;
95
+ }
96
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
97
+ // Check if session is expired (older than 24 hours)
98
+ const timestamp = new Date(data.timestamp);
99
+ const now = new Date();
100
+ const hoursSinceCreation = (now.getTime() - timestamp.getTime()) / (1000 * 60 * 60);
101
+ if (hoursSinceCreation > 24) {
102
+ // Session expired, delete it
103
+ fs.unlinkSync(filePath);
104
+ return null;
105
+ }
106
+ return {
107
+ encryptionKey: new Uint8Array(data.encryptionKey),
108
+ sessionId: data.sessionId,
109
+ };
110
+ }
111
+ catch (error) {
112
+ console.error('Failed to load persisted session key:', error);
113
+ return null;
114
+ }
115
+ }
116
+ /**
117
+ * Deletes a persisted session
118
+ * @param deviceId - Current device ID
119
+ * @param targetDeviceId - Target device ID
120
+ */
121
+ function deletePersistedSession(deviceId, targetDeviceId) {
122
+ try {
123
+ const filePath = getSessionFilePath(deviceId, targetDeviceId);
124
+ if (fs.existsSync(filePath)) {
125
+ fs.unlinkSync(filePath);
126
+ }
127
+ }
128
+ catch (error) {
129
+ console.error('Failed to delete persisted session:', error);
130
+ }
131
+ }
132
+ /**
133
+ * Deletes all persisted sessions for a device
134
+ * @param deviceId - Current device ID
135
+ */
136
+ function deleteAllPersistedSessions(deviceId) {
137
+ try {
138
+ if (!fs.existsSync(SESSION_STORE_DIR)) {
139
+ return;
140
+ }
141
+ const files = fs.readdirSync(SESSION_STORE_DIR);
142
+ for (const file of files) {
143
+ if (file.startsWith(`${deviceId}-`)) {
144
+ fs.unlinkSync(path.join(SESSION_STORE_DIR, file));
145
+ }
146
+ }
147
+ }
148
+ catch (error) {
149
+ console.error('Failed to delete all persisted sessions:', error);
150
+ }
151
+ }
152
+ /**
153
+ * Lists all persisted sessions for a device
154
+ * @param deviceId - Current device ID
155
+ * @returns Array of target device IDs with active sessions
156
+ */
157
+ function listPersistedSessions(deviceId) {
158
+ try {
159
+ if (!fs.existsSync(SESSION_STORE_DIR)) {
160
+ return [];
161
+ }
162
+ const files = fs.readdirSync(SESSION_STORE_DIR);
163
+ const prefix = `${deviceId}-`;
164
+ return files
165
+ .filter((file) => file.startsWith(prefix) && file.endsWith('.json'))
166
+ .map((file) => file.substring(prefix.length, file.length - 5)); // Remove prefix and .json
167
+ }
168
+ catch (error) {
169
+ console.error('Failed to list persisted sessions:', error);
170
+ return [];
171
+ }
172
+ }
173
+ //# sourceMappingURL=sessionPersistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionPersistence.js","sourceRoot":"","sources":["../../src/crypto/sessionPersistence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * E2EE Type Definitions for ForkOff CLI
3
+ *
4
+ * X25519 key exchange + AES-256-GCM encryption
5
+ */
6
+ export interface E2EEKeyPair {
7
+ publicKey: string;
8
+ privateKey: string;
9
+ }
10
+ export interface EncryptedPayload {
11
+ ciphertext: string;
12
+ nonce: string;
13
+ authTag: string;
14
+ }
15
+ export interface EncryptedMessage {
16
+ senderDeviceId: string;
17
+ recipientDeviceId: string;
18
+ sessionId: string;
19
+ payload: EncryptedPayload;
20
+ messageCounter: number;
21
+ timestamp: string;
22
+ }
23
+ export interface SessionKeys {
24
+ encryptionKey: Uint8Array;
25
+ sessionId: string;
26
+ }
27
+ export interface KeyExchangeInit {
28
+ senderDeviceId: string;
29
+ ephemeralPublicKey: string;
30
+ }
31
+ export interface KeyExchangeAck {
32
+ recipientDeviceId: string;
33
+ ephemeralPublicKey: string;
34
+ }
35
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/crypto/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,UAAU,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;CAC5B"}
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ /**
3
+ * E2EE Type Definitions for ForkOff CLI
4
+ *
5
+ * X25519 key exchange + AES-256-GCM encryption
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/crypto/types.ts"],"names":[],"mappings":";AAAA;;;;GAIG"}
@@ -0,0 +1,47 @@
1
+ import { WebSocketClient } from '../websocket';
2
+ /**
3
+ * WebSocket E2EE Integration
4
+ * Adds end-to-end encryption capabilities to WebSocket client
5
+ */
6
+ export declare class WebSocketE2EEIntegration {
7
+ private wsClient;
8
+ private e2eeManager;
9
+ private enabled;
10
+ constructor(wsClient: WebSocketClient);
11
+ /**
12
+ * Initializes E2EE and sets up event handlers
13
+ */
14
+ initialize(deviceId: string, apiUrl: string, authToken: string): Promise<void>;
15
+ /**
16
+ * Sets up WebSocket event handlers for E2EE
17
+ */
18
+ private setupEventHandlers;
19
+ /**
20
+ * Initiates key exchange with a target device
21
+ */
22
+ initiateKeyExchange(targetDeviceId: string): Promise<void>;
23
+ /**
24
+ * Encrypts and sends a message to a target device
25
+ */
26
+ sendEncryptedMessage(plaintext: string, targetDeviceId: string, sessionId: string): void;
27
+ /**
28
+ * Checks if E2EE session exists for a device
29
+ */
30
+ hasSession(deviceId: string): boolean;
31
+ /**
32
+ * Checks if E2EE is enabled
33
+ */
34
+ isEnabled(): boolean;
35
+ /**
36
+ * Cleans up E2EE sessions
37
+ */
38
+ cleanup(): void;
39
+ private emitKeyExchangeInit;
40
+ private emitKeyExchangeAck;
41
+ private emitEncryptedMessage;
42
+ }
43
+ /**
44
+ * Factory function to create E2EE-enabled WebSocket client
45
+ */
46
+ export declare function createE2EEWebSocketClient(wsClient: WebSocketClient, deviceId: string, apiUrl: string, authToken: string): Promise<WebSocketE2EEIntegration>;
47
+ //# sourceMappingURL=websocketE2EE.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocketE2EE.d.ts","sourceRoot":"","sources":["../../src/crypto/websocketE2EE.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAI/C;;;GAGG;AACH,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,OAAO,CAAS;gBAEZ,QAAQ,EAAE,eAAe;IAIrC;;OAEG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpF;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA6D1B;;OAEG;IACG,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhE;;OAEG;IACH,oBAAoB,CAClB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,GAChB,IAAI;IAqBP;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIrC;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,oBAAoB;CAG7B;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,eAAe,EACzB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,wBAAwB,CAAC,CAGnC"}
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebSocketE2EEIntegration = void 0;
4
+ exports.createE2EEWebSocketClient = createE2EEWebSocketClient;
5
+ const e2eeManager_1 = require("./e2eeManager");
6
+ /**
7
+ * WebSocket E2EE Integration
8
+ * Adds end-to-end encryption capabilities to WebSocket client
9
+ */
10
+ class WebSocketE2EEIntegration {
11
+ constructor(wsClient) {
12
+ this.e2eeManager = null;
13
+ this.enabled = false;
14
+ this.wsClient = wsClient;
15
+ }
16
+ /**
17
+ * Initializes E2EE and sets up event handlers
18
+ */
19
+ async initialize(deviceId, apiUrl, authToken) {
20
+ this.e2eeManager = new e2eeManager_1.E2EEManager(deviceId, apiUrl, authToken);
21
+ await this.e2eeManager.initialize();
22
+ this.setupEventHandlers();
23
+ this.enabled = true;
24
+ }
25
+ /**
26
+ * Sets up WebSocket event handlers for E2EE
27
+ */
28
+ setupEventHandlers() {
29
+ if (!this.e2eeManager)
30
+ return;
31
+ // Handle incoming key exchange init
32
+ this.wsClient.on('encrypted_key_exchange_init', async (data) => {
33
+ if (!this.e2eeManager)
34
+ return;
35
+ try {
36
+ // Handle key exchange and send ack
37
+ const ackPayload = await this.e2eeManager.handleKeyExchangeInit(data.senderDeviceId, data.ephemeralPublicKey);
38
+ // Emit ack back to sender
39
+ this.emitKeyExchangeAck(data.senderDeviceId, ackPayload.ephemeralPublicKey);
40
+ }
41
+ catch (error) {
42
+ console.error('[E2EE] Failed to handle key exchange init:', error);
43
+ }
44
+ });
45
+ // Handle incoming key exchange ack
46
+ this.wsClient.on('encrypted_key_exchange_ack', async (data) => {
47
+ if (!this.e2eeManager)
48
+ return;
49
+ try {
50
+ await this.e2eeManager.handleKeyExchangeAck(data.senderDeviceId, data.ephemeralPublicKey);
51
+ console.log(`[E2EE] Key exchange completed with ${data.senderDeviceId}`);
52
+ }
53
+ catch (error) {
54
+ console.error('[E2EE] Failed to handle key exchange ack:', error);
55
+ }
56
+ });
57
+ // Handle incoming encrypted messages
58
+ this.wsClient.on('encrypted_message', (data) => {
59
+ if (!this.e2eeManager)
60
+ return;
61
+ try {
62
+ const plaintext = this.e2eeManager.decryptMessage(data, data.senderDeviceId);
63
+ // Emit decrypted message as a regular user_message event
64
+ this.wsClient.emit('decrypted_message', {
65
+ ...data,
66
+ decryptedContent: plaintext,
67
+ });
68
+ }
69
+ catch (error) {
70
+ console.error('[E2EE] Failed to decrypt message:', error);
71
+ }
72
+ });
73
+ }
74
+ /**
75
+ * Initiates key exchange with a target device
76
+ */
77
+ async initiateKeyExchange(targetDeviceId) {
78
+ if (!this.e2eeManager) {
79
+ throw new Error('E2EE not initialized');
80
+ }
81
+ const initPayload = await this.e2eeManager.initiateKeyExchange(targetDeviceId);
82
+ // Emit key exchange init via WebSocket
83
+ this.emitKeyExchangeInit(targetDeviceId, initPayload.ephemeralPublicKey);
84
+ }
85
+ /**
86
+ * Encrypts and sends a message to a target device
87
+ */
88
+ sendEncryptedMessage(plaintext, targetDeviceId, sessionId) {
89
+ if (!this.e2eeManager) {
90
+ throw new Error('E2EE not initialized');
91
+ }
92
+ if (!this.e2eeManager.hasSessionKey(targetDeviceId)) {
93
+ throw new Error(`No E2EE session with ${targetDeviceId}. Must complete key exchange first.`);
94
+ }
95
+ const encryptedMessage = this.e2eeManager.encryptMessage(plaintext, targetDeviceId, sessionId);
96
+ // Emit encrypted message via WebSocket
97
+ this.emitEncryptedMessage(encryptedMessage);
98
+ }
99
+ /**
100
+ * Checks if E2EE session exists for a device
101
+ */
102
+ hasSession(deviceId) {
103
+ return this.e2eeManager?.hasSessionKey(deviceId) ?? false;
104
+ }
105
+ /**
106
+ * Checks if E2EE is enabled
107
+ */
108
+ isEnabled() {
109
+ return this.enabled && this.e2eeManager !== null;
110
+ }
111
+ /**
112
+ * Cleans up E2EE sessions
113
+ */
114
+ cleanup() {
115
+ this.e2eeManager?.cleanup();
116
+ }
117
+ // Private WebSocket emit methods
118
+ emitKeyExchangeInit(recipientDeviceId, ephemeralPublicKey) {
119
+ this.wsClient.emitKeyExchangeInit({
120
+ recipientDeviceId,
121
+ senderDeviceId: this.e2eeManager?.['deviceId'] || '',
122
+ ephemeralPublicKey,
123
+ });
124
+ }
125
+ emitKeyExchangeAck(recipientDeviceId, ephemeralPublicKey) {
126
+ this.wsClient.emitKeyExchangeAck({
127
+ recipientDeviceId,
128
+ senderDeviceId: this.e2eeManager?.['deviceId'] || '',
129
+ ephemeralPublicKey,
130
+ });
131
+ }
132
+ emitEncryptedMessage(message) {
133
+ this.wsClient.emitEncryptedMessage(message);
134
+ }
135
+ }
136
+ exports.WebSocketE2EEIntegration = WebSocketE2EEIntegration;
137
+ /**
138
+ * Factory function to create E2EE-enabled WebSocket client
139
+ */
140
+ function createE2EEWebSocketClient(wsClient, deviceId, apiUrl, authToken) {
141
+ const integration = new WebSocketE2EEIntegration(wsClient);
142
+ return integration.initialize(deviceId, apiUrl, authToken).then(() => integration);
143
+ }
144
+ //# sourceMappingURL=websocketE2EE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocketE2EE.js","sourceRoot":"","sources":["../../src/crypto/websocketE2EE.ts"],"names":[],"mappings":";;;AAoLA,8DAQC;AA3LD,+CAA4C;AAG5C;;;GAGG;AACH,MAAa,wBAAwB;IAKnC,YAAY,QAAyB;QAH7B,gBAAW,GAAuB,IAAI,CAAC;QACvC,YAAO,GAAG,KAAK,CAAC;QAGtB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,QAAgB,EAAE,MAAc,EAAE,SAAiB;QAClE,IAAI,CAAC,WAAW,GAAG,IAAI,yBAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QAChE,MAAM,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC;QACpC,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACtB,CAAC;IAED;;OAEG;IACK,kBAAkB;QACxB,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO;QAE9B,oCAAoC;QACpC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,6BAA6B,EAAE,KAAK,EAAE,IAGtD,EAAE,EAAE;YACH,IAAI,CAAC,IAAI,CAAC,WAAW;gBAAE,OAAO;YAE9B,IAAI,CAAC;gBACH,mCAAmC;gBACnC,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,qBAAqB,CAC7D,IAAI,CAAC,cAAc,EACnB,IAAI,CAAC,kBAAkB,CACxB,CAAC;gBAEF,0BAA0B;gBAC1B,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,cAAc,EAAE,UAAU,CAAC,kBAAkB,CAAC,CAAC;YAC9E,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,4CAA4C,EAAE,KAAK,CAAC,CAAC;YACrE,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,mCAAmC;QACnC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,4BAA4B,EAAE,KAAK,EAAE,IAGrD,EAAE,EAAE;YACH,IAAI,CAAC,IAAI,CAAC,WAAW;gBAAE,OAAO;YAE9B,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,WAAW,CAAC,oBAAoB,CACzC,IAAI,CAAC,cAAc,EACnB,IAAI,CAAC,kBAAkB,CACxB,CAAC;gBAEF,OAAO,CAAC,GAAG,CAAC,sCAAsC,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC;YAC3E,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,2CAA2C,EAAE,KAAK,CAAC,CAAC;YACpE,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,qCAAqC;QACrC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,IAAsB,EAAE,EAAE;YAC/D,IAAI,CAAC,IAAI,CAAC,WAAW;gBAAE,OAAO;YAE9B,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;gBAE7E,yDAAyD;gBACzD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,mBAAmB,EAAE;oBACtC,GAAG,IAAI;oBACP,gBAAgB,EAAE,SAAS;iBAC5B,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,mCAAmC,EAAE,KAAK,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,mBAAmB,CAAC,cAAsB;QAC9C,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC1C,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,cAAc,CAAC,CAAC;QAE/E,uCAAuC;QACvC,IAAI,CAAC,mBAAmB,CAAC,cAAc,EAAE,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAC3E,CAAC;IAED;;OAEG;IACH,oBAAoB,CAClB,SAAiB,EACjB,cAAsB,EACtB,SAAiB;QAEjB,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC1C,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,cAAc,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CACb,wBAAwB,cAAc,qCAAqC,CAC5E,CAAC;QACJ,CAAC;QAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,WAAW,CAAC,cAAc,CACtD,SAAS,EACT,cAAc,EACd,SAAS,CACV,CAAC;QAEF,uCAAuC;QACvC,IAAI,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;IAC9C,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,QAAgB;QACzB,OAAO,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC;IAC5D,CAAC;IAED;;OAEG;IACH,SAAS;QACP,OAAO,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC;IACnD,CAAC;IAED;;OAEG;IACH,OAAO;QACL,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE,CAAC;IAC9B,CAAC;IAED,iCAAiC;IAEzB,mBAAmB,CAAC,iBAAyB,EAAE,kBAA0B;QAC/E,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC;YAChC,iBAAiB;YACjB,cAAc,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE;YACpD,kBAAkB;SACnB,CAAC,CAAC;IACL,CAAC;IAEO,kBAAkB,CAAC,iBAAyB,EAAE,kBAA0B;QAC9E,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC;YAC/B,iBAAiB;YACjB,cAAc,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE;YACpD,kBAAkB;SACnB,CAAC,CAAC;IACL,CAAC;IAEO,oBAAoB,CAAC,OAAyB;QACpD,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;CACF;AAvKD,4DAuKC;AAED;;GAEG;AACH,SAAgB,yBAAyB,CACvC,QAAyB,EACzB,QAAgB,EAChB,MAAc,EACd,SAAiB;IAEjB,MAAM,WAAW,GAAG,IAAI,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IAC3D,OAAO,WAAW,CAAC,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC;AACrF,CAAC"}
package/dist/index.js CHANGED
@@ -542,9 +542,14 @@ async function startConnection() {
542
542
  websocket_1.wsClient.on('claude_start_session', async (data) => {
543
543
  console.log(chalk_1.default.cyan(`[Claude] Start session request: ${data.directory}`));
544
544
  try {
545
- const result = await tools_1.claudeProcessManager.startSession(data.directory, data.terminalSessionId);
545
+ const result = await tools_1.claudeProcessManager.startSession(data.directory, data.terminalSessionId, data.dangerouslySkipPermissions);
546
546
  websocket_1.wsClient.sendToolStatusUpdate('claude_code', 'active');
547
547
  websocket_1.wsClient.sendTerminalCwd({ terminalSessionId: data.terminalSessionId, cwd: result.cwd });
548
+ // Notify mobile that the session is ready for input
549
+ websocket_1.wsClient.sendClaudeSessionEvent({
550
+ sessionKey: data.terminalSessionId,
551
+ event: { type: 'ready' },
552
+ });
548
553
  console.log(chalk_1.default.green(`[Claude] Session started: ${data.terminalSessionId}`));
549
554
  }
550
555
  catch (error) {
@@ -570,7 +575,7 @@ async function startConnection() {
570
575
  }
571
576
  resolvedDir = path.resolve(resolvedDir);
572
577
  // Register session info for later use when message is sent (don't spawn yet)
573
- tools_1.claudeProcessManager.registerSession(data.sessionKey, resolvedDir, data.terminalSessionId);
578
+ tools_1.claudeProcessManager.registerSession(data.sessionKey, resolvedDir, data.terminalSessionId, data.dangerouslySkipPermissions);
574
579
  websocket_1.wsClient.sendToolStatusUpdate('claude_code', 'active');
575
580
  websocket_1.wsClient.sendClaudeSessionUpdate({
576
581
  sessionKey: data.sessionKey,
@@ -618,6 +623,79 @@ async function startConnection() {
618
623
  websocket_1.wsClient.sendDirectoryListResponse({ requestId: data.requestId, entries: [], currentPath: data.path });
619
624
  }
620
625
  });
626
+ // Handle read file requests from mobile (e.g., CLAUDE.md)
627
+ websocket_1.wsClient.on('read_file', async (data) => {
628
+ console.log(chalk_1.default.dim(`[File] Read request: ${data.filePath}`));
629
+ try {
630
+ // SECURITY: Whitelist of allowed filenames
631
+ const allowedFiles = ['CLAUDE.md', 'README.md', 'package.json'];
632
+ const fileName = path.basename(data.filePath);
633
+ if (!allowedFiles.includes(fileName)) {
634
+ console.warn(chalk_1.default.yellow(`[File] Access denied - file not in whitelist: ${fileName}`));
635
+ websocket_1.wsClient.sendReadFileResponse({
636
+ requestId: data.requestId,
637
+ exists: false,
638
+ fileName,
639
+ error: 'File not allowed',
640
+ });
641
+ return;
642
+ }
643
+ // Resolve path
644
+ let resolvedPath = data.filePath;
645
+ if (resolvedPath === '~' || resolvedPath.startsWith('~/')) {
646
+ resolvedPath = resolvedPath === '~' ? os.homedir() : resolvedPath.replace('~', os.homedir());
647
+ }
648
+ resolvedPath = path.resolve(resolvedPath);
649
+ // SECURITY: Only allow access under home directory
650
+ const homeDir = os.homedir();
651
+ if (!resolvedPath.startsWith(homeDir)) {
652
+ console.warn(chalk_1.default.yellow(`[File] Access denied - path outside home directory: ${resolvedPath}`));
653
+ websocket_1.wsClient.sendReadFileResponse({
654
+ requestId: data.requestId,
655
+ exists: false,
656
+ fileName,
657
+ error: 'Path outside home directory',
658
+ });
659
+ return;
660
+ }
661
+ // Check if file exists
662
+ if (!fs.existsSync(resolvedPath)) {
663
+ websocket_1.wsClient.sendReadFileResponse({
664
+ requestId: data.requestId,
665
+ exists: false,
666
+ fileName,
667
+ });
668
+ return;
669
+ }
670
+ // SECURITY: Check file size (max 100KB)
671
+ const stats = fs.statSync(resolvedPath);
672
+ if (stats.size > 100 * 1024) {
673
+ websocket_1.wsClient.sendReadFileResponse({
674
+ requestId: data.requestId,
675
+ exists: true,
676
+ fileName,
677
+ error: 'File too large (max 100KB)',
678
+ });
679
+ return;
680
+ }
681
+ const content = fs.readFileSync(resolvedPath, 'utf-8');
682
+ websocket_1.wsClient.sendReadFileResponse({
683
+ requestId: data.requestId,
684
+ content,
685
+ exists: true,
686
+ fileName,
687
+ });
688
+ }
689
+ catch (error) {
690
+ console.error(chalk_1.default.red(`[File] Error: ${error.message}`));
691
+ websocket_1.wsClient.sendReadFileResponse({
692
+ requestId: data.requestId,
693
+ exists: false,
694
+ fileName: path.basename(data.filePath),
695
+ error: error.message,
696
+ });
697
+ }
698
+ });
621
699
  // Handle transcript fetch requests from mobile
622
700
  websocket_1.wsClient.on('transcript_fetch', async (data) => {
623
701
  console.log(chalk_1.default.dim(`[Transcript] Fetching: ${data.sessionKey}, offset: ${data.offset}, limit: ${data.limit}, reverse: ${data.reverse}`));
@@ -817,6 +895,11 @@ async function startConnection() {
817
895
  console.log(chalk_1.default.yellow(`[Claude] Approval request: ${data.approvalId}`));
818
896
  websocket_1.wsClient.sendClaudeApprovalRequest(data);
819
897
  });
898
+ // Forward tool activity events to mobile (non-blocking notifications)
899
+ tools_1.claudeProcessManager.on('tool_activity', (data) => {
900
+ console.log(chalk_1.default.dim(`[Claude] Tool activity: ${data.toolName} - ${data.inputSummary?.substring(0, 60)}`));
901
+ websocket_1.wsClient.sendToolActivity(data);
902
+ });
820
903
  // Handle Claude approval responses from mobile
821
904
  websocket_1.wsClient.on('claude_approval_response', (data) => {
822
905
  console.log(chalk_1.default.green(`[Claude] Approval response: ${data.approvalId} -> ${data.response}`));
@@ -835,6 +918,24 @@ async function startConnection() {
835
918
  }
836
919
  // Check if this session is registered (either active or registered for later spawn)
837
920
  if (!tools_1.claudeProcessManager.isClaudeSession(terminalSessionId)) {
921
+ // Fresh session from auto-prompt (quick action): start new session with directory
922
+ if (data.directory) {
923
+ console.log(chalk_1.default.cyan(`[Claude] Starting fresh session for auto-prompt in ${data.directory}`));
924
+ const sent = await tools_1.claudeProcessManager.startAndSendMessage(data.directory, terminalSessionId, data.message);
925
+ if (sent) {
926
+ // Notify mobile that session is ready
927
+ websocket_1.wsClient.sendClaudeSessionUpdate({
928
+ sessionKey: terminalSessionId,
929
+ directory: data.directory,
930
+ state: 'active',
931
+ lastUsedAt: new Date().toISOString(),
932
+ });
933
+ }
934
+ else {
935
+ console.log(chalk_1.default.yellow(`[Claude] Failed to start fresh session`));
936
+ }
937
+ return;
938
+ }
838
939
  console.log(chalk_1.default.yellow(`[Claude] Session not registered: ${terminalSessionId}`));
839
940
  console.log(chalk_1.default.dim(`[Claude] Hint: Mobile should send claude_resume_session first`));
840
941
  return;