forkoff 1.0.18 → 1.1.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/LICENSE CHANGED
@@ -1,12 +1,16 @@
1
- Copyright (c) 2026 ForkOff. All rights reserved.
1
+ MIT License
2
2
 
3
- This software and associated documentation files (the "Software") are the
4
- proprietary property of ForkOff. The Software is provided for viewing and
5
- reference purposes only.
3
+ Copyright (c) 2026 ForkOff
6
4
 
7
- No part of the Software may be copied, modified, merged, published,
8
- distributed, sublicensed, sold, or otherwise used without the prior written
9
- permission of ForkOff.
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
10
14
 
11
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12
16
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
package/README.md CHANGED
@@ -5,37 +5,31 @@
5
5
  <h1 align="center">ForkOff CLI</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>Bridge your AI coding tools to your mobile device</strong>
8
+ <strong>Control your AI coding sessions from your phone</strong>
9
9
  </p>
10
10
 
11
- > ## 🚀 **OPEN BETA**
12
- >
13
- > ForkOff is now in **open beta**! Download the mobile app and get started:
14
- >
15
- > 📱 **[Join the TestFlight Beta](https://testflight.apple.com/join/dhh5FrN7)**
16
-
17
- ---
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/forkoff"><img src="https://img.shields.io/npm/v/forkoff.svg" alt="npm version"></a>
13
+ <a href="https://github.com/Forkoff-app/forkoff-cli/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/forkoff.svg" alt="MIT License"></a>
14
+ <a href="https://www.npmjs.com/package/forkoff"><img src="https://img.shields.io/npm/dm/forkoff.svg" alt="npm downloads"></a>
15
+ </p>
18
16
 
19
17
  <p align="center">
20
- <a href="https://forkoff.app">Website</a>
21
- <a href="#installation">Installation</a>
22
- <a href="#quick-start">Quick Start</a>
23
- <a href="#commands">Commands</a>
24
- <a href="#programmatic-usage">API</a>
25
- <a href="#configuration">Configuration</a>
18
+ <a href="https://forkoff.app">Website</a> &bull;
19
+ <a href="#installation">Installation</a> &bull;
20
+ <a href="#quick-start">Quick Start</a> &bull;
21
+ <a href="#commands">Commands</a> &bull;
22
+ <a href="#programmatic-usage">API</a> &bull;
23
+ <a href="#security">Security</a>
26
24
  </p>
27
25
 
28
26
  ---
29
27
 
30
- ## Overview
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.
31
29
 
32
- ForkOff CLI connects your development machine to the ForkOff mobile app, enabling you to:
33
-
34
- - **Control AI coding sessions** from your phone
35
- - **Approve code changes** on the go
36
- - **Send prompts** to Claude, Cursor, and other AI tools
37
- - **Monitor progress** in real-time
38
- - **Get notifications** for permission requests
30
+ > **Open Source** &mdash; ForkOff CLI is now MIT licensed. Contributions welcome!
31
+ >
32
+ > **Open Beta** &mdash; [Join the iOS TestFlight](https://testflight.apple.com/join/dhh5FrN7)
39
33
 
40
34
  ## Installation
41
35
 
@@ -45,24 +39,35 @@ npm install -g forkoff
45
39
 
46
40
  ## Quick Start
47
41
 
48
- ### 1. Pair with Mobile App
42
+ ### 1. Pair with your phone
49
43
 
50
44
  ```bash
51
45
  forkoff pair
52
46
  ```
53
47
 
54
- Scan the QR code with your ForkOff mobile app to link your device.
48
+ Scan the QR code with the ForkOff mobile app to link your device.
55
49
 
56
- ### 2. Stay Connected
50
+ ### 2. Stay connected
57
51
 
58
52
  ```bash
59
53
  forkoff connect
60
54
  ```
61
55
 
62
- Keep this running to receive commands from your mobile app.
56
+ Keep this running to stream sessions to your phone in real-time.
63
57
 
64
58
  ---
65
59
 
60
+ ## Features
61
+
62
+ - **Real-time session monitoring** &mdash; See Claude Code output on your phone as it happens
63
+ - **Interactive approvals** &mdash; Approve or deny tool use (file edits, bash commands) from mobile
64
+ - **Configurable permission rules** &mdash; Auto-approve safe tools, require approval for destructive ones
65
+ - **End-to-end encryption** &mdash; All session data encrypted between CLI and mobile
66
+ - **Usage analytics** &mdash; Track token usage, session counts, and streaks across devices
67
+ - **Multi-device support** &mdash; Connect multiple CLI instances, analytics aggregate automatically
68
+ - **Auto-start** &mdash; Optionally launch on login so your phone is always connected
69
+ - **Direct P2P connection** &mdash; Embedded relay server, no cloud dependency for session data
70
+
66
71
  ## Commands
67
72
 
68
73
  | Command | Description |
@@ -70,31 +75,18 @@ Keep this running to receive commands from your mobile app.
70
75
  | `forkoff pair` | Generate QR code to pair with mobile app |
71
76
  | `forkoff connect` | Connect and listen for commands |
72
77
  | `forkoff status` | Check connection status |
73
- | `forkoff disconnect` | Disconnect from server |
78
+ | `forkoff disconnect` | Disconnect and unpair device |
74
79
  | `forkoff config` | View/modify configuration |
75
80
  | `forkoff startup` | Manage automatic startup on login |
76
- | `forkoff startup --enable` | Enable automatic startup |
77
- | `forkoff startup --disable` | Disable automatic startup |
78
- | `forkoff startup --status` | Show startup registration status |
79
- | `forkoff help` | Show available commands and usage |
81
+ | `forkoff tools` | Detect AI coding tools on your machine |
82
+ | `forkoff logs` | List debug log files for troubleshooting |
80
83
 
81
- ### Configuration Options
84
+ ### Configuration
82
85
 
83
86
  ```bash
84
- # Show current configuration
85
- forkoff config --show
86
-
87
- # Set custom API URL
88
- forkoff config --api https://your-server.com/api
89
-
90
- # Set custom WebSocket URL
91
- forkoff config --ws wss://your-server.com
92
-
93
- # Set device name
94
- forkoff config --name "My MacBook Pro"
95
-
96
- # Reset all configuration
97
- forkoff config --reset
87
+ forkoff config --show # Show current config
88
+ forkoff config --name "My MBP" # Set device name
89
+ forkoff config --reset # Reset to defaults
98
90
  ```
99
91
 
100
92
  ### Global Options
@@ -102,93 +94,66 @@ forkoff config --reset
102
94
  | Option | Description |
103
95
  |--------|-------------|
104
96
  | `-q, --quiet` | Suppress all output (for background operation) |
105
-
106
- ### Background Operation
107
-
108
- Run ForkOff silently in the background with no console output:
109
-
110
- ```bash
111
- forkoff connect --quiet
112
- ```
113
-
114
- This is used by the automatic startup feature and is useful for running ForkOff as a background service.
97
+ | `--debug` | Enable debug logging to file (`~/.forkoff-cli/logs/`) |
115
98
 
116
99
  ### Automatic Startup
117
100
 
118
- ForkOff automatically starts on login so your device stays connected without manual intervention. Startup is **enabled by default** when you run `forkoff pair` or `forkoff connect`, it registers itself to launch `forkoff connect --quiet` on login.
101
+ Startup is enabled by default &mdash; `forkoff pair` and `forkoff connect` register the CLI to launch on login.
119
102
 
120
- - **Windows**: Adds a `ForkOffCLI` entry to the `HKCU\...\Run` registry key (no admin required)
121
- - **macOS**: Installs a launchd agent (`~/Library/LaunchAgents/app.forkoff.cli.plist`) with the explicit node binary path for nvm/fnm compatibility
122
-
123
- To disable automatic startup:
103
+ - **Windows**: Registry key (`HKCU\...\Run`)
104
+ - **macOS**: launchd agent (`~/Library/LaunchAgents/app.forkoff.cli.plist`)
124
105
 
125
106
  ```bash
126
- forkoff startup --disable
107
+ forkoff startup --disable # Disable auto-start
108
+ forkoff startup --enable # Re-enable
109
+ forkoff startup --status # Check registration
127
110
  ```
128
111
 
129
- Once disabled, `pair` and `connect` will not re-register startup. To re-enable:
112
+ ---
113
+
114
+ ## Security
130
115
 
131
- ```bash
132
- forkoff startup --enable
133
- ```
116
+ ForkOff uses end-to-end encryption (X25519 ECDH + XSalsa20-Poly1305) so the relay server never sees your code, prompts, or approvals &mdash; only opaque encrypted blobs routed between device UUIDs.
117
+
118
+ | Layer | Implementation |
119
+ |-------|---------------|
120
+ | **Key exchange** | X25519 ECDH with HKDF-SHA256 directional key derivation |
121
+ | **Authentication** | Ed25519 identity signatures on ephemeral keys (MITM protection) |
122
+ | **Encryption** | XSalsa20-Poly1305 authenticated encryption (NaCl secretbox) |
123
+ | **Identity** | TOFU (Trust On First Use) with key pinning |
124
+ | **Replay protection** | Per-peer monotonic message counters |
125
+ | **Session expiry** | Automatic re-key every 24 hours or 10,000 messages |
126
+ | **Key storage** | OS keychain (macOS Keychain, Windows Credential Manager, Linux libsecret) |
127
+ | **Enforcement** | 24 sensitive event types encrypted; plaintext fallback only when E2EE unavailable |
134
128
 
135
- Running `forkoff disconnect` also removes the startup registration.
129
+ No additional setup required &mdash; 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.
136
130
 
137
131
  ---
138
132
 
139
133
  ## Programmatic Usage
140
134
 
141
- Integrate ForkOff into your AI coding tools:
135
+ Integrate ForkOff into your own AI coding tools:
142
136
 
143
137
  ```typescript
144
138
  import { createIntegration } from 'forkoff';
145
139
 
146
140
  const forkoff = createIntegration();
147
-
148
- // Connect to ForkOff server
149
141
  await forkoff.connect();
150
142
 
151
- // Handle incoming messages from mobile app
152
- forkoff.onMessageReceived((sessionId, content, requestedBy) => {
153
- console.log(`Message from ${requestedBy}: ${content}`);
154
-
155
- // Send a response
156
- forkoff.sendMessage(sessionId, 'Processing your request...');
157
- });
158
-
159
- // Stream responses in real-time
160
- const stream = forkoff.startStreaming(sessionId);
161
- stream.write('Here is ');
162
- stream.write('a streaming ');
163
- stream.write('response.');
164
- stream.end();
165
-
166
143
  // Request approval for code changes
167
144
  const approval = await forkoff.requestApproval(
168
- sessionId,
169
- messageId,
170
- 'CODE_CHANGE',
171
- 'Add authentication middleware',
145
+ sessionId, messageId, 'CODE_CHANGE',
146
+ 'Add auth middleware',
172
147
  { filePath: 'src/middleware/auth.ts', diff: '...' }
173
148
  );
174
149
 
175
- if (approval.status === 'approved') {
176
- // Apply the changes
177
- }
178
-
179
- // Send terminal output
180
- forkoff.sendTerminalOutput(sessionId, '> npm install\n Done', 'stdout');
181
- forkoff.sendTerminalExit(sessionId, 0);
182
-
183
- // Update device status
184
- forkoff.setStatus('busy');
150
+ // Stream terminal output
151
+ forkoff.sendTerminalOutput(sessionId, '> npm install\nDone', 'stdout');
185
152
  ```
186
153
 
187
154
  ---
188
155
 
189
- ## Configuration
190
-
191
- Configuration files are stored at:
156
+ ## Configuration Files
192
157
 
193
158
  | Platform | Location |
194
159
  |----------|----------|
@@ -196,26 +161,22 @@ Configuration files are stored at:
196
161
  | **macOS** | `~/Library/Preferences/forkoff-cli/config.json` |
197
162
  | **Linux** | `~/.config/forkoff-cli/config.json` |
198
163
 
199
- ---
200
-
201
- ## Security
202
-
203
- Your data stays yours. All communication between the ForkOff CLI and your mobile device is protected with **end-to-end encryption (E2EE)**:
204
-
205
- - Messages, code, and commands are encrypted on-device before leaving your machine
206
- - The ForkOff server never sees your plaintext data — it only relays encrypted payloads
207
- - Each device pair establishes a unique encrypted channel using ephemeral key exchange
208
- - Session keys are derived per-connection, so even if one session is compromised, others remain secure
164
+ ## Development
209
165
 
210
- No additional setup required — E2EE is enabled automatically when you pair your device.
166
+ ```bash
167
+ git clone https://github.com/Forkoff-app/forkoff-cli.git
168
+ cd forkoff-cli
169
+ npm install
170
+ npm run dev # Run with ts-node
171
+ npm run build # Compile TypeScript
172
+ npm test # Run tests
173
+ ```
211
174
 
212
175
  ## Requirements
213
176
 
214
177
  - Node.js 18+
215
- - ForkOff mobile app
178
+ - [ForkOff mobile app](https://testflight.apple.com/join/dhh5FrN7) (iOS)
216
179
 
217
- ---
180
+ ## License
218
181
 
219
- <p align="center">
220
- Made with ❤️ by the ForkOff team
221
- </p>
182
+ [MIT](LICENSE)
@@ -0,0 +1,30 @@
1
+ import { EventEmitter } from 'events';
2
+ export interface CloudRelayOptions {
3
+ url: string;
4
+ deviceId: string;
5
+ deviceName: string;
6
+ relayToken?: string | null;
7
+ }
8
+ export declare class CloudRelayClient extends EventEmitter {
9
+ private socket;
10
+ private url;
11
+ private deviceId;
12
+ private deviceName;
13
+ private relayToken;
14
+ /** The pairing code the CLI generated — sent to relay for registration */
15
+ private currentPairingCode;
16
+ constructor(options: CloudRelayOptions);
17
+ /** Set the pairing code — will be registered with the relay on connect */
18
+ setPairingCode(code: string): void;
19
+ /** Connect to the cloud relay as a CLI client */
20
+ start(): Promise<void>;
21
+ /** Emit an event to the mobile client via the relay */
22
+ emitToMobile(event: string, data: any): void;
23
+ /** Check if mobile is connected (based on relay notifications) */
24
+ hasMobileConnection(): boolean;
25
+ /** Get the connected mobile device ID (set after pairing or mobile_connected) */
26
+ getMobileDeviceId(): string | null;
27
+ /** Graceful shutdown */
28
+ stop(): Promise<void>;
29
+ }
30
+ //# sourceMappingURL=cloud-client.d.ts.map
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CloudRelayClient = void 0;
4
+ /**
5
+ * Cloud relay client — CLI connects as a Socket.io CLIENT to the relay server
6
+ * (e.g., wss://api.forkoff.app). The relay routes events to/from the paired mobile.
7
+ * Same interface as EmbeddedRelayServer so WebSocketClient can use either.
8
+ */
9
+ const socket_io_client_1 = require("socket.io-client");
10
+ const events_1 = require("events");
11
+ const config_1 = require("./config");
12
+ /** Events the cloud relay forwards from mobile → CLI (same set as EmbeddedRelayServer) */
13
+ const MOBILE_EVENTS = [
14
+ 'terminal_command', 'terminal_create', 'user_message',
15
+ 'claude_start_session', 'claude_resume_session', 'claude_sessions_request',
16
+ 'directory_list', 'read_file', 'transcript_fetch', 'transcript_subscribe',
17
+ 'transcript_unsubscribe', 'approval_response', 'claude_approval_response',
18
+ 'permission_response', 'permission_rules_sync', 'claude_abort', 'tab_complete',
19
+ 'subscribe_device', 'unsubscribe_device',
20
+ 'sdk_session_history', 'usage_stats_request', 'session_settings_update',
21
+ 'transcript_subscribe_sdk',
22
+ // E2EE key exchange and encrypted messages from mobile
23
+ 'encrypted_key_exchange_init', 'encrypted_key_exchange_ack', 'encrypted_message',
24
+ ];
25
+ class CloudRelayClient extends events_1.EventEmitter {
26
+ constructor(options) {
27
+ super();
28
+ this.socket = null;
29
+ /** The pairing code the CLI generated — sent to relay for registration */
30
+ this.currentPairingCode = null;
31
+ this.url = options.url;
32
+ this.deviceId = options.deviceId;
33
+ this.deviceName = options.deviceName;
34
+ this.relayToken = options.relayToken ?? null;
35
+ }
36
+ /** Set the pairing code — will be registered with the relay on connect */
37
+ setPairingCode(code) {
38
+ this.currentPairingCode = code;
39
+ // If already connected, register immediately
40
+ if (this.socket?.connected) {
41
+ this.socket.emit('register_pairing_code', {
42
+ code,
43
+ deviceId: this.deviceId,
44
+ deviceName: this.deviceName,
45
+ platform: process.platform,
46
+ });
47
+ }
48
+ }
49
+ /** Connect to the cloud relay as a CLI client */
50
+ start() {
51
+ return new Promise((resolve, reject) => {
52
+ this.socket = (0, socket_io_client_1.io)(this.url, {
53
+ auth: {
54
+ clientType: 'cli',
55
+ deviceId: this.deviceId,
56
+ deviceName: this.deviceName,
57
+ platform: process.platform,
58
+ hostname: require('os').hostname(),
59
+ relayToken: this.relayToken,
60
+ userId: config_1.config.userId,
61
+ },
62
+ transports: ['websocket'],
63
+ reconnection: true,
64
+ reconnectionAttempts: Infinity,
65
+ reconnectionDelay: 1000,
66
+ reconnectionDelayMax: 15000,
67
+ timeout: 10000,
68
+ });
69
+ let resolved = false;
70
+ this.socket.on('connect', () => {
71
+ console.log(`[CloudRelay] Connected to ${this.url}`);
72
+ // Register pairing code if we have one
73
+ if (this.currentPairingCode) {
74
+ this.socket.emit('register_pairing_code', {
75
+ code: this.currentPairingCode,
76
+ deviceId: this.deviceId,
77
+ deviceName: this.deviceName,
78
+ platform: process.platform,
79
+ });
80
+ }
81
+ if (!resolved) {
82
+ resolved = true;
83
+ resolve();
84
+ }
85
+ });
86
+ this.socket.on('connect_error', (err) => {
87
+ console.error(`[CloudRelay] Connection error: ${err.message}`);
88
+ if (!resolved) {
89
+ resolved = true;
90
+ reject(new Error(`Failed to connect to cloud relay at ${this.url}: ${err.message}`));
91
+ }
92
+ });
93
+ this.socket.on('disconnect', (reason) => {
94
+ console.log(`[CloudRelay] Disconnected: ${reason}`);
95
+ });
96
+ // Handle cloud pairing flow: relay sends pair_device when mobile enters our code
97
+ this.socket.on('pair_device', (data) => {
98
+ console.log(`[CloudRelay] Pairing request received from mobile`);
99
+ // Emit internally so WebSocketClient can handle it
100
+ this.emit('pair_device', { mobileDeviceId: data.mobileDeviceId });
101
+ // Send ack back through relay — relay will forward to mobile with mobileRelayToken
102
+ this.socket.emit('pair_device_ack', {
103
+ deviceId: this.deviceId,
104
+ deviceName: this.deviceName,
105
+ platform: process.platform,
106
+ mobileDeviceId: data.mobileDeviceId,
107
+ pairId: data.pairId,
108
+ cliRelayToken: data.cliRelayToken,
109
+ });
110
+ // Store relay credentials locally
111
+ if (data.cliRelayToken) {
112
+ config_1.config.relayToken = data.cliRelayToken;
113
+ this.relayToken = data.cliRelayToken;
114
+ }
115
+ if (data.pairId) {
116
+ config_1.config.pairId = data.pairId;
117
+ }
118
+ });
119
+ // Handle mobile connected notification from relay
120
+ this.socket.on('mobile_connected', (data) => {
121
+ console.log(`[CloudRelay] Mobile connected: ${data.deviceId || 'unknown'}`);
122
+ this.emit('mobile_connected', { deviceId: data.deviceId || data.mobileDeviceId });
123
+ });
124
+ // Handle mobile disconnected notification from relay
125
+ this.socket.on('mobile_disconnected', (data) => {
126
+ console.log(`[CloudRelay] Mobile disconnected`);
127
+ this.emit('mobile_disconnected', { deviceId: data.deviceId, reason: data.reason || 'disconnected' });
128
+ });
129
+ // Forward all mobile events → internal EventEmitter (same as EmbeddedRelayServer)
130
+ for (const event of MOBILE_EVENTS) {
131
+ this.socket.on(event, (data) => {
132
+ this.emit(event, data);
133
+ });
134
+ }
135
+ });
136
+ }
137
+ /** Emit an event to the mobile client via the relay */
138
+ emitToMobile(event, data) {
139
+ if (this.socket?.connected) {
140
+ // Relay will route this to the paired mobile client
141
+ this.socket.emit(event, data);
142
+ }
143
+ }
144
+ /** Check if mobile is connected (based on relay notifications) */
145
+ hasMobileConnection() {
146
+ return this.socket?.connected ?? false;
147
+ }
148
+ /** Get the connected mobile device ID (set after pairing or mobile_connected) */
149
+ getMobileDeviceId() {
150
+ // The relay manages this — we don't track directly in cloud mode
151
+ return null;
152
+ }
153
+ /** Graceful shutdown */
154
+ stop() {
155
+ return new Promise((resolve) => {
156
+ if (this.socket) {
157
+ this.socket.disconnect();
158
+ this.socket = null;
159
+ }
160
+ resolve();
161
+ });
162
+ }
163
+ }
164
+ exports.CloudRelayClient = CloudRelayClient;
165
+ //# sourceMappingURL=cloud-client.js.map
package/dist/config.d.ts CHANGED
@@ -25,6 +25,12 @@ declare class Config {
25
25
  set startupBinaryPath(value: string | null);
26
26
  get relayPort(): number;
27
27
  set relayPort(value: number);
28
+ get relayMode(): 'cloud' | 'local';
29
+ set relayMode(value: 'cloud' | 'local');
30
+ get relayToken(): string | null;
31
+ set relayToken(value: string | null);
32
+ get pairId(): string | null;
33
+ set pairId(value: string | null);
28
34
  get isPaired(): boolean;
29
35
  ensureDeviceId(): string;
30
36
  getMachineId(): string;
package/dist/config.js CHANGED
@@ -87,6 +87,9 @@ const defaultConfig = {
87
87
  userId: null,
88
88
  startupEnabled: true,
89
89
  startupBinaryPath: null,
90
+ relayMode: 'cloud',
91
+ relayToken: null,
92
+ pairId: null,
90
93
  };
91
94
  class Config {
92
95
  constructor() {
@@ -221,6 +224,27 @@ class Config {
221
224
  this.data.relayPort = value;
222
225
  this.save();
223
226
  }
227
+ get relayMode() {
228
+ return this.data.relayMode;
229
+ }
230
+ set relayMode(value) {
231
+ this.data.relayMode = value;
232
+ this.save();
233
+ }
234
+ get relayToken() {
235
+ return this.data.relayToken;
236
+ }
237
+ set relayToken(value) {
238
+ this.data.relayToken = value;
239
+ this.save();
240
+ }
241
+ get pairId() {
242
+ return this.data.pairId;
243
+ }
244
+ set pairId(value) {
245
+ this.data.pairId = value;
246
+ this.save();
247
+ }
224
248
  get isPaired() {
225
249
  return !!this.deviceId && !!this.pairedAt;
226
250
  }
@@ -5,6 +5,8 @@ export declare class E2EEManager {
5
5
  private signingKeyPair;
6
6
  private initialized;
7
7
  private sessions;
8
+ private static readonly SESSION_MAX_AGE_MS;
9
+ private static readonly SESSION_MAX_MESSAGES;
8
10
  private static readonly MAX_PENDING_EXCHANGES;
9
11
  private static readonly PENDING_EXCHANGE_TTL_MS;
10
12
  private pendingExchanges;
@@ -19,6 +21,11 @@ export declare class E2EEManager {
19
21
  getSigningPublicKey(): string | null;
20
22
  /** Check if manager is initialized */
21
23
  isInitialized(): boolean;
24
+ /**
25
+ * Check if a session has expired (age or message count).
26
+ * Returns true if the session should be torn down and re-keyed.
27
+ */
28
+ isSessionExpired(deviceId: string): boolean;
22
29
  /**
23
30
  * Sign a key exchange payload with our Ed25519 identity key.
24
31
  * The signed message is: "prefix:senderDeviceId:ephemeralPublicKey[:recipientDeviceId]"
@@ -52,6 +59,7 @@ export declare class E2EEManager {
52
59
  handleKeyExchangeAck(ack: KeyExchangeAck): void;
53
60
  /**
54
61
  * Attempts to restore a persisted session after reconnection.
62
+ * If the session has expired, it is deleted from persistence and not restored.
55
63
  */
56
64
  restorePersistedSession(targetDeviceId: string): Promise<boolean>;
57
65
  /** Lists all devices with persisted sessions */