cursorconnect 0.1.2 → 0.1.5

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 (39) hide show
  1. package/README.md +6 -5
  2. package/bridge-runtime/dist/agent-title-match.js +16 -0
  3. package/bridge-runtime/dist/chat-display-store.d.ts +13 -0
  4. package/bridge-runtime/dist/chat-display-store.js +29 -0
  5. package/bridge-runtime/dist/chat-display.d.ts +11 -0
  6. package/bridge-runtime/dist/chat-display.js +290 -0
  7. package/bridge-runtime/dist/chat-sync.d.ts +6 -0
  8. package/bridge-runtime/dist/chat-sync.js +88 -0
  9. package/bridge-runtime/dist/extract-page.js +99 -3
  10. package/bridge-runtime/dist/history-pipeline-log.d.ts +16 -0
  11. package/bridge-runtime/dist/history-pipeline-log.js +29 -0
  12. package/bridge-runtime/dist/jsonl-index.d.ts +15 -3
  13. package/bridge-runtime/dist/jsonl-index.js +48 -12
  14. package/bridge-runtime/dist/message-filter.d.ts +10 -0
  15. package/bridge-runtime/dist/message-filter.js +65 -5
  16. package/bridge-runtime/dist/pairing-code.d.ts +3 -0
  17. package/bridge-runtime/dist/pairing-code.js +17 -0
  18. package/bridge-runtime/dist/pairing-identity.js +4 -7
  19. package/bridge-runtime/dist/relay.d.ts +8 -0
  20. package/bridge-runtime/dist/relay.js +254 -25
  21. package/bridge-runtime/dist/sidebar-merge.js +2 -2
  22. package/bridge-runtime/dist/types.d.ts +9 -1
  23. package/config.env.defaults +3 -0
  24. package/dist/big-code.js +36 -5
  25. package/dist/bridge-dir.js +6 -1
  26. package/dist/cli-version.js +13 -0
  27. package/dist/diagnose.js +224 -0
  28. package/dist/index.js +56 -92
  29. package/dist/launch.js +52 -14
  30. package/dist/pairing-code.js +18 -0
  31. package/dist/pairing-identity.js +6 -8
  32. package/dist/pairing-ttl.js +3 -0
  33. package/dist/print-pairing.js +18 -25
  34. package/dist/relay-config.js +49 -0
  35. package/dist/repo-root.js +2 -2
  36. package/dist/semver.js +21 -0
  37. package/dist/version-check.js +31 -0
  38. package/package.json +7 -3
  39. package/version-policy.json +8 -0
@@ -4,10 +4,14 @@ import { randomBytes, timingSafeEqual } from 'crypto';
4
4
  import { basename } from 'path';
5
5
  import { readAllowedMediaFile, resolveMediaPathParam } from './media-path.js';
6
6
  import { Server as SocketServer } from 'socket.io';
7
+ import { ChatDisplayStore } from './chat-display-store.js';
8
+ import { filterClientDisplayList, prepareChatMessagesForDisplay, } from './chat-display.js';
9
+ import { isChatSyncedWithCursor } from './chat-sync.js';
7
10
  import { mergeSidebarWithJsonl } from './sidebar-merge.js';
8
11
  import { transcribeAudioBuffer } from './openai-transcribe.js';
9
12
  import { extensionForMime, saveUploadedImage } from './image-upload-store.js';
10
13
  import { RelayUpstream } from './relay-upstream.js';
14
+ import { bridgePipelineLog, bridgePipelineReportLines, bridgePipelineSnapshot, } from './history-pipeline-log.js';
11
15
  export class Relay {
12
16
  stateManager;
13
17
  commandExecutor;
@@ -16,12 +20,17 @@ export class Relay {
16
20
  messageDebugStore;
17
21
  domExtractor;
18
22
  lastJsonlIndex = { repos: [], updatedAt: 0 };
23
+ indexEmitTimer = null;
24
+ lastIndexBroadcastAt = 0;
25
+ lastSidebarIndexKey = '';
26
+ lastJsonlIndexKey = '';
19
27
  config;
20
28
  app = express();
21
29
  httpServer = createServer(this.app);
22
30
  io;
23
31
  tokens = new Set();
24
32
  upstream = null;
33
+ chatDisplay = new ChatDisplayStore();
25
34
  constructor(config, stateManager, commandExecutor, cdpBridge, jsonlIndex, messageDebugStore, domExtractor) {
26
35
  this.stateManager = stateManager;
27
36
  this.commandExecutor = commandExecutor;
@@ -32,6 +41,7 @@ export class Relay {
32
41
  this.config = config;
33
42
  this.io = new SocketServer(this.httpServer, {
34
43
  cors: { origin: true, credentials: true },
44
+ maxHttpBufferSize: 20e6,
35
45
  });
36
46
  this.setupHttp();
37
47
  this.setupSocket();
@@ -82,6 +92,91 @@ export class Relay {
82
92
  lastError: state.lastError,
83
93
  });
84
94
  });
95
+ this.app.get('/api/agents/index', async (req, res) => {
96
+ if (!this.checkMediaAuth(req)) {
97
+ res.status(401).json({ error: 'Unauthorized' });
98
+ return;
99
+ }
100
+ try {
101
+ const stale = Date.now() - (this.lastJsonlIndex.updatedAt ?? 0) > 60_000;
102
+ const index = this.lastJsonlIndex.repos.length && !stale
103
+ ? this.lastJsonlIndex
104
+ : await this.jsonlIndex.rebuild({ broadcast: false });
105
+ this.lastJsonlIndex = index;
106
+ const merged = mergeSidebarWithJsonl(this.stateManager.getState().sidebarRepos, index, this.stateManager.getState().composerIdByTitle);
107
+ const bytes = JSON.stringify(merged).length;
108
+ bridgePipelineLog({
109
+ dir: 'out',
110
+ event: 'http:agents:index',
111
+ bytes,
112
+ detail: `repos=${merged.repos?.length ?? 0}`,
113
+ });
114
+ res.json(merged);
115
+ }
116
+ catch (err) {
117
+ res.status(500).json({ error: err.message });
118
+ }
119
+ });
120
+ this.app.get('/api/agents/history', async (req, res) => {
121
+ if (!this.checkMediaAuth(req)) {
122
+ res.status(401).json({ error: 'Unauthorized' });
123
+ return;
124
+ }
125
+ const agentId = String(req.query.agentId ?? '').trim();
126
+ const title = typeof req.query.title === 'string' ? req.query.title.trim() : undefined;
127
+ const limitRaw = Number(req.query.limit ?? 0);
128
+ const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? limitRaw : undefined;
129
+ const requestId = typeof req.query.requestId === 'string' ? req.query.requestId.trim() : undefined;
130
+ if (!agentId) {
131
+ res.status(400).json({ error: 'agentId required' });
132
+ return;
133
+ }
134
+ bridgePipelineLog({
135
+ dir: 'in',
136
+ event: 'http:agents:history',
137
+ requestId,
138
+ agentId,
139
+ detail: `limit=${limit ?? 'all'} title=${title?.slice(0, 32) ?? '-'}`,
140
+ });
141
+ try {
142
+ const history = await this.jsonlIndex.loadHistory(agentId, {
143
+ title,
144
+ composerIdByTitle: this.stateManager.getState().composerIdByTitle,
145
+ limit,
146
+ });
147
+ const messages = this.chatDisplay.applyHistory(agentId, history.messages);
148
+ const body = { ...history, messages, requestId, updatedAt: Date.now() };
149
+ const bytes = JSON.stringify(body).length;
150
+ bridgePipelineLog({
151
+ dir: 'out',
152
+ event: 'http:agents:history',
153
+ requestId,
154
+ agentId,
155
+ bytes,
156
+ msgs: history.messages?.length ?? 0,
157
+ detail: `total=${history.totalMessages ?? '?'}`,
158
+ });
159
+ res.json(body);
160
+ }
161
+ catch (err) {
162
+ bridgePipelineLog({
163
+ dir: 'internal',
164
+ event: 'http:agents:history:ERR',
165
+ requestId,
166
+ agentId,
167
+ detail: err.message,
168
+ });
169
+ res.status(500).json({ error: err.message });
170
+ }
171
+ });
172
+ this.app.get('/api/debug/history-pipeline', (_req, res) => {
173
+ res.json({
174
+ ok: true,
175
+ at: Date.now(),
176
+ lines: bridgePipelineReportLines(),
177
+ entries: bridgePipelineSnapshot(),
178
+ });
179
+ });
85
180
  this.app.get('/media/file', (req, res) => {
86
181
  if (!this.checkMediaAuth(req)) {
87
182
  res.status(401).json({ error: 'Unauthorized' });
@@ -212,37 +307,108 @@ export class Relay {
212
307
  this.io.on('connection', (socket) => this.onConnect(socket));
213
308
  }
214
309
  broadcast(event, payload) {
310
+ if (event === 'agents:history' || event === 'agent:history') {
311
+ const h = payload;
312
+ const bytes = JSON.stringify(payload ?? {}).length;
313
+ bridgePipelineLog({
314
+ dir: 'out',
315
+ event: `broadcast:${event}`,
316
+ requestId: h?.requestId,
317
+ agentId: h?.agentId,
318
+ bytes,
319
+ msgs: h?.messages?.length ?? 0,
320
+ detail: `upstream=${this.upstream?.connected ?? false}`,
321
+ });
322
+ }
215
323
  this.io.emit(event, payload);
216
324
  this.upstream?.emit(event, payload);
217
325
  }
326
+ prepareStateMessages(raw, patch) {
327
+ const state = { ...this.stateManager.getState(), ...patch };
328
+ for (const [agentId, meta] of this.jsonlIndex.getSubscribedAgents()) {
329
+ if (isChatSyncedWithCursor(agentId, meta.title, state)) {
330
+ return this.chatDisplay.mergeLiveForAgent(agentId, raw);
331
+ }
332
+ }
333
+ return filterClientDisplayList(prepareChatMessagesForDisplay(raw));
334
+ }
335
+ withDisplayState(payload) {
336
+ if (!payload.messages?.length)
337
+ return payload;
338
+ return {
339
+ ...payload,
340
+ messages: this.prepareStateMessages(payload.messages, payload),
341
+ };
342
+ }
218
343
  wireEvents() {
219
344
  this.stateManager.on('state:full', (state) => {
220
- this.broadcast('state:full', state);
345
+ this.broadcast('state:full', this.withDisplayState(state));
221
346
  this.emitAgentsIndex();
222
347
  });
223
348
  this.stateManager.on('state:patch', (patch) => {
224
- this.broadcast('state:patch', patch);
225
- if (patch.sidebarRepos || patch.composerIdByTitle)
226
- this.emitAgentsIndex();
349
+ this.broadcast('state:patch', this.withDisplayState(patch));
350
+ if (patch.sidebarRepos || patch.composerIdByTitle) {
351
+ const key = JSON.stringify([patch.sidebarRepos, patch.composerIdByTitle]);
352
+ if (key !== this.lastSidebarIndexKey) {
353
+ this.lastSidebarIndexKey = key;
354
+ this.emitAgentsIndex();
355
+ }
356
+ }
227
357
  });
228
358
  this.jsonlIndex.on('agents:index', (index) => {
359
+ const key = JSON.stringify(index.repos?.map((r) => [r.id, r.agents.length]));
360
+ if (key === this.lastJsonlIndexKey)
361
+ return;
362
+ this.lastJsonlIndexKey = key;
229
363
  this.lastJsonlIndex = index;
230
364
  this.emitAgentsIndex();
231
365
  });
232
- this.jsonlIndex.on('agent:history', (history) => this.broadcast('agent:history', history));
366
+ this.jsonlIndex.on('agent:history', (history) => {
367
+ const messages = this.chatDisplay.applyHistory(history.agentId, history.messages, {
368
+ mergeWithCache: true,
369
+ });
370
+ this.broadcast('agent:history', { ...history, messages });
371
+ });
233
372
  }
234
- emitAgentsIndex() {
235
- const sidebar = this.stateManager.getState().sidebarRepos;
236
- const merged = mergeSidebarWithJsonl(sidebar, this.lastJsonlIndex, this.stateManager.getState().composerIdByTitle);
237
- this.broadcast('agents:index', merged);
373
+ emitAgentsIndex(force = false) {
374
+ const send = () => {
375
+ const merged = mergeSidebarWithJsonl(this.stateManager.getState().sidebarRepos, this.lastJsonlIndex, this.stateManager.getState().composerIdByTitle);
376
+ this.broadcast('agents:index', merged);
377
+ this.lastIndexBroadcastAt = Date.now();
378
+ };
379
+ if (force) {
380
+ if (this.indexEmitTimer)
381
+ clearTimeout(this.indexEmitTimer);
382
+ this.indexEmitTimer = null;
383
+ send();
384
+ return;
385
+ }
386
+ const elapsed = Date.now() - this.lastIndexBroadcastAt;
387
+ if (elapsed >= 60_000) {
388
+ send();
389
+ return;
390
+ }
391
+ if (this.indexEmitTimer)
392
+ return;
393
+ this.indexEmitTimer = setTimeout(() => {
394
+ this.indexEmitTimer = null;
395
+ send();
396
+ }, 60_000 - elapsed);
238
397
  }
239
- pushFullStateToRemote() {
240
- this.broadcast('state:full', this.stateManager.getState());
241
- void this.jsonlIndex.rebuild().then((index) => {
398
+ refreshAgentsIndex(force = false) {
399
+ void this.jsonlIndex.rebuild({ broadcast: false }).then((index) => {
242
400
  this.lastJsonlIndex = index;
243
- this.broadcast('agents:index', mergeSidebarWithJsonl(this.stateManager.getState().sidebarRepos, index, this.stateManager.getState().composerIdByTitle));
401
+ this.emitAgentsIndex(force);
244
402
  });
245
403
  }
404
+ pushFullStateToRemote() {
405
+ this.broadcast('state:full', this.withDisplayState(this.stateManager.getState()));
406
+ if (this.lastJsonlIndex.updatedAt > 0) {
407
+ this.emitAgentsIndex(true);
408
+ return;
409
+ }
410
+ this.refreshAgentsIndex(true);
411
+ }
246
412
  async trySwitchWindowForAgent(agentId) {
247
413
  const projectDir = this.jsonlIndex.findProjectDirForAgent(agentId);
248
414
  if (!projectDir)
@@ -273,11 +439,19 @@ export class Relay {
273
439
  this.dispatchClientEvent(event, args, reply);
274
440
  }
275
441
  onConnect(socket) {
276
- socket.emit('state:full', this.stateManager.getState());
277
- void this.jsonlIndex.rebuild().then((index) => {
278
- this.lastJsonlIndex = index;
442
+ socket.emit('state:full', this.withDisplayState(this.stateManager.getState()));
443
+ const sendIndex = (index) => {
279
444
  socket.emit('agents:index', mergeSidebarWithJsonl(this.stateManager.getState().sidebarRepos, index, this.stateManager.getState().composerIdByTitle));
280
- });
445
+ };
446
+ if (this.lastJsonlIndex.updatedAt > 0) {
447
+ sendIndex(this.lastJsonlIndex);
448
+ }
449
+ else {
450
+ void this.jsonlIndex.rebuild({ broadcast: false }).then((index) => {
451
+ this.lastJsonlIndex = index;
452
+ sendIndex(index);
453
+ });
454
+ }
281
455
  const reply = (ev, payload) => {
282
456
  socket.emit(ev, payload);
283
457
  };
@@ -297,7 +471,7 @@ export class Relay {
297
471
  await this.runAgentsFocus(data, reply);
298
472
  });
299
473
  socket.on('agents:refresh', () => {
300
- void this.jsonlIndex.rebuild();
474
+ this.refreshAgentsIndex(true);
301
475
  });
302
476
  socket.on('disconnect', () => {
303
477
  /* subscriptions stay global — one phone session typical */
@@ -325,7 +499,7 @@ export class Relay {
325
499
  return;
326
500
  }
327
501
  if (event === 'agents:refresh') {
328
- void this.jsonlIndex.rebuild();
502
+ this.refreshAgentsIndex(true);
329
503
  }
330
504
  }
331
505
  async runCommand(payload, reply) {
@@ -353,12 +527,65 @@ export class Relay {
353
527
  this.domExtractor.pollNow();
354
528
  }
355
529
  }
356
- async runAgentsHistory({ agentId, title }, reply) {
357
- const history = await this.jsonlIndex.loadHistory(agentId, {
358
- title,
359
- composerIdByTitle: this.stateManager.getState().composerIdByTitle,
530
+ async runAgentsHistory({ agentId, title, requestId, limit, }, reply) {
531
+ const t0 = Date.now();
532
+ const viaUpstream = this.upstream?.connected ?? false;
533
+ const socketLimit = limit && limit > 0 ? limit : 15;
534
+ console.log(`[relay] agents:history req agentId=${agentId} title=${title?.slice(0, 48) ?? '-'} limit=${socketLimit} rid=${requestId ?? '-'} upstream=${viaUpstream}`);
535
+ this.jsonlIndex.historyReplyInFlight.add(agentId);
536
+ bridgePipelineLog({
537
+ dir: 'in',
538
+ event: 'agents:history:req',
539
+ requestId,
540
+ agentId,
541
+ detail: `limit=${socketLimit} title=${title?.slice(0, 32) ?? '-'} upstream=${viaUpstream}`,
360
542
  });
361
- reply('agents:history', history);
543
+ try {
544
+ const history = await this.jsonlIndex.loadHistory(agentId, {
545
+ title,
546
+ composerIdByTitle: this.stateManager.getState().composerIdByTitle,
547
+ limit: socketLimit,
548
+ });
549
+ const messages = this.chatDisplay.applyHistory(agentId, history.messages);
550
+ const ms = Date.now() - t0;
551
+ const bytes = JSON.stringify(messages).length;
552
+ console.log(`[relay] agents:history ok agentId=${history.agentId} msgs=${history.messages?.length ?? 0}/${history.totalMessages ?? '?'} bytes=${bytes} ms=${ms} rid=${requestId ?? '-'}`);
553
+ bridgePipelineLog({
554
+ dir: 'out',
555
+ event: 'agents:history:reply',
556
+ requestId,
557
+ agentId: history.agentId,
558
+ bytes,
559
+ msgs: history.messages?.length ?? 0,
560
+ detail: `total=${history.totalMessages ?? '?'} ms=${ms}`,
561
+ });
562
+ reply('agents:history', {
563
+ ...history,
564
+ messages,
565
+ requestId,
566
+ updatedAt: Date.now(),
567
+ });
568
+ }
569
+ catch (err) {
570
+ console.error(`[relay] agents:history fail agentId=${agentId} ms=${Date.now() - t0} rid=${requestId ?? '-'}:`, err.message);
571
+ bridgePipelineLog({
572
+ dir: 'internal',
573
+ event: 'agents:history:fail',
574
+ requestId,
575
+ agentId,
576
+ detail: err.message,
577
+ });
578
+ reply('agents:history', {
579
+ agentId,
580
+ messages: [],
581
+ totalMessages: 0,
582
+ requestId,
583
+ updatedAt: Date.now(),
584
+ });
585
+ }
586
+ finally {
587
+ this.jsonlIndex.historyReplyInFlight.delete(agentId);
588
+ }
362
589
  }
363
590
  async runAgentsSubscribe({ agentId, title, focus, }) {
364
591
  if (!agentId)
@@ -379,8 +606,10 @@ export class Relay {
379
606
  }
380
607
  }
381
608
  runAgentsUnsubscribe({ agentId }) {
382
- if (agentId)
609
+ if (agentId) {
383
610
  this.jsonlIndex.unsubscribe(agentId);
611
+ this.chatDisplay.clearAgent(agentId);
612
+ }
384
613
  }
385
614
  async runAgentsFocus({ agentId, title }, reply) {
386
615
  if (!agentId)
@@ -3,7 +3,7 @@ export { normalizeAgentTitle } from './agent-title-match.js';
3
3
  /** Prefer Cursor DOM sidebar order/names; attach JSONL ids when titles match. */
4
4
  export function mergeSidebarWithJsonl(sidebarRepos, jsonl, composerIdByTitle) {
5
5
  if (!sidebarRepos?.length)
6
- return jsonl;
6
+ return { ...jsonl, listSource: 'jsonl' };
7
7
  const byTitle = new Map();
8
8
  for (const repo of jsonl.repos) {
9
9
  for (const agent of repo.agents) {
@@ -43,5 +43,5 @@ export function mergeSidebarWithJsonl(sidebarRepos, jsonl, composerIdByTitle) {
43
43
  };
44
44
  }),
45
45
  }));
46
- return { repos, updatedAt: Date.now() };
46
+ return { repos, updatedAt: Date.now(), listSource: 'sidebar' };
47
47
  }
@@ -229,15 +229,23 @@ export interface RepoGroup {
229
229
  export interface AgentsIndex {
230
230
  repos: RepoGroup[];
231
231
  updatedAt: number;
232
+ /** `sidebar` = порядок/названия из CDP; `jsonl` = архив на диске (не как в Cursor). */
233
+ listSource?: 'sidebar' | 'jsonl';
232
234
  }
233
235
  export interface HistoryMessage {
234
236
  role: 'user' | 'assistant' | 'system';
235
237
  text: string;
238
+ html?: string;
239
+ /** Absolute paths from JSONL `<image_files>` (served via GET /media/file). */
240
+ images?: string[];
236
241
  ts?: number;
237
242
  }
238
243
  export interface AgentHistory {
239
244
  agentId: string;
240
- messages: HistoryMessage[];
245
+ /** Display-ready chat rows (normalized, deduped, filtered on bridge). */
246
+ messages: ChatMessage[];
241
247
  updatedAt?: number;
248
+ requestId?: string;
249
+ totalMessages?: number;
242
250
  }
243
251
  export type ExtractedPageState = Omit<CursorState, 'connected' | 'windows' | 'activeWindowId' | 'windowSnapshots' | 'updatedAt'>;
@@ -0,0 +1,3 @@
1
+ # CursorConnect — встроенный relay (переопределение: ~/.cursorconnect/config.env)
2
+ RELAY_URL=https://cc.fanpay.online
3
+ RELAY_TOKEN=cursorconnect-test-relay-2026
package/dist/big-code.js CHANGED
@@ -1,5 +1,5 @@
1
- /** 5×5 block digits for terminal pairing code. */
2
- const DIGITS = {
1
+ /** 5×5 block glyphs (█) for pairing code — A–Z and 0–9. */
2
+ const GLYPHS = {
3
3
  '0': ['█████', '█ █', '█ █', '█ █', '█████'],
4
4
  '1': [' █ ', ' ██ ', ' █ ', ' █ ', ' ███ '],
5
5
  '2': ['█████', ' █', '█████', '█ ', '█████'],
@@ -10,13 +10,44 @@ const DIGITS = {
10
10
  '7': ['█████', ' █', ' █ ', ' █ ', ' █ '],
11
11
  '8': ['█████', '█ █', '█████', '█ █', '█████'],
12
12
  '9': ['█████', '█ █', '█████', ' █', '█████'],
13
+ A: [' ███ ', '█ █', '█████', '█ █', '█ █'],
14
+ B: ['████ ', '█ █', '████ ', '█ █', '████ '],
15
+ C: [' ████', '█ ', '█ ', '█ ', ' ████'],
16
+ D: ['████ ', '█ █', '█ █', '█ █', '████ '],
17
+ E: ['█████', '█ ', '████ ', '█ ', '█████'],
18
+ F: ['█████', '█ ', '████ ', '█ ', '█ '],
19
+ G: [' ████', '█ ', '█ ██', '█ █', ' ████'],
20
+ H: ['█ █', '█ █', '█████', '█ █', '█ █'],
21
+ I: [' ███ ', ' █ ', ' █ ', ' █ ', ' ███ '],
22
+ J: [' ███', ' █', ' █', '█ █', ' ███ '],
23
+ K: ['█ █', '█ █ ', '███ ', '█ █ ', '█ █'],
24
+ L: ['█ ', '█ ', '█ ', '█ ', '█████'],
25
+ M: ['█ █', '██ ██', '█ █ █', '█ █', '█ █'],
26
+ N: ['█ █', '██ █', '█ █ █', '█ ██', '█ █'],
27
+ O: [' ███ ', '█ █', '█ █', '█ █', ' ███ '],
28
+ P: ['████ ', '█ █', '████ ', '█ ', '█ '],
29
+ Q: [' ███ ', '█ █', '█ █', '█ ██', ' ████'],
30
+ R: ['████ ', '█ █', '████ ', '█ █ ', '█ █'],
31
+ S: [' ████', '█ ', ' ███ ', ' █', '████ '],
32
+ T: ['█████', ' █ ', ' █ ', ' █ ', ' █ '],
33
+ U: ['█ █', '█ █', '█ █', '█ █', ' ███ '],
34
+ V: ['█ █', '█ █', '█ █', ' █ █ ', ' █ '],
35
+ W: ['█ █', '█ █', '█ █ █', '██ ██', '█ █'],
36
+ X: ['█ █', ' █ █ ', ' █ ', ' █ █ ', '█ █'],
37
+ Y: ['█ █', ' █ █ ', ' █ ', ' █ ', ' █ '],
38
+ Z: ['█████', ' █ ', ' █ ', ' █ ', '█████'],
13
39
  };
40
+ const FALLBACK = GLYPHS['8'];
14
41
  const GAP = ' ';
15
42
  export function renderBigCode(code) {
16
- const digits = code.replace(/\D/g, '').slice(0, 6);
43
+ const chars = code
44
+ .toUpperCase()
45
+ .replace(/[^A-Z0-9]/g, '')
46
+ .slice(0, 6)
47
+ .split('');
17
48
  const rows = ['', '', '', '', ''];
18
- for (const ch of digits) {
19
- const art = DIGITS[ch] ?? DIGITS['8'];
49
+ for (const ch of chars) {
50
+ const art = GLYPHS[ch] ?? FALLBACK;
20
51
  for (let i = 0; i < 5; i++) {
21
52
  rows[i] += art[i] + GAP;
22
53
  }
@@ -51,7 +51,7 @@ export function resolveBridgeDir() {
51
51
  if (isValidBridgeDir(bridge))
52
52
  return bridge;
53
53
  }
54
- throw new Error('Не найден bridge.\n' +
54
+ throw new Error('Не найден Cursor Connect (bridge).\n' +
55
55
  ' npm install -g cursorconnect && cursorconnect start\n' +
56
56
  ' (для разработки: клон репо + npm install в корне)');
57
57
  }
@@ -65,6 +65,11 @@ export function ensureUserConfig() {
65
65
  copyFileSync(devEnv, USER_CONFIG_ENV);
66
66
  return;
67
67
  }
68
+ const bundled = join(resolvePackageRoot(), 'config.env.defaults');
69
+ if (existsSync(bundled)) {
70
+ copyFileSync(bundled, USER_CONFIG_ENV);
71
+ return;
72
+ }
68
73
  const template = join(resolvePackageRoot(), 'bridge-runtime', '.env.example');
69
74
  if (existsSync(template)) {
70
75
  copyFileSync(template, USER_CONFIG_ENV);
@@ -0,0 +1,13 @@
1
+ import { readFileSync } from 'fs';
2
+ import { dirname, join } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
5
+ export const CLI_VERSION = (() => {
6
+ try {
7
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
8
+ return pkg.version?.trim() || '0.0.0';
9
+ }
10
+ catch {
11
+ return '0.0.0';
12
+ }
13
+ })();