mobilecoder-mcp 1.0.5 → 2.0.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.
@@ -1,110 +0,0 @@
1
- import { exec } from 'child_process';
2
- import { promisify } from 'util';
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- import * as os from 'os';
6
-
7
- const execAsync = promisify(exec);
8
-
9
- export interface DetectedTool {
10
- id: string;
11
- name: string;
12
- version: string;
13
- path: string;
14
- isInstalled: boolean;
15
- }
16
-
17
- export class ToolDetector {
18
- async detectAll(): Promise<DetectedTool[]> {
19
- const tools = [
20
- this.checkClaude(),
21
- this.checkGemini(),
22
- this.checkQoder(),
23
- this.checkKiro(),
24
- this.checkAider(),
25
- this.checkCursor()
26
- ];
27
-
28
- const results = await Promise.all(tools);
29
- return results.filter(t => t.isInstalled);
30
- }
31
-
32
- private async checkCommand(command: string): Promise<string | null> {
33
- try {
34
- const { stdout } = await execAsync(`${command} --version`);
35
- return stdout.trim();
36
- } catch {
37
- return null;
38
- }
39
- }
40
-
41
- private async checkClaude(): Promise<DetectedTool> {
42
- const version = await this.checkCommand('claude');
43
- return {
44
- id: 'claude',
45
- name: 'Claude Code',
46
- version: version || '',
47
- path: '',
48
- isInstalled: !!version
49
- };
50
- }
51
-
52
- private async checkGemini(): Promise<DetectedTool> {
53
- const version = await this.checkCommand('gemini');
54
- return {
55
- id: 'gemini',
56
- name: 'Gemini CLI',
57
- version: version || '',
58
- path: '',
59
- isInstalled: !!version
60
- };
61
- }
62
-
63
- private async checkQoder(): Promise<DetectedTool> {
64
- const version = await this.checkCommand('qoder');
65
- return {
66
- id: 'qoder',
67
- name: 'Qoder',
68
- version: version || '',
69
- path: '',
70
- isInstalled: !!version
71
- };
72
- }
73
-
74
- private async checkKiro(): Promise<DetectedTool> {
75
- const version = await this.checkCommand('kiro');
76
- return {
77
- id: 'kiro',
78
- name: 'Kiro',
79
- version: version || '',
80
- path: '',
81
- isInstalled: !!version
82
- };
83
- }
84
-
85
- private async checkAider(): Promise<DetectedTool> {
86
- const version = await this.checkCommand('aider');
87
- return {
88
- id: 'aider',
89
- name: 'Aider',
90
- version: version || '',
91
- path: '',
92
- isInstalled: !!version
93
- };
94
- }
95
-
96
- private async checkCursor(): Promise<DetectedTool> {
97
- // Cursor is an app, not typically a CLI command for version checking in the same way
98
- // We check for config file existence as a proxy for "installed/configured"
99
- const configPath = path.join(os.homedir(), '.cursor', 'mcp.json');
100
- const isInstalled = fs.existsSync(configPath);
101
-
102
- return {
103
- id: 'mcp', // Maps to 'mcp' in ToolSelector
104
- name: 'Cursor',
105
- version: 'App',
106
- path: configPath,
107
- isInstalled
108
- };
109
- }
110
- }
package/src/webrtc.ts DELETED
@@ -1,369 +0,0 @@
1
- import SimplePeer from 'simple-peer';
2
- import { createRequire } from 'module';
3
- import { createHash } from 'crypto';
4
- const require = createRequire(import.meta.url);
5
-
6
- // wrtc modülü Node.js ortamında WebRTC desteği sağlar
7
- let wrtc: any;
8
-
9
- // Try loading wrtc implementations with better error logging
10
- try {
11
- // @roamhq/wrtc is often more stable on modern Node.js versions
12
- wrtc = require('@roamhq/wrtc');
13
- // console.log('✅ Loaded @roamhq/wrtc');
14
- } catch (e) {
15
- try {
16
- wrtc = require('wrtc');
17
- // console.log('✅ Loaded wrtc');
18
- } catch (e2) {
19
- console.warn('⚠️ wrtc module not available. Install @roamhq/wrtc or wrtc for WebRTC support.');
20
- }
21
- }
22
-
23
- // Simple HMAC implementation for Node.js
24
- function generateHMAC(data: string, secret: string): string {
25
- return createHash('sha256').update(data + secret).digest('hex');
26
- }
27
-
28
- // Validate SDP for security
29
- function validateSDP(sdp: string): boolean {
30
- if (!sdp || typeof sdp !== 'string') return false;
31
-
32
- // Basic sanity check
33
- if (sdp.length > 10000) return false;
34
-
35
- // Check for DTLS fingerprint (critical for encryption)
36
- if (!sdp.includes('a=fingerprint:')) {
37
- // console.warn('⚠️ [MCP] SDP missing DTLS fingerprint');
38
- // Still allow, as some implementations might differ, but warn
39
- }
40
-
41
- return true;
42
- }
43
-
44
- export class WebRTCConnection {
45
- private peer: SimplePeer.Instance | null = null;
46
- private code: string;
47
- private signalingUrl: string;
48
- private isConnected: boolean = false;
49
- private pollingInterval?: NodeJS.Timeout;
50
- private onMessageCallback?: (message: any) => void;
51
- private onConnectCallback?: () => void;
52
- private onDisconnectCallback?: () => void;
53
- private debug: boolean;
54
- private hmacSecret: string;
55
- private cleanupInterval?: NodeJS.Timeout;
56
- private messageNonce = new Set<string>();
57
-
58
- constructor(code: string, signalingUrl: string, debug: boolean = false) {
59
- this.code = code;
60
- this.signalingUrl = signalingUrl;
61
- this.debug = debug;
62
-
63
- // Generate HMAC secret for this session
64
- this.hmacSecret = createHash('sha256')
65
- .update(code + Date.now() + Math.random())
66
- .digest('hex');
67
-
68
- // Start cleanup interval
69
- this.startCleanup();
70
-
71
- // Validate signaling URL
72
- try {
73
- const url = new URL(signalingUrl);
74
- if (!url.protocol.startsWith('https') && url.hostname !== 'localhost' && url.hostname !== '127.0.0.1') {
75
- console.warn('⚠️ [MCP] Warning: Signaling URL should use HTTPS in production');
76
- }
77
- } catch (e) {
78
- console.error('❌ [MCP] Invalid signaling URL:', signalingUrl);
79
- }
80
- }
81
-
82
- private startCleanup(): void {
83
- this.cleanupInterval = setInterval(() => {
84
- // Clean up old nonces (prevent memory leak)
85
- if (this.messageNonce.size > 1000) {
86
- this.messageNonce.clear();
87
- }
88
- }, 60000); // Every minute
89
- }
90
-
91
-
92
- private async sendAnswerWithRetry(signal: any, attempts = 3): Promise<void> {
93
- const timestamp = new Date().toLocaleTimeString();
94
- const signalType = signal.type || (signal.candidate ? 'candidate' : 'answer');
95
-
96
- // Use trickle endpoint for ICE candidates, standard answer endpoint for SDP answer
97
- const endpoint = signal.candidate ? '/signaling/answer' : '/signaling/answer';
98
-
99
- if (this.debug) console.log(`[${timestamp}] 📤 [Desktop] Sending ${signalType} to server...`);
100
-
101
- for (let i = 0; i < attempts; i++) {
102
- try {
103
- const response = await fetch(`${this.signalingUrl}${endpoint}`, {
104
- method: 'POST',
105
- headers: { 'Content-Type': 'application/json' },
106
- body: JSON.stringify({ code: this.code, signal })
107
- });
108
-
109
- if (!response.ok) {
110
- throw new Error(`Server returned ${response.status}`);
111
- }
112
-
113
- if (this.debug) console.log(`[${timestamp}] ✅ [Desktop] ${signalType} sent.`);
114
- return;
115
- } catch (error) {
116
- console.error(`[${timestamp}] ❌ [Desktop] Failed to send ${signalType} (Attempt ${i + 1}/${attempts}):`, error);
117
- if (i < attempts - 1) {
118
- await new Promise(r => setTimeout(r, 1000)); // Wait 1s
119
- }
120
- }
121
- }
122
- console.error(`[${timestamp}] ❌ [Desktop] Failed to send ${signalType} after all attempts.`);
123
- }
124
-
125
- async connect(): Promise<void> {
126
- const timestamp = new Date().toLocaleTimeString();
127
- console.log(`[${timestamp}] 📡 Connecting to signaling server: ${this.signalingUrl}`);
128
- console.log(`[${timestamp}] 🔑 Using connection code: ${this.code}`);
129
-
130
- return new Promise((resolve, reject) => {
131
- try {
132
- if (!wrtc) {
133
- console.warn(`[${timestamp}] ⚠️ WebRTC initialized without Node.js native support (wrtc). Connection will fail.`);
134
- console.warn(`[${timestamp}] 💡 Please install @roamhq/wrtc or wrtc package.`);
135
- }
136
-
137
- // Fetch dynamic ICE servers with timeout
138
- const iceController = new AbortController();
139
- const iceTimeout = setTimeout(() => iceController.abort(), 3000);
140
-
141
- fetch(`${this.signalingUrl}/signaling/ice-servers`, { signal: iceController.signal })
142
- .then(res => res.json())
143
- .then((data: any) => {
144
- clearTimeout(iceTimeout);
145
- this.initPeer(data.iceServers, resolve, reject);
146
- })
147
- .catch(err => {
148
- clearTimeout(iceTimeout);
149
- console.warn(`[${timestamp}] ⚠️ Failed to fetch ICE servers (using fallback):`, err.message);
150
- // Fallback to Google STUN
151
- this.initPeer([{ urls: "stun:stun.l.google.com:19302" }], resolve, reject);
152
- });
153
- } catch (error) {
154
- console.error(`[${timestamp}] ❌ Initialization error:`, error);
155
- reject(error);
156
- }
157
- });
158
- }
159
-
160
- private initPeer(iceServers: any[], resolve: any, reject: any) {
161
- const timestamp = new Date().toLocaleTimeString();
162
-
163
- // Ensure iceServers is an array
164
- const validIceServers = Array.isArray(iceServers) ? iceServers : [{ urls: "stun:stun.l.google.com:19302" }];
165
-
166
- if (this.debug) {
167
- console.log(`[${timestamp}] 🧊 Using ICE servers:`, validIceServers.length);
168
- }
169
-
170
- const rtcConfig = {
171
- iceServers: validIceServers,
172
- iceCandidatePoolSize: 10,
173
- };
174
-
175
- try {
176
- this.peer = new SimplePeer({
177
- initiator: false,
178
- trickle: true,
179
- wrtc: wrtc,
180
- config: rtcConfig
181
- });
182
-
183
- this.setupPeerListeners(resolve, reject);
184
- console.log(`[${timestamp}] ⌛ Waiting for mobile device to initiate connection...`);
185
- this.startPollingForOffer();
186
- } catch (err: any) {
187
- console.error(`[${timestamp}] ❌ Failed to create SimplePeer:`, err);
188
- reject(err);
189
- }
190
- }
191
-
192
- private setupPeerListeners(resolve: any, reject: any) {
193
- if (!this.peer) return;
194
-
195
- // Monitor ICE connection state
196
- this.peer.on('iceConnectionStateChange', () => {
197
- const state = (this.peer as any)._pc?.iceConnectionState;
198
- const timestamp = new Date().toLocaleTimeString();
199
- if (this.debug) console.log(`[${timestamp}] 🔍 ICE Connection State: ${state}`);
200
-
201
- if (state === 'connected' || state === 'completed') {
202
- const pc = (this.peer as any)._pc;
203
- if (pc) {
204
- pc.getStats().then((stats: any) => {
205
- let usingRelay = false;
206
- stats.forEach((report: any) => {
207
- if (report.type === 'candidate-pair' && report.selected) {
208
- const localCandidate = stats.get(report.localCandidateId);
209
- if (localCandidate && localCandidate.candidateType === 'relay') usingRelay = true;
210
- }
211
- });
212
- if (usingRelay) console.log(`[${timestamp}] 🌐 TURN relay is being used (IP leak protected)`);
213
- else console.log(`[${timestamp}] ✅ Direct P2P connection established`);
214
- });
215
- }
216
- } else if (state === 'failed' || state === 'disconnected') {
217
- if (state === 'failed') console.error(`[${timestamp}] ❌ ICE connection failed`);
218
- this.isConnected = false;
219
- }
220
- });
221
-
222
- this.peer.on('signal', async (signal: any) => {
223
- // Send answer and trickle ICE candidates
224
- await this.sendAnswerWithRetry(signal);
225
- });
226
-
227
- this.peer.on('connect', () => {
228
- const timestamp = new Date().toLocaleTimeString();
229
- console.log(`\n[${timestamp}] ✨ [ONLINE] Connected to mobile device!`);
230
- this.isConnected = true;
231
-
232
- // Send immediate handshake to update UI
233
- this.send({ type: 'handshake', status: 'ready', timestamp: Date.now() });
234
-
235
- if (this.onConnectCallback) this.onConnectCallback();
236
- resolve();
237
- });
238
-
239
- this.peer.on('data', (data: any) => {
240
- try {
241
- const message = JSON.parse(data.toString());
242
- this.handleIncomingMessage(message);
243
- } catch (e) {
244
- // Silent fail for invalid JSON
245
- }
246
- });
247
-
248
- this.peer.on('error', (err: any) => {
249
- // Ignore routine errors during disconnect
250
- if (err.code === 'ERR_DATA_CHANNEL') return;
251
- console.error('❌ WebRTC error:', err.message || err);
252
- this.isConnected = false;
253
- });
254
-
255
- this.peer.on('close', () => {
256
- console.log('❌ Connection closed');
257
- this.isConnected = false;
258
- });
259
- }
260
-
261
- private handleIncomingMessage(message: any): void {
262
- // Validate message structure
263
- if (!message || typeof message !== 'object') {
264
- return;
265
- }
266
-
267
- // Ping/Pong for Keepalive
268
- if (message.type === 'ping') {
269
- this.send({ type: 'pong', timestamp: Date.now() });
270
- return;
271
- }
272
-
273
- // Check for nonce to prevent replay attacks
274
- if (message.nonce) {
275
- if (this.messageNonce.has(message.nonce)) {
276
- return;
277
- }
278
- this.messageNonce.add(message.nonce);
279
- }
280
-
281
- if (this.onMessageCallback) {
282
- this.onMessageCallback(message);
283
- }
284
- }
285
-
286
- private async startPollingForOffer(): Promise<void> {
287
- let pollCount = 0;
288
- const poll = async () => {
289
- try {
290
- // Stop if peer is connected or destroyed
291
- if (this.peer && (this.peer as any).connected) {
292
- if (this.pollingInterval) {
293
- clearInterval(this.pollingInterval);
294
- this.pollingInterval = undefined;
295
- }
296
- return;
297
- }
298
-
299
- pollCount++;
300
- const response = await fetch(`${this.signalingUrl}/signaling/poll?code=${this.code}`);
301
-
302
- if (response.ok) {
303
- const data = await response.json() as { signal?: any };
304
- if (data.signal && this.peer) {
305
- // console.log(`[Desktop] Received signal`);
306
- this.peer.signal(data.signal);
307
- }
308
- }
309
- } catch (error) {
310
- // Ignore polling errors
311
- }
312
- };
313
-
314
- // Poll every 1 second
315
- this.pollingInterval = setInterval(poll, 1000);
316
- poll();
317
- }
318
-
319
- send(message: any): void {
320
- if (this.peer && this.isConnected) {
321
- try {
322
- this.peer.send(JSON.stringify(message));
323
- } catch (error) {
324
- // Ignore send errors
325
- }
326
- }
327
- }
328
-
329
- onMessage(callback: (message: any) => void): void {
330
- this.onMessageCallback = callback;
331
- }
332
-
333
- onConnect(callback: () => void): void {
334
- this.onConnectCallback = callback;
335
- }
336
-
337
- onDisconnect(callback: () => void): void {
338
- this.onDisconnectCallback = callback;
339
- }
340
-
341
- disconnect(): void {
342
- if (this.pollingInterval) {
343
- clearInterval(this.pollingInterval);
344
- }
345
- if (this.cleanupInterval) {
346
- clearInterval(this.cleanupInterval);
347
- }
348
- if (this.peer) {
349
- this.peer.removeAllListeners();
350
- this.peer.destroy();
351
- this.peer = null;
352
- }
353
- this.isConnected = false;
354
- this.messageNonce.clear();
355
- }
356
-
357
- // Cleanup method for proper resource disposal
358
- destroy(): void {
359
- this.disconnect();
360
- this.onMessageCallback = undefined;
361
- this.onConnectCallback = undefined;
362
- this.onDisconnectCallback = undefined;
363
- }
364
-
365
- getConnected(): boolean {
366
- return this.isConnected;
367
- }
368
- }
369
-