@1presence/bridge 0.78.0 → 0.80.0

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/dist/claude.js CHANGED
@@ -41,10 +41,11 @@ export function paint(code, s) {
41
41
  }
42
42
  export const SECTION_COLORS = {
43
43
  system: '35',
44
- user: '34',
44
+ user: '33',
45
45
  assistant: '32',
46
46
  input: '36',
47
- result: '33',
47
+ result: '90',
48
+ error: '31',
48
49
  };
49
50
  function debugBlock(label, colorCode, body) {
50
51
  const rule = `── ${label} `.padEnd(64, '─');
@@ -117,6 +118,21 @@ function joinErrorDetail(...parts) {
117
118
  const SDK_STDERR_ERROR_RE = /\b(error|exception|fail(?:ed|ure)?|invalid|unauthor|forbidden|refus|denied|40[0-9]|429|5\d\d|overloaded|rate.?limit)\b/i;
118
119
  const REMOTE_MCP_SERVER_NAME = '1presence';
119
120
  const MCP_SESSION_EXPIRED_RE = /session not found or expired|Error POSTing to endpoint \(HTTP 404\)/i;
121
+ const MCP_TRANSPORT_DEAD_RE = /SSE (?:stream|error)|not connected|connection (?:closed|error|reset|refused|dropped)|dropped connection|socket hang up|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EPIPE|fetch failed|terminated|transport (?:closed|error)|request timed out|-32001|\(HTTP 50\d\)/i;
122
+ const OUTBOUND_NETWORK_TOOLS = new Set([
123
+ 'mcp__1presence__web_fetch',
124
+ 'mcp__1presence__web_search',
125
+ ]);
126
+ export function shouldReconnectRemoteMcp(args) {
127
+ const { toolName, isError, text } = args;
128
+ if (MCP_SESSION_EXPIRED_RE.test(text))
129
+ return true;
130
+ if (!isError || !toolName.startsWith('mcp__1presence__'))
131
+ return false;
132
+ if (OUTBOUND_NETWORK_TOOLS.has(toolName))
133
+ return false;
134
+ return MCP_TRANSPORT_DEAD_RE.test(text);
135
+ }
120
136
  function toolResultText(content) {
121
137
  if (typeof content === 'string')
122
138
  return content;
@@ -208,6 +224,11 @@ export function spawnClaude(params) {
208
224
  }
209
225
  else {
210
226
  process.stderr.write(`[bridge] session ${presenceSessionId}\n`);
227
+ const prompt = text.replace(/\s+/g, ' ').trim();
228
+ if (prompt) {
229
+ const shown = prompt.length > 300 ? `${prompt.slice(0, 300)}…` : prompt;
230
+ process.stderr.write(paint(SECTION_COLORS.user, `[bridge] ▸ ${shown}`) + '\n');
231
+ }
211
232
  }
212
233
  void vaultFileOpen;
213
234
  void clientCapabilities;
@@ -329,7 +350,7 @@ export function spawnClaude(params) {
329
350
  if (blockText && TOOL_CALL_XML_RE.test(blockText)) {
330
351
  const cleaned = stripToolCallXml(blockText);
331
352
  if (cleaned !== blockText) {
332
- process.stderr.write(paint(SECTION_COLORS.result, `[bridge] suppressed confabulated tool-call XML in assistant text`) + '\n');
353
+ process.stderr.write(paint(SECTION_COLORS.error, `[bridge] suppressed confabulated tool-call XML in assistant text`) + '\n');
333
354
  block['text'] = cleaned;
334
355
  blockText = cleaned;
335
356
  }
@@ -343,7 +364,7 @@ export function spawnClaude(params) {
343
364
  apiErrorText = blockText.trim();
344
365
  if (isAuthFailure)
345
366
  sawAuthFailure = true;
346
- process.stderr.write(paint(SECTION_COLORS.result, `[bridge] ${blockText.replace(/\n+/g, ' ')}`) + '\n');
367
+ process.stderr.write(paint(SECTION_COLORS.error, `[bridge] ${blockText.replace(/\n+/g, ' ')}`) + '\n');
347
368
  return false;
348
369
  }
349
370
  producedRealOutput = true;
@@ -361,22 +382,30 @@ export function spawnClaude(params) {
361
382
  process.stderr.write('\n');
362
383
  }
363
384
  }
364
- if ((verbose || debug) && type === 'user') {
385
+ if (type === 'user') {
365
386
  const msg = event['message'];
366
387
  const content = msg?.['content'];
367
388
  if (Array.isArray(content)) {
368
389
  for (const block of content) {
369
- if (block['type'] === 'tool_result') {
370
- const id = block['tool_use_id'] ?? '';
371
- const out = block['content'];
372
- if (debug) {
373
- const name = toolNames.get(id) ?? id ?? 'result';
374
- const errFlag = block['is_error'] ? ' [error]' : '';
375
- debugBlock(`result ← ${name}${errFlag}`, SECTION_COLORS.result, formatPayload(out));
376
- }
377
- else {
378
- process.stderr.write(paint(SECTION_COLORS.result, `[bridge:verbose] ─── output ${id} ───\n${formatPayload(out)}\n[bridge:verbose] ─── end output ───`) + '\n');
379
- }
390
+ if (block['type'] !== 'tool_result')
391
+ continue;
392
+ const id = block['tool_use_id'] ?? '';
393
+ const out = block['content'];
394
+ const name = toolNames.get(id) ?? id ?? 'result';
395
+ if (block['is_error']) {
396
+ const detail = toolResultText(out).replace(/\s+/g, ' ').trim();
397
+ const shown = detail.length > 400 ? `${detail.slice(0, 400)}…` : detail;
398
+ process.stderr.write(paint(SECTION_COLORS.error, `[bridge] ✗ tool error ← ${name}: ${shown || 'no detail'}`) + '\n');
399
+ }
400
+ if (!verbose && !debug)
401
+ continue;
402
+ const errFlag = block['is_error'] ? ' [error]' : '';
403
+ const bodyColor = block['is_error'] ? SECTION_COLORS.error : SECTION_COLORS.result;
404
+ if (debug) {
405
+ debugBlock(`result ← ${name}${errFlag}`, bodyColor, formatPayload(out));
406
+ }
407
+ else {
408
+ process.stderr.write(paint(bodyColor, `[bridge:verbose] ─── output ${name}${errFlag} ───\n${formatPayload(out)}\n[bridge:verbose] ─── end output ───`) + '\n');
380
409
  }
381
410
  }
382
411
  }
@@ -393,7 +422,7 @@ export function spawnClaude(params) {
393
422
  const detail = joinErrorDetail(event['result'], event['errors']);
394
423
  apiErrorText = joinErrorDetail(apiErrorText, detail) || apiErrorText;
395
424
  const subtype = event['subtype'] ? ` subtype=${event['subtype']}` : '';
396
- process.stderr.write(paint(SECTION_COLORS.result, `[bridge] result error${status != null ? ` (${status})` : ''}${subtype}: ${apiErrorText || 'unknown'}`) + '\n');
425
+ process.stderr.write(paint(SECTION_COLORS.error, `[bridge] result error${status != null ? ` (${status})` : ''}${subtype}: ${apiErrorText || 'unknown'}`) + '\n');
397
426
  if (!detail) {
398
427
  let raw;
399
428
  try {
@@ -402,7 +431,7 @@ export function spawnClaude(params) {
402
431
  catch {
403
432
  raw = String(event);
404
433
  }
405
- process.stderr.write(paint(SECTION_COLORS.result, `[bridge] result raw: ${raw.slice(0, 2000)}`) + '\n');
434
+ process.stderr.write(paint(SECTION_COLORS.error, `[bridge] result raw: ${raw.slice(0, 2000)}`) + '\n');
406
435
  }
407
436
  }
408
437
  }
@@ -477,11 +506,11 @@ export function spawnClaude(params) {
477
506
  return;
478
507
  mcpReconnecting = true;
479
508
  lastMcpReconnectAt = now;
480
- process.stderr.write(paint(SECTION_COLORS.result, '[bridge] remote MCP session expired — reconnecting and retrying') + '\n');
509
+ process.stderr.write(paint(SECTION_COLORS.error, '[bridge] remote MCP connection lost — reconnecting and retrying') + '\n');
481
510
  onNotice?.('Reconnecting to 1Presence tools…');
482
511
  void q.reconnectMcpServer(REMOTE_MCP_SERVER_NAME)
483
512
  .then(() => process.stderr.write(paint(SECTION_COLORS.result, '[bridge] remote MCP reconnected') + '\n'))
484
- .catch((err) => process.stderr.write(paint(SECTION_COLORS.result, `[bridge] remote MCP reconnect failed: ${err.message}`) + '\n'))
513
+ .catch((err) => process.stderr.write(paint(SECTION_COLORS.error, `[bridge] remote MCP reconnect failed: ${err.message}`) + '\n'))
485
514
  .finally(() => { mcpReconnecting = false; });
486
515
  };
487
516
  try {
@@ -500,7 +529,7 @@ export function spawnClaude(params) {
500
529
  }
501
530
  else if (subtype === 'api_retry') {
502
531
  const r = m;
503
- process.stderr.write(paint(SECTION_COLORS.result, `[bridge] api retry ${r.attempt ?? '?'}/${r.max_retries ?? '?'}${r.error_status != null ? ` (${r.error_status})` : ''}: ${r.error ?? 'unknown'}${r.retry_delay_ms != null ? `, next in ${r.retry_delay_ms}ms` : ''}`) + '\n');
532
+ process.stderr.write(paint(SECTION_COLORS.error, `[bridge] api retry ${r.attempt ?? '?'}/${r.max_retries ?? '?'}${r.error_status != null ? ` (${r.error_status})` : ''}: ${r.error ?? 'unknown'}${r.retry_delay_ms != null ? `, next in ${r.retry_delay_ms}ms` : ''}`) + '\n');
504
533
  const attempt = r.attempt ?? 0;
505
534
  if (attempt >= 2 || (r.retry_delay_ms ?? 0) >= 2000) {
506
535
  onNotice?.(formatRetryNotice(attempt, r.max_retries ?? 0, r.retry_delay_ms));
@@ -525,7 +554,7 @@ export function spawnClaude(params) {
525
554
  const full = joinErrorDetail(`API Error: ${am.error}`, msgText, rid);
526
555
  if (!apiErrorText || /^API Error: \w+$/.test(apiErrorText))
527
556
  apiErrorText = full;
528
- process.stderr.write(paint(SECTION_COLORS.result, `[bridge] assistant error: ${joinErrorDetail(am.error, msgText, rid)}`) + '\n');
557
+ process.stderr.write(paint(SECTION_COLORS.error, `[bridge] assistant error: ${joinErrorDetail(am.error, msgText, rid)}`) + '\n');
529
558
  break;
530
559
  }
531
560
  const event = { type: 'assistant', message: am.message, error: am.error };
@@ -540,8 +569,16 @@ export function spawnClaude(params) {
540
569
  const content = um.message?.['content'];
541
570
  if (Array.isArray(content)) {
542
571
  for (const block of content) {
543
- if (block && typeof block === 'object' && block['type'] === 'tool_result'
544
- && MCP_SESSION_EXPIRED_RE.test(toolResultText(block['content']))) {
572
+ if (!block || typeof block !== 'object')
573
+ continue;
574
+ const b = block;
575
+ if (b['type'] !== 'tool_result')
576
+ continue;
577
+ if (shouldReconnectRemoteMcp({
578
+ toolName: toolNames.get(b['tool_use_id'] ?? '') ?? '',
579
+ isError: b['is_error'] === true,
580
+ text: toolResultText(b['content']),
581
+ })) {
545
582
  maybeReconnectRemoteMcp(q);
546
583
  break;
547
584
  }
@@ -612,7 +649,7 @@ export function spawnClaude(params) {
612
649
  return;
613
650
  const message = err?.message ?? String(err);
614
651
  const stack = err?.stack;
615
- process.stderr.write(paint(SECTION_COLORS.result, `[bridge] query() threw: ${stack || message}`) + '\n');
652
+ process.stderr.write(paint(SECTION_COLORS.error, `[bridge] query() threw: ${stack || message}`) + '\n');
616
653
  if (/40[13]\b|unauthor|invalid (api key|authentication)|please run \/login/i.test(message)) {
617
654
  sawAuthFailure = true;
618
655
  }
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ if (__dirname.endsWith('dist')) {
21
21
  if (existsSync(srcDir)) {
22
22
  const newest = (dir) => Math.max(...readdirSync(dir).map(f => statSync(join(dir, f)).mtimeMs));
23
23
  if (newest(srcDir) > newest(__dirname)) {
24
- console.error('Bridge dist is stale (src/ has been edited since last build). Run: npm run build');
24
+ console.error(paint(SECTION_COLORS.error, 'Bridge dist is stale (src/ has been edited since last build). Run: npm run build'));
25
25
  process.exit(1);
26
26
  }
27
27
  }
@@ -205,7 +205,7 @@ async function handleDocReconRequest(req) {
205
205
  mediaType = req.sourceContentType || 'application/pdf';
206
206
  }
207
207
  catch (err) {
208
- console.error(`[bridge] doc_recon: download failed (reqId: ${req.reqId}): ${err.message}`);
208
+ console.error(paint(SECTION_COLORS.error, `[bridge] doc_recon: download failed (reqId: ${req.reqId}): ${err.message}`));
209
209
  send({ type: 'doc_recon_response', reqId: req.reqId, error: `download failed: ${err.message}` });
210
210
  return;
211
211
  }
@@ -256,7 +256,7 @@ async function handleDocReconRequest(req) {
256
256
  }
257
257
  catch (err) {
258
258
  if (!abort.signal.aborted) {
259
- console.error(`[bridge] doc_recon: query failed (reqId: ${req.reqId}): ${err.message}`);
259
+ console.error(paint(SECTION_COLORS.error, `[bridge] doc_recon: query failed (reqId: ${req.reqId}): ${err.message}`));
260
260
  send({ type: 'doc_recon_response', reqId: req.reqId, error: err.message });
261
261
  }
262
262
  }
@@ -275,7 +275,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
275
275
  if (!isTokenValid(auth.token)) {
276
276
  const message = 'Authentication expired and refresh failed — please restart the bridge to sign in again.';
277
277
  stopTurnTimer();
278
- console.error(`[bridge] ${message} (${err.message})`);
278
+ console.error(paint(SECTION_COLORS.error, `[bridge] ${message} (${err.message})`));
279
279
  if (currentWs?.readyState === WebSocket.OPEN) {
280
280
  currentWs.send(JSON.stringify({ type: 'error', conversationId, message }));
281
281
  }
@@ -289,7 +289,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
289
289
  catch (err) {
290
290
  const message = `System prompt refresh failed: ${err.message}`;
291
291
  stopTurnTimer();
292
- console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message}`);
292
+ console.error(paint(SECTION_COLORS.error, `[${new Date().toLocaleTimeString()}] ✗ ${message}`));
293
293
  if (currentWs?.readyState === WebSocket.OPEN) {
294
294
  currentWs.send(JSON.stringify({ type: 'error', conversationId, message }));
295
295
  }
@@ -407,7 +407,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
407
407
  },
408
408
  onError: (message, usage, model) => {
409
409
  const elapsed = stopTurnTimer();
410
- console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message} (${formatElapsed(elapsed)})`);
410
+ console.error(paint(SECTION_COLORS.error, `[${new Date().toLocaleTimeString()}] ✗ ${message} (${formatElapsed(elapsed)})`));
411
411
  const mapped = toBridgeUsage(usage);
412
412
  if (currentWs?.readyState === WebSocket.OPEN) {
413
413
  currentWs.send(JSON.stringify({
@@ -505,7 +505,7 @@ function connect(auth, retryDelay = 1000) {
505
505
  }
506
506
  catch (err) {
507
507
  const preview = raw.toString().slice(0, 200);
508
- console.error(`[bridge] failed to parse ws message as JSON: ${err.message} (raw: ${preview})`);
508
+ console.error(paint(SECTION_COLORS.error, `[bridge] failed to parse ws message as JSON: ${err.message} (raw: ${preview})`));
509
509
  return;
510
510
  }
511
511
  if (msg.type === 'pong') {
@@ -525,7 +525,7 @@ function connect(auth, retryDelay = 1000) {
525
525
  const req = msg;
526
526
  if (req.reqId && req.systemPrompt && req.sourceUrl) {
527
527
  handleDocReconRequest(req).catch((err) => {
528
- console.error(`[bridge] doc_recon: unhandled error (reqId: ${req.reqId}): ${err.message}`);
528
+ console.error(paint(SECTION_COLORS.error, `[bridge] doc_recon: unhandled error (reqId: ${req.reqId}): ${err.message}`));
529
529
  if (currentWs?.readyState === WebSocket.OPEN) {
530
530
  currentWs.send(JSON.stringify({ type: 'doc_recon_response', reqId: req.reqId, error: err.message }));
531
531
  }
@@ -542,13 +542,13 @@ function connect(auth, retryDelay = 1000) {
542
542
  startTurnTimer();
543
543
  handleMessage(conversationId, text, sessionId ?? null, hist, auth, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug, model).catch((err) => {
544
544
  stopTurnTimer();
545
- console.error(`[bridge] handleMessage error: ${err.message}`);
545
+ console.error(paint(SECTION_COLORS.error, `[bridge] handleMessage error: ${err.message}`));
546
546
  });
547
547
  });
548
548
  ws.on('close', (code) => {
549
549
  stopPing();
550
550
  if (code === 4003) {
551
- console.error('Local Claude Code is not enabled for your account. To request access, email hello@1presence.com.');
551
+ console.error(paint(SECTION_COLORS.error, 'Local Claude Code is not enabled for your account. To request access, email hello@1presence.com.'));
552
552
  process.exit(1);
553
553
  }
554
554
  if (code === 4004) {
@@ -564,9 +564,10 @@ function connect(auth, retryDelay = 1000) {
564
564
  scheduleReconnect(code, retryDelay);
565
565
  });
566
566
  ws.on('error', (err) => {
567
- console.error(`[bridge] ws error: ${err.message}`);
568
- if (VERBOSE && err.stack)
569
- console.error(err.stack);
567
+ console.error(paint(SECTION_COLORS.error, `[bridge] ws error: ${err.message}`));
568
+ const wsStack = err.stack;
569
+ if (VERBOSE && wsStack)
570
+ console.error(paint(SECTION_COLORS.error, wsStack));
570
571
  });
571
572
  return ws;
572
573
  }
@@ -586,21 +587,21 @@ function scheduleReconnect(closeCode, retryDelay) {
586
587
  if (authFailure) {
587
588
  authRejections++;
588
589
  if (authRejections > MAX_AUTH_REJECTIONS) {
589
- console.error('Authentication keeps being rejected even after refreshing — please restart the bridge to sign in again.');
590
+ console.error(paint(SECTION_COLORS.error, 'Authentication keeps being rejected even after refreshing — please restart the bridge to sign in again.'));
590
591
  process.exit(1);
591
592
  }
592
593
  try {
593
594
  const refreshed = await forceRefreshToken(currentAuth);
594
595
  if (!refreshed) {
595
- console.error('Authentication failed and no refresh token is available — please restart the bridge to sign in again.');
596
+ console.error(paint(SECTION_COLORS.error, 'Authentication failed and no refresh token is available — please restart the bridge to sign in again.'));
596
597
  process.exit(1);
597
598
  }
598
599
  currentAuth = refreshed;
599
600
  console.log(`[bridge] token refreshed (attempt ${authRejections}/${MAX_AUTH_REJECTIONS}) — reconnecting with a new token.`);
600
601
  }
601
602
  catch (err) {
602
- console.error(`[bridge] token refresh failed: ${err.message}`);
603
- console.error('Please restart the bridge to sign in again.');
603
+ console.error(paint(SECTION_COLORS.error, `[bridge] token refresh failed: ${err.message}`));
604
+ console.error(paint(SECTION_COLORS.error, 'Please restart the bridge to sign in again.'));
604
605
  process.exit(1);
605
606
  }
606
607
  }
@@ -616,8 +617,8 @@ function scheduleReconnect(closeCode, retryDelay) {
616
617
  await writeSetupFiles(currentAuth);
617
618
  }
618
619
  catch (err) {
619
- console.error(`[bridge] reconnect setup failed: ${err.message}`);
620
- console.error('[bridge] will retry connection anyway — system prompt may be stale until next refresh');
620
+ console.error(paint(SECTION_COLORS.error, `[bridge] reconnect setup failed: ${err.message}`));
621
+ console.error(paint(SECTION_COLORS.error, '[bridge] will retry connection anyway — system prompt may be stale until next refresh'));
621
622
  }
622
623
  connect(currentAuth, nextDelay);
623
624
  }, delay);
@@ -632,7 +633,7 @@ async function ensureClaudeCodeLogin() {
632
633
  console.log(paint(SECTION_COLORS.assistant, `✓ Claude Code signed in${who}${plan}`));
633
634
  return;
634
635
  }
635
- console.log(paint(SECTION_COLORS.result, '\n⚠ Your local Claude Code isn’t signed in.'));
636
+ console.log(paint(SECTION_COLORS.error, '\n⚠ Your local Claude Code isn’t signed in.'));
636
637
  console.log(' Local Mode runs on your own Claude subscription — Claude Code has to be signed in on this machine, or every message will fail.');
637
638
  if (!process.stdin.isTTY) {
638
639
  console.log(' Not an interactive terminal, so I can’t prompt. Sign in with `claude auth login`, then start the bridge again.\n');
@@ -656,7 +657,7 @@ async function ensureClaudeCodeLogin() {
656
657
  console.log(paint(SECTION_COLORS.assistant, `✓ Signed in${who} — continuing.\n`));
657
658
  }
658
659
  else {
659
- console.log(paint(SECTION_COLORS.result, ' Still not signed in — starting anyway. Sign in any time and resend your message.\n'));
660
+ console.log(paint(SECTION_COLORS.error, ' Still not signed in — starting anyway. Sign in any time and resend your message.\n'));
660
661
  }
661
662
  }
662
663
  async function main() {
@@ -682,8 +683,8 @@ async function main() {
682
683
  }
683
684
  catch (err) {
684
685
  process.stdout.write(' FAILED.\n');
685
- console.error(`[bridge] setup failed: ${err.message}`);
686
- console.error('[bridge] cannot start without a system prompt from the gateway. Check network, auth, and that the gateway is reachable.');
686
+ console.error(paint(SECTION_COLORS.error, `[bridge] setup failed: ${err.message}`));
687
+ console.error(paint(SECTION_COLORS.error, '[bridge] cannot start without a system prompt from the gateway. Check network, auth, and that the gateway is reachable.'));
687
688
  process.exit(1);
688
689
  }
689
690
  process.stdout.write(' done.\n');
@@ -696,26 +697,27 @@ async function main() {
696
697
  process.on('SIGINT', shutdown);
697
698
  process.on('SIGTERM', shutdown);
698
699
  process.on('uncaughtException', (err) => {
699
- console.error(`[bridge] uncaughtException: ${err.message}`);
700
+ console.error(paint(SECTION_COLORS.error, `[bridge] uncaughtException: ${err.message}`));
700
701
  if (err.stack)
701
- console.error(err.stack);
702
+ console.error(paint(SECTION_COLORS.error, err.stack));
702
703
  });
703
704
  process.on('unhandledRejection', (reason) => {
704
705
  const message = reason instanceof Error ? reason.message : String(reason);
705
706
  const stack = reason instanceof Error ? reason.stack : undefined;
706
- console.error(`[bridge] unhandledRejection: ${message}`);
707
+ console.error(paint(SECTION_COLORS.error, `[bridge] unhandledRejection: ${message}`));
707
708
  if (stack)
708
- console.error(stack);
709
+ console.error(paint(SECTION_COLORS.error, stack));
709
710
  });
710
711
  }
711
712
  main().catch((err) => {
712
713
  if (err instanceof AuthCancelledError) {
713
- console.error(`\n${err.message}`);
714
- console.error('Run `npx @1presence/bridge` again when you are ready to sign in.');
714
+ console.error(paint(SECTION_COLORS.error, `\n${err.message}`));
715
+ console.error(paint(SECTION_COLORS.error, 'Run `npx @1presence/bridge` again when you are ready to sign in.'));
715
716
  process.exit(0);
716
717
  }
717
- console.error('Fatal:', err.message);
718
- if (err.stack)
719
- console.error(err.stack);
718
+ console.error(paint(SECTION_COLORS.error, `Fatal: ${err.message}`));
719
+ const fatalStack = err.stack;
720
+ if (fatalStack)
721
+ console.error(paint(SECTION_COLORS.error, fatalStack));
720
722
  process.exit(1);
721
723
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1presence/bridge",
3
- "version": "0.78.0",
3
+ "version": "0.80.0",
4
4
  "description": "Run 1Presence on your Mac and use your Claude.ai Pro subscription from any device",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",