cursor-bridge 1.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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const ws_1 = require("ws");
8
+ const uuid_1 = require("uuid");
9
+ 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;
38
+ }
39
+ function banner() {
40
+ const ip = getLocalIP();
41
+ const pairUrl = `cursor-bridge://${ip}:${PORT}/${TOKEN}`;
42
+ console.clear();
43
+ console.log('');
44
+ console.log(chalk_1.default.hex('#A855F7').bold(' ╔══════════════════════════════════════╗'));
45
+ console.log(chalk_1.default.hex('#A855F7').bold(' ║') +
46
+ chalk_1.default.white.bold(' IDE For Cursor — Mobile Bridge ') +
47
+ chalk_1.default.hex('#A855F7').bold('║'));
48
+ console.log(chalk_1.default.hex('#A855F7').bold(' ╚══════════════════════════════════════╝'));
49
+ console.log('');
50
+ console.log(chalk_1.default.gray(' Scan this QR code with the mobile app:'));
51
+ 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
+ });
56
+ 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));
61
+ console.log('');
62
+ console.log(chalk_1.default.gray(' Waiting for mobile connection...'));
63
+ console.log('');
64
+ }
65
+ const wss = new ws_1.WebSocketServer({ port: PORT });
66
+ wss.on('connection', (ws) => {
67
+ const clientId = (0, uuid_1.v4)();
68
+ ws.on('message', (raw) => {
69
+ try {
70
+ const message = JSON.parse(raw.toString());
71
+ 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
+ }));
95
+ console.log(chalk_1.default.hex('#22C55E')(' ✓ ') +
96
+ chalk_1.default.white(`${clientType} device connected and paired!`));
97
+ }
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'));
114
+ }
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'));
127
+ }
128
+ break;
129
+ }
130
+ case 'ping': {
131
+ ws.send(JSON.stringify({
132
+ type: 'pong',
133
+ payload: {},
134
+ timestamp: Date.now(),
135
+ }));
136
+ break;
137
+ }
138
+ }
139
+ }
140
+ catch {
141
+ ws.send(JSON.stringify({
142
+ type: 'error',
143
+ payload: { message: 'Invalid message format' },
144
+ }));
145
+ }
146
+ });
147
+ 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
+ }
162
+ });
163
+ });
164
+ // Heartbeat
165
+ setInterval(() => {
166
+ clients.forEach(client => {
167
+ if (client.ws.readyState === ws_1.WebSocket.OPEN) {
168
+ client.ws.ping();
169
+ }
170
+ });
171
+ }, 30000);
172
+ banner();
173
+ process.on('SIGINT', () => {
174
+ console.log('');
175
+ console.log(chalk_1.default.gray(' Bridge stopped.'));
176
+ wss.close();
177
+ process.exit(0);
178
+ });
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "cursor-bridge",
3
+ "version": "1.0.0",
4
+ "description": "CLI bridge to connect IDE For Cursor mobile app with desktop Cursor IDE",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "cursor-bridge": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/index.js",
12
+ "dev": "ts-node src/index.ts"
13
+ },
14
+ "keywords": ["cursor", "ide", "mobile", "bridge", "cli"],
15
+ "author": "",
16
+ "license": "MIT",
17
+ "dependencies": {
18
+ "chalk": "^4.1.2",
19
+ "ws": "^8.20.0",
20
+ "uuid": "^13.0.0",
21
+ "qrcode-terminal": "^0.12.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/ws": "^8.18.1",
25
+ "@types/node": "^25.5.2",
26
+ "typescript": "^6.0.2",
27
+ "ts-node": "^10.9.2"
28
+ }
29
+ }
package/src/index.ts ADDED
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { WebSocketServer, WebSocket } from 'ws';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import chalk from 'chalk';
6
+ import qrcode from 'qrcode-terminal';
7
+ import os from 'os';
8
+
9
+ const PORT = Number(process.argv[2]) || 9090;
10
+ const TOKEN = uuidv4();
11
+
12
+ interface BridgeMessage {
13
+ type: string;
14
+ payload: Record<string, unknown>;
15
+ from?: string;
16
+ timestamp: number;
17
+ }
18
+
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>();
27
+
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
+ }
55
+
56
+ function banner() {
57
+ const ip = getLocalIP();
58
+ const pairUrl = `cursor-bridge://${ip}:${PORT}/${TOKEN}`;
59
+
60
+ console.clear();
61
+ console.log('');
62
+ console.log(
63
+ chalk.hex('#A855F7').bold(' ╔══════════════════════════════════════╗'),
64
+ );
65
+ console.log(
66
+ chalk.hex('#A855F7').bold(' ║') +
67
+ chalk.white.bold(' IDE For Cursor — Mobile Bridge ') +
68
+ chalk.hex('#A855F7').bold('║'),
69
+ );
70
+ console.log(
71
+ chalk.hex('#A855F7').bold(' ╚══════════════════════════════════════╝'),
72
+ );
73
+ console.log('');
74
+ console.log(chalk.gray(' Scan this QR code with the mobile app:'));
75
+ 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
+
82
+ console.log('');
83
+ console.log(chalk.gray(' Or connect manually:'));
84
+ console.log(
85
+ chalk.white(' Host: ') + chalk.hex('#A855F7')(ip),
86
+ );
87
+ console.log(
88
+ chalk.white(' Port: ') + chalk.hex('#A855F7')(String(PORT)),
89
+ );
90
+ console.log(
91
+ chalk.white(' Token: ') + chalk.hex('#A855F7')(TOKEN),
92
+ );
93
+ console.log('');
94
+ console.log(chalk.gray(' Waiting for mobile connection...'));
95
+ console.log('');
96
+ }
97
+
98
+ const wss = new WebSocketServer({ port: PORT });
99
+
100
+ wss.on('connection', (ws: WebSocket) => {
101
+ const clientId = uuidv4();
102
+
103
+ ws.on('message', (raw: Buffer) => {
104
+ try {
105
+ const message: BridgeMessage = JSON.parse(raw.toString());
106
+
107
+ 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
+ );
142
+ console.log(
143
+ chalk.hex('#22C55E')(' ✓ ') +
144
+ chalk.white(`${clientType} device connected and paired!`),
145
+ );
146
+ } else {
147
+ console.log(
148
+ chalk.hex('#F59E0B')(' ⟳ ') +
149
+ chalk.white(`${clientType} device connected, waiting for peer...`),
150
+ );
151
+ }
152
+ break;
153
+ }
154
+
155
+ 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
+ }
190
+
191
+ case 'ping': {
192
+ ws.send(
193
+ JSON.stringify({
194
+ type: 'pong',
195
+ payload: {},
196
+ timestamp: Date.now(),
197
+ }),
198
+ );
199
+ break;
200
+ }
201
+ }
202
+ } catch {
203
+ ws.send(
204
+ JSON.stringify({
205
+ type: 'error',
206
+ payload: { message: 'Invalid message format' },
207
+ }),
208
+ );
209
+ }
210
+ });
211
+
212
+ 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
+ }
231
+ });
232
+ });
233
+
234
+ // Heartbeat
235
+ setInterval(() => {
236
+ clients.forEach(client => {
237
+ if (client.ws.readyState === WebSocket.OPEN) {
238
+ client.ws.ping();
239
+ }
240
+ });
241
+ }, 30000);
242
+
243
+ banner();
244
+
245
+ process.on('SIGINT', () => {
246
+ console.log('');
247
+ console.log(chalk.gray(' Bridge stopped.'));
248
+ wss.close();
249
+ process.exit(0);
250
+ });
package/src/types.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ declare module 'qrcode-terminal' {
2
+ function generate(
3
+ text: string,
4
+ options?: { small?: boolean },
5
+ callback?: (qr: string) => void,
6
+ ): void;
7
+ export = { generate };
8
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }