cursor-bridge 1.0.0 → 1.2.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.
Files changed (3) hide show
  1. package/dist/index.js +73 -127
  2. package/package.json +2 -4
  3. package/src/index.ts +93 -174
package/dist/index.js CHANGED
@@ -4,41 +4,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const ws_1 = require("ws");
8
- const uuid_1 = require("uuid");
7
+ const ws_1 = __importDefault(require("ws"));
9
8
  const chalk_1 = __importDefault(require("chalk"));
10
- const qrcode_terminal_1 = __importDefault(require("qrcode-terminal"));
11
- const os_1 = __importDefault(require("os"));
12
- const PORT = Number(process.argv[2]) || 9090;
13
- const TOKEN = (0, uuid_1.v4)();
14
- const clients = new Map();
15
- function getLocalIP() {
16
- const interfaces = os_1.default.networkInterfaces();
17
- for (const name of Object.keys(interfaces)) {
18
- for (const iface of interfaces[name] || []) {
19
- if (iface.family === 'IPv4' && !iface.internal) {
20
- return iface.address;
21
- }
22
- }
23
- }
24
- return 'localhost';
25
- }
26
- function findPeer(clientId) {
27
- const client = clients.get(clientId);
28
- if (!client)
29
- return undefined;
30
- for (const [, other] of clients) {
31
- if (other.id !== clientId &&
32
- other.token === client.token &&
33
- other.type !== client.type) {
34
- return other;
35
- }
36
- }
37
- return undefined;
9
+ const BRIDGE_URL = 'wss://cursor-226b2ae97542.herokuapp.com';
10
+ function generatePairCode() {
11
+ return Math.floor(100000 + Math.random() * 900000).toString();
38
12
  }
13
+ const PAIR_CODE = generatePairCode();
14
+ let ws = null;
15
+ let reconnectTimer = null;
39
16
  function banner() {
40
- const ip = getLocalIP();
41
- const pairUrl = `cursor-bridge://${ip}:${PORT}/${TOKEN}`;
42
17
  console.clear();
43
18
  console.log('');
44
19
  console.log(chalk_1.default.hex('#A855F7').bold(' ╔══════════════════════════════════════╗'));
@@ -47,132 +22,103 @@ function banner() {
47
22
  chalk_1.default.hex('#A855F7').bold('║'));
48
23
  console.log(chalk_1.default.hex('#A855F7').bold(' ╚══════════════════════════════════════╝'));
49
24
  console.log('');
50
- console.log(chalk_1.default.gray(' Scan this QR code with the mobile app:'));
25
+ console.log(chalk_1.default.gray(' Enter this code in the mobile app to pair:'));
51
26
  console.log('');
52
- qrcode_terminal_1.default.generate(pairUrl, { small: true }, (qr) => {
53
- const lines = qr.split('\n');
54
- lines.forEach(line => console.log(' ' + line));
55
- });
27
+ console.log(' ' +
28
+ chalk_1.default.bgHex('#A855F7').white.bold(` ${PAIR_CODE.split('').join(' ')} `));
56
29
  console.log('');
57
- console.log(chalk_1.default.gray(' Or connect manually:'));
58
- console.log(chalk_1.default.white(' Host: ') + chalk_1.default.hex('#A855F7')(ip));
59
- console.log(chalk_1.default.white(' Port: ') + chalk_1.default.hex('#A855F7')(String(PORT)));
60
- console.log(chalk_1.default.white(' Token: ') + chalk_1.default.hex('#A855F7')(TOKEN));
30
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────────'));
31
+ console.log(chalk_1.default.gray(' Bridge: ') + chalk_1.default.white(BRIDGE_URL));
32
+ console.log(chalk_1.default.gray(' ─────────────────────────────────────────'));
61
33
  console.log('');
62
- console.log(chalk_1.default.gray(' Waiting for mobile connection...'));
34
+ console.log(chalk_1.default.gray(' Connecting to bridge server...'));
63
35
  console.log('');
64
36
  }
65
- const wss = new ws_1.WebSocketServer({ port: PORT });
66
- wss.on('connection', (ws) => {
67
- const clientId = (0, uuid_1.v4)();
37
+ function connect() {
38
+ ws = new ws_1.default(BRIDGE_URL);
39
+ ws.on('open', () => {
40
+ console.log(chalk_1.default.hex('#22C55E')(' ✓ ') +
41
+ chalk_1.default.white('Connected to bridge server'));
42
+ ws.send(JSON.stringify({
43
+ type: 'pair',
44
+ payload: { pairCode: PAIR_CODE, clientType: 'desktop' },
45
+ timestamp: Date.now(),
46
+ }));
47
+ console.log(chalk_1.default.gray(' Waiting for mobile device...'));
48
+ console.log('');
49
+ });
68
50
  ws.on('message', (raw) => {
69
51
  try {
70
52
  const message = JSON.parse(raw.toString());
71
53
  switch (message.type) {
72
- case 'pair': {
73
- const { token, clientType } = message.payload;
74
- clients.set(clientId, {
75
- id: clientId,
76
- ws,
77
- type: clientType,
78
- token,
79
- });
80
- const peer = findPeer(clientId);
81
- ws.send(JSON.stringify({
82
- type: 'status',
83
- payload: {
84
- status: peer ? 'connected' : 'waiting',
85
- clientId,
86
- },
87
- timestamp: Date.now(),
88
- }));
89
- if (peer) {
90
- peer.ws.send(JSON.stringify({
91
- type: 'status',
92
- payload: { status: 'connected' },
93
- timestamp: Date.now(),
94
- }));
54
+ case 'status': {
55
+ const status = message.payload.status;
56
+ if (status === 'connected') {
95
57
  console.log(chalk_1.default.hex('#22C55E')(' ✓ ') +
96
- chalk_1.default.white(`${clientType} device connected and paired!`));
58
+ chalk_1.default.white('Mobile device paired successfully!'));
97
59
  }
98
- else {
99
- console.log(chalk_1.default.hex('#F59E0B')(' ') +
100
- chalk_1.default.white(`${clientType} device connected, waiting for peer...`));
101
- }
102
- break;
103
- }
104
- case 'command': {
105
- const peer = findPeer(clientId);
106
- if (peer && peer.ws.readyState === ws_1.WebSocket.OPEN) {
107
- peer.ws.send(JSON.stringify({
108
- ...message,
109
- from: clientId,
110
- timestamp: Date.now(),
111
- }));
112
- console.log(chalk_1.default.hex('#A855F7')(' → ') +
113
- chalk_1.default.gray('Command forwarded to desktop'));
60
+ else if (status === 'waiting') {
61
+ console.log(chalk_1.default.gray(' Waiting for mobile device...'));
114
62
  }
115
- break;
116
- }
117
- case 'response': {
118
- const peer = findPeer(clientId);
119
- if (peer && peer.ws.readyState === ws_1.WebSocket.OPEN) {
120
- peer.ws.send(JSON.stringify({
121
- ...message,
122
- from: clientId,
123
- timestamp: Date.now(),
124
- }));
125
- console.log(chalk_1.default.hex('#A855F7')(' ← ') +
126
- chalk_1.default.gray('Response forwarded to mobile'));
63
+ else if (status === 'disconnected') {
64
+ console.log(chalk_1.default.hex('#EF4444')(' ✗ ') +
65
+ chalk_1.default.gray('Mobile device disconnected'));
127
66
  }
128
67
  break;
129
68
  }
130
- case 'ping': {
69
+ case 'command': {
70
+ const prompt = message.payload.prompt;
71
+ console.log(chalk_1.default.hex('#A855F7')(' → ') +
72
+ chalk_1.default.white('Command received: ') +
73
+ chalk_1.default.gray(prompt.substring(0, 80) + (prompt.length > 80 ? '...' : '')));
74
+ // Send response back
131
75
  ws.send(JSON.stringify({
132
- type: 'pong',
133
- payload: {},
76
+ type: 'response',
77
+ payload: {
78
+ status: 'received',
79
+ message: `Command received: ${prompt.substring(0, 50)}`,
80
+ },
134
81
  timestamp: Date.now(),
135
82
  }));
136
83
  break;
137
84
  }
85
+ case 'error': {
86
+ console.log(chalk_1.default.hex('#EF4444')(' ✗ ') +
87
+ chalk_1.default.gray(message.payload.message || 'Unknown error'));
88
+ break;
89
+ }
138
90
  }
139
91
  }
140
92
  catch {
141
- ws.send(JSON.stringify({
142
- type: 'error',
143
- payload: { message: 'Invalid message format' },
144
- }));
93
+ console.warn(chalk_1.default.gray(' Invalid message received'));
145
94
  }
146
95
  });
147
96
  ws.on('close', () => {
148
- const client = clients.get(clientId);
149
- if (client) {
150
- const peer = findPeer(clientId);
151
- if (peer && peer.ws.readyState === ws_1.WebSocket.OPEN) {
152
- peer.ws.send(JSON.stringify({
153
- type: 'status',
154
- payload: { status: 'disconnected' },
155
- timestamp: Date.now(),
156
- }));
157
- }
158
- console.log(chalk_1.default.hex('#EF4444')(' ✗ ') +
159
- chalk_1.default.gray(`${client.type} device disconnected`));
160
- clients.delete(clientId);
161
- }
97
+ console.log(chalk_1.default.hex('#EF4444')(' ✗ ') +
98
+ chalk_1.default.gray('Disconnected from bridge server'));
99
+ scheduleReconnect();
162
100
  });
163
- });
164
- // Heartbeat
165
- setInterval(() => {
166
- clients.forEach(client => {
167
- if (client.ws.readyState === ws_1.WebSocket.OPEN) {
168
- client.ws.ping();
169
- }
101
+ ws.on('error', (err) => {
102
+ console.error(chalk_1.default.hex('#EF4444')(' ✗ ') +
103
+ chalk_1.default.gray(`Connection error: ${err.message}`));
170
104
  });
171
- }, 30000);
105
+ }
106
+ function scheduleReconnect() {
107
+ if (reconnectTimer)
108
+ return;
109
+ console.log(chalk_1.default.gray(' Reconnecting in 3s...'));
110
+ reconnectTimer = setTimeout(() => {
111
+ reconnectTimer = null;
112
+ connect();
113
+ }, 3000);
114
+ }
172
115
  banner();
116
+ connect();
173
117
  process.on('SIGINT', () => {
174
118
  console.log('');
175
119
  console.log(chalk_1.default.gray(' Bridge stopped.'));
176
- wss.close();
120
+ if (reconnectTimer)
121
+ clearTimeout(reconnectTimer);
122
+ ws?.close();
177
123
  process.exit(0);
178
124
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-bridge",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "CLI bridge to connect IDE For Cursor mobile app with desktop Cursor IDE",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -16,9 +16,7 @@
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
18
  "chalk": "^4.1.2",
19
- "ws": "^8.20.0",
20
- "uuid": "^13.0.0",
21
- "qrcode-terminal": "^0.12.0"
19
+ "ws": "^8.20.0"
22
20
  },
23
21
  "devDependencies": {
24
22
  "@types/ws": "^8.18.1",
package/src/index.ts CHANGED
@@ -1,62 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { WebSocketServer, WebSocket } from 'ws';
4
- import { v4 as uuidv4 } from 'uuid';
3
+ import WebSocket from 'ws';
5
4
  import chalk from 'chalk';
6
- import qrcode from 'qrcode-terminal';
7
5
  import os from 'os';
8
6
 
9
- const PORT = Number(process.argv[2]) || 9090;
10
- const TOKEN = uuidv4();
7
+ const BRIDGE_URL = 'wss://cursor-226b2ae97542.herokuapp.com';
11
8
 
12
- interface BridgeMessage {
13
- type: string;
14
- payload: Record<string, unknown>;
15
- from?: string;
16
- timestamp: number;
9
+ function generatePairCode(): string {
10
+ return Math.floor(100000 + Math.random() * 900000).toString();
17
11
  }
18
12
 
19
- interface ConnectedClient {
20
- id: string;
21
- ws: WebSocket;
22
- type: 'mobile' | 'desktop';
23
- token: string;
24
- }
25
-
26
- const clients = new Map<string, ConnectedClient>();
13
+ const PAIR_CODE = generatePairCode();
27
14
 
28
- function getLocalIP(): string {
29
- const interfaces = os.networkInterfaces();
30
- for (const name of Object.keys(interfaces)) {
31
- for (const iface of interfaces[name] || []) {
32
- if (iface.family === 'IPv4' && !iface.internal) {
33
- return iface.address;
34
- }
35
- }
36
- }
37
- return 'localhost';
38
- }
39
-
40
- function findPeer(clientId: string): ConnectedClient | undefined {
41
- const client = clients.get(clientId);
42
- if (!client) return undefined;
43
-
44
- for (const [, other] of clients) {
45
- if (
46
- other.id !== clientId &&
47
- other.token === client.token &&
48
- other.type !== client.type
49
- ) {
50
- return other;
51
- }
52
- }
53
- return undefined;
54
- }
15
+ let ws: WebSocket | null = null;
16
+ let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
55
17
 
56
18
  function banner() {
57
- const ip = getLocalIP();
58
- const pairUrl = `cursor-bridge://${ip}:${PORT}/${TOKEN}`;
59
-
60
19
  console.clear();
61
20
  console.log('');
62
21
  console.log(
@@ -71,180 +30,140 @@ function banner() {
71
30
  chalk.hex('#A855F7').bold(' ╚══════════════════════════════════════╝'),
72
31
  );
73
32
  console.log('');
74
- console.log(chalk.gray(' Scan this QR code with the mobile app:'));
33
+ console.log(
34
+ chalk.gray(' Enter this code in the mobile app to pair:'),
35
+ );
75
36
  console.log('');
76
-
77
- qrcode.generate(pairUrl, { small: true }, (qr: string) => {
78
- const lines = qr.split('\n');
79
- lines.forEach(line => console.log(' ' + line));
80
- });
81
-
37
+ console.log(
38
+ ' ' +
39
+ chalk.bgHex('#A855F7').white.bold(` ${PAIR_CODE.split('').join(' ')} `),
40
+ );
82
41
  console.log('');
83
- console.log(chalk.gray(' Or connect manually:'));
84
42
  console.log(
85
- chalk.white(' Host: ') + chalk.hex('#A855F7')(ip),
43
+ chalk.gray(' ─────────────────────────────────────────'),
86
44
  );
87
45
  console.log(
88
- chalk.white(' Port: ') + chalk.hex('#A855F7')(String(PORT)),
46
+ chalk.gray(' Bridge: ') + chalk.white(BRIDGE_URL),
89
47
  );
90
48
  console.log(
91
- chalk.white(' Token: ') + chalk.hex('#A855F7')(TOKEN),
49
+ chalk.gray(' ─────────────────────────────────────────'),
92
50
  );
93
51
  console.log('');
94
- console.log(chalk.gray(' Waiting for mobile connection...'));
52
+ console.log(chalk.gray(' Connecting to bridge server...'));
95
53
  console.log('');
96
54
  }
97
55
 
98
- const wss = new WebSocketServer({ port: PORT });
99
-
100
- wss.on('connection', (ws: WebSocket) => {
101
- const clientId = uuidv4();
56
+ function connect() {
57
+ ws = new WebSocket(BRIDGE_URL);
58
+
59
+ ws.on('open', () => {
60
+ console.log(
61
+ chalk.hex('#22C55E')(' ✓ ') +
62
+ chalk.white('Connected to bridge server'),
63
+ );
64
+
65
+ ws!.send(
66
+ JSON.stringify({
67
+ type: 'pair',
68
+ payload: { pairCode: PAIR_CODE, clientType: 'desktop' },
69
+ timestamp: Date.now(),
70
+ }),
71
+ );
72
+
73
+ console.log(chalk.gray(' Waiting for mobile device...'));
74
+ console.log('');
75
+ });
102
76
 
103
77
  ws.on('message', (raw: Buffer) => {
104
78
  try {
105
- const message: BridgeMessage = JSON.parse(raw.toString());
79
+ const message = JSON.parse(raw.toString());
106
80
 
107
81
  switch (message.type) {
108
- case 'pair': {
109
- const { token, clientType } = message.payload as {
110
- token: string;
111
- clientType: 'mobile' | 'desktop';
112
- };
113
-
114
- clients.set(clientId, {
115
- id: clientId,
116
- ws,
117
- type: clientType,
118
- token,
119
- });
120
-
121
- const peer = findPeer(clientId);
122
-
123
- ws.send(
124
- JSON.stringify({
125
- type: 'status',
126
- payload: {
127
- status: peer ? 'connected' : 'waiting',
128
- clientId,
129
- },
130
- timestamp: Date.now(),
131
- }),
132
- );
133
-
134
- if (peer) {
135
- peer.ws.send(
136
- JSON.stringify({
137
- type: 'status',
138
- payload: { status: 'connected' },
139
- timestamp: Date.now(),
140
- }),
141
- );
82
+ case 'status': {
83
+ const status = message.payload.status as string;
84
+ if (status === 'connected') {
142
85
  console.log(
143
86
  chalk.hex('#22C55E')(' ✓ ') +
144
- chalk.white(`${clientType} device connected and paired!`),
87
+ chalk.white('Mobile device paired successfully!'),
145
88
  );
146
- } else {
89
+ } else if (status === 'waiting') {
90
+ console.log(chalk.gray(' Waiting for mobile device...'));
91
+ } else if (status === 'disconnected') {
147
92
  console.log(
148
- chalk.hex('#F59E0B')(' ') +
149
- chalk.white(`${clientType} device connected, waiting for peer...`),
93
+ chalk.hex('#EF4444')(' ') +
94
+ chalk.gray('Mobile device disconnected'),
150
95
  );
151
96
  }
152
97
  break;
153
98
  }
154
99
 
155
100
  case 'command': {
156
- const peer = findPeer(clientId);
157
- if (peer && peer.ws.readyState === WebSocket.OPEN) {
158
- peer.ws.send(
159
- JSON.stringify({
160
- ...message,
161
- from: clientId,
162
- timestamp: Date.now(),
163
- }),
164
- );
165
- console.log(
166
- chalk.hex('#A855F7')(' → ') +
167
- chalk.gray('Command forwarded to desktop'),
168
- );
169
- }
170
- break;
171
- }
172
-
173
- case 'response': {
174
- const peer = findPeer(clientId);
175
- if (peer && peer.ws.readyState === WebSocket.OPEN) {
176
- peer.ws.send(
177
- JSON.stringify({
178
- ...message,
179
- from: clientId,
180
- timestamp: Date.now(),
181
- }),
182
- );
183
- console.log(
184
- chalk.hex('#A855F7')(' ← ') +
185
- chalk.gray('Response forwarded to mobile'),
186
- );
187
- }
188
- break;
189
- }
101
+ const prompt = message.payload.prompt as string;
102
+ console.log(
103
+ chalk.hex('#A855F7')(' → ') +
104
+ chalk.white('Command received: ') +
105
+ chalk.gray(prompt.substring(0, 80) + (prompt.length > 80 ? '...' : '')),
106
+ );
190
107
 
191
- case 'ping': {
192
- ws.send(
108
+ // Send response back
109
+ ws!.send(
193
110
  JSON.stringify({
194
- type: 'pong',
195
- payload: {},
111
+ type: 'response',
112
+ payload: {
113
+ status: 'received',
114
+ message: `Command received: ${prompt.substring(0, 50)}`,
115
+ },
196
116
  timestamp: Date.now(),
197
117
  }),
198
118
  );
199
119
  break;
200
120
  }
121
+
122
+ case 'error': {
123
+ console.log(
124
+ chalk.hex('#EF4444')(' ✗ ') +
125
+ chalk.gray(message.payload.message || 'Unknown error'),
126
+ );
127
+ break;
128
+ }
201
129
  }
202
130
  } catch {
203
- ws.send(
204
- JSON.stringify({
205
- type: 'error',
206
- payload: { message: 'Invalid message format' },
207
- }),
208
- );
131
+ console.warn(chalk.gray(' Invalid message received'));
209
132
  }
210
133
  });
211
134
 
212
135
  ws.on('close', () => {
213
- const client = clients.get(clientId);
214
- if (client) {
215
- const peer = findPeer(clientId);
216
- if (peer && peer.ws.readyState === WebSocket.OPEN) {
217
- peer.ws.send(
218
- JSON.stringify({
219
- type: 'status',
220
- payload: { status: 'disconnected' },
221
- timestamp: Date.now(),
222
- }),
223
- );
224
- }
225
- console.log(
226
- chalk.hex('#EF4444')(' ✗ ') +
227
- chalk.gray(`${client.type} device disconnected`),
228
- );
229
- clients.delete(clientId);
230
- }
136
+ console.log(
137
+ chalk.hex('#EF4444')(' ✗ ') +
138
+ chalk.gray('Disconnected from bridge server'),
139
+ );
140
+ scheduleReconnect();
231
141
  });
232
- });
233
142
 
234
- // Heartbeat
235
- setInterval(() => {
236
- clients.forEach(client => {
237
- if (client.ws.readyState === WebSocket.OPEN) {
238
- client.ws.ping();
239
- }
143
+ ws.on('error', (err) => {
144
+ console.error(
145
+ chalk.hex('#EF4444')(' ✗ ') +
146
+ chalk.gray(`Connection error: ${err.message}`),
147
+ );
240
148
  });
241
- }, 30000);
149
+ }
150
+
151
+ function scheduleReconnect() {
152
+ if (reconnectTimer) return;
153
+ console.log(chalk.gray(' Reconnecting in 3s...'));
154
+ reconnectTimer = setTimeout(() => {
155
+ reconnectTimer = null;
156
+ connect();
157
+ }, 3000);
158
+ }
242
159
 
243
160
  banner();
161
+ connect();
244
162
 
245
163
  process.on('SIGINT', () => {
246
164
  console.log('');
247
165
  console.log(chalk.gray(' Bridge stopped.'));
248
- wss.close();
166
+ if (reconnectTimer) clearTimeout(reconnectTimer);
167
+ ws?.close();
249
168
  process.exit(0);
250
169
  });