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.
- package/dist/index.js +73 -127
- package/package.json +2 -4
- 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
|
|
11
|
-
|
|
12
|
-
|
|
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('
|
|
25
|
+
console.log(chalk_1.default.gray(' Enter this code in the mobile app to pair:'));
|
|
51
26
|
console.log('');
|
|
52
|
-
|
|
53
|
-
|
|
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('
|
|
58
|
-
console.log(chalk_1.default.
|
|
59
|
-
console.log(chalk_1.default.
|
|
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('
|
|
34
|
+
console.log(chalk_1.default.gray(' Connecting to bridge server...'));
|
|
63
35
|
console.log('');
|
|
64
36
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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 '
|
|
73
|
-
const
|
|
74
|
-
|
|
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(
|
|
58
|
+
chalk_1.default.white('Mobile device paired successfully!'));
|
|
97
59
|
}
|
|
98
|
-
else {
|
|
99
|
-
console.log(chalk_1.default.
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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 '
|
|
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: '
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
165
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
10
|
-
const TOKEN = uuidv4();
|
|
7
|
+
const BRIDGE_URL = 'wss://cursor-226b2ae97542.herokuapp.com';
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
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(
|
|
33
|
+
console.log(
|
|
34
|
+
chalk.gray(' Enter this code in the mobile app to pair:'),
|
|
35
|
+
);
|
|
75
36
|
console.log('');
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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.
|
|
43
|
+
chalk.gray(' ─────────────────────────────────────────'),
|
|
86
44
|
);
|
|
87
45
|
console.log(
|
|
88
|
-
chalk.
|
|
46
|
+
chalk.gray(' Bridge: ') + chalk.white(BRIDGE_URL),
|
|
89
47
|
);
|
|
90
48
|
console.log(
|
|
91
|
-
chalk.
|
|
49
|
+
chalk.gray(' ─────────────────────────────────────────'),
|
|
92
50
|
);
|
|
93
51
|
console.log('');
|
|
94
|
-
console.log(chalk.gray('
|
|
52
|
+
console.log(chalk.gray(' Connecting to bridge server...'));
|
|
95
53
|
console.log('');
|
|
96
54
|
}
|
|
97
55
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
79
|
+
const message = JSON.parse(raw.toString());
|
|
106
80
|
|
|
107
81
|
switch (message.type) {
|
|
108
|
-
case '
|
|
109
|
-
const
|
|
110
|
-
|
|
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(
|
|
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('#
|
|
149
|
-
chalk.
|
|
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
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
192
|
-
ws
|
|
108
|
+
// Send response back
|
|
109
|
+
ws!.send(
|
|
193
110
|
JSON.stringify({
|
|
194
|
-
type: '
|
|
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
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
143
|
+
ws.on('error', (err) => {
|
|
144
|
+
console.error(
|
|
145
|
+
chalk.hex('#EF4444')(' ✗ ') +
|
|
146
|
+
chalk.gray(`Connection error: ${err.message}`),
|
|
147
|
+
);
|
|
240
148
|
});
|
|
241
|
-
}
|
|
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
|
-
|
|
166
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
167
|
+
ws?.close();
|
|
249
168
|
process.exit(0);
|
|
250
169
|
});
|