@hmduc16031996/claude-mb-bridge 2.4.5 → 2.4.7

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 CHANGED
@@ -6,45 +6,62 @@ 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.5')
9
+ .version('2.4.1')
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
+ .option('--ide <type>', 'IDE type: claude_code, cursor, or gemini', 'claude_code')
15
+ // Named tunnel options (use your own Cloudflare tunnel instead of trycloudflare)
16
+ .option('--cloudflare-token <cfToken>', 'Cloudflare tunnel token for named tunnel')
17
+ .option('--tunnel-url <url>', 'Public URL of your named Cloudflare tunnel (e.g. https://mobileideforusers.kanddlabs.com)')
15
18
  .action(async (options) => {
16
- const { token, server, path, port, ide } = options;
19
+ const { token, server, path, port, ide, cloudflareToken, tunnelUrl } = options;
17
20
  if (!token) {
18
21
  console.error('Error: --token is required');
19
22
  process.exit(1);
20
23
  }
24
+ // Validate named-tunnel options: both must be provided together or neither
25
+ const useNamedTunnel = !!(cloudflareToken || tunnelUrl);
26
+ if (useNamedTunnel && !(cloudflareToken && tunnelUrl)) {
27
+ console.error('Error: --cloudflare-token and --tunnel-url must both be provided when using a named tunnel');
28
+ process.exit(1);
29
+ }
30
+ let tunnelResult = null;
31
+ let terminalServer = null;
21
32
  // Define cleanup function
22
33
  const cleanup = async () => {
23
34
  console.log('\nšŸ‘‹ Cleaning up session and shutting down...');
24
35
  try {
25
- // Optional: Notify server to end session
26
36
  await fetch(`${server}/api/sessions/${token}/expire`, { method: 'POST' }).catch(() => { });
27
37
  }
28
38
  finally {
29
- tunnelResult.cleanup();
30
- terminalServer.close();
39
+ tunnelResult?.cleanup();
40
+ terminalServer?.close();
31
41
  process.exit(0);
32
42
  }
33
43
  };
34
44
  // 1. Start local terminal server
35
45
  console.log('šŸ“¦ Starting terminal server...');
36
46
  const localPort = parseInt(port, 10);
37
- const { server: terminalServer, actualPort } = await startTerminalServer(localPort, path, token, ide);
47
+ const { server: ts, actualPort } = await startTerminalServer(localPort, path, token, ide);
48
+ terminalServer = ts;
38
49
  console.log(`āœ… Terminal server started on port ${actualPort}`);
39
- // 2. Start Cloudflare Tunnel
40
- console.log('🌐 Establishing secure tunnel...');
50
+ // 2. Start Cloudflare Tunnel (named or quick)
41
51
  const tunnel = new CloudflareTunnel();
42
- const tunnelResult = await tunnel.start(actualPort);
52
+ if (useNamedTunnel) {
53
+ console.log(`🌐 Connecting named Cloudflare tunnel → ${tunnelUrl} ...`);
54
+ tunnelResult = await tunnel.startNamed(cloudflareToken, tunnelUrl);
55
+ }
56
+ else {
57
+ console.log('🌐 Establishing ephemeral Cloudflare tunnel...');
58
+ tunnelResult = await tunnel.startQuick(actualPort);
59
+ }
43
60
  if (!tunnelResult.url) {
44
61
  console.error('āŒ Failed to start Cloudflare Tunnel.');
45
62
  process.exit(1);
46
63
  }
47
- console.log('āœ… Secure tunnel established');
64
+ console.log(`āœ… Tunnel active: ${tunnelResult.url}`);
48
65
  // 3. Validate token and report public URL to central server
49
66
  console.log('šŸ”‘ Validating session token...');
50
67
  try {
@@ -61,11 +78,12 @@ program
61
78
  throw new Error('Invalid token or session expired');
62
79
  }
63
80
  console.log('āœ… Session connected and authorized');
81
+ console.log(`šŸ“± Mobile app can now connect via: ${tunnelResult.url}`);
64
82
  }
65
83
  catch (err) {
66
84
  console.error(`āŒ Validation failed: ${err.message}`);
67
85
  console.error(' Ensure the central server is available');
68
- cleanup();
86
+ await cleanup();
69
87
  }
70
88
  // Handle graceful shutdown signals
71
89
  process.on('SIGINT', cleanup);
package/dist/server.js CHANGED
@@ -92,12 +92,6 @@ export function startTerminalServer(port, workingDir, terminalToken, ide = 'clau
92
92
  app.get('/health', (req, res) => {
93
93
  res.json({ status: 'ok' });
94
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
95
  // Session Info endpoint (used by frontend to know which IDE it is bridging)
102
96
  app.get('/api/session-info', (req, res) => {
103
97
  res.json({ ide });
@@ -322,7 +316,7 @@ export function startTerminalServer(port, workingDir, terminalToken, ide = 'clau
322
316
  reject(err);
323
317
  }
324
318
  });
325
- server.listen(port, '0.0.0.0', () => {
319
+ server.listen(port, () => {
326
320
  const address = server.address();
327
321
  const actualPort = typeof address === 'string' ? 0 : address?.port || 0;
328
322
  resolve({ server, actualPort });
package/dist/tunnel.d.ts CHANGED
@@ -4,6 +4,15 @@ export interface TunnelResult {
4
4
  }
5
5
  export declare class CloudflareTunnel {
6
6
  private process;
7
- start(port: number): Promise<TunnelResult>;
7
+ /**
8
+ * Start a named Cloudflare tunnel using a pre-existing tunnel token.
9
+ * The public URL is known ahead of time (configured in Cloudflare dashboard).
10
+ */
11
+ startNamed(tunnelToken: string, publicUrl: string): Promise<TunnelResult>;
12
+ /**
13
+ * Start an ephemeral quick tunnel (trycloudflare.com).
14
+ * The public URL is parsed from cloudflared stderr output.
15
+ */
16
+ startQuick(port: number): Promise<TunnelResult>;
8
17
  private cleanup;
9
18
  }
package/dist/tunnel.js CHANGED
@@ -1,7 +1,59 @@
1
1
  import { spawn } from 'child_process';
2
2
  export class CloudflareTunnel {
3
3
  process = null;
4
- async start(port) {
4
+ /**
5
+ * Start a named Cloudflare tunnel using a pre-existing tunnel token.
6
+ * The public URL is known ahead of time (configured in Cloudflare dashboard).
7
+ */
8
+ async startNamed(tunnelToken, publicUrl) {
9
+ return new Promise((resolve) => {
10
+ try {
11
+ const proc = spawn('cloudflared', ['tunnel', 'run', '--token', tunnelToken], {
12
+ stdio: ['ignore', 'pipe', 'pipe'],
13
+ });
14
+ this.process = proc;
15
+ // Give cloudflared a moment to connect; fail if it exits immediately
16
+ const timeout = setTimeout(() => {
17
+ // Still resolving with the known public URL — named tunnels don't print the URL
18
+ resolve({ url: publicUrl, cleanup: () => this.cleanup() });
19
+ }, 5000);
20
+ proc.stdout?.on('data', (data) => {
21
+ console.log('[cloudflared]', data.toString().trim());
22
+ });
23
+ proc.stderr?.on('data', (data) => {
24
+ const line = data.toString().trim();
25
+ if (line)
26
+ console.log('[cloudflared]', line);
27
+ // If we see a connection established log, resolve immediately
28
+ if (line.includes('Connection') || line.includes('registered')) {
29
+ clearTimeout(timeout);
30
+ resolve({ url: publicUrl, cleanup: () => this.cleanup() });
31
+ }
32
+ });
33
+ proc.on('error', (err) => {
34
+ clearTimeout(timeout);
35
+ console.error('[cloudflared] Failed to start:', err.message);
36
+ resolve({ url: null, cleanup: () => this.cleanup() });
37
+ });
38
+ proc.on('exit', (code) => {
39
+ clearTimeout(timeout);
40
+ if (code !== 0) {
41
+ console.error(`[cloudflared] Exited with code ${code}`);
42
+ }
43
+ this.process = null;
44
+ });
45
+ }
46
+ catch (err) {
47
+ console.error('[cloudflared] Exception:', err.message);
48
+ resolve({ url: null, cleanup: () => this.cleanup() });
49
+ }
50
+ });
51
+ }
52
+ /**
53
+ * Start an ephemeral quick tunnel (trycloudflare.com).
54
+ * The public URL is parsed from cloudflared stderr output.
55
+ */
56
+ async startQuick(port) {
5
57
  return new Promise((resolve) => {
6
58
  try {
7
59
  const proc = spawn('cloudflared', ['tunnel', '--url', `http://localhost:${port}`], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hmduc16031996/claude-mb-bridge",
3
- "version": "2.4.5",
3
+ "version": "2.4.7",
4
4
  "description": "Bridge between Claude Code CLI and your mobile app via WebView",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",