@hmduc16031996/claude-mb-bridge 2.4.3 → 2.4.5
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/dist/index.js +4 -3
- package/dist/server.d.ts +1 -1
- package/dist/server.js +23 -9
- package/package.json +1 -1
- package/public/app.js +33 -0
- package/public/index.html +2 -2
package/dist/index.js
CHANGED
|
@@ -6,13 +6,14 @@ const program = new Command();
|
|
|
6
6
|
program
|
|
7
7
|
.name('claude-mobile-bridge')
|
|
8
8
|
.description('Bridge Claude Code CLI to mobile via WebView')
|
|
9
|
-
.version('2.4.
|
|
9
|
+
.version('2.4.5')
|
|
10
10
|
.option('--token <token>', 'Pairing token from mobile app')
|
|
11
11
|
.option('--server <url>', 'Backend server URL', 'http://127.0.0.1:3110')
|
|
12
12
|
.option('--path <path>', 'Working directory', process.cwd())
|
|
13
13
|
.option('--port <port>', 'Local port for terminal server', '38473')
|
|
14
|
+
.option('--ide <type>', 'IDE type: claude_code or cursor', 'claude_code')
|
|
14
15
|
.action(async (options) => {
|
|
15
|
-
const { token, server, path, port } = options;
|
|
16
|
+
const { token, server, path, port, ide } = options;
|
|
16
17
|
if (!token) {
|
|
17
18
|
console.error('Error: --token is required');
|
|
18
19
|
process.exit(1);
|
|
@@ -33,7 +34,7 @@ program
|
|
|
33
34
|
// 1. Start local terminal server
|
|
34
35
|
console.log('📦 Starting terminal server...');
|
|
35
36
|
const localPort = parseInt(port, 10);
|
|
36
|
-
const { server: terminalServer, actualPort } = await startTerminalServer(localPort, path, token);
|
|
37
|
+
const { server: terminalServer, actualPort } = await startTerminalServer(localPort, path, token, ide);
|
|
37
38
|
console.log(`✅ Terminal server started on port ${actualPort}`);
|
|
38
39
|
// 2. Start Cloudflare Tunnel
|
|
39
40
|
console.log('🌐 Establishing secure tunnel...');
|
package/dist/server.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare function startTerminalServer(port: number, workingDir: string, terminalToken: string, onDisconnect?: () => void): Promise<{
|
|
1
|
+
export declare function startTerminalServer(port: number, workingDir: string, terminalToken: string, ide?: string, onDisconnect?: () => void): Promise<{
|
|
2
2
|
server: any;
|
|
3
3
|
actualPort: number;
|
|
4
4
|
}>;
|
package/dist/server.js
CHANGED
|
@@ -12,12 +12,14 @@ const pty = require('node-pty');
|
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
class Session {
|
|
14
14
|
term;
|
|
15
|
+
ide;
|
|
15
16
|
id;
|
|
16
17
|
cwd;
|
|
17
18
|
createdAt;
|
|
18
|
-
constructor(id, cwd, shell) {
|
|
19
|
+
constructor(id, cwd, shell, ide = 'claude_code') {
|
|
19
20
|
this.id = id;
|
|
20
21
|
this.cwd = cwd;
|
|
22
|
+
this.ide = ide;
|
|
21
23
|
this.createdAt = new Date().toISOString();
|
|
22
24
|
this.term = pty.spawn(shell, [], {
|
|
23
25
|
name: 'xterm-256color',
|
|
@@ -30,7 +32,9 @@ class Session {
|
|
|
30
32
|
FORCE_COLOR: '1',
|
|
31
33
|
}
|
|
32
34
|
});
|
|
33
|
-
|
|
35
|
+
// Map IDE type to command
|
|
36
|
+
const command = ide === 'cursor' ? 'agent' : (ide === 'gemini' ? 'gemini' : 'claude');
|
|
37
|
+
this.term.write(`${command}\r`);
|
|
34
38
|
}
|
|
35
39
|
getInfo() {
|
|
36
40
|
return {
|
|
@@ -51,7 +55,7 @@ class Session {
|
|
|
51
55
|
}
|
|
52
56
|
class SessionManager {
|
|
53
57
|
sessions = new Map();
|
|
54
|
-
createSession(cwd) {
|
|
58
|
+
createSession(cwd, ide = 'claude_code') {
|
|
55
59
|
const id = randomUUID().slice(0, 8);
|
|
56
60
|
let shell = '/bin/bash';
|
|
57
61
|
if (process.platform === 'win32') {
|
|
@@ -60,7 +64,7 @@ class SessionManager {
|
|
|
60
64
|
else if (process.platform === 'darwin') {
|
|
61
65
|
shell = fs.existsSync('/bin/zsh') ? '/bin/zsh' : '/bin/bash';
|
|
62
66
|
}
|
|
63
|
-
const session = new Session(id, cwd, shell);
|
|
67
|
+
const session = new Session(id, cwd, shell, ide);
|
|
64
68
|
this.sessions.set(id, session);
|
|
65
69
|
return session;
|
|
66
70
|
}
|
|
@@ -78,7 +82,7 @@ class SessionManager {
|
|
|
78
82
|
return Array.from(this.sessions.values()).map(s => s.getInfo());
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
|
-
export function startTerminalServer(port, workingDir, terminalToken, onDisconnect) {
|
|
85
|
+
export function startTerminalServer(port, workingDir, terminalToken, ide = 'claude_code', onDisconnect) {
|
|
82
86
|
const app = express();
|
|
83
87
|
const server = createServer(app);
|
|
84
88
|
const wss = new WebSocketServer({ server });
|
|
@@ -88,6 +92,16 @@ export function startTerminalServer(port, workingDir, terminalToken, onDisconnec
|
|
|
88
92
|
app.get('/health', (req, res) => {
|
|
89
93
|
res.json({ status: 'ok' });
|
|
90
94
|
});
|
|
95
|
+
// Catch-all: serve index.html for any non-API route (SPA fallback)
|
|
96
|
+
app.get('*', (req, res, next) => {
|
|
97
|
+
if (req.path.startsWith('/api/'))
|
|
98
|
+
return next();
|
|
99
|
+
res.sendFile(path.join(publicPath, 'index.html'));
|
|
100
|
+
});
|
|
101
|
+
// Session Info endpoint (used by frontend to know which IDE it is bridging)
|
|
102
|
+
app.get('/api/session-info', (req, res) => {
|
|
103
|
+
res.json({ ide });
|
|
104
|
+
});
|
|
91
105
|
// Ports endpoint - returns empty list (no port detection in bridge mode)
|
|
92
106
|
app.get('/api/ports', (req, res) => {
|
|
93
107
|
res.json([]);
|
|
@@ -181,7 +195,7 @@ export function startTerminalServer(port, workingDir, terminalToken, onDisconnec
|
|
|
181
195
|
authenticated = true;
|
|
182
196
|
sendControl({ type: 'auth:success' });
|
|
183
197
|
// Auto-create initial session and attach
|
|
184
|
-
const initialSession = sessionManager.createSession(workingDir);
|
|
198
|
+
const initialSession = sessionManager.createSession(workingDir, ide);
|
|
185
199
|
attachToSession(initialSession.id);
|
|
186
200
|
console.log('✅ Client authenticated');
|
|
187
201
|
}
|
|
@@ -205,7 +219,7 @@ export function startTerminalServer(port, workingDir, terminalToken, onDisconnec
|
|
|
205
219
|
sendControl({ type: 'error', error: 'Directory not found' });
|
|
206
220
|
break;
|
|
207
221
|
}
|
|
208
|
-
const session = sessionManager.createSession(cwd);
|
|
222
|
+
const session = sessionManager.createSession(cwd, ide);
|
|
209
223
|
sendControl({ type: 'session:created', session: session.getInfo() });
|
|
210
224
|
attachToSession(session.id);
|
|
211
225
|
break;
|
|
@@ -302,13 +316,13 @@ export function startTerminalServer(port, workingDir, terminalToken, onDisconnec
|
|
|
302
316
|
server.on('error', (err) => {
|
|
303
317
|
if (err.code === 'EADDRINUSE' && port !== 0) {
|
|
304
318
|
console.warn(`⚠️ Port ${port} is busy, trying a random port...`);
|
|
305
|
-
resolve(startTerminalServer(0, workingDir, terminalToken, onDisconnect));
|
|
319
|
+
resolve(startTerminalServer(0, workingDir, terminalToken, ide, onDisconnect));
|
|
306
320
|
}
|
|
307
321
|
else {
|
|
308
322
|
reject(err);
|
|
309
323
|
}
|
|
310
324
|
});
|
|
311
|
-
server.listen(port, () => {
|
|
325
|
+
server.listen(port, '0.0.0.0', () => {
|
|
312
326
|
const address = server.address();
|
|
313
327
|
const actualPort = typeof address === 'string' ? 0 : address?.port || 0;
|
|
314
328
|
resolve({ server, actualPort });
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -378,6 +378,9 @@ class ClaudeRemote {
|
|
|
378
378
|
this.bindEvents();
|
|
379
379
|
this.initTerminal();
|
|
380
380
|
|
|
381
|
+
// Fetch IDE info for branding
|
|
382
|
+
this.fetchSessionInfo();
|
|
383
|
+
|
|
381
384
|
// Initialize notification manager
|
|
382
385
|
this.notificationManager = new NotificationManager(this);
|
|
383
386
|
this.updateNotifyToggleState();
|
|
@@ -408,6 +411,8 @@ class ClaudeRemote {
|
|
|
408
411
|
tokenInput: document.getElementById('token-input'),
|
|
409
412
|
connectBtn: document.getElementById('connect-btn'),
|
|
410
413
|
authError: document.getElementById('auth-error'),
|
|
414
|
+
mainTitle: document.getElementById('main-title'),
|
|
415
|
+
mainSubtitle: document.getElementById('main-subtitle'),
|
|
411
416
|
|
|
412
417
|
// Main
|
|
413
418
|
header: document.getElementById('header'),
|
|
@@ -494,6 +499,34 @@ class ClaudeRemote {
|
|
|
494
499
|
this.scheduleDebounceTimer = null;
|
|
495
500
|
}
|
|
496
501
|
|
|
502
|
+
async fetchSessionInfo() {
|
|
503
|
+
try {
|
|
504
|
+
const res = await fetch('/api/session-info');
|
|
505
|
+
const data = await res.json();
|
|
506
|
+
if (data.ide) {
|
|
507
|
+
this.updateBranding(data.ide);
|
|
508
|
+
}
|
|
509
|
+
} catch (err) {
|
|
510
|
+
console.warn('Failed to fetch session info:', err);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
updateBranding(ide) {
|
|
515
|
+
const isCursor = ide === 'cursor';
|
|
516
|
+
const isGemini = ide === 'gemini';
|
|
517
|
+
const name = isGemini ? 'Gemini' : (isCursor ? 'Cursor' : 'Claude Code');
|
|
518
|
+
|
|
519
|
+
if (this.elements.mainTitle) {
|
|
520
|
+
this.elements.mainTitle.textContent = `${name} Remote`;
|
|
521
|
+
}
|
|
522
|
+
if (this.elements.mainSubtitle) {
|
|
523
|
+
this.elements.mainSubtitle.textContent = `Connect to your local ${name} session from anywhere`;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Update page title
|
|
527
|
+
document.title = `${name} Remote`;
|
|
528
|
+
}
|
|
529
|
+
|
|
497
530
|
initTerminal() {
|
|
498
531
|
// Create terminal with mobile-friendly settings
|
|
499
532
|
this.terminal = new Terminal({
|
package/public/index.html
CHANGED
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
<img src="/ic_logo.png" width="64" height="64" alt="Claude Code" style="border-radius:12px;">
|
|
37
37
|
</div>
|
|
38
38
|
|
|
39
|
-
<h1>Claude Code Remote</h1>
|
|
40
|
-
<p class="subtitle">Connect to your local Claude Code session from anywhere</p>
|
|
39
|
+
<h1 id="main-title">Claude Code Remote</h1>
|
|
40
|
+
<p id="main-subtitle" class="subtitle">Connect to your local Claude Code session from anywhere</p>
|
|
41
41
|
|
|
42
42
|
<form id="auth-form" onsubmit="return false;">
|
|
43
43
|
<input
|