cursorconnect 0.1.5 → 0.1.7
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/bridge-runtime/.env.example +10 -2
- package/bridge-runtime/connector-version.json +1 -0
- package/bridge-runtime/dist/agent-completion-push.d.ts +42 -0
- package/bridge-runtime/dist/agent-completion-push.js +220 -0
- package/bridge-runtime/dist/agent-title-match.d.ts +8 -7
- package/bridge-runtime/dist/agent-title-match.js +11 -1
- package/bridge-runtime/dist/chat-display-store.d.ts +21 -9
- package/bridge-runtime/dist/chat-display-store.js +94 -23
- package/bridge-runtime/dist/chat-display.d.ts +2 -0
- package/bridge-runtime/dist/chat-display.js +197 -33
- package/bridge-runtime/dist/chat-history-mode.d.ts +5 -0
- package/bridge-runtime/dist/chat-history-mode.js +7 -0
- package/bridge-runtime/dist/command-executor.d.ts +2 -0
- package/bridge-runtime/dist/command-executor.js +44 -0
- package/bridge-runtime/dist/composer-title-index.d.ts +1 -0
- package/bridge-runtime/dist/composer-title-index.js +7 -7
- package/bridge-runtime/dist/connector-client-version.d.ts +2 -0
- package/bridge-runtime/dist/connector-client-version.js +43 -0
- package/bridge-runtime/dist/debug-chats-page.d.ts +2 -0
- package/bridge-runtime/dist/debug-chats-page.js +491 -0
- package/bridge-runtime/dist/dom-transcript-store.d.ts +17 -0
- package/bridge-runtime/dist/dom-transcript-store.js +76 -0
- package/bridge-runtime/dist/extract-page.js +56 -85
- package/bridge-runtime/dist/history-limit.d.ts +2 -0
- package/bridge-runtime/dist/history-limit.js +2 -0
- package/bridge-runtime/dist/history-request.d.ts +8 -0
- package/bridge-runtime/dist/history-request.js +7 -0
- package/bridge-runtime/dist/index.js +4 -0
- package/bridge-runtime/dist/jsonl-index.d.ts +21 -3
- package/bridge-runtime/dist/jsonl-index.js +237 -73
- package/bridge-runtime/dist/jsonl-live-debug.d.ts +24 -0
- package/bridge-runtime/dist/jsonl-live-debug.js +175 -0
- package/bridge-runtime/dist/media-path.d.ts +2 -0
- package/bridge-runtime/dist/media-path.js +17 -0
- package/bridge-runtime/dist/message-filter.d.ts +2 -0
- package/bridge-runtime/dist/message-filter.js +21 -5
- package/bridge-runtime/dist/pairing-code.d.ts +2 -0
- package/bridge-runtime/dist/pairing-code.js +9 -2
- package/bridge-runtime/dist/relay-upstream.d.ts +2 -1
- package/bridge-runtime/dist/relay-upstream.js +13 -2
- package/bridge-runtime/dist/relay.d.ts +21 -0
- package/bridge-runtime/dist/relay.js +332 -28
- package/bridge-runtime/dist/types.d.ts +21 -0
- package/bridge-runtime/selectors.json +4 -5
- package/dist/bundled-bridge-check.js +25 -0
- package/dist/index.js +87 -10
- package/dist/launch.js +47 -0
- package/dist/macos-autostart.js +87 -0
- package/dist/pairing-code.js +12 -3
- package/dist/print-pairing.js +2 -0
- package/dist/run-service.js +31 -0
- package/dist/startup-check.js +165 -0
- package/package.json +1 -1
- package/version-policy.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { join, resolve } from 'path';
|
|
3
3
|
import { ensurePairingIdentity, loadPairingIdentity, refreshPairingCode, } from './pairing-identity.js';
|
|
4
|
-
import { ensureCursorCdp, isBridgeRunning, startBridge, stopBridge,
|
|
4
|
+
import { ensureCursorCdp, isBridgeRunning, startBridge, stopBridge, } from './launch.js';
|
|
5
5
|
import { printPairingToTerminal } from './print-pairing.js';
|
|
6
6
|
import { BRIDGE_LOG_FILE } from './paths.js';
|
|
7
7
|
import { ensureUserConfig, isValidBridgeDir, resolveBridgeDir, USER_CONFIG_ENV, } from './bridge-dir.js';
|
|
8
8
|
import { configFilePath, saveInstallConfig } from './repo-root.js';
|
|
9
9
|
import { formatDiagnoseReport, runDiagnose } from './diagnose.js';
|
|
10
|
-
import {
|
|
10
|
+
import { awaitStartupReadiness, formatStartupChecklist, printStartupFailure, snapshotStartupReadiness, } from './startup-check.js';
|
|
11
|
+
import { DEFAULT_RELAY_URL, loadEnvFile, resolveRelayConfig } from './relay-config.js';
|
|
11
12
|
import { ensureCliVersionForRelay } from './version-check.js';
|
|
13
|
+
import { assertBundledBridgeSupportsRelay, bundledBridgeHasClientVersion, } from './bundled-bridge-check.js';
|
|
12
14
|
import { CLI_VERSION } from './cli-version.js';
|
|
15
|
+
import { askYesNo } from './ask.js';
|
|
16
|
+
import { installCursorCdpLauncher, installMacAutostart, isAutostartInstalled, uninstallMacAutostart, } from './macos-autostart.js';
|
|
17
|
+
import { runServiceLoop } from './run-service.js';
|
|
13
18
|
function bridgeEnv(identity) {
|
|
14
19
|
ensureUserConfig();
|
|
15
20
|
const bridgeDotEnv = join(resolveBridgeDir(), '.env');
|
|
@@ -41,6 +46,7 @@ function startFlags(argv) {
|
|
|
41
46
|
};
|
|
42
47
|
}
|
|
43
48
|
async function cmdStart(argv) {
|
|
49
|
+
assertBundledBridgeSupportsRelay();
|
|
44
50
|
const { restartCursor, noRestartCursor } = startFlags(argv);
|
|
45
51
|
const identity = ensurePairingIdentity();
|
|
46
52
|
const refreshed = refreshPairingCode(identity);
|
|
@@ -61,15 +67,57 @@ async function cmdStart(argv) {
|
|
|
61
67
|
stopBridge();
|
|
62
68
|
}
|
|
63
69
|
startBridge(env);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
const readiness = await awaitStartupReadiness(relayUrl, refreshed, {
|
|
71
|
+
waitMs: 45_000,
|
|
72
|
+
requireCdp: cdpPortOk,
|
|
73
|
+
});
|
|
74
|
+
if (!readiness.ready) {
|
|
75
|
+
printStartupFailure(readiness, BRIDGE_LOG_FILE);
|
|
76
|
+
process.exit(1);
|
|
67
77
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
console.error(`[cursorconnect] CDP не подключён (cdp:false) · лог: ${BRIDGE_LOG_FILE}`);
|
|
78
|
+
if (cdpPortOk && !readiness.localCdp) {
|
|
79
|
+
console.warn(`[cursorconnect] CDP не подключён (cdp:false) · лог: ${BRIDGE_LOG_FILE}`);
|
|
71
80
|
}
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(' Проверки перед pairing:');
|
|
83
|
+
for (const line of formatStartupChecklist(readiness)) {
|
|
84
|
+
console.log(` ${line}`);
|
|
85
|
+
}
|
|
86
|
+
console.log('');
|
|
72
87
|
printPairingToTerminal(refreshed);
|
|
88
|
+
if (process.stdin.isTTY && !isAutostartInstalled()) {
|
|
89
|
+
const yes = await askYesNo('Включить автозапуск Cursor Connect при входе в macOS? (y/n): ');
|
|
90
|
+
if (yes) {
|
|
91
|
+
installMacAutostart();
|
|
92
|
+
const cdp = installCursorCdpLauncher();
|
|
93
|
+
console.log('');
|
|
94
|
+
console.log(' Автозапуск включён (launchd).');
|
|
95
|
+
console.log(' Cursor с CDP: откройте из Dock скрипт');
|
|
96
|
+
console.log(` ${cdp}`);
|
|
97
|
+
console.log(' (перетащите в Dock или «Программы при входе» в настройках macOS).');
|
|
98
|
+
console.log(' Код pairing: cursorconnect status');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function cmdRunService() {
|
|
103
|
+
assertBundledBridgeSupportsRelay();
|
|
104
|
+
const identity = ensurePairingIdentity();
|
|
105
|
+
refreshPairingCode(identity);
|
|
106
|
+
const env = bridgeEnv(identity);
|
|
107
|
+
await ensureCliVersionForRelay(env.RELAY_URL);
|
|
108
|
+
console.log('[cursorconnect] run-service (фон, для launchd)…');
|
|
109
|
+
await runServiceLoop(env);
|
|
110
|
+
}
|
|
111
|
+
async function cmdInstallAutostart() {
|
|
112
|
+
installMacAutostart();
|
|
113
|
+
const cdp = installCursorCdpLauncher();
|
|
114
|
+
console.log('Автозапуск Cursor Connect включён.');
|
|
115
|
+
console.log(`Cursor с CDP (9222): ${cdp}`);
|
|
116
|
+
console.log('Код на телефон: cursorconnect status');
|
|
117
|
+
}
|
|
118
|
+
async function cmdUninstallAutostart() {
|
|
119
|
+
uninstallMacAutostart();
|
|
120
|
+
console.log('Автозапуск Cursor Connect отключён.');
|
|
73
121
|
}
|
|
74
122
|
async function cmdStop() {
|
|
75
123
|
stopBridge();
|
|
@@ -102,7 +150,24 @@ async function cmdStatus() {
|
|
|
102
150
|
console.error('Identity не найден — запустите: cursorconnect start');
|
|
103
151
|
process.exit(1);
|
|
104
152
|
}
|
|
105
|
-
console.
|
|
153
|
+
console.log(`CLI: ${CLI_VERSION}`);
|
|
154
|
+
console.log(bundledBridgeHasClientVersion()
|
|
155
|
+
? 'Bridge bundle: OK (clientVersion в relay-upstream)'
|
|
156
|
+
: 'Bridge bundle: STALE — обновите npm install -g cursorconnect@latest');
|
|
157
|
+
console.log(`Bridge process: ${isBridgeRunning() ? 'running' : 'stopped'}`);
|
|
158
|
+
const { relayUrl: relayUrlRaw } = resolveRelayConfig([USER_CONFIG_ENV]);
|
|
159
|
+
const relayUrl = (relayUrlRaw || DEFAULT_RELAY_URL).replace(/\/$/, '');
|
|
160
|
+
const readiness = await snapshotStartupReadiness(relayUrl, identity);
|
|
161
|
+
console.log('');
|
|
162
|
+
console.log(' Состояние:');
|
|
163
|
+
for (const line of formatStartupChecklist(readiness)) {
|
|
164
|
+
console.log(` ${line}`);
|
|
165
|
+
}
|
|
166
|
+
if (!readiness.ready) {
|
|
167
|
+
console.log('');
|
|
168
|
+
console.warn(' Код в терминале может не принять приложение — выполните: cursorconnect start');
|
|
169
|
+
}
|
|
170
|
+
console.log('');
|
|
106
171
|
printPairingToTerminal(identity);
|
|
107
172
|
}
|
|
108
173
|
async function main() {
|
|
@@ -125,6 +190,15 @@ async function main() {
|
|
|
125
190
|
case 'init':
|
|
126
191
|
await cmdInit(rest[0]);
|
|
127
192
|
break;
|
|
193
|
+
case 'run-service':
|
|
194
|
+
await cmdRunService();
|
|
195
|
+
break;
|
|
196
|
+
case 'install-autostart':
|
|
197
|
+
await cmdInstallAutostart();
|
|
198
|
+
break;
|
|
199
|
+
case 'uninstall-autostart':
|
|
200
|
+
await cmdUninstallAutostart();
|
|
201
|
+
break;
|
|
128
202
|
default:
|
|
129
203
|
console.log(`Usage: cursorconnect <command>
|
|
130
204
|
|
|
@@ -133,12 +207,15 @@ async function main() {
|
|
|
133
207
|
start --no-restart-cursor — не трогать Cursor (спросит y/n в TTY)
|
|
134
208
|
stop — остановить Cursor Connect
|
|
135
209
|
status — код и статус
|
|
210
|
+
install-autostart — Mac: Cursor Connect при входе (launchd)
|
|
211
|
+
uninstall-autostart — отключить автозапуск
|
|
136
212
|
diagnose — проверка relay / Cursor Connect / socket (без гаданий)
|
|
137
213
|
init [path] — только для разработки (клон репо)
|
|
138
214
|
|
|
139
215
|
Установка (из любой папки):
|
|
140
216
|
npm install -g cursorconnect
|
|
141
|
-
cursorconnect start
|
|
217
|
+
cursorconnect start # первый раз: код + опционально автозапуск
|
|
218
|
+
cursorconnect install-autostart # без консоли каждый день`);
|
|
142
219
|
process.exit(cmd === 'help' ? 0 : 1);
|
|
143
220
|
}
|
|
144
221
|
}
|
package/dist/launch.js
CHANGED
|
@@ -121,6 +121,53 @@ export async function waitRelayConnector(relayUrl, maxMs = 45_000) {
|
|
|
121
121
|
}
|
|
122
122
|
return false;
|
|
123
123
|
}
|
|
124
|
+
/** Mac в relay-комнате, код на сервере, токен зарегистрирован (иначе app получит 404). */
|
|
125
|
+
export async function waitRelayPairingReady(relayUrl, roomId, maxMs = 45_000, clientToken) {
|
|
126
|
+
const base = relayUrl.replace(/\/$/, '');
|
|
127
|
+
const deadline = Date.now() + maxMs;
|
|
128
|
+
let lastConnector = false;
|
|
129
|
+
let lastCodes = 0;
|
|
130
|
+
let lastTokens = 0;
|
|
131
|
+
let lastTokenRegistered = false;
|
|
132
|
+
while (Date.now() < deadline) {
|
|
133
|
+
try {
|
|
134
|
+
const q = new URLSearchParams({ roomId });
|
|
135
|
+
if (clientToken)
|
|
136
|
+
q.set('token', clientToken);
|
|
137
|
+
const res = await fetch(`${base}/api/diagnostics?${q}`, {
|
|
138
|
+
signal: AbortSignal.timeout(8_000),
|
|
139
|
+
});
|
|
140
|
+
if (res.ok) {
|
|
141
|
+
const d = (await res.json());
|
|
142
|
+
lastConnector = Boolean(d.connectorInRoom);
|
|
143
|
+
lastCodes = d.pairingCodesActive ?? 0;
|
|
144
|
+
lastTokens = d.tokensRegistered ?? 0;
|
|
145
|
+
lastTokenRegistered = d.tokenRegistered === true;
|
|
146
|
+
const tokenOk = clientToken ? lastTokenRegistered : lastTokens > 0;
|
|
147
|
+
if (lastConnector && lastCodes > 0 && tokenOk) {
|
|
148
|
+
return {
|
|
149
|
+
ok: true,
|
|
150
|
+
connectorInRoom: true,
|
|
151
|
+
pairingCodesActive: lastCodes,
|
|
152
|
+
tokensRegistered: lastTokens,
|
|
153
|
+
tokenRegistered: lastTokenRegistered,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
/* retry */
|
|
160
|
+
}
|
|
161
|
+
await sleep(800);
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
connectorInRoom: lastConnector,
|
|
166
|
+
pairingCodesActive: lastCodes,
|
|
167
|
+
tokensRegistered: lastTokens,
|
|
168
|
+
tokenRegistered: lastTokenRegistered,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
124
171
|
export function isCursorRunning() {
|
|
125
172
|
try {
|
|
126
173
|
execSync('pgrep -x Cursor', { stdio: 'ignore' });
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'fs';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { CURSORCONNECT_DIR } from './paths.js';
|
|
6
|
+
const LABEL = 'com.cursorconnect.service';
|
|
7
|
+
const LAUNCH_AGENT = join(homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
8
|
+
const CURSOR_CDP_SCRIPT = join(CURSORCONNECT_DIR, 'bin', 'open-cursor-cdp.sh');
|
|
9
|
+
export function resolveCursorconnectBin() {
|
|
10
|
+
try {
|
|
11
|
+
return execSync('command -v cursorconnect', { encoding: 'utf-8' }).trim();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return 'cursorconnect';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function launchctlLoad() {
|
|
18
|
+
execSync(`launchctl bootout gui/$(id -u) "${LAUNCH_AGENT}" 2>/dev/null || true`, {
|
|
19
|
+
stdio: 'ignore',
|
|
20
|
+
shell: '/bin/bash',
|
|
21
|
+
});
|
|
22
|
+
execSync(`launchctl bootstrap gui/$(id -u) "${LAUNCH_AGENT}"`, {
|
|
23
|
+
stdio: 'inherit',
|
|
24
|
+
shell: '/bin/bash',
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export function isAutostartInstalled() {
|
|
28
|
+
return existsSync(LAUNCH_AGENT);
|
|
29
|
+
}
|
|
30
|
+
export function installMacAutostart() {
|
|
31
|
+
mkdirSync(CURSORCONNECT_DIR, { recursive: true, mode: 0o700 });
|
|
32
|
+
const bin = resolveCursorconnectBin();
|
|
33
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
34
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
35
|
+
<plist version="1.0">
|
|
36
|
+
<dict>
|
|
37
|
+
<key>Label</key>
|
|
38
|
+
<string>${LABEL}</string>
|
|
39
|
+
<key>ProgramArguments</key>
|
|
40
|
+
<array>
|
|
41
|
+
<string>${bin}</string>
|
|
42
|
+
<string>run-service</string>
|
|
43
|
+
</array>
|
|
44
|
+
<key>RunAtLoad</key>
|
|
45
|
+
<true/>
|
|
46
|
+
<key>KeepAlive</key>
|
|
47
|
+
<true/>
|
|
48
|
+
<key>StandardOutPath</key>
|
|
49
|
+
<string>${join(CURSORCONNECT_DIR, 'launchd.out.log')}</string>
|
|
50
|
+
<key>StandardErrorPath</key>
|
|
51
|
+
<string>${join(CURSORCONNECT_DIR, 'launchd.err.log')}</string>
|
|
52
|
+
<key>EnvironmentVariables</key>
|
|
53
|
+
<dict>
|
|
54
|
+
<key>PATH</key>
|
|
55
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
56
|
+
</dict>
|
|
57
|
+
</dict>
|
|
58
|
+
</plist>
|
|
59
|
+
`;
|
|
60
|
+
mkdirSync(join(homedir(), 'Library', 'LaunchAgents'), { recursive: true });
|
|
61
|
+
writeFileSync(LAUNCH_AGENT, plist, { mode: 0o644 });
|
|
62
|
+
launchctlLoad();
|
|
63
|
+
}
|
|
64
|
+
export function uninstallMacAutostart() {
|
|
65
|
+
if (existsSync(LAUNCH_AGENT)) {
|
|
66
|
+
try {
|
|
67
|
+
execSync(`launchctl bootout gui/$(id -u) "${LAUNCH_AGENT}" 2>/dev/null || true`, {
|
|
68
|
+
stdio: 'ignore',
|
|
69
|
+
shell: '/bin/bash',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* ignore */
|
|
74
|
+
}
|
|
75
|
+
unlinkSync(LAUNCH_AGENT);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Скрипт: Cursor с CDP (порт 9222) — положите в Dock вместо обычного Cursor. */
|
|
79
|
+
export function installCursorCdpLauncher() {
|
|
80
|
+
mkdirSync(join(CURSORCONNECT_DIR, 'bin'), { recursive: true, mode: 0o700 });
|
|
81
|
+
const script = `#!/bin/bash
|
|
82
|
+
# Запуск Cursor с --remote-debugging-port=9222 (нужен для Cursor Connect)
|
|
83
|
+
open -a Cursor --args --remote-debugging-port=9222
|
|
84
|
+
`;
|
|
85
|
+
writeFileSync(CURSOR_CDP_SCRIPT, script, { mode: 0o755 });
|
|
86
|
+
return CURSOR_CDP_SCRIPT;
|
|
87
|
+
}
|
package/dist/pairing-code.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { randomBytes } from 'crypto';
|
|
2
2
|
export const PAIRING_CODE_LENGTH = 6;
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Без визуально похожих пар: 0/O/D/Q, 1/I/L, 8/B, 2/Z, 5/S, 6/G.
|
|
5
|
+
* Синхронизировать с bridge/src/pairing-code.ts и relay/src/pairing-code.ts.
|
|
6
|
+
*/
|
|
7
|
+
export const PAIRING_CODE_ALPHABET = 'ACDEFHJKMNPRTUVWXY3479';
|
|
8
|
+
const ALPHABET_SET = new Set(PAIRING_CODE_ALPHABET.split(''));
|
|
5
9
|
export function generatePairingCode() {
|
|
6
10
|
const bytes = randomBytes(PAIRING_CODE_LENGTH);
|
|
11
|
+
const n = PAIRING_CODE_ALPHABET.length;
|
|
7
12
|
let out = '';
|
|
8
13
|
for (let i = 0; i < PAIRING_CODE_LENGTH; i++) {
|
|
9
|
-
out +=
|
|
14
|
+
out += PAIRING_CODE_ALPHABET[bytes[i] % n];
|
|
10
15
|
}
|
|
11
16
|
return out;
|
|
12
17
|
}
|
|
@@ -14,5 +19,9 @@ export function normalizePairingCode(raw) {
|
|
|
14
19
|
const code = raw.replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
|
|
15
20
|
if (code.length !== PAIRING_CODE_LENGTH)
|
|
16
21
|
return null;
|
|
22
|
+
for (const ch of code) {
|
|
23
|
+
if (!ALPHABET_SET.has(ch))
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
17
26
|
return code;
|
|
18
27
|
}
|
package/dist/print-pairing.js
CHANGED
|
@@ -14,6 +14,8 @@ export function printPairingToTerminal(identity) {
|
|
|
14
14
|
console.log('');
|
|
15
15
|
console.log(' Введите код в приложении Cursor Connect:');
|
|
16
16
|
console.log('');
|
|
17
|
+
console.log(` ${code}`);
|
|
18
|
+
console.log('');
|
|
17
19
|
console.log(renderBigCode(code));
|
|
18
20
|
console.log('');
|
|
19
21
|
console.log(' Код одноразовый');
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ensurePairingIdentity, refreshPairingCode, } from './pairing-identity.js';
|
|
2
|
+
import { isBridgeRunning, startBridge, } from './launch.js';
|
|
3
|
+
import { BRIDGE_LOG_FILE } from './paths.js';
|
|
4
|
+
const WATCH_MS = 8_000;
|
|
5
|
+
function sleep(ms) {
|
|
6
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
7
|
+
}
|
|
8
|
+
/** Держит Cursor Connect в фоне (для launchd KeepAlive). */
|
|
9
|
+
export async function runServiceLoop(bridgeEnv) {
|
|
10
|
+
const identity = ensurePairingIdentity();
|
|
11
|
+
refreshPairingCode(identity);
|
|
12
|
+
const boot = () => {
|
|
13
|
+
if (isBridgeRunning())
|
|
14
|
+
return;
|
|
15
|
+
try {
|
|
16
|
+
startBridge(bridgeEnv);
|
|
17
|
+
console.log(`[cursorconnect] service: bridge started → ${BRIDGE_LOG_FILE}`);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
console.error('[cursorconnect] service: start failed', e);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
boot();
|
|
24
|
+
for (;;) {
|
|
25
|
+
await sleep(WATCH_MS);
|
|
26
|
+
if (!isBridgeRunning()) {
|
|
27
|
+
console.error('[cursorconnect] service: bridge exited, restarting…');
|
|
28
|
+
boot();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { BRIDGE_LOG_FILE } from './paths.js';
|
|
3
|
+
import { waitBridgeHealth, waitRelayPairingReady } from './launch.js';
|
|
4
|
+
export async function fetchRelayRoomDiagnostics(relayUrl, roomId, token) {
|
|
5
|
+
const base = relayUrl.replace(/\/$/, '');
|
|
6
|
+
const q = new URLSearchParams({ roomId });
|
|
7
|
+
if (token)
|
|
8
|
+
q.set('token', token);
|
|
9
|
+
try {
|
|
10
|
+
const res = await fetch(`${base}/api/diagnostics?${q}`, {
|
|
11
|
+
signal: AbortSignal.timeout(12_000),
|
|
12
|
+
});
|
|
13
|
+
if (!res.ok)
|
|
14
|
+
return null;
|
|
15
|
+
const d = (await res.json());
|
|
16
|
+
return {
|
|
17
|
+
ok: Boolean(d.ok),
|
|
18
|
+
connectorInRoom: Boolean(d.connectorInRoom),
|
|
19
|
+
clientsInRoom: Number(d.clientsInRoom ?? 0),
|
|
20
|
+
pairingCodesActive: Number(d.pairingCodesActive ?? 0),
|
|
21
|
+
tokensRegistered: Number(d.tokensRegistered ?? 0),
|
|
22
|
+
tokenRegistered: typeof d.tokenRegistered === 'boolean' ? d.tokenRegistered : null,
|
|
23
|
+
tokenAccepted: typeof d.tokenAccepted === 'boolean' ? d.tokenAccepted : null,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function normalizePairingCode(code) {
|
|
31
|
+
return code.replace(/[^A-Z0-9]/g, '').toUpperCase().slice(0, 6);
|
|
32
|
+
}
|
|
33
|
+
export function buildStartupReadiness(identity, local, relay, relayReachable) {
|
|
34
|
+
const code = normalizePairingCode(identity.pairingCode);
|
|
35
|
+
const codeExpiresSec = Math.max(0, Math.floor((identity.pairingCodeExpiresAt - Date.now()) / 1000));
|
|
36
|
+
const codeValid = code.length === 6 && codeExpiresSec > 0;
|
|
37
|
+
const connectorInRoom = Boolean(relay?.connectorInRoom);
|
|
38
|
+
const pairingCodesActive = relay?.pairingCodesActive ?? 0;
|
|
39
|
+
const tokensRegistered = relay?.tokensRegistered ?? 0;
|
|
40
|
+
const tokenRegistered = relay?.tokenRegistered === true;
|
|
41
|
+
const ready = local.ok &&
|
|
42
|
+
codeValid &&
|
|
43
|
+
relayReachable &&
|
|
44
|
+
connectorInRoom &&
|
|
45
|
+
pairingCodesActive > 0 &&
|
|
46
|
+
tokensRegistered > 0 &&
|
|
47
|
+
tokenRegistered;
|
|
48
|
+
return {
|
|
49
|
+
localBridgeOk: local.ok,
|
|
50
|
+
localCdp: local.cdp,
|
|
51
|
+
relayReachable,
|
|
52
|
+
connectorInRoom,
|
|
53
|
+
pairingCodesActive,
|
|
54
|
+
tokensRegistered,
|
|
55
|
+
tokenRegistered,
|
|
56
|
+
ready,
|
|
57
|
+
code,
|
|
58
|
+
codeExpiresSec,
|
|
59
|
+
machineLabel: identity.machineLabel,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Ждём bridge + relay (комната, код на сервере, токен зарегистрирован). */
|
|
63
|
+
export async function awaitStartupReadiness(relayUrl, identity, opts) {
|
|
64
|
+
const waitMs = opts?.waitMs ?? 45_000;
|
|
65
|
+
const health = await waitBridgeHealth(Math.min(waitMs, 25_000), {
|
|
66
|
+
requireCdp: opts?.requireCdp === true,
|
|
67
|
+
});
|
|
68
|
+
let relayReachable = false;
|
|
69
|
+
try {
|
|
70
|
+
const res = await fetch(`${relayUrl.replace(/\/$/, '')}/health`, {
|
|
71
|
+
signal: AbortSignal.timeout(8_000),
|
|
72
|
+
});
|
|
73
|
+
relayReachable = res.ok;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
relayReachable = false;
|
|
77
|
+
}
|
|
78
|
+
await waitRelayPairingReady(relayUrl, identity.roomId, waitMs, identity.clientToken);
|
|
79
|
+
const relay = await fetchRelayRoomDiagnostics(relayUrl, identity.roomId, identity.clientToken);
|
|
80
|
+
return buildStartupReadiness(identity, { ok: health.ok, cdp: health.cdp }, relay, relayReachable);
|
|
81
|
+
}
|
|
82
|
+
/** Однократная проверка (для status). */
|
|
83
|
+
export async function snapshotStartupReadiness(relayUrl, identity) {
|
|
84
|
+
let local = { ok: false, cdp: false };
|
|
85
|
+
try {
|
|
86
|
+
const res = await fetch('http://127.0.0.1:3847/health', {
|
|
87
|
+
signal: AbortSignal.timeout(3_000),
|
|
88
|
+
});
|
|
89
|
+
if (res.ok) {
|
|
90
|
+
const j = (await res.json());
|
|
91
|
+
local = { ok: Boolean(j.ok), cdp: Boolean(j.cdp) };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
/* ignore */
|
|
96
|
+
}
|
|
97
|
+
let relayReachable = false;
|
|
98
|
+
try {
|
|
99
|
+
const res = await fetch(`${relayUrl.replace(/\/$/, '')}/health`, {
|
|
100
|
+
signal: AbortSignal.timeout(8_000),
|
|
101
|
+
});
|
|
102
|
+
relayReachable = res.ok;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
relayReachable = false;
|
|
106
|
+
}
|
|
107
|
+
const relay = await fetchRelayRoomDiagnostics(relayUrl, identity.roomId, identity.clientToken);
|
|
108
|
+
return buildStartupReadiness(identity, local, relay, relayReachable);
|
|
109
|
+
}
|
|
110
|
+
export function formatStartupChecklist(r) {
|
|
111
|
+
const mark = (ok) => (ok ? '✓' : '✗');
|
|
112
|
+
const lines = [
|
|
113
|
+
`${mark(r.localBridgeOk)} Cursor Connect на Mac (localhost:3847)`,
|
|
114
|
+
r.localBridgeOk
|
|
115
|
+
? ` cdp: ${r.localCdp ? 'подключён — live-чаты из Cursor' : 'нет — только архив JSONL'}`
|
|
116
|
+
: ' запустите cursorconnect start',
|
|
117
|
+
`${mark(r.relayReachable)} Relay ${r.relayReachable ? 'доступен' : 'недоступен'}`,
|
|
118
|
+
`${mark(r.connectorInRoom)} Mac в вашей комнате на relay`,
|
|
119
|
+
`${mark(r.pairingCodesActive > 0)} Код на сервере (можно вводить в приложении)`,
|
|
120
|
+
r.pairingCodesActive > 0
|
|
121
|
+
? ` активных кодов: ${r.pairingCodesActive}`
|
|
122
|
+
: ' код не зарегистрирован — повторите start (не вводите старый код из терминала)',
|
|
123
|
+
`${mark(r.tokenRegistered)} Токен телефона зарегистрирован bridge'ем`,
|
|
124
|
+
];
|
|
125
|
+
if (r.ready) {
|
|
126
|
+
lines.push('', ` Готово к pairing: код ${r.code} · ~${Math.ceil(r.codeExpiresSec / 60)} мин · одноразовый`);
|
|
127
|
+
}
|
|
128
|
+
return lines;
|
|
129
|
+
}
|
|
130
|
+
export function printStartupFailure(r, logPath = BRIDGE_LOG_FILE) {
|
|
131
|
+
console.error('');
|
|
132
|
+
console.error('[cursorconnect] Запуск не готов — приложение код не примет.');
|
|
133
|
+
console.error('');
|
|
134
|
+
for (const line of formatStartupChecklist(r)) {
|
|
135
|
+
console.error(` ${line}`);
|
|
136
|
+
}
|
|
137
|
+
console.error('');
|
|
138
|
+
if (!r.connectorInRoom) {
|
|
139
|
+
console.error(' Mac не в комнате: npm install -g cursorconnect@latest && cursorconnect stop && cursorconnect start');
|
|
140
|
+
}
|
|
141
|
+
else if (r.pairingCodesActive === 0) {
|
|
142
|
+
console.error(' Код не на relay (уже введён или bridge не успел): cursorconnect stop && cursorconnect start');
|
|
143
|
+
console.error(' Не вводите код из прошлого запуска — только свежий после start.');
|
|
144
|
+
}
|
|
145
|
+
else if (!r.tokenRegistered) {
|
|
146
|
+
console.error(' Токен не зарегистрирован — перезапустите bridge (cursorconnect start).');
|
|
147
|
+
}
|
|
148
|
+
if (!r.localBridgeOk) {
|
|
149
|
+
console.error(` Лог: ${logPath}`);
|
|
150
|
+
tailLogLines(logPath, 12).forEach((l) => console.error(` ${l}`));
|
|
151
|
+
}
|
|
152
|
+
console.error('');
|
|
153
|
+
}
|
|
154
|
+
function tailLogLines(path, n) {
|
|
155
|
+
if (!existsSync(path))
|
|
156
|
+
return ['(лог пуст)'];
|
|
157
|
+
try {
|
|
158
|
+
const raw = readFileSync(path, 'utf-8');
|
|
159
|
+
const lines = raw.trim().split('\n').filter(Boolean);
|
|
160
|
+
return lines.slice(-n);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return ['(не удалось прочитать лог)'];
|
|
164
|
+
}
|
|
165
|
+
}
|
package/package.json
CHANGED
package/version-policy.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"minCliVersion": "0.1.4",
|
|
3
3
|
"minAppVersion": "0.2.2",
|
|
4
|
-
"latestCliVersion": "0.1.
|
|
4
|
+
"latestCliVersion": "0.1.7",
|
|
5
5
|
"latestAppVersion": "0.2.2",
|
|
6
6
|
"updateCliCommand": "npm install -g cursorconnect@latest",
|
|
7
7
|
"updateAppHint": "Обновите CursorConnect в App Store / TestFlight до последней сборки."
|