forkoff 1.0.17 → 1.0.19

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 (158) hide show
  1. package/LICENSE +11 -7
  2. package/README.md +77 -118
  3. package/dist/approval.d.ts +1 -0
  4. package/dist/approval.js +9 -0
  5. package/dist/config.d.ts +3 -0
  6. package/dist/config.js +62 -16
  7. package/dist/crypto/e2eeManager.d.ts +49 -52
  8. package/dist/crypto/e2eeManager.js +256 -181
  9. package/dist/crypto/encryption.d.ts +8 -10
  10. package/dist/crypto/encryption.js +29 -94
  11. package/dist/crypto/index.d.ts +10 -0
  12. package/dist/crypto/index.js +22 -0
  13. package/dist/crypto/keyExchange.d.ts +6 -20
  14. package/dist/crypto/keyExchange.js +18 -110
  15. package/dist/crypto/keyGeneration.d.ts +2 -13
  16. package/dist/crypto/keyGeneration.js +14 -88
  17. package/dist/crypto/keyStorage.d.ts +32 -5
  18. package/dist/crypto/keyStorage.js +152 -8
  19. package/dist/crypto/sessionPersistence.d.ts +7 -13
  20. package/dist/crypto/sessionPersistence.js +108 -33
  21. package/dist/crypto/types.d.ts +24 -3
  22. package/dist/crypto/types.js +2 -1
  23. package/dist/crypto/websocketE2EE.d.ts +6 -17
  24. package/dist/crypto/websocketE2EE.js +21 -38
  25. package/dist/index.js +203 -280
  26. package/dist/integration.d.ts +0 -1
  27. package/dist/integration.js +2 -4
  28. package/dist/logger.d.ts +15 -0
  29. package/dist/logger.js +209 -1
  30. package/dist/server.d.ts +30 -0
  31. package/dist/server.js +162 -0
  32. package/dist/startup.js +15 -6
  33. package/dist/terminal.d.ts +1 -0
  34. package/dist/terminal.js +94 -1
  35. package/dist/tools/claude-process.d.ts +8 -0
  36. package/dist/tools/claude-process.js +199 -26
  37. package/dist/tools/claude-sessions.d.ts +1 -0
  38. package/dist/tools/claude-sessions.js +36 -10
  39. package/dist/tools/detector.js +11 -3
  40. package/dist/tools/permission-hook.js +94 -27
  41. package/dist/tools/permission-ipc.d.ts +1 -0
  42. package/dist/tools/permission-ipc.js +61 -14
  43. package/dist/transcript-streamer.d.ts +1 -0
  44. package/dist/transcript-streamer.js +18 -4
  45. package/dist/usage-tracker.d.ts +45 -0
  46. package/dist/usage-tracker.js +243 -0
  47. package/dist/websocket.d.ts +43 -12
  48. package/dist/websocket.js +418 -214
  49. package/package.json +5 -4
  50. package/dist/__tests__/cli-commands.test.d.ts +0 -6
  51. package/dist/__tests__/cli-commands.test.d.ts.map +0 -1
  52. package/dist/__tests__/cli-commands.test.js +0 -213
  53. package/dist/__tests__/cli-commands.test.js.map +0 -1
  54. package/dist/__tests__/crypto/e2e-integration.test.d.ts +0 -17
  55. package/dist/__tests__/crypto/e2e-integration.test.d.ts.map +0 -1
  56. package/dist/__tests__/crypto/e2e-integration.test.js +0 -338
  57. package/dist/__tests__/crypto/e2e-integration.test.js.map +0 -1
  58. package/dist/__tests__/crypto/e2eeManager.test.d.ts +0 -2
  59. package/dist/__tests__/crypto/e2eeManager.test.d.ts.map +0 -1
  60. package/dist/__tests__/crypto/e2eeManager.test.js +0 -242
  61. package/dist/__tests__/crypto/e2eeManager.test.js.map +0 -1
  62. package/dist/__tests__/crypto/encryption.test.d.ts +0 -2
  63. package/dist/__tests__/crypto/encryption.test.d.ts.map +0 -1
  64. package/dist/__tests__/crypto/encryption.test.js +0 -116
  65. package/dist/__tests__/crypto/encryption.test.js.map +0 -1
  66. package/dist/__tests__/crypto/keyExchange.test.d.ts +0 -2
  67. package/dist/__tests__/crypto/keyExchange.test.d.ts.map +0 -1
  68. package/dist/__tests__/crypto/keyExchange.test.js +0 -84
  69. package/dist/__tests__/crypto/keyExchange.test.js.map +0 -1
  70. package/dist/__tests__/crypto/keyGeneration.test.d.ts +0 -2
  71. package/dist/__tests__/crypto/keyGeneration.test.d.ts.map +0 -1
  72. package/dist/__tests__/crypto/keyGeneration.test.js +0 -61
  73. package/dist/__tests__/crypto/keyGeneration.test.js.map +0 -1
  74. package/dist/__tests__/crypto/keyStorage.test.d.ts +0 -2
  75. package/dist/__tests__/crypto/keyStorage.test.d.ts.map +0 -1
  76. package/dist/__tests__/crypto/keyStorage.test.js +0 -133
  77. package/dist/__tests__/crypto/keyStorage.test.js.map +0 -1
  78. package/dist/__tests__/crypto/websocketIntegration.test.d.ts +0 -2
  79. package/dist/__tests__/crypto/websocketIntegration.test.d.ts.map +0 -1
  80. package/dist/__tests__/crypto/websocketIntegration.test.js +0 -259
  81. package/dist/__tests__/crypto/websocketIntegration.test.js.map +0 -1
  82. package/dist/__tests__/startup.test.d.ts +0 -11
  83. package/dist/__tests__/startup.test.d.ts.map +0 -1
  84. package/dist/__tests__/startup.test.js +0 -241
  85. package/dist/__tests__/startup.test.js.map +0 -1
  86. package/dist/__tests__/tools/claude-process.test.d.ts +0 -8
  87. package/dist/__tests__/tools/claude-process.test.d.ts.map +0 -1
  88. package/dist/__tests__/tools/claude-process.test.js +0 -430
  89. package/dist/__tests__/tools/claude-process.test.js.map +0 -1
  90. package/dist/__tests__/tools/permission-hook.test.d.ts +0 -17
  91. package/dist/__tests__/tools/permission-hook.test.d.ts.map +0 -1
  92. package/dist/__tests__/tools/permission-hook.test.js +0 -616
  93. package/dist/__tests__/tools/permission-hook.test.js.map +0 -1
  94. package/dist/__tests__/tools/permission-ipc.test.d.ts +0 -11
  95. package/dist/__tests__/tools/permission-ipc.test.d.ts.map +0 -1
  96. package/dist/__tests__/tools/permission-ipc.test.js +0 -612
  97. package/dist/__tests__/tools/permission-ipc.test.js.map +0 -1
  98. package/dist/__tests__/websocket.test.d.ts +0 -13
  99. package/dist/__tests__/websocket.test.d.ts.map +0 -1
  100. package/dist/__tests__/websocket.test.js +0 -204
  101. package/dist/__tests__/websocket.test.js.map +0 -1
  102. package/dist/api.d.ts +0 -44
  103. package/dist/api.d.ts.map +0 -1
  104. package/dist/api.js +0 -76
  105. package/dist/api.js.map +0 -1
  106. package/dist/approval.d.ts.map +0 -1
  107. package/dist/approval.js.map +0 -1
  108. package/dist/config.d.ts.map +0 -1
  109. package/dist/config.js.map +0 -1
  110. package/dist/crypto/e2eeManager.d.ts.map +0 -1
  111. package/dist/crypto/e2eeManager.js.map +0 -1
  112. package/dist/crypto/encryption.d.ts.map +0 -1
  113. package/dist/crypto/encryption.js.map +0 -1
  114. package/dist/crypto/keyExchange.d.ts.map +0 -1
  115. package/dist/crypto/keyExchange.js.map +0 -1
  116. package/dist/crypto/keyGeneration.d.ts.map +0 -1
  117. package/dist/crypto/keyGeneration.js.map +0 -1
  118. package/dist/crypto/keyStorage.d.ts.map +0 -1
  119. package/dist/crypto/keyStorage.js.map +0 -1
  120. package/dist/crypto/sessionPersistence.d.ts.map +0 -1
  121. package/dist/crypto/sessionPersistence.js.map +0 -1
  122. package/dist/crypto/types.d.ts.map +0 -1
  123. package/dist/crypto/types.js.map +0 -1
  124. package/dist/crypto/websocketE2EE.d.ts.map +0 -1
  125. package/dist/crypto/websocketE2EE.js.map +0 -1
  126. package/dist/index.d.ts.map +0 -1
  127. package/dist/index.js.map +0 -1
  128. package/dist/integration.d.ts.map +0 -1
  129. package/dist/integration.js.map +0 -1
  130. package/dist/logger.d.ts.map +0 -1
  131. package/dist/logger.js.map +0 -1
  132. package/dist/startup.d.ts.map +0 -1
  133. package/dist/startup.js.map +0 -1
  134. package/dist/terminal.d.ts.map +0 -1
  135. package/dist/terminal.js.map +0 -1
  136. package/dist/tools/__tests__/claude-sessions.test.d.ts +0 -2
  137. package/dist/tools/__tests__/claude-sessions.test.d.ts.map +0 -1
  138. package/dist/tools/__tests__/claude-sessions.test.js +0 -306
  139. package/dist/tools/__tests__/claude-sessions.test.js.map +0 -1
  140. package/dist/tools/claude-hooks.d.ts.map +0 -1
  141. package/dist/tools/claude-hooks.js.map +0 -1
  142. package/dist/tools/claude-process.d.ts.map +0 -1
  143. package/dist/tools/claude-process.js.map +0 -1
  144. package/dist/tools/claude-sessions.d.ts.map +0 -1
  145. package/dist/tools/claude-sessions.js.map +0 -1
  146. package/dist/tools/detector.d.ts.map +0 -1
  147. package/dist/tools/detector.js.map +0 -1
  148. package/dist/tools/index.d.ts.map +0 -1
  149. package/dist/tools/index.js.map +0 -1
  150. package/dist/tools/permission-hook.d.ts.map +0 -1
  151. package/dist/tools/permission-hook.js.map +0 -1
  152. package/dist/tools/permission-ipc.d.ts.map +0 -1
  153. package/dist/tools/permission-ipc.js.map +0 -1
  154. package/dist/transcript-streamer.d.ts.map +0 -1
  155. package/dist/transcript-streamer.js.map +0 -1
  156. package/dist/websocket.d.ts.map +0 -1
  157. package/dist/websocket.js.map +0 -1
  158. package/jest.config.js +0 -18
@@ -0,0 +1,243 @@
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.UsageTracker = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const os = __importStar(require("os"));
40
+ function getConfigDir() {
41
+ return process.platform === 'win32'
42
+ ? path.join(process.env.APPDATA || os.homedir(), 'forkoff-cli')
43
+ : path.join(os.homedir(), '.config', 'forkoff-cli');
44
+ }
45
+ function getTodayKey() {
46
+ return new Date().toISOString().split('T')[0]; // "YYYY-MM-DD"
47
+ }
48
+ class UsageTracker {
49
+ constructor() {
50
+ this.saveTimer = null;
51
+ this.dirty = false;
52
+ const configDir = getConfigDir();
53
+ if (!fs.existsSync(configDir)) {
54
+ fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
55
+ }
56
+ this.filePath = path.join(configDir, 'usage.json');
57
+ this.data = this.load();
58
+ }
59
+ load() {
60
+ try {
61
+ if (fs.existsSync(this.filePath)) {
62
+ return JSON.parse(fs.readFileSync(this.filePath, 'utf-8'));
63
+ }
64
+ }
65
+ catch {
66
+ // Corrupted file — start fresh
67
+ }
68
+ return {
69
+ daily: {},
70
+ totalInputTokens: 0,
71
+ totalOutputTokens: 0,
72
+ totalSessionCount: 0,
73
+ activeDays: [],
74
+ };
75
+ }
76
+ scheduleSave() {
77
+ this.dirty = true;
78
+ if (this.saveTimer)
79
+ return;
80
+ this.saveTimer = setTimeout(() => {
81
+ this.saveTimer = null;
82
+ if (this.dirty) {
83
+ this.saveNow();
84
+ }
85
+ }, 2000);
86
+ }
87
+ saveNow() {
88
+ try {
89
+ const tmpFile = this.filePath + '.tmp.' + process.pid;
90
+ fs.writeFileSync(tmpFile, JSON.stringify(this.data, null, 2), { encoding: 'utf-8', mode: 0o600 });
91
+ fs.renameSync(tmpFile, this.filePath);
92
+ this.dirty = false;
93
+ }
94
+ catch (err) {
95
+ console.error(`[UsageTracker] Failed to save: ${err.message}`);
96
+ }
97
+ }
98
+ ensureDay(dateKey) {
99
+ if (!this.data.daily[dateKey]) {
100
+ this.data.daily[dateKey] = { inputTokens: 0, outputTokens: 0, sessionCount: 0 };
101
+ if (!this.data.activeDays.includes(dateKey)) {
102
+ this.data.activeDays.push(dateKey);
103
+ this.data.activeDays.sort();
104
+ }
105
+ }
106
+ return this.data.daily[dateKey];
107
+ }
108
+ recordUsage(inputTokens, outputTokens) {
109
+ const today = getTodayKey();
110
+ const day = this.ensureDay(today);
111
+ day.inputTokens += inputTokens;
112
+ day.outputTokens += outputTokens;
113
+ this.data.totalInputTokens += inputTokens;
114
+ this.data.totalOutputTokens += outputTokens;
115
+ this.scheduleSave();
116
+ }
117
+ recordSessionStart() {
118
+ const today = getTodayKey();
119
+ const day = this.ensureDay(today);
120
+ day.sessionCount += 1;
121
+ this.data.totalSessionCount += 1;
122
+ this.scheduleSave();
123
+ }
124
+ /**
125
+ * Get usage stats matching the mobile UsageStats type.
126
+ */
127
+ getUsageStats(period = 'all') {
128
+ const now = new Date();
129
+ let startDate = null;
130
+ if (period === 'day') {
131
+ startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
132
+ }
133
+ else if (period === 'week') {
134
+ startDate = new Date(now);
135
+ startDate.setDate(startDate.getDate() - 7);
136
+ }
137
+ else if (period === 'month') {
138
+ startDate = new Date(now);
139
+ startDate.setMonth(startDate.getMonth() - 1);
140
+ }
141
+ let inputTokens = 0;
142
+ let outputTokens = 0;
143
+ let sessionCount = 0;
144
+ if (!startDate) {
145
+ // 'all' — use totals directly
146
+ inputTokens = this.data.totalInputTokens;
147
+ outputTokens = this.data.totalOutputTokens;
148
+ sessionCount = this.data.totalSessionCount;
149
+ }
150
+ else {
151
+ const startKey = startDate.toISOString().split('T')[0];
152
+ for (const [dateKey, day] of Object.entries(this.data.daily)) {
153
+ if (dateKey >= startKey) {
154
+ inputTokens += day.inputTokens;
155
+ outputTokens += day.outputTokens;
156
+ sessionCount += day.sessionCount;
157
+ }
158
+ }
159
+ }
160
+ const totalTokens = inputTokens + outputTokens;
161
+ // Rough cost estimate: $3/M input, $15/M output (Claude Sonnet pricing)
162
+ const estimatedCostUsd = (inputTokens * 3 + outputTokens * 15) / 1000000;
163
+ return {
164
+ totalInputTokens: String(inputTokens),
165
+ totalOutputTokens: String(outputTokens),
166
+ totalTokens: String(totalTokens),
167
+ totalSessionCount: sessionCount,
168
+ estimatedCostUsd: Math.round(estimatedCostUsd * 100) / 100,
169
+ period,
170
+ };
171
+ }
172
+ /**
173
+ * Get daily usage matching the mobile TokenUsageDaily[] type.
174
+ */
175
+ getDailyUsage(startDate, endDate) {
176
+ const result = [];
177
+ const sortedDays = Object.keys(this.data.daily).sort();
178
+ for (const dateKey of sortedDays) {
179
+ if (startDate && dateKey < startDate)
180
+ continue;
181
+ if (endDate && dateKey > endDate)
182
+ continue;
183
+ const day = this.data.daily[dateKey];
184
+ const total = day.inputTokens + day.outputTokens;
185
+ const cost = (day.inputTokens * 3 + day.outputTokens * 15) / 1000000;
186
+ result.push({
187
+ date: dateKey,
188
+ inputTokens: String(day.inputTokens),
189
+ outputTokens: String(day.outputTokens),
190
+ totalTokens: String(total),
191
+ sessionCount: day.sessionCount,
192
+ estimatedCostUsd: Math.round(cost * 100) / 100,
193
+ });
194
+ }
195
+ return result;
196
+ }
197
+ /**
198
+ * Get streak info matching the mobile StreakInfo type.
199
+ */
200
+ getStreakInfo() {
201
+ const activeDays = this.data.activeDays;
202
+ const totalActiveDays = activeDays.length;
203
+ if (totalActiveDays === 0) {
204
+ return { currentStreak: 0, totalActiveDays: 0 };
205
+ }
206
+ // Calculate current streak from today backwards
207
+ const today = getTodayKey();
208
+ let currentStreak = 0;
209
+ let checkDate = new Date(today + 'T00:00:00');
210
+ // Allow today or yesterday as the start of the streak
211
+ const todayInList = activeDays.includes(today);
212
+ if (!todayInList) {
213
+ checkDate.setDate(checkDate.getDate() - 1);
214
+ const yesterday = checkDate.toISOString().split('T')[0];
215
+ if (!activeDays.includes(yesterday)) {
216
+ return { currentStreak: 0, totalActiveDays };
217
+ }
218
+ }
219
+ while (true) {
220
+ const key = checkDate.toISOString().split('T')[0];
221
+ if (activeDays.includes(key)) {
222
+ currentStreak++;
223
+ checkDate.setDate(checkDate.getDate() - 1);
224
+ }
225
+ else {
226
+ break;
227
+ }
228
+ }
229
+ return { currentStreak, totalActiveDays };
230
+ }
231
+ /** Flush pending writes immediately (call before exit). */
232
+ flush() {
233
+ if (this.saveTimer) {
234
+ clearTimeout(this.saveTimer);
235
+ this.saveTimer = null;
236
+ }
237
+ if (this.dirty) {
238
+ this.saveNow();
239
+ }
240
+ }
241
+ }
242
+ exports.UsageTracker = UsageTracker;
243
+ //# sourceMappingURL=usage-tracker.js.map
@@ -1,8 +1,11 @@
1
1
  import { EventEmitter } from 'events';
2
+ import { E2EEManager } from './crypto/e2eeManager';
3
+ import type { UsageTracker } from './usage-tracker';
2
4
  type DeviceStatus = 'online' | 'offline' | 'busy' | 'syncing';
3
5
  interface ClaudeSessionUpdate {
4
6
  sessionKey: string;
5
7
  directory: string;
8
+ name?: string;
6
9
  state: 'active' | 'inactive';
7
10
  lastUsedAt: string;
8
11
  transcriptPath?: string;
@@ -28,20 +31,52 @@ interface ClaudeApprovalRequest {
28
31
  context: string[];
29
32
  options: string[];
30
33
  promptText: string;
34
+ toolName?: string;
35
+ toolInput?: any;
31
36
  }
32
37
  export declare class WebSocketClient extends EventEmitter {
33
- private socket;
34
- private reconnectAttempts;
38
+ private server;
35
39
  private heartbeatInterval;
36
40
  private _sessionId;
41
+ private e2eeManager;
42
+ private e2eeInitialized;
43
+ private e2eePeerDeviceId;
44
+ private pendingSensitiveMessages;
45
+ private static readonly SENSITIVE_QUEUE_TTL_MS;
46
+ private static readonly MAX_PENDING_SENSITIVE;
47
+ private usageTracker;
37
48
  get sessionId(): string;
38
- connect(): Promise<void>;
49
+ /** Start the embedded relay server and wire up event forwarding */
50
+ startServer(port: number): Promise<void>;
51
+ /** Set pairing code on the embedded server for in-process validation */
52
+ setPairingCode(code: string): void;
53
+ /**
54
+ * Initialize E2EE manager: generate/load keys.
55
+ * Called automatically on startServer. Non-blocking.
56
+ */
57
+ private initE2EE;
58
+ /**
59
+ * Send a sensitive event: encrypt if E2EE session exists with the target device.
60
+ * For events in ENFORCED_SENSITIVE_EVENTS, plaintext fallback is REFUSED — messages
61
+ * are queued until E2EE session establishes, or dropped after timeout.
62
+ */
63
+ emitSensitive(event: string, data: any, targetDeviceId?: string): void;
64
+ /**
65
+ * Flush queued sensitive messages now that E2EE session is established.
66
+ * Drops messages older than SENSITIVE_QUEUE_TTL_MS.
67
+ */
68
+ private flushSensitiveQueue;
69
+ /** Get the E2EE manager (for external key exchange initiation) */
70
+ getE2EEManager(): E2EEManager | null;
71
+ /** Check if E2EE session is active with a device */
72
+ isE2EEActive(deviceId: string): boolean;
73
+ /** SECURITY: Check if plaintext inbound events should be dropped (E2EE active with peer) */
74
+ private shouldDropPlaintextInbound;
39
75
  disconnect(): void;
40
76
  private startHeartbeat;
41
77
  private stopHeartbeat;
42
78
  sendHeartbeat(status?: DeviceStatus): void;
43
79
  updateStatus(status: DeviceStatus): void;
44
- setSyncing(syncing: boolean): void;
45
80
  sendClaudeSessionUpdate(session: ClaudeSessionUpdate): void;
46
81
  sendClaudeSessions(sessions: ClaudeSessionUpdate[]): void;
47
82
  sendToolStatusUpdate(toolType: string, status: 'active' | 'inactive' | 'error'): void;
@@ -95,14 +130,6 @@ export declare class WebSocketClient extends EventEmitter {
95
130
  sessionKey: string;
96
131
  entry: TranscriptEntry;
97
132
  }): void;
98
- sendRpcResponse(data: {
99
- requestId: string;
100
- result?: any;
101
- error?: {
102
- code: number;
103
- message: string;
104
- };
105
- }): void;
106
133
  sendClaudeApprovalRequest(data: ClaudeApprovalRequest): void;
107
134
  sendToolActivity(data: {
108
135
  terminalSessionId: string;
@@ -164,6 +191,10 @@ export declare class WebSocketClient extends EventEmitter {
164
191
  ephemeralPublicKey: string;
165
192
  }): void;
166
193
  emitEncryptedMessage(data: any): void;
194
+ /** Set the usage tracker instance (called from index.ts after instantiation) */
195
+ setUsageTracker(tracker: UsageTracker): void;
196
+ /** Send all usage stats to mobile (called after E2EE established and on request) */
197
+ sendAllUsageStats(): void;
167
198
  sendClaudeSessionEvent(data: {
168
199
  sessionKey: string;
169
200
  event: {