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.
Files changed (54) hide show
  1. package/bridge-runtime/.env.example +10 -2
  2. package/bridge-runtime/connector-version.json +1 -0
  3. package/bridge-runtime/dist/agent-completion-push.d.ts +42 -0
  4. package/bridge-runtime/dist/agent-completion-push.js +220 -0
  5. package/bridge-runtime/dist/agent-title-match.d.ts +8 -7
  6. package/bridge-runtime/dist/agent-title-match.js +11 -1
  7. package/bridge-runtime/dist/chat-display-store.d.ts +21 -9
  8. package/bridge-runtime/dist/chat-display-store.js +94 -23
  9. package/bridge-runtime/dist/chat-display.d.ts +2 -0
  10. package/bridge-runtime/dist/chat-display.js +197 -33
  11. package/bridge-runtime/dist/chat-history-mode.d.ts +5 -0
  12. package/bridge-runtime/dist/chat-history-mode.js +7 -0
  13. package/bridge-runtime/dist/command-executor.d.ts +2 -0
  14. package/bridge-runtime/dist/command-executor.js +44 -0
  15. package/bridge-runtime/dist/composer-title-index.d.ts +1 -0
  16. package/bridge-runtime/dist/composer-title-index.js +7 -7
  17. package/bridge-runtime/dist/connector-client-version.d.ts +2 -0
  18. package/bridge-runtime/dist/connector-client-version.js +43 -0
  19. package/bridge-runtime/dist/debug-chats-page.d.ts +2 -0
  20. package/bridge-runtime/dist/debug-chats-page.js +491 -0
  21. package/bridge-runtime/dist/dom-transcript-store.d.ts +17 -0
  22. package/bridge-runtime/dist/dom-transcript-store.js +76 -0
  23. package/bridge-runtime/dist/extract-page.js +56 -85
  24. package/bridge-runtime/dist/history-limit.d.ts +2 -0
  25. package/bridge-runtime/dist/history-limit.js +2 -0
  26. package/bridge-runtime/dist/history-request.d.ts +8 -0
  27. package/bridge-runtime/dist/history-request.js +7 -0
  28. package/bridge-runtime/dist/index.js +4 -0
  29. package/bridge-runtime/dist/jsonl-index.d.ts +21 -3
  30. package/bridge-runtime/dist/jsonl-index.js +237 -73
  31. package/bridge-runtime/dist/jsonl-live-debug.d.ts +24 -0
  32. package/bridge-runtime/dist/jsonl-live-debug.js +175 -0
  33. package/bridge-runtime/dist/media-path.d.ts +2 -0
  34. package/bridge-runtime/dist/media-path.js +17 -0
  35. package/bridge-runtime/dist/message-filter.d.ts +2 -0
  36. package/bridge-runtime/dist/message-filter.js +21 -5
  37. package/bridge-runtime/dist/pairing-code.d.ts +2 -0
  38. package/bridge-runtime/dist/pairing-code.js +9 -2
  39. package/bridge-runtime/dist/relay-upstream.d.ts +2 -1
  40. package/bridge-runtime/dist/relay-upstream.js +13 -2
  41. package/bridge-runtime/dist/relay.d.ts +21 -0
  42. package/bridge-runtime/dist/relay.js +332 -28
  43. package/bridge-runtime/dist/types.d.ts +21 -0
  44. package/bridge-runtime/selectors.json +4 -5
  45. package/dist/bundled-bridge-check.js +25 -0
  46. package/dist/index.js +87 -10
  47. package/dist/launch.js +47 -0
  48. package/dist/macos-autostart.js +87 -0
  49. package/dist/pairing-code.js +12 -3
  50. package/dist/print-pairing.js +2 -0
  51. package/dist/run-service.js +31 -0
  52. package/dist/startup-check.js +165 -0
  53. package/package.json +1 -1
  54. 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, waitBridgeHealth, waitRelayConnector, } from './launch.js';
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 { loadEnvFile, resolveRelayConfig } from './relay-config.js';
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
- let health = await waitBridgeHealth(12_000, { requireCdp: false });
65
- if (health.ok && cdpPortOk && !health.cdp) {
66
- health = await waitBridgeHealth(15_000, { requireCdp: true });
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
- await waitRelayConnector(relayUrl, 12_000);
69
- if (cdpPortOk && health.ok && !health.cdp) {
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.error(`[cursorconnect] Cursor Connect работает: ${isBridgeRunning()}`);
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 # код pairing, relay уже в пакете`);
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
+ }
@@ -1,12 +1,17 @@
1
1
  import { randomBytes } from 'crypto';
2
2
  export const PAIRING_CODE_LENGTH = 6;
3
- /** Latin letters + digits (ASCII), uppercase in storage. */
4
- const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
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 += ALPHABET[bytes[i] % ALPHABET.length];
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
  }
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursorconnect",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "CLI: Cursor Connect on Mac + relay pairing — install once, run from anywhere",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "minCliVersion": "0.1.4",
3
3
  "minAppVersion": "0.2.2",
4
- "latestCliVersion": "0.1.4",
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 до последней сборки."