forkoff 1.1.1 → 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 +27 -11
- package/package.json +9 -4
- package/E2EE-COMPLETE.md +0 -290
- package/eas.json +0 -21
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** —
|
|
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
|
-
- **
|
|
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` |
|
|
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
|
|
82
|
-
| `forkoff logs` | List
|
|
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.
|
|
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** |
|
|
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://
|
|
194
|
+
- [ForkOff mobile app](https://github.com/Forkoff-app/forkoff-react-native) (iOS/Android)
|
|
179
195
|
|
|
180
196
|
## License
|
|
181
197
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forkoff",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "CLI tool to connect
|
|
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
|
-
"
|
|
44
|
-
"
|
|
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
|
package/eas.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"cli": {
|
|
3
|
-
"version": ">= 16.26.0",
|
|
4
|
-
"appVersionSource": "remote"
|
|
5
|
-
},
|
|
6
|
-
"build": {
|
|
7
|
-
"development": {
|
|
8
|
-
"developmentClient": true,
|
|
9
|
-
"distribution": "internal"
|
|
10
|
-
},
|
|
11
|
-
"preview": {
|
|
12
|
-
"distribution": "internal"
|
|
13
|
-
},
|
|
14
|
-
"production": {
|
|
15
|
-
"autoIncrement": true
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"submit": {
|
|
19
|
-
"production": {}
|
|
20
|
-
}
|
|
21
|
-
}
|