cursor-bridge 1.3.0 → 1.4.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.
- package/dist/index.js +73 -46
- package/package.json +1 -1
- package/src/index.ts +118 -56
package/dist/index.js
CHANGED
|
@@ -6,48 +6,58 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const ws_1 = __importDefault(require("ws"));
|
|
8
8
|
const child_process_1 = require("child_process");
|
|
9
|
+
const fs_1 = require("fs");
|
|
10
|
+
const path_1 = require("path");
|
|
9
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
10
12
|
const BRIDGE_URL = 'wss://cursor-226b2ae97542.herokuapp.com';
|
|
11
|
-
function
|
|
13
|
+
function getCursorAuthFromDB() {
|
|
14
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
15
|
+
const dbPath = (0, path_1.join)(home, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'state.vscdb');
|
|
16
|
+
if (!(0, fs_1.existsSync)(dbPath)) {
|
|
17
|
+
console.log(chalk_1.default.hex('#EF4444')(' ✗ ') +
|
|
18
|
+
chalk_1.default.gray('Cursor database not found. Is Cursor installed?'));
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
12
21
|
try {
|
|
13
|
-
//
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
chalk_1.default.white('Sent to Cursor IDE'));
|
|
22
|
+
// Read auth tokens from Cursor's SQLite database
|
|
23
|
+
const query = (key) => {
|
|
24
|
+
const result = (0, child_process_1.execSync)(`sqlite3 "${dbPath}" "SELECT value FROM ItemTable WHERE key='${key}'" 2>/dev/null`, { encoding: 'utf-8', timeout: 5000 }).trim();
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
const accessToken = query('cursorAuth/accessToken');
|
|
28
|
+
const refreshToken = query('cursorAuth/refreshToken');
|
|
29
|
+
const machineId = query('storage.serviceMachineId');
|
|
30
|
+
if (!accessToken || !refreshToken) {
|
|
31
|
+
console.log(chalk_1.default.hex('#EF4444')(' ✗ ') +
|
|
32
|
+
chalk_1.default.gray('Not logged into Cursor. Please log in first.'));
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
// macMachineId is optional
|
|
36
|
+
let macMachineId;
|
|
37
|
+
try {
|
|
38
|
+
macMachineId = query('storage.macMachineId') || undefined;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// not available on all platforms
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
accessToken: accessToken.replace(/"/g, ''),
|
|
45
|
+
refreshToken: refreshToken.replace(/"/g, ''),
|
|
46
|
+
machineId: machineId.replace(/"/g, ''),
|
|
47
|
+
macMachineId: macMachineId?.replace(/"/g, ''),
|
|
48
|
+
};
|
|
41
49
|
}
|
|
42
50
|
catch (err) {
|
|
43
51
|
console.log(chalk_1.default.hex('#EF4444')(' ✗ ') +
|
|
44
|
-
chalk_1.default.gray('Failed to
|
|
52
|
+
chalk_1.default.gray('Failed to read Cursor auth. Is Cursor installed and logged in?'));
|
|
53
|
+
return null;
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
56
|
function generatePairCode() {
|
|
48
57
|
return Math.floor(100000 + Math.random() * 900000).toString();
|
|
49
58
|
}
|
|
50
59
|
const PAIR_CODE = generatePairCode();
|
|
60
|
+
const cursorAuth = getCursorAuthFromDB();
|
|
51
61
|
let ws = null;
|
|
52
62
|
let reconnectTimer = null;
|
|
53
63
|
function banner() {
|
|
@@ -65,22 +75,40 @@ function banner() {
|
|
|
65
75
|
chalk_1.default.bgHex('#A855F7').white.bold(` ${PAIR_CODE.split('').join(' ')} `));
|
|
66
76
|
console.log('');
|
|
67
77
|
console.log(chalk_1.default.gray(' ─────────────────────────────────────────'));
|
|
68
|
-
|
|
78
|
+
if (cursorAuth) {
|
|
79
|
+
console.log(chalk_1.default.hex('#22C55E')(' ✓ ') +
|
|
80
|
+
chalk_1.default.white('Cursor auth detected'));
|
|
81
|
+
console.log(chalk_1.default.gray(' Mode: ') +
|
|
82
|
+
chalk_1.default.white('Direct API (works without Cursor open)'));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.log(chalk_1.default.hex('#F59E0B')(' ⚠ ') +
|
|
86
|
+
chalk_1.default.gray('No Cursor auth found — limited functionality'));
|
|
87
|
+
}
|
|
69
88
|
console.log(chalk_1.default.gray(' ─────────────────────────────────────────'));
|
|
70
89
|
console.log('');
|
|
71
|
-
console.log(chalk_1.default.gray(' Connecting to bridge server...'));
|
|
72
|
-
console.log('');
|
|
73
90
|
}
|
|
74
91
|
function connect() {
|
|
75
92
|
ws = new ws_1.default(BRIDGE_URL);
|
|
76
93
|
ws.on('open', () => {
|
|
77
94
|
console.log(chalk_1.default.hex('#22C55E')(' ✓ ') +
|
|
78
95
|
chalk_1.default.white('Connected to bridge server'));
|
|
96
|
+
// Pair as desktop client
|
|
79
97
|
ws.send(JSON.stringify({
|
|
80
98
|
type: 'pair',
|
|
81
99
|
payload: { pairCode: PAIR_CODE, clientType: 'desktop' },
|
|
82
100
|
timestamp: Date.now(),
|
|
83
101
|
}));
|
|
102
|
+
// Send Cursor auth to bridge so it can proxy API calls
|
|
103
|
+
if (cursorAuth) {
|
|
104
|
+
ws.send(JSON.stringify({
|
|
105
|
+
type: 'auth',
|
|
106
|
+
payload: cursorAuth,
|
|
107
|
+
timestamp: Date.now(),
|
|
108
|
+
}));
|
|
109
|
+
console.log(chalk_1.default.hex('#22C55E')(' ✓ ') +
|
|
110
|
+
chalk_1.default.white('Cursor auth sent to bridge'));
|
|
111
|
+
}
|
|
84
112
|
console.log(chalk_1.default.gray(' Waiting for mobile device...'));
|
|
85
113
|
console.log('');
|
|
86
114
|
});
|
|
@@ -93,30 +121,29 @@ function connect() {
|
|
|
93
121
|
if (status === 'connected') {
|
|
94
122
|
console.log(chalk_1.default.hex('#22C55E')(' ✓ ') +
|
|
95
123
|
chalk_1.default.white('Mobile device paired successfully!'));
|
|
124
|
+
console.log(chalk_1.default.gray(' Ready to receive prompts from mobile.'));
|
|
125
|
+
console.log('');
|
|
96
126
|
}
|
|
97
127
|
else if (status === 'waiting') {
|
|
98
|
-
|
|
128
|
+
// already shown
|
|
99
129
|
}
|
|
100
130
|
else if (status === 'disconnected') {
|
|
101
131
|
console.log(chalk_1.default.hex('#EF4444')(' ✗ ') +
|
|
102
132
|
chalk_1.default.gray('Mobile device disconnected'));
|
|
103
133
|
}
|
|
134
|
+
else if (status === 'auth_stored') {
|
|
135
|
+
console.log(chalk_1.default.hex('#22C55E')(' ✓ ') +
|
|
136
|
+
chalk_1.default.white('Auth credentials stored on bridge'));
|
|
137
|
+
}
|
|
104
138
|
break;
|
|
105
139
|
}
|
|
106
140
|
case 'command': {
|
|
107
141
|
const prompt = message.payload.prompt;
|
|
108
142
|
console.log(chalk_1.default.hex('#A855F7')(' → ') +
|
|
109
|
-
chalk_1.default.white('
|
|
110
|
-
chalk_1.default.gray(prompt.substring(0, 80) +
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
type: 'response',
|
|
114
|
-
payload: {
|
|
115
|
-
status: 'sent',
|
|
116
|
-
message: 'Sent to Cursor IDE',
|
|
117
|
-
},
|
|
118
|
-
timestamp: Date.now(),
|
|
119
|
-
}));
|
|
143
|
+
chalk_1.default.white('Prompt: ') +
|
|
144
|
+
chalk_1.default.gray(prompt.substring(0, 80) +
|
|
145
|
+
(prompt.length > 80 ? '...' : '')));
|
|
146
|
+
// Bridge handles API proxy now — no need to do anything locally
|
|
120
147
|
break;
|
|
121
148
|
}
|
|
122
149
|
case 'error': {
|
|
@@ -135,7 +162,7 @@ function connect() {
|
|
|
135
162
|
chalk_1.default.gray('Disconnected from bridge server'));
|
|
136
163
|
scheduleReconnect();
|
|
137
164
|
});
|
|
138
|
-
ws.on('error',
|
|
165
|
+
ws.on('error', err => {
|
|
139
166
|
console.error(chalk_1.default.hex('#EF4444')(' ✗ ') +
|
|
140
167
|
chalk_1.default.gray(`Connection error: ${err.message}`));
|
|
141
168
|
});
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,50 +2,81 @@
|
|
|
2
2
|
|
|
3
3
|
import WebSocket from 'ws';
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
5
7
|
import chalk from 'chalk';
|
|
6
8
|
|
|
7
9
|
const BRIDGE_URL = 'wss://cursor-226b2ae97542.herokuapp.com';
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.replace(/\n/g, '\\n');
|
|
16
|
-
|
|
17
|
-
const script = `
|
|
18
|
-
tell application "Cursor"
|
|
19
|
-
activate
|
|
20
|
-
end tell
|
|
21
|
-
delay 0.5
|
|
22
|
-
tell application "System Events"
|
|
23
|
-
tell process "Cursor"
|
|
24
|
-
-- Open Composer with Cmd+I
|
|
25
|
-
keystroke "i" using command down
|
|
26
|
-
delay 0.3
|
|
27
|
-
-- Type the prompt
|
|
28
|
-
keystroke "${escaped}"
|
|
29
|
-
delay 0.2
|
|
30
|
-
-- Submit with Enter
|
|
31
|
-
key code 36
|
|
32
|
-
end tell
|
|
33
|
-
end tell
|
|
34
|
-
`;
|
|
35
|
-
|
|
36
|
-
execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, {
|
|
37
|
-
timeout: 10000,
|
|
38
|
-
});
|
|
11
|
+
interface CursorAuth {
|
|
12
|
+
accessToken: string;
|
|
13
|
+
refreshToken: string;
|
|
14
|
+
machineId: string;
|
|
15
|
+
macMachineId?: string;
|
|
16
|
+
}
|
|
39
17
|
|
|
18
|
+
function getCursorAuthFromDB(): CursorAuth | null {
|
|
19
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
20
|
+
const dbPath = join(
|
|
21
|
+
home,
|
|
22
|
+
'Library',
|
|
23
|
+
'Application Support',
|
|
24
|
+
'Cursor',
|
|
25
|
+
'User',
|
|
26
|
+
'globalStorage',
|
|
27
|
+
'state.vscdb',
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (!existsSync(dbPath)) {
|
|
40
31
|
console.log(
|
|
41
|
-
chalk.hex('#
|
|
42
|
-
chalk.
|
|
32
|
+
chalk.hex('#EF4444')(' ✗ ') +
|
|
33
|
+
chalk.gray('Cursor database not found. Is Cursor installed?'),
|
|
43
34
|
);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Read auth tokens from Cursor's SQLite database
|
|
40
|
+
const query = (key: string): string => {
|
|
41
|
+
const result = execSync(
|
|
42
|
+
`sqlite3 "${dbPath}" "SELECT value FROM ItemTable WHERE key='${key}'" 2>/dev/null`,
|
|
43
|
+
{ encoding: 'utf-8', timeout: 5000 },
|
|
44
|
+
).trim();
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const accessToken = query('cursorAuth/accessToken');
|
|
49
|
+
const refreshToken = query('cursorAuth/refreshToken');
|
|
50
|
+
const machineId = query('storage.serviceMachineId');
|
|
51
|
+
|
|
52
|
+
if (!accessToken || !refreshToken) {
|
|
53
|
+
console.log(
|
|
54
|
+
chalk.hex('#EF4444')(' ✗ ') +
|
|
55
|
+
chalk.gray('Not logged into Cursor. Please log in first.'),
|
|
56
|
+
);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// macMachineId is optional
|
|
61
|
+
let macMachineId: string | undefined;
|
|
62
|
+
try {
|
|
63
|
+
macMachineId = query('storage.macMachineId') || undefined;
|
|
64
|
+
} catch {
|
|
65
|
+
// not available on all platforms
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
accessToken: accessToken.replace(/"/g, ''),
|
|
70
|
+
refreshToken: refreshToken.replace(/"/g, ''),
|
|
71
|
+
machineId: machineId.replace(/"/g, ''),
|
|
72
|
+
macMachineId: macMachineId?.replace(/"/g, ''),
|
|
73
|
+
};
|
|
44
74
|
} catch (err) {
|
|
45
75
|
console.log(
|
|
46
76
|
chalk.hex('#EF4444')(' ✗ ') +
|
|
47
|
-
chalk.gray('Failed to
|
|
77
|
+
chalk.gray('Failed to read Cursor auth. Is Cursor installed and logged in?'),
|
|
48
78
|
);
|
|
79
|
+
return null;
|
|
49
80
|
}
|
|
50
81
|
}
|
|
51
82
|
|
|
@@ -54,6 +85,7 @@ function generatePairCode(): string {
|
|
|
54
85
|
}
|
|
55
86
|
|
|
56
87
|
const PAIR_CODE = generatePairCode();
|
|
88
|
+
const cursorAuth = getCursorAuthFromDB();
|
|
57
89
|
|
|
58
90
|
let ws: WebSocket | null = null;
|
|
59
91
|
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
@@ -85,15 +117,27 @@ function banner() {
|
|
|
85
117
|
console.log(
|
|
86
118
|
chalk.gray(' ─────────────────────────────────────────'),
|
|
87
119
|
);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
120
|
+
|
|
121
|
+
if (cursorAuth) {
|
|
122
|
+
console.log(
|
|
123
|
+
chalk.hex('#22C55E')(' ✓ ') +
|
|
124
|
+
chalk.white('Cursor auth detected'),
|
|
125
|
+
);
|
|
126
|
+
console.log(
|
|
127
|
+
chalk.gray(' Mode: ') +
|
|
128
|
+
chalk.white('Direct API (works without Cursor open)'),
|
|
129
|
+
);
|
|
130
|
+
} else {
|
|
131
|
+
console.log(
|
|
132
|
+
chalk.hex('#F59E0B')(' ⚠ ') +
|
|
133
|
+
chalk.gray('No Cursor auth found — limited functionality'),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
91
137
|
console.log(
|
|
92
138
|
chalk.gray(' ─────────────────────────────────────────'),
|
|
93
139
|
);
|
|
94
140
|
console.log('');
|
|
95
|
-
console.log(chalk.gray(' Connecting to bridge server...'));
|
|
96
|
-
console.log('');
|
|
97
141
|
}
|
|
98
142
|
|
|
99
143
|
function connect() {
|
|
@@ -105,6 +149,7 @@ function connect() {
|
|
|
105
149
|
chalk.white('Connected to bridge server'),
|
|
106
150
|
);
|
|
107
151
|
|
|
152
|
+
// Pair as desktop client
|
|
108
153
|
ws!.send(
|
|
109
154
|
JSON.stringify({
|
|
110
155
|
type: 'pair',
|
|
@@ -113,6 +158,21 @@ function connect() {
|
|
|
113
158
|
}),
|
|
114
159
|
);
|
|
115
160
|
|
|
161
|
+
// Send Cursor auth to bridge so it can proxy API calls
|
|
162
|
+
if (cursorAuth) {
|
|
163
|
+
ws!.send(
|
|
164
|
+
JSON.stringify({
|
|
165
|
+
type: 'auth',
|
|
166
|
+
payload: cursorAuth,
|
|
167
|
+
timestamp: Date.now(),
|
|
168
|
+
}),
|
|
169
|
+
);
|
|
170
|
+
console.log(
|
|
171
|
+
chalk.hex('#22C55E')(' ✓ ') +
|
|
172
|
+
chalk.white('Cursor auth sent to bridge'),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
116
176
|
console.log(chalk.gray(' Waiting for mobile device...'));
|
|
117
177
|
console.log('');
|
|
118
178
|
});
|
|
@@ -129,13 +189,22 @@ function connect() {
|
|
|
129
189
|
chalk.hex('#22C55E')(' ✓ ') +
|
|
130
190
|
chalk.white('Mobile device paired successfully!'),
|
|
131
191
|
);
|
|
192
|
+
console.log(
|
|
193
|
+
chalk.gray(' Ready to receive prompts from mobile.'),
|
|
194
|
+
);
|
|
195
|
+
console.log('');
|
|
132
196
|
} else if (status === 'waiting') {
|
|
133
|
-
|
|
197
|
+
// already shown
|
|
134
198
|
} else if (status === 'disconnected') {
|
|
135
199
|
console.log(
|
|
136
200
|
chalk.hex('#EF4444')(' ✗ ') +
|
|
137
201
|
chalk.gray('Mobile device disconnected'),
|
|
138
202
|
);
|
|
203
|
+
} else if (status === 'auth_stored') {
|
|
204
|
+
console.log(
|
|
205
|
+
chalk.hex('#22C55E')(' ✓ ') +
|
|
206
|
+
chalk.white('Auth credentials stored on bridge'),
|
|
207
|
+
);
|
|
139
208
|
}
|
|
140
209
|
break;
|
|
141
210
|
}
|
|
@@ -144,29 +213,22 @@ function connect() {
|
|
|
144
213
|
const prompt = message.payload.prompt as string;
|
|
145
214
|
console.log(
|
|
146
215
|
chalk.hex('#A855F7')(' → ') +
|
|
147
|
-
chalk.white('
|
|
148
|
-
chalk.gray(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
ws!.send(
|
|
154
|
-
JSON.stringify({
|
|
155
|
-
type: 'response',
|
|
156
|
-
payload: {
|
|
157
|
-
status: 'sent',
|
|
158
|
-
message: 'Sent to Cursor IDE',
|
|
159
|
-
},
|
|
160
|
-
timestamp: Date.now(),
|
|
161
|
-
}),
|
|
216
|
+
chalk.white('Prompt: ') +
|
|
217
|
+
chalk.gray(
|
|
218
|
+
prompt.substring(0, 80) +
|
|
219
|
+
(prompt.length > 80 ? '...' : ''),
|
|
220
|
+
),
|
|
162
221
|
);
|
|
222
|
+
// Bridge handles API proxy now — no need to do anything locally
|
|
163
223
|
break;
|
|
164
224
|
}
|
|
165
225
|
|
|
166
226
|
case 'error': {
|
|
167
227
|
console.log(
|
|
168
228
|
chalk.hex('#EF4444')(' ✗ ') +
|
|
169
|
-
chalk.gray(
|
|
229
|
+
chalk.gray(
|
|
230
|
+
(message.payload.message as string) || 'Unknown error',
|
|
231
|
+
),
|
|
170
232
|
);
|
|
171
233
|
break;
|
|
172
234
|
}
|
|
@@ -184,7 +246,7 @@ function connect() {
|
|
|
184
246
|
scheduleReconnect();
|
|
185
247
|
});
|
|
186
248
|
|
|
187
|
-
ws.on('error',
|
|
249
|
+
ws.on('error', err => {
|
|
188
250
|
console.error(
|
|
189
251
|
chalk.hex('#EF4444')(' ✗ ') +
|
|
190
252
|
chalk.gray(`Connection error: ${err.message}`),
|