cursorconnect 0.1.2 → 0.1.4
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/README.md +5 -4
- package/bridge-runtime/dist/agent-title-match.js +16 -0
- package/bridge-runtime/dist/chat-display-store.d.ts +13 -0
- package/bridge-runtime/dist/chat-display-store.js +29 -0
- package/bridge-runtime/dist/chat-display.d.ts +11 -0
- package/bridge-runtime/dist/chat-display.js +290 -0
- package/bridge-runtime/dist/chat-sync.d.ts +6 -0
- package/bridge-runtime/dist/chat-sync.js +88 -0
- package/bridge-runtime/dist/extract-page.js +99 -3
- package/bridge-runtime/dist/history-pipeline-log.d.ts +16 -0
- package/bridge-runtime/dist/history-pipeline-log.js +29 -0
- package/bridge-runtime/dist/jsonl-index.d.ts +15 -3
- package/bridge-runtime/dist/jsonl-index.js +48 -12
- package/bridge-runtime/dist/message-filter.d.ts +10 -0
- package/bridge-runtime/dist/message-filter.js +65 -5
- package/bridge-runtime/dist/pairing-code.d.ts +3 -0
- package/bridge-runtime/dist/pairing-code.js +17 -0
- package/bridge-runtime/dist/pairing-identity.js +4 -7
- package/bridge-runtime/dist/relay.d.ts +8 -0
- package/bridge-runtime/dist/relay.js +254 -25
- package/bridge-runtime/dist/sidebar-merge.js +2 -2
- package/bridge-runtime/dist/types.d.ts +9 -1
- package/config.env.defaults +3 -0
- package/dist/bridge-dir.js +5 -0
- package/dist/cli-version.js +13 -0
- package/dist/diagnose.js +224 -0
- package/dist/index.js +56 -55
- package/dist/launch.js +45 -13
- package/dist/pairing-code.js +18 -0
- package/dist/pairing-identity.js +3 -6
- package/dist/print-pairing.js +9 -7
- package/dist/relay-config.js +49 -0
- package/dist/semver.js +21 -0
- package/dist/version-check.js +31 -0
- package/package.json +6 -2
- package/version-policy.json +8 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync } from 'fs';
|
|
3
2
|
import { join, resolve } from 'path';
|
|
4
3
|
import { ensurePairingIdentity, loadPairingIdentity, refreshPairingCode, } from './pairing-identity.js';
|
|
5
4
|
import { ensureCursorCdp, isBridgeRunning, startBridge, stopBridge, waitBridgeHealth, waitRelayConnector, } from './launch.js';
|
|
@@ -7,40 +6,24 @@ import { printPairingToTerminal } from './print-pairing.js';
|
|
|
7
6
|
import { BRIDGE_LOG_FILE } from './paths.js';
|
|
8
7
|
import { ensureUserConfig, isValidBridgeDir, resolveBridgeDir, USER_CONFIG_ENV, } from './bridge-dir.js';
|
|
9
8
|
import { configFilePath, saveInstallConfig } from './repo-root.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
for (const line of readFileSync(path, 'utf-8').split('\n')) {
|
|
15
|
-
const t = line.trim();
|
|
16
|
-
if (!t || t.startsWith('#'))
|
|
17
|
-
continue;
|
|
18
|
-
const i = t.indexOf('=');
|
|
19
|
-
if (i < 1)
|
|
20
|
-
continue;
|
|
21
|
-
out[t.slice(0, i).trim()] = t.slice(i + 1).trim();
|
|
22
|
-
}
|
|
23
|
-
return out;
|
|
24
|
-
}
|
|
9
|
+
import { formatDiagnoseReport, runDiagnose } from './diagnose.js';
|
|
10
|
+
import { loadEnvFile, resolveRelayConfig } from './relay-config.js';
|
|
11
|
+
import { ensureCliVersionForRelay } from './version-check.js';
|
|
12
|
+
import { CLI_VERSION } from './cli-version.js';
|
|
25
13
|
function bridgeEnv(identity) {
|
|
26
14
|
ensureUserConfig();
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (existsSync(p)) {
|
|
34
|
-
file = { ...file, ...loadEnvFile(p) };
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const relayUrl = file.RELAY_URL ?? process.env.RELAY_URL ?? '';
|
|
38
|
-
const relayToken = file.RELAY_TOKEN ?? process.env.RELAY_TOKEN ?? '';
|
|
39
|
-
if (!relayUrl || !relayToken) {
|
|
40
|
-
console.error(`Нужны RELAY_URL и RELAY_TOKEN в ${USER_CONFIG_ENV}\n` +
|
|
41
|
-
'(создаётся при установке; отредактируйте и снова: cursorconnect start)');
|
|
15
|
+
const bridgeDotEnv = join(resolveBridgeDir(), '.env');
|
|
16
|
+
const { relayUrl, relayToken } = resolveRelayConfig([bridgeDotEnv]);
|
|
17
|
+
if (!relayToken) {
|
|
18
|
+
console.error('[cursorconnect] Не найден RELAY_TOKEN (встроенный конфиг пакета повреждён).\n' +
|
|
19
|
+
` Переустановите: npm install -g cursorconnect\n` +
|
|
20
|
+
` Или укажите RELAY_TOKEN в ${USER_CONFIG_ENV} (свой relay).`);
|
|
42
21
|
process.exit(1);
|
|
43
22
|
}
|
|
23
|
+
const file = {
|
|
24
|
+
...loadEnvFile(bridgeDotEnv),
|
|
25
|
+
...loadEnvFile(USER_CONFIG_ENV),
|
|
26
|
+
};
|
|
44
27
|
return {
|
|
45
28
|
RELAY_URL: relayUrl,
|
|
46
29
|
RELAY_TOKEN: relayToken,
|
|
@@ -48,39 +31,49 @@ function bridgeEnv(identity) {
|
|
|
48
31
|
SERVER_HOST: '127.0.0.1',
|
|
49
32
|
SERVER_PORT: '3847',
|
|
50
33
|
CDP_URL: file.CDP_URL ?? 'http://127.0.0.1:9222',
|
|
34
|
+
CURSORCONNECT_CLI_VERSION: CLI_VERSION,
|
|
51
35
|
};
|
|
52
36
|
}
|
|
53
37
|
function startFlags(argv) {
|
|
54
38
|
return {
|
|
55
39
|
restartCursor: argv.includes('--restart-cursor') || argv.includes('-r'),
|
|
40
|
+
noRestartCursor: argv.includes('--no-restart-cursor'),
|
|
56
41
|
};
|
|
57
42
|
}
|
|
58
43
|
async function cmdStart(argv) {
|
|
59
|
-
const { restartCursor } = startFlags(argv);
|
|
44
|
+
const { restartCursor, noRestartCursor } = startFlags(argv);
|
|
60
45
|
const identity = ensurePairingIdentity();
|
|
61
46
|
const refreshed = refreshPairingCode(identity);
|
|
62
47
|
const env = bridgeEnv(refreshed);
|
|
63
48
|
const relayUrl = env.RELAY_URL;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
49
|
+
await ensureCliVersionForRelay(relayUrl);
|
|
50
|
+
// 1) Сначала CDP (порт 9222), иначе bridge стартует без сайдбара Cursor.
|
|
51
|
+
const cdpPortOk = await ensureCursorCdp({
|
|
52
|
+
cdpUrl: env.CDP_URL,
|
|
53
|
+
restartCursor,
|
|
54
|
+
noRestartCursor,
|
|
55
|
+
});
|
|
56
|
+
if (!cdpPortOk) {
|
|
57
|
+
console.warn('[cursorconnect] CDP порт недоступен — bridge запустится, список чатов будет из архива JSONL.');
|
|
67
58
|
}
|
|
68
|
-
console.log(`Компьютер: ${refreshed.machineLabel}`);
|
|
69
59
|
if (isBridgeRunning()) {
|
|
70
|
-
console.log('Перезапуск bridge
|
|
60
|
+
console.log('Перезапуск bridge…');
|
|
71
61
|
stopBridge();
|
|
72
62
|
}
|
|
73
63
|
startBridge(env);
|
|
74
64
|
console.log(`Bridge в фоне → ${BRIDGE_LOG_FILE}`);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
65
|
+
// 2) Ждём, пока bridge реально подключится к workbench (health.cdp), не только :9222.
|
|
66
|
+
const health = await waitBridgeHealth(45_000, { requireCdp: cdpPortOk });
|
|
67
|
+
const connectorOk = await waitRelayConnector(relayUrl, 30_000);
|
|
68
|
+
const cdpReady = cdpPortOk && health.cdp;
|
|
69
|
+
if (cdpPortOk && !health.cdp) {
|
|
70
|
+
console.warn('[cursorconnect] Порт 9222 открыт, но bridge не подключился к Cursor (cdp:false). ' +
|
|
71
|
+
'Проверьте: cursorconnect status · tail -f ' +
|
|
72
|
+
BRIDGE_LOG_FILE);
|
|
73
|
+
}
|
|
81
74
|
printPairingToTerminal(refreshed, {
|
|
82
75
|
bridge: health.ok,
|
|
83
|
-
cdp:
|
|
76
|
+
cdp: cdpReady,
|
|
84
77
|
connector: connectorOk,
|
|
85
78
|
});
|
|
86
79
|
}
|
|
@@ -102,11 +95,17 @@ async function cmdInit(pathArg) {
|
|
|
102
95
|
console.log(`repoRoot: ${abs}`);
|
|
103
96
|
console.log('\nДальше: cursorconnect start');
|
|
104
97
|
}
|
|
98
|
+
async function cmdDiagnose() {
|
|
99
|
+
const report = await runDiagnose();
|
|
100
|
+
console.log(formatDiagnoseReport(report));
|
|
101
|
+
const failed = report.checks.some((c) => !c.ok);
|
|
102
|
+
process.exit(failed ? 1 : 0);
|
|
103
|
+
}
|
|
105
104
|
async function cmdStatus() {
|
|
106
105
|
const identity = loadPairingIdentity();
|
|
107
106
|
ensureUserConfig();
|
|
108
|
-
const
|
|
109
|
-
const relayUrl =
|
|
107
|
+
const bridgeDotEnv = join(resolveBridgeDir(), '.env');
|
|
108
|
+
const { relayUrl } = resolveRelayConfig([bridgeDotEnv]);
|
|
110
109
|
console.log('Bridge running:', isBridgeRunning());
|
|
111
110
|
if (!identity) {
|
|
112
111
|
console.log('Identity не найден — запустите: cursorconnect start');
|
|
@@ -133,18 +132,15 @@ async function cmdStatus() {
|
|
|
133
132
|
catch {
|
|
134
133
|
/* ignore */
|
|
135
134
|
}
|
|
135
|
+
printPairingToTerminal(identity, { bridge: bridgeOk, cdp, connector });
|
|
136
136
|
}
|
|
137
|
-
|
|
137
|
+
else {
|
|
138
138
|
printPairingToTerminal(identity, {
|
|
139
139
|
bridge: bridgeOk,
|
|
140
140
|
cdp,
|
|
141
|
-
connector,
|
|
141
|
+
connector: false,
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
|
-
else {
|
|
145
|
-
console.log('Room:', identity.roomId);
|
|
146
|
-
console.log('Код:', identity.pairingCode);
|
|
147
|
-
}
|
|
148
144
|
}
|
|
149
145
|
async function main() {
|
|
150
146
|
const argv = process.argv.slice(2);
|
|
@@ -160,21 +156,26 @@ async function main() {
|
|
|
160
156
|
case 'status':
|
|
161
157
|
await cmdStatus();
|
|
162
158
|
break;
|
|
159
|
+
case 'diagnose':
|
|
160
|
+
await cmdDiagnose();
|
|
161
|
+
break;
|
|
163
162
|
case 'init':
|
|
164
163
|
await cmdInit(rest[0]);
|
|
165
164
|
break;
|
|
166
165
|
default:
|
|
167
166
|
console.log(`Usage: cursorconnect <command>
|
|
168
167
|
|
|
169
|
-
start
|
|
168
|
+
start — CDP (авто-перезапуск Cursor) + bridge + код
|
|
169
|
+
start -r — то же (явный перезапуск Cursor)
|
|
170
|
+
start --no-restart-cursor — не трогать Cursor (спросит y/n в TTY)
|
|
170
171
|
stop — остановить bridge
|
|
171
172
|
status — код и статус
|
|
173
|
+
diagnose — проверка relay / bridge / socket (без гаданий)
|
|
172
174
|
init [path] — только для разработки (клон репо)
|
|
173
175
|
|
|
174
176
|
Установка (из любой папки):
|
|
175
177
|
npm install -g cursorconnect
|
|
176
|
-
#
|
|
177
|
-
cursorconnect start`);
|
|
178
|
+
cursorconnect start # код pairing, relay уже в пакете`);
|
|
178
179
|
process.exit(cmd === 'help' ? 0 : 1);
|
|
179
180
|
}
|
|
180
181
|
}
|
package/dist/launch.js
CHANGED
|
@@ -34,8 +34,22 @@ export function stopBridge() {
|
|
|
34
34
|
/* ignore */
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
function killProcessesOnBridgePort() {
|
|
38
|
+
try {
|
|
39
|
+
execSync('lsof -t -i:3847 2>/dev/null | xargs kill -TERM 2>/dev/null', {
|
|
40
|
+
stdio: 'ignore',
|
|
41
|
+
shell: '/bin/bash',
|
|
42
|
+
});
|
|
43
|
+
/* give old bridge time to exit */
|
|
44
|
+
execSync('sleep 0.5', { stdio: 'ignore', shell: '/bin/bash' });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
/* port free */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
37
50
|
export function startBridge(env) {
|
|
38
51
|
stopBridge();
|
|
52
|
+
killProcessesOnBridgePort();
|
|
39
53
|
const bridgeDir = resolveBridgeDir();
|
|
40
54
|
const logFd = openLogAppend();
|
|
41
55
|
const child = spawn('node', ['dist/index.js'], {
|
|
@@ -51,19 +65,35 @@ export function startBridge(env) {
|
|
|
51
65
|
function openLogAppend() {
|
|
52
66
|
return openSync(BRIDGE_LOG_FILE, 'a');
|
|
53
67
|
}
|
|
54
|
-
export async function waitBridgeHealth(maxMs = 30_000) {
|
|
68
|
+
export async function waitBridgeHealth(maxMs = 30_000, opts) {
|
|
69
|
+
const requireCdp = opts?.requireCdp === true;
|
|
55
70
|
const deadline = Date.now() + maxMs;
|
|
56
71
|
while (Date.now() < deadline) {
|
|
57
72
|
try {
|
|
58
73
|
const res = await fetch('http://127.0.0.1:3847/health');
|
|
59
|
-
if (res.ok)
|
|
60
|
-
|
|
74
|
+
if (res.ok) {
|
|
75
|
+
const j = (await res.json());
|
|
76
|
+
const ok = Boolean(j.ok);
|
|
77
|
+
const cdp = Boolean(j.cdp);
|
|
78
|
+
if (ok && (!requireCdp || cdp))
|
|
79
|
+
return { ok, cdp };
|
|
80
|
+
}
|
|
61
81
|
}
|
|
62
82
|
catch {
|
|
63
83
|
/* retry */
|
|
64
84
|
}
|
|
65
85
|
await sleep(500);
|
|
66
86
|
}
|
|
87
|
+
try {
|
|
88
|
+
const res = await fetch('http://127.0.0.1:3847/health');
|
|
89
|
+
if (res.ok) {
|
|
90
|
+
const j = (await res.json());
|
|
91
|
+
return { ok: Boolean(j.ok), cdp: Boolean(j.cdp) };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
/* ignore */
|
|
96
|
+
}
|
|
67
97
|
return { ok: false, cdp: false };
|
|
68
98
|
}
|
|
69
99
|
export async function waitRelayConnector(relayUrl, maxMs = 45_000) {
|
|
@@ -146,29 +176,31 @@ export async function ensureCursorCdp(opts = {}) {
|
|
|
146
176
|
if (!running) {
|
|
147
177
|
console.log('[cursorconnect] Cursor не запущен — старт с --remote-debugging-port=9222…');
|
|
148
178
|
launchCursorWithCdp();
|
|
149
|
-
|
|
179
|
+
if (await waitCdp(cdpUrl, 50))
|
|
180
|
+
return true;
|
|
181
|
+
printManualCdpInstructions();
|
|
182
|
+
return false;
|
|
150
183
|
}
|
|
151
|
-
// Cursor запущен,
|
|
152
|
-
let shouldRestart =
|
|
184
|
+
// Cursor запущен, порт 9222 не отвечает — обычный запуск без --remote-debugging-port
|
|
185
|
+
let shouldRestart = opts.noRestartCursor !== true;
|
|
153
186
|
if (!shouldRestart) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return false;
|
|
187
|
+
shouldRestart = Boolean(opts.restartCursor);
|
|
188
|
+
if (!shouldRestart && process.stdin.isTTY) {
|
|
189
|
+
shouldRestart = await askYesNo('Cursor без CDP. Перезапустить с --remote-debugging-port=9222? (y/n): ');
|
|
158
190
|
}
|
|
159
|
-
shouldRestart = await askYesNo('Cursor запущен без CDP. Перезапустить с --remote-debugging-port=9222? (y/n): ');
|
|
160
191
|
}
|
|
161
192
|
if (!shouldRestart) {
|
|
193
|
+
console.warn('[cursorconnect] Cursor без CDP — список чатов будет только из JSONL.');
|
|
162
194
|
printManualCdpInstructions();
|
|
163
195
|
return false;
|
|
164
196
|
}
|
|
197
|
+
console.log('[cursorconnect] Cursor без CDP — перезапуск с --remote-debugging-port=9222…');
|
|
165
198
|
const quitOk = await quitCursor();
|
|
166
199
|
if (!quitOk) {
|
|
167
|
-
console.warn('[cursorconnect] Не удалось закрыть Cursor.');
|
|
200
|
+
console.warn('[cursorconnect] Не удалось закрыть Cursor (Cmd+Q вручную).');
|
|
168
201
|
printManualCdpInstructions();
|
|
169
202
|
return false;
|
|
170
203
|
}
|
|
171
|
-
console.log('[cursorconnect] Запуск Cursor с --remote-debugging-port=9222…');
|
|
172
204
|
launchCursorWithCdp();
|
|
173
205
|
if (await waitCdp(cdpUrl, 50))
|
|
174
206
|
return true;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
export const PAIRING_CODE_LENGTH = 6;
|
|
3
|
+
/** Latin letters + digits (ASCII), uppercase in storage. */
|
|
4
|
+
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
5
|
+
export function generatePairingCode() {
|
|
6
|
+
const bytes = randomBytes(PAIRING_CODE_LENGTH);
|
|
7
|
+
let out = '';
|
|
8
|
+
for (let i = 0; i < PAIRING_CODE_LENGTH; i++) {
|
|
9
|
+
out += ALPHABET[bytes[i] % ALPHABET.length];
|
|
10
|
+
}
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
export function normalizePairingCode(raw) {
|
|
14
|
+
const code = raw.replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
|
|
15
|
+
if (code.length !== PAIRING_CODE_LENGTH)
|
|
16
|
+
return null;
|
|
17
|
+
return code;
|
|
18
|
+
}
|
package/dist/pairing-identity.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { randomBytes, randomUUID } from 'crypto';
|
|
2
|
+
import { generatePairingCode } from './pairing-code.js';
|
|
2
3
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
3
4
|
import { homedir, hostname } from 'os';
|
|
4
5
|
import { join } from 'path';
|
|
5
6
|
const DIR = join(homedir(), '.cursorconnect');
|
|
6
7
|
const FILE = join(DIR, 'identity.json');
|
|
7
|
-
function formatPairingCode() {
|
|
8
|
-
const n = randomBytes(3).readUIntBE(0, 3) % 1_000_000;
|
|
9
|
-
return String(n).padStart(6, '0');
|
|
10
|
-
}
|
|
11
8
|
export function pairingIdentityPath() {
|
|
12
9
|
return FILE;
|
|
13
10
|
}
|
|
@@ -35,7 +32,7 @@ export function ensurePairingIdentity(machineLabel) {
|
|
|
35
32
|
roomId: randomUUID(),
|
|
36
33
|
clientToken: randomBytes(32).toString('hex'),
|
|
37
34
|
machineLabel: machineLabel?.trim() || defaultMachineLabel(),
|
|
38
|
-
pairingCode:
|
|
35
|
+
pairingCode: generatePairingCode(),
|
|
39
36
|
pairingCodeExpiresAt: Date.now() + 10 * 60_000,
|
|
40
37
|
createdAt: now,
|
|
41
38
|
updatedAt: now,
|
|
@@ -47,7 +44,7 @@ export function refreshPairingCode(identity, machineLabel) {
|
|
|
47
44
|
const next = {
|
|
48
45
|
...identity,
|
|
49
46
|
machineLabel: machineLabel?.trim() || identity.machineLabel,
|
|
50
|
-
pairingCode:
|
|
47
|
+
pairingCode: generatePairingCode(),
|
|
51
48
|
pairingCodeExpiresAt: Date.now() + 10 * 60_000,
|
|
52
49
|
updatedAt: new Date().toISOString(),
|
|
53
50
|
};
|
package/dist/print-pairing.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
import { renderBigCode } from './big-code.js';
|
|
2
1
|
export function printPairingToTerminal(identity, status) {
|
|
3
2
|
const expiresIn = Math.max(0, Math.floor((identity.pairingCodeExpiresAt - Date.now()) / 1000));
|
|
4
|
-
const
|
|
3
|
+
const ready = status.bridge && status.cdp && status.connector;
|
|
5
4
|
const code = identity.pairingCode;
|
|
6
5
|
console.log('\n────────────────────────────────────────');
|
|
7
|
-
console.log(
|
|
6
|
+
console.log(' CursorConnect');
|
|
7
|
+
console.log(` Компьютер: ${identity.machineLabel}`);
|
|
8
8
|
console.log('');
|
|
9
9
|
console.log(' Введите код в приложении CursorConnect:');
|
|
10
10
|
console.log('');
|
|
11
|
-
console.log(
|
|
11
|
+
console.log(` ${code}`);
|
|
12
12
|
console.log('');
|
|
13
|
-
console.log(
|
|
13
|
+
console.log(' Код одноразовый — после подключения снова: cursorconnect start');
|
|
14
14
|
console.log(` Действует ~${Math.max(1, Math.floor(expiresIn / 60))} мин`);
|
|
15
|
-
|
|
16
|
-
if (!ok) {
|
|
15
|
+
if (!ready) {
|
|
17
16
|
const parts = [];
|
|
18
17
|
if (!status.bridge)
|
|
19
18
|
parts.push('bridge');
|
|
@@ -21,12 +20,15 @@ export function printPairingToTerminal(identity, status) {
|
|
|
21
20
|
parts.push('Cursor');
|
|
22
21
|
if (!status.connector)
|
|
23
22
|
parts.push('сервер');
|
|
23
|
+
console.log('');
|
|
24
24
|
console.log(` Ожидание: ${parts.join(', ')}…`);
|
|
25
25
|
console.log(' Подождите 10–20 с и снова: cursorconnect status');
|
|
26
26
|
}
|
|
27
27
|
else {
|
|
28
|
+
console.log('');
|
|
28
29
|
console.log(' Готово — введите код на телефоне.');
|
|
29
30
|
}
|
|
31
|
+
console.log('');
|
|
30
32
|
console.log('────────────────────────────────────────');
|
|
31
33
|
console.log(' Bridge в фоне · cursorconnect stop');
|
|
32
34
|
console.log(' Консоль можно закрыть.\n');
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { resolvePackageRoot } from './bridge-dir.js';
|
|
4
|
+
import { USER_CONFIG_ENV } from './bridge-dir.js';
|
|
5
|
+
/** Shipped with npm package (generated at publish from bridge/.env). */
|
|
6
|
+
export const BUNDLED_CONFIG_ENV = join(resolvePackageRoot(), 'config.env.defaults');
|
|
7
|
+
/** Public production relay (same as app `extra.bridgeUrl`). */
|
|
8
|
+
export const DEFAULT_RELAY_URL = 'https://cc.fanpay.online';
|
|
9
|
+
export function loadEnvFile(path) {
|
|
10
|
+
if (!existsSync(path))
|
|
11
|
+
return {};
|
|
12
|
+
const out = {};
|
|
13
|
+
for (const line of readFileSync(path, 'utf-8').split('\n')) {
|
|
14
|
+
const t = line.trim();
|
|
15
|
+
if (!t || t.startsWith('#'))
|
|
16
|
+
continue;
|
|
17
|
+
const i = t.indexOf('=');
|
|
18
|
+
if (i < 1)
|
|
19
|
+
continue;
|
|
20
|
+
out[t.slice(0, i).trim()] = t.slice(i + 1).trim();
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function pickNonEmpty(...values) {
|
|
25
|
+
for (const v of values) {
|
|
26
|
+
const t = v?.trim();
|
|
27
|
+
if (t)
|
|
28
|
+
return t;
|
|
29
|
+
}
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
/** Relay URL + connector token: bundled defaults → user config → bridge .env → process.env. */
|
|
33
|
+
export function resolveRelayConfig(extraPaths = []) {
|
|
34
|
+
const layers = [];
|
|
35
|
+
if (existsSync(BUNDLED_CONFIG_ENV))
|
|
36
|
+
layers.push(loadEnvFile(BUNDLED_CONFIG_ENV));
|
|
37
|
+
for (const p of extraPaths) {
|
|
38
|
+
if (existsSync(p))
|
|
39
|
+
layers.push(loadEnvFile(p));
|
|
40
|
+
}
|
|
41
|
+
if (existsSync(USER_CONFIG_ENV))
|
|
42
|
+
layers.push(loadEnvFile(USER_CONFIG_ENV));
|
|
43
|
+
const merged = {};
|
|
44
|
+
for (const layer of layers)
|
|
45
|
+
Object.assign(merged, layer);
|
|
46
|
+
const relayUrl = pickNonEmpty(merged.RELAY_URL, process.env.RELAY_URL, DEFAULT_RELAY_URL);
|
|
47
|
+
const relayToken = pickNonEmpty(merged.RELAY_TOKEN, process.env.RELAY_TOKEN);
|
|
48
|
+
return { relayUrl, relayToken };
|
|
49
|
+
}
|
package/dist/semver.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function parseSemver(v) {
|
|
2
|
+
const m = v.trim().match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
3
|
+
if (!m)
|
|
4
|
+
return null;
|
|
5
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
6
|
+
}
|
|
7
|
+
export function compareSemver(a, b) {
|
|
8
|
+
const pa = parseSemver(a);
|
|
9
|
+
const pb = parseSemver(b);
|
|
10
|
+
if (!pa || !pb)
|
|
11
|
+
return null;
|
|
12
|
+
for (let i = 0; i < 3; i++) {
|
|
13
|
+
if (pa[i] !== pb[i])
|
|
14
|
+
return pa[i] < pb[i] ? -1 : 1;
|
|
15
|
+
}
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
export function isVersionAtLeast(current, minimum) {
|
|
19
|
+
const c = compareSemver(current, minimum);
|
|
20
|
+
return c !== null && c >= 0;
|
|
21
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isVersionAtLeast } from './semver.js';
|
|
2
|
+
import { CLI_VERSION } from './cli-version.js';
|
|
3
|
+
export async function fetchRemoteVersionPolicy(relayUrl) {
|
|
4
|
+
const base = relayUrl.replace(/\/$/, '');
|
|
5
|
+
try {
|
|
6
|
+
const res = await fetch(`${base}/api/version-policy`, {
|
|
7
|
+
signal: AbortSignal.timeout(12_000),
|
|
8
|
+
});
|
|
9
|
+
if (!res.ok)
|
|
10
|
+
return null;
|
|
11
|
+
return (await res.json());
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function assertCliVersionAllowed(policy, current = CLI_VERSION) {
|
|
18
|
+
if (isVersionAtLeast(current, policy.minCliVersion))
|
|
19
|
+
return;
|
|
20
|
+
console.error(`[cursorconnect] Версия ${current} устарела (нужна ≥ ${policy.minCliVersion}).\n` +
|
|
21
|
+
` ${policy.updateCliCommand}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
export async function ensureCliVersionForRelay(relayUrl) {
|
|
25
|
+
const policy = await fetchRemoteVersionPolicy(relayUrl);
|
|
26
|
+
if (!policy?.minCliVersion) {
|
|
27
|
+
console.warn('[cursorconnect] Не удалось проверить версию на relay — продолжаем без проверки.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
assertCliVersionAllowed(policy);
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursorconnect",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "CLI: CursorConnect bridge + relay pairing — install once, run from anywhere",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist/**/*.js",
|
|
12
12
|
"bridge-runtime/**",
|
|
13
|
+
"config.env.defaults",
|
|
14
|
+
"version-policy.json",
|
|
13
15
|
"README.md"
|
|
14
16
|
],
|
|
15
17
|
"scripts": {
|
|
@@ -29,7 +31,9 @@
|
|
|
29
31
|
"bridge",
|
|
30
32
|
"relay"
|
|
31
33
|
],
|
|
32
|
-
"dependencies": {
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"socket.io-client": "^4.8.0"
|
|
36
|
+
},
|
|
33
37
|
"devDependencies": {
|
|
34
38
|
"@types/node": "^22.0.0",
|
|
35
39
|
"tsx": "^4.19.0",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"minCliVersion": "0.1.4",
|
|
3
|
+
"minAppVersion": "0.2.2",
|
|
4
|
+
"latestCliVersion": "0.1.4",
|
|
5
|
+
"latestAppVersion": "0.2.2",
|
|
6
|
+
"updateCliCommand": "npm install -g cursorconnect@latest",
|
|
7
|
+
"updateAppHint": "Обновите CursorConnect в App Store / TestFlight до последней сборки."
|
|
8
|
+
}
|