cursorconnect 0.1.6 → 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 (51) hide show
  1. package/bridge-runtime/.env.example +10 -2
  2. package/bridge-runtime/connector-version.json +1 -1
  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/debug-chats-page.d.ts +2 -0
  18. package/bridge-runtime/dist/debug-chats-page.js +491 -0
  19. package/bridge-runtime/dist/dom-transcript-store.d.ts +17 -0
  20. package/bridge-runtime/dist/dom-transcript-store.js +76 -0
  21. package/bridge-runtime/dist/extract-page.js +56 -85
  22. package/bridge-runtime/dist/history-limit.d.ts +2 -0
  23. package/bridge-runtime/dist/history-limit.js +2 -0
  24. package/bridge-runtime/dist/history-request.d.ts +8 -0
  25. package/bridge-runtime/dist/history-request.js +7 -0
  26. package/bridge-runtime/dist/index.js +1 -0
  27. package/bridge-runtime/dist/jsonl-index.d.ts +21 -3
  28. package/bridge-runtime/dist/jsonl-index.js +237 -73
  29. package/bridge-runtime/dist/jsonl-live-debug.d.ts +24 -0
  30. package/bridge-runtime/dist/jsonl-live-debug.js +175 -0
  31. package/bridge-runtime/dist/media-path.d.ts +2 -0
  32. package/bridge-runtime/dist/media-path.js +17 -0
  33. package/bridge-runtime/dist/message-filter.d.ts +2 -0
  34. package/bridge-runtime/dist/message-filter.js +21 -5
  35. package/bridge-runtime/dist/pairing-code.d.ts +2 -0
  36. package/bridge-runtime/dist/pairing-code.js +9 -2
  37. package/bridge-runtime/dist/relay-upstream.d.ts +2 -1
  38. package/bridge-runtime/dist/relay-upstream.js +4 -1
  39. package/bridge-runtime/dist/relay.d.ts +21 -0
  40. package/bridge-runtime/dist/relay.js +332 -28
  41. package/bridge-runtime/dist/types.d.ts +21 -0
  42. package/bridge-runtime/selectors.json +4 -5
  43. package/dist/index.js +79 -20
  44. package/dist/launch.js +23 -5
  45. package/dist/macos-autostart.js +87 -0
  46. package/dist/pairing-code.js +12 -3
  47. package/dist/print-pairing.js +2 -0
  48. package/dist/run-service.js +31 -0
  49. package/dist/startup-check.js +165 -0
  50. package/package.json +1 -1
  51. package/version-policy.json +1 -1
package/dist/index.js CHANGED
@@ -1,16 +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, waitRelayPairingReady, } 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';
12
13
  import { assertBundledBridgeSupportsRelay, bundledBridgeHasClientVersion, } from './bundled-bridge-check.js';
13
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';
14
18
  function bridgeEnv(identity) {
15
19
  ensureUserConfig();
16
20
  const bridgeDotEnv = join(resolveBridgeDir(), '.env');
@@ -63,27 +67,57 @@ async function cmdStart(argv) {
63
67
  stopBridge();
64
68
  }
65
69
  startBridge(env);
66
- let health = await waitBridgeHealth(12_000, { requireCdp: false });
67
- if (health.ok && cdpPortOk && !health.cdp) {
68
- health = await waitBridgeHealth(15_000, { requireCdp: true });
69
- }
70
- const pairingReady = await waitRelayPairingReady(relayUrl, refreshed.roomId, 30_000);
71
- if (!pairingReady.ok) {
72
- console.error('[cursorconnect] Код не зарегистрирован на relay — приложение его не примет.');
73
- if (!pairingReady.connectorInRoom) {
74
- console.error(' Mac не подключён к серверу. Обновите CLI: npm install -g cursorconnect@latest');
75
- console.error(' Затем снова: cursorconnect start');
76
- console.error(` Лог bridge: ${BRIDGE_LOG_FILE}`);
77
- }
78
- else {
79
- console.error(' Connector в комнате, но код не на сервере — подождите и повторите start.');
80
- }
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);
81
76
  process.exit(1);
82
77
  }
83
- if (cdpPortOk && health.ok && !health.cdp) {
84
- console.error(`[cursorconnect] CDP не подключён (cdp:false) · лог: ${BRIDGE_LOG_FILE}`);
78
+ if (cdpPortOk && !readiness.localCdp) {
79
+ console.warn(`[cursorconnect] CDP не подключён (cdp:false) · лог: ${BRIDGE_LOG_FILE}`);
80
+ }
81
+ console.log('');
82
+ console.log(' Проверки перед pairing:');
83
+ for (const line of formatStartupChecklist(readiness)) {
84
+ console.log(` ${line}`);
85
85
  }
86
+ console.log('');
86
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 отключён.');
87
121
  }
88
122
  async function cmdStop() {
89
123
  stopBridge();
@@ -121,6 +155,19 @@ async function cmdStatus() {
121
155
  ? 'Bridge bundle: OK (clientVersion в relay-upstream)'
122
156
  : 'Bridge bundle: STALE — обновите npm install -g cursorconnect@latest');
123
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('');
124
171
  printPairingToTerminal(identity);
125
172
  }
126
173
  async function main() {
@@ -143,6 +190,15 @@ async function main() {
143
190
  case 'init':
144
191
  await cmdInit(rest[0]);
145
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;
146
202
  default:
147
203
  console.log(`Usage: cursorconnect <command>
148
204
 
@@ -151,12 +207,15 @@ async function main() {
151
207
  start --no-restart-cursor — не трогать Cursor (спросит y/n в TTY)
152
208
  stop — остановить Cursor Connect
153
209
  status — код и статус
210
+ install-autostart — Mac: Cursor Connect при входе (launchd)
211
+ uninstall-autostart — отключить автозапуск
154
212
  diagnose — проверка relay / Cursor Connect / socket (без гаданий)
155
213
  init [path] — только для разработки (клон репо)
156
214
 
157
215
  Установка (из любой папки):
158
216
  npm install -g cursorconnect
159
- cursorconnect start # код pairing, relay уже в пакете`);
217
+ cursorconnect start # первый раз: код + опционально автозапуск
218
+ cursorconnect install-autostart # без консоли каждый день`);
160
219
  process.exit(cmd === 'help' ? 0 : 1);
161
220
  }
162
221
  }
package/dist/launch.js CHANGED
@@ -121,21 +121,37 @@ export async function waitRelayConnector(relayUrl, maxMs = 45_000) {
121
121
  }
122
122
  return false;
123
123
  }
124
- /** Mac в relay-комнате и pairing-код зарегистрирован (иначе app получит 404). */
125
- export async function waitRelayPairingReady(relayUrl, roomId, maxMs = 45_000) {
124
+ /** Mac в relay-комнате, код на сервере, токен зарегистрирован (иначе app получит 404). */
125
+ export async function waitRelayPairingReady(relayUrl, roomId, maxMs = 45_000, clientToken) {
126
126
  const base = relayUrl.replace(/\/$/, '');
127
127
  const deadline = Date.now() + maxMs;
128
128
  let lastConnector = false;
129
129
  let lastCodes = 0;
130
+ let lastTokens = 0;
131
+ let lastTokenRegistered = false;
130
132
  while (Date.now() < deadline) {
131
133
  try {
132
- const res = await fetch(`${base}/api/diagnostics?roomId=${encodeURIComponent(roomId)}`, { signal: AbortSignal.timeout(8_000) });
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
+ });
133
140
  if (res.ok) {
134
141
  const d = (await res.json());
135
142
  lastConnector = Boolean(d.connectorInRoom);
136
143
  lastCodes = d.pairingCodesActive ?? 0;
137
- if (lastConnector && lastCodes > 0) {
138
- return { ok: true, connectorInRoom: true, pairingCodesActive: lastCodes };
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
+ };
139
155
  }
140
156
  }
141
157
  }
@@ -148,6 +164,8 @@ export async function waitRelayPairingReady(relayUrl, roomId, maxMs = 45_000) {
148
164
  ok: false,
149
165
  connectorInRoom: lastConnector,
150
166
  pairingCodesActive: lastCodes,
167
+ tokensRegistered: lastTokens,
168
+ tokenRegistered: lastTokenRegistered,
151
169
  };
152
170
  }
153
171
  export function isCursorRunning() {
@@ -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.6",
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.6",
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 до последней сборки."