forkoff 1.1.0 โ†’ 1.1.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.
package/README.md CHANGED
@@ -27,9 +27,7 @@
27
27
 
28
28
  ForkOff CLI connects [Claude Code](https://claude.ai/code) on your laptop to the ForkOff mobile app, giving you real-time monitoring, interactive approvals, and usage analytics from anywhere.
29
29
 
30
- > **Open Source** — ForkOff CLI is now MIT licensed. Contributions welcome!
31
- >
32
- > **Open Beta** — [Join the iOS TestFlight](https://testflight.apple.com/join/dhh5FrN7)
30
+ > **Open Source** — MIT licensed. Contributions welcome!
33
31
 
34
32
  ## Installation
35
33
 
@@ -66,29 +64,47 @@ Keep this running to stream sessions to your phone in real-time.
66
64
  - **Usage analytics** — Track token usage, session counts, and streaks across devices
67
65
  - **Multi-device support** — Connect multiple CLI instances, analytics aggregate automatically
68
66
  - **Auto-start** — Optionally launch on login so your phone is always connected
69
- - **Direct P2P connection** — Embedded relay server, no cloud dependency for session data
67
+ - **Local relay option** — Run with `--local` for a direct P2P connection without cloud dependency
70
68
 
71
69
  ## Commands
72
70
 
73
71
  | Command | Description |
74
72
  |---------|-------------|
75
- | `forkoff pair` | Generate QR code to pair with mobile app |
76
- | `forkoff connect` | Connect and listen for commands |
73
+ | `forkoff pair [--local]` | Generate QR code to pair with mobile app |
74
+ | `forkoff connect [--local]` | Reconnect to ForkOff (for previously paired devices) |
77
75
  | `forkoff status` | Check connection status |
78
76
  | `forkoff disconnect` | Disconnect and unpair device |
79
77
  | `forkoff config` | View/modify configuration |
80
78
  | `forkoff startup` | Manage automatic startup on login |
81
- | `forkoff tools` | Detect AI coding tools on your machine |
82
- | `forkoff logs` | List debug log files for troubleshooting |
79
+ | `forkoff tools` | Detect AI tools, install/uninstall hooks |
80
+ | `forkoff logs` | List, view, or clean debug logs |
83
81
 
84
82
  ### Configuration
85
83
 
86
84
  ```bash
87
85
  forkoff config --show # Show current config
88
86
  forkoff config --name "My MBP" # Set device name
87
+ forkoff config --port 8080 # Set relay server port
89
88
  forkoff config --reset # Reset to defaults
90
89
  ```
91
90
 
91
+ ### Tools
92
+
93
+ ```bash
94
+ forkoff tools --detect # Detect installed AI tools
95
+ forkoff tools --install-hooks # Install ForkOff hooks for Claude Code
96
+ forkoff tools --uninstall-hooks # Remove ForkOff hooks
97
+ forkoff tools --watch # Watch tool status changes
98
+ ```
99
+
100
+ ### Logs
101
+
102
+ ```bash
103
+ forkoff logs # List debug log files
104
+ forkoff logs --latest # Print path to most recent log
105
+ forkoff logs --clean # Delete all log files
106
+ ```
107
+
92
108
  ### Global Options
93
109
 
94
110
  | Option | Description |
@@ -126,7 +142,7 @@ ForkOff uses end-to-end encryption (X25519 ECDH + XSalsa20-Poly1305) so the rela
126
142
  | **Key storage** | OS keychain (macOS Keychain, Windows Credential Manager, Linux libsecret) |
127
143
  | **Enforcement** | 24 sensitive event types encrypted; plaintext fallback only when E2EE unavailable |
128
144
 
129
- No additional setup required — E2EE is enabled automatically when you pair. See [SECURITY.md](https://github.com/Forkoff-app/forkoff-cli/blob/main/docs/SECURITY.md) for the full whitepaper.
145
+ No additional setup required — E2EE is enabled automatically when you pair.
130
146
 
131
147
  ---
132
148
 
@@ -158,7 +174,7 @@ forkoff.sendTerminalOutput(sessionId, '> npm install\nDone', 'stdout');
158
174
  | Platform | Location |
159
175
  |----------|----------|
160
176
  | **Windows** | `%APPDATA%\forkoff-cli\config.json` |
161
- | **macOS** | `~/Library/Preferences/forkoff-cli/config.json` |
177
+ | **macOS** | `~/.config/forkoff-cli/config.json` |
162
178
  | **Linux** | `~/.config/forkoff-cli/config.json` |
163
179
 
164
180
  ## Development
@@ -175,7 +191,7 @@ npm test # Run tests
175
191
  ## Requirements
176
192
 
177
193
  - Node.js 18+
178
- - [ForkOff mobile app](https://testflight.apple.com/join/dhh5FrN7) (iOS)
194
+ - [ForkOff mobile app](https://github.com/Forkoff-app/forkoff-react-native) (iOS/Android)
179
195
 
180
196
  ## License
181
197
 
package/dist/index.js CHANGED
@@ -454,6 +454,7 @@ function createProgram() {
454
454
  const spinner = (0, logger_1.createSpinner)('Initializing...').start();
455
455
  try {
456
456
  tools_1.PermissionIpcManager.cleanupStaleTempFiles();
457
+ tools_1.claudeProcessManager.cleanupAllPermissionState();
457
458
  spinner.succeed('Ready!\n');
458
459
  // Detect connected tools
459
460
  spinner.start('Detecting AI coding tools...');
@@ -1097,6 +1098,13 @@ function createProgram() {
1097
1098
  websocket_1.wsClient.on('disconnected', (reason) => {
1098
1099
  console.log(chalk_1.default.yellow(`\nMobile disconnected: ${reason}`));
1099
1100
  console.log(chalk_1.default.dim('Waiting for mobile to reconnect...'));
1101
+ tools_1.claudeProcessManager.autoAllowAllPendingPrompts();
1102
+ tools_1.claudeProcessManager.cleanupAllPermissionState();
1103
+ tools_1.claudeProcessManager.clearAllTakenOver();
1104
+ });
1105
+ websocket_1.wsClient.on('session_release', (data) => {
1106
+ console.log(chalk_1.default.dim(`[Session] Mobile released session: ${data.sessionKey}`));
1107
+ tools_1.claudeProcessManager.releaseSession(data.sessionKey);
1100
1108
  });
1101
1109
  websocket_1.wsClient.on('error', (error) => {
1102
1110
  console.error(chalk_1.default.red(`Connection error: ${error.message}`));
@@ -241,6 +241,11 @@ declare class ClaudeProcessManager extends EventEmitter {
241
241
  * Clear all taken-over sessions (e.g., when mobile disconnects).
242
242
  */
243
243
  clearAllTakenOver(): void;
244
+ /**
245
+ * Release a single session โ€” clean up its hooks and IPC.
246
+ * Called when mobile navigates away from the session screen after taking over.
247
+ */
248
+ releaseSession(sessionKey: string): void;
244
249
  /**
245
250
  * Get all pending permission prompts across all IPC managers.
246
251
  * Used to sync pending permissions to mobile on take-over.
@@ -822,6 +822,50 @@ class ClaudeProcessManager extends events_1.EventEmitter {
822
822
  this.takenOverSessions.clear();
823
823
  console.log(`[Claude Process] All taken-over sessions cleared`);
824
824
  }
825
+ /**
826
+ * Release a single session โ€” clean up its hooks and IPC.
827
+ * Called when mobile navigates away from the session screen after taking over.
828
+ */
829
+ releaseSession(sessionKey) {
830
+ // Find the process by sessionKey or terminalSessionId
831
+ let terminalSessionId;
832
+ let directory;
833
+ for (const [id, info] of this.processes) {
834
+ if (id === sessionKey || info.sessionKey === sessionKey) {
835
+ terminalSessionId = id;
836
+ directory = info.directory;
837
+ break;
838
+ }
839
+ }
840
+ // Also check closed sessions in case the process already exited
841
+ if (!terminalSessionId) {
842
+ for (const [id, info] of this.closedSessions) {
843
+ if (id === sessionKey || info.sessionKey === sessionKey) {
844
+ terminalSessionId = id;
845
+ directory = info.directory;
846
+ break;
847
+ }
848
+ }
849
+ }
850
+ if (terminalSessionId) {
851
+ // Stop permission IPC for this session
852
+ this.stopPermissionIpc(terminalSessionId);
853
+ // Remove hook from directory if no other active sessions use it
854
+ if (directory) {
855
+ const otherSessionsInDir = Array.from(this.processes.values())
856
+ .filter(p => p.directory === directory && p.terminalSessionId !== terminalSessionId);
857
+ if (otherSessionsInDir.length === 0) {
858
+ this.removeHook(directory);
859
+ }
860
+ }
861
+ // Clear taken-over state
862
+ this.takenOverSessions.delete(terminalSessionId);
863
+ console.log(`[Claude Process] Session released: ${terminalSessionId}`);
864
+ }
865
+ else {
866
+ console.log(`[Claude Process] Session release: no matching session found for ${sessionKey}`);
867
+ }
868
+ }
825
869
  /**
826
870
  * Get all pending permission prompts across all IPC managers.
827
871
  * Used to sync pending permissions to mobile on take-over.
package/dist/websocket.js CHANGED
@@ -89,7 +89,7 @@ const PLAINTEXT_DROP_EVENTS = [
89
89
  'directory_list', 'transcript_fetch', 'transcript_subscribe',
90
90
  'read_file', 'claude_approval_response', 'permission_response',
91
91
  'permission_rules_sync', 'claude_abort', 'tab_complete',
92
- 'usage_stats_request', 'sdk_session_history',
92
+ 'usage_stats_request', 'sdk_session_history', 'session_release',
93
93
  ];
94
94
  /** Events forwarded from server that do NOT need plaintext-drop checking */
95
95
  const PASSTHROUGH_EVENTS = [
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "forkoff",
3
- "version": "1.1.0",
4
- "description": "CLI tool to connect your AI coding tools to mobile | Open Beta - Download the app: https://testflight.apple.com/join/dhh5FrN7",
3
+ "version": "1.1.2",
4
+ "description": "CLI tool to connect Claude Code to the ForkOff mobile app for remote monitoring and approvals",
5
5
  "main": "dist/integration.js",
6
6
  "types": "dist/integration.d.ts",
7
7
  "homepage": "https://forkoff.app",
@@ -39,10 +39,15 @@
39
39
  "coding",
40
40
  "mobile",
41
41
  "claude",
42
+ "claude-code",
42
43
  "forkoff",
43
- "beta",
44
- "testflight"
44
+ "remote-control",
45
+ "developer-tools",
46
+ "websocket"
45
47
  ],
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ },
46
51
  "author": {
47
52
  "name": "ForkOff",
48
53
  "email": "support@forkoff.app",
package/E2EE-COMPLETE.md DELETED
@@ -1,290 +0,0 @@
1
- # โœ… E2EE Implementation Complete
2
-
3
- ## ๐ŸŽฏ Final Status: PRODUCTION READY
4
-
5
- **Total Test Coverage: 185 tests passing**
6
- - Mobile (React Native): 90 tests โœ…
7
- - Backend (NestJS): 23 tests โœ…
8
- - CLI (Node.js): 72 tests โœ…
9
-
10
- ---
11
-
12
- ## ๐Ÿ” Security Features
13
-
14
- ### Encryption
15
- - **Algorithm**: X25519 (ECDH) + AES-256-GCM
16
- - **Key Exchange**: Ephemeral Diffie-Hellman with HKDF key derivation
17
- - **Authentication**: Authenticated encryption with 16-byte auth tags
18
- - **Forward Secrecy**: Each session uses unique ephemeral keys
19
-
20
- ### Security Properties
21
- โœ… End-to-end encryption (backend cannot decrypt)
22
- โœ… Perfect forward secrecy (ephemeral keys)
23
- โœ… Authenticated encryption (tamper detection)
24
- โœ… Replay protection (message counters)
25
- โœ… Key rotation support (version tracking)
26
-
27
- ---
28
-
29
- ## ๐ŸŒ Network Resilience (IP Change Handling)
30
-
31
- ### Problem
32
- When a device's IP changes (WiFi โ†’ cellular, network switch), the WebSocket connection drops.
33
-
34
- ### Solution Implemented
35
-
36
- #### 1. **Persistent Session Storage**
37
- - Session keys stored to disk: `~/.forkoff-cli/sessions/`
38
- - Survives process restarts and IP changes
39
- - Automatic 24-hour expiration
40
-
41
- #### 2. **Session Restoration API**
42
- ```typescript
43
- // After reconnection, restore previous E2EE sessions
44
- await e2eeManager.restorePersistedSession(targetDeviceId);
45
-
46
- // List all devices with active sessions
47
- const devices = e2eeManager.listPersistedDevices();
48
- ```
49
-
50
- #### 3. **Automatic Reconnection Flow**
51
- 1. IP changes โ†’ WebSocket disconnects
52
- 2. WebSocket automatically reconnects (socket.io)
53
- 3. E2EE manager restores persisted sessions
54
- 4. Encrypted communication resumes seamlessly
55
-
56
- ---
57
-
58
- ## ๐Ÿ“ File Structure
59
-
60
- ### CLI (`forkoff-cli/src/crypto/`)
61
- ```
62
- crypto/
63
- โ”œโ”€โ”€ types.ts # Shared E2EE type definitions
64
- โ”œโ”€โ”€ keyGeneration.ts # X25519 key pair generation (8 tests)
65
- โ”œโ”€โ”€ keyStorage.ts # OS keychain + in-memory storage (10 tests)
66
- โ”œโ”€โ”€ encryption.ts # AES-256-GCM encrypt/decrypt (13 tests)
67
- โ”œโ”€โ”€ keyExchange.ts # ECDH + HKDF key derivation (9 tests)
68
- โ”œโ”€โ”€ e2eeManager.ts # Orchestration layer (12 tests)
69
- โ”œโ”€โ”€ sessionPersistence.ts # Disk-based session storage (NEW)
70
- โ””โ”€โ”€ websocketE2EE.ts # WebSocket integration (11 tests)
71
-
72
- __tests__/crypto/
73
- โ”œโ”€โ”€ keyGeneration.test.ts
74
- โ”œโ”€โ”€ keyStorage.test.ts
75
- โ”œโ”€โ”€ encryption.test.ts
76
- โ”œโ”€โ”€ keyExchange.test.ts
77
- โ”œโ”€โ”€ e2eeManager.test.ts
78
- โ”œโ”€โ”€ websocketIntegration.test.ts
79
- โ””โ”€โ”€ e2e-integration.test.ts # End-to-end flow verification (9 tests)
80
- ```
81
-
82
- ### Backend (`forkoff-api/src/crypto/`)
83
- ```
84
- crypto/
85
- โ”œโ”€โ”€ crypto.service.ts # Public key storage & retrieval (9 tests)
86
- โ”œโ”€โ”€ crypto.controller.ts # REST API endpoints (7 tests)
87
- โ””โ”€โ”€ dto/
88
-
89
- websocket/
90
- โ””โ”€โ”€ websocket.gateway.ts # E2EE message forwarding (7 tests)
91
- ```
92
-
93
- ### Mobile (`forkoff/services/crypto/`)
94
- ```
95
- crypto/
96
- โ”œโ”€โ”€ keyGeneration.ts # Key pair generation
97
- โ”œโ”€โ”€ keyStorage.ts # Secure store + session keys
98
- โ”œโ”€โ”€ encryption.ts # AES-256-GCM encryption
99
- โ”œโ”€โ”€ keyExchange.ts # X25519 key exchange
100
- โ””โ”€โ”€ e2eeManager.ts # E2EE orchestration
101
- ```
102
-
103
- ---
104
-
105
- ## ๐Ÿ”„ How It Works
106
-
107
- ### Initial Setup
108
- 1. **Device registers**: Generates X25519 key pair, stores private key in OS keychain
109
- 2. **Upload public key**: Sends public key to backend API
110
- 3. **Backend stores**: Public key stored in PostgreSQL (Device table)
111
-
112
- ### Key Exchange Flow
113
- ```
114
- Mobile Backend CLI
115
- | | |
116
- |--[init: ephemeral_pk]--->|---[forward]------------>|
117
- | | |
118
- |<-[ack: ephemeral_pk]-----|<--[forward]-------------|
119
- | | |
120
- Both sides derive shared secret using ECDH
121
- Both sides derive session key using HKDF
122
- ```
123
-
124
- ### Encrypted Messaging
125
- ```
126
- Mobile Backend CLI
127
- | | |
128
- |--[encrypted_message]---->|---[forward]------------>|
129
- | {ciphertext, nonce, | |
130
- | authTag, counter} | |
131
- | | |
132
- |<-[encrypted_message]-----|<--[forward]-------------|
133
- | | |
134
- ```
135
-
136
- ### Reconnection After IP Change
137
- ```
138
- CLI Reconnects Backend Mobile
139
- | | |
140
- | WebSocket reconnects | |
141
- |<--------------------------->| |
142
- | | |
143
- | Restore persisted sessions | |
144
- | (load from disk) | |
145
- | | |
146
- |--[encrypted_message]------->|---[forward]---------->|
147
- | Communication resumes! | |
148
- ```
149
-
150
- ---
151
-
152
- ## ๐Ÿงช Test Verification
153
-
154
- ### Unit Tests (Crypto Primitives)
155
- - โœ… Key generation produces valid X25519 keys
156
- - โœ… Keys are properly Base64-encoded
157
- - โœ… Encryption/decryption round-trip works
158
- - โœ… Unicode and emoji preserved
159
- - โœ… Large messages (10KB+) supported
160
- - โœ… ECDH produces matching shared secrets
161
- - โœ… HKDF derives correct session keys
162
-
163
- ### Integration Tests (E2EE Flow)
164
- - โœ… Full bidirectional encrypted communication
165
- - โœ… Key exchange completes successfully
166
- - โœ… Messages encrypted/decrypted correctly
167
- - โœ… Replay attacks detected and rejected
168
- - โœ… Tampered messages rejected
169
- - โœ… Multiple concurrent sessions supported
170
- - โœ… Session persistence across reconnections
171
-
172
- ### Security Tests
173
- - โœ… Tampered ciphertext rejected
174
- - โœ… Tampered nonce rejected
175
- - โœ… Tampered auth tag rejected
176
- - โœ… Wrong key cannot decrypt
177
- - โœ… Message counters prevent replay attacks
178
-
179
- ---
180
-
181
- ## ๐Ÿš€ Usage Example
182
-
183
- ### Initialize E2EE
184
- ```typescript
185
- import { E2EEManager } from './crypto/e2eeManager';
186
- import { WebSocketE2EEIntegration } from './crypto/websocketE2EE';
187
-
188
- // Initialize E2EE manager
189
- const e2ee = new E2EEManager(deviceId, apiUrl, authToken);
190
- await e2ee.initialize();
191
-
192
- // Integrate with WebSocket
193
- const wsIntegration = new WebSocketE2EEIntegration(wsClient);
194
- await wsIntegration.initialize(deviceId, apiUrl, authToken);
195
- ```
196
-
197
- ### Send Encrypted Message
198
- ```typescript
199
- // Initiate key exchange (if not already done)
200
- if (!e2ee.hasSessionKey(targetDeviceId)) {
201
- await wsIntegration.initiateKeyExchange(targetDeviceId);
202
- // Wait for key exchange to complete...
203
- }
204
-
205
- // Send encrypted message
206
- wsIntegration.sendEncryptedMessage(
207
- 'Secret message',
208
- targetDeviceId,
209
- sessionId
210
- );
211
- ```
212
-
213
- ### Handle Reconnection After IP Change
214
- ```typescript
215
- wsClient.on('reconnect', async () => {
216
- // Restore all persisted sessions
217
- const devices = e2ee.listPersistedDevices();
218
-
219
- for (const deviceId of devices) {
220
- const restored = await e2ee.restorePersistedSession(deviceId);
221
- if (restored) {
222
- console.log(`Restored E2EE session with ${deviceId}`);
223
- }
224
- }
225
- });
226
- ```
227
-
228
- ---
229
-
230
- ## ๐Ÿ“Š Performance Characteristics
231
-
232
- - **Key Generation**: ~50ms (X25519)
233
- - **Encryption**: <5ms (AES-256-GCM, typical message)
234
- - **Decryption**: <5ms (AES-256-GCM, typical message)
235
- - **Key Exchange**: ~100ms (includes API calls)
236
- - **Session Restoration**: <10ms (load from disk)
237
-
238
- ---
239
-
240
- ## ๐Ÿ”ง Configuration
241
-
242
- ### Session Expiration
243
- Sessions automatically expire after 24 hours. Configurable in `sessionPersistence.ts`:
244
- ```typescript
245
- const hoursSinceCreation = (now.getTime() - timestamp.getTime()) / (1000 * 60 * 60);
246
- if (hoursSinceCreation > 24) { // Change this value
247
- // Session expired
248
- }
249
- ```
250
-
251
- ### Storage Location
252
- Sessions stored at: `~/.forkoff-cli/sessions/`
253
-
254
- Private keys stored in OS keychain via `keytar`
255
-
256
- ---
257
-
258
- ## ๐ŸŽ“ What We Learned
259
-
260
- 1. **TDD Works**: Writing tests first caught numerous edge cases
261
- 2. **Message Counters Matter**: Replay protection is crucial
262
- 3. **Persistence Is Hard**: Mocking file system for tests is complex
263
- 4. **Network Resilience**: IP changes are common, plan for them
264
- 5. **Security Trade-offs**: Perfect forward secrecy vs. session resumption
265
-
266
- ---
267
-
268
- ## โœจ Future Enhancements (Not Implemented)
269
-
270
- - [ ] Double Ratchet Algorithm (Signal Protocol)
271
- - [ ] Group messaging encryption
272
- - [ ] Key rotation on schedule
273
- - [ ] Post-quantum cryptography (Kyber)
274
- - [ ] Hardware security module integration
275
- - [ ] Encrypted file transfers
276
-
277
- ---
278
-
279
- ## ๐Ÿ“– References
280
-
281
- - X25519: RFC 7748
282
- - AES-GCM: NIST SP 800-38D
283
- - HKDF: RFC 5869
284
- - Signal Protocol: https://signal.org/docs/
285
-
286
- ---
287
-
288
- **Built with โค๏ธ using Test-Driven Development**
289
-
290
- **Status**: โœ… PRODUCTION READY - All 185 tests passing