@1presence/bridge 0.79.0 → 0.81.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/README.md CHANGED
@@ -51,6 +51,8 @@ Conversations are stateful — the bridge maps each 1Presence conversation to a
51
51
 
52
52
  Your OAuth tokens and vault data stay server-side — nothing sensitive is stored locally beyond the auth token.
53
53
 
54
+ On startup the bridge makes one request to the public npm registry (`registry.npmjs.org`) to check for a newer version, and self-updates via `npx` when one exists. The request carries no account data — it is the same anonymous lookup `npm view` performs — and if the registry is unreachable the bridge starts normally on the version you have. Beyond that check, the bridge talks only to the 1Presence gateway and your local `claude` install.
55
+
54
56
  ## Model
55
57
 
56
58
  **Every start** the bridge asks which Claude model to use — the choice is held in memory for that run only and nothing is written to disk, so restarting always asks again. Pick "Use Claude Code default" to defer to your local Claude Code default, or pin one of the latest models per family: `claude-opus-5`, `claude-fable-5`, `claude-sonnet-5`, `claude-haiku-4-5`. If the prompt times out it auto-selects "Use Claude Code default", which tracks whatever model Claude Code serves for your plan.
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, '─');
@@ -223,6 +224,11 @@ export function spawnClaude(params) {
223
224
  }
224
225
  else {
225
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
+ }
226
232
  }
227
233
  void vaultFileOpen;
228
234
  void clientCapabilities;
@@ -344,7 +350,7 @@ export function spawnClaude(params) {
344
350
  if (blockText && TOOL_CALL_XML_RE.test(blockText)) {
345
351
  const cleaned = stripToolCallXml(blockText);
346
352
  if (cleaned !== blockText) {
347
- 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');
348
354
  block['text'] = cleaned;
349
355
  blockText = cleaned;
350
356
  }
@@ -358,7 +364,7 @@ export function spawnClaude(params) {
358
364
  apiErrorText = blockText.trim();
359
365
  if (isAuthFailure)
360
366
  sawAuthFailure = true;
361
- 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');
362
368
  return false;
363
369
  }
364
370
  producedRealOutput = true;
@@ -386,19 +392,20 @@ export function spawnClaude(params) {
386
392
  const id = block['tool_use_id'] ?? '';
387
393
  const out = block['content'];
388
394
  const name = toolNames.get(id) ?? id ?? 'result';
389
- if (block['is_error'] && !verbose && !debug) {
395
+ if (block['is_error']) {
390
396
  const detail = toolResultText(out).replace(/\s+/g, ' ').trim();
391
397
  const shown = detail.length > 400 ? `${detail.slice(0, 400)}…` : detail;
392
- process.stderr.write(paint(SECTION_COLORS.result, `[bridge] tool error ← ${name}: ${shown || 'no detail'}`) + '\n');
398
+ process.stderr.write(paint(SECTION_COLORS.error, `[bridge] ✗ tool error ← ${name}: ${shown || 'no detail'}`) + '\n');
393
399
  }
394
400
  if (!verbose && !debug)
395
401
  continue;
402
+ const errFlag = block['is_error'] ? ' [error]' : '';
403
+ const bodyColor = block['is_error'] ? SECTION_COLORS.error : SECTION_COLORS.result;
396
404
  if (debug) {
397
- const errFlag = block['is_error'] ? ' [error]' : '';
398
- debugBlock(`result ← ${name}${errFlag}`, SECTION_COLORS.result, formatPayload(out));
405
+ debugBlock(`result ← ${name}${errFlag}`, bodyColor, formatPayload(out));
399
406
  }
400
407
  else {
401
- process.stderr.write(paint(SECTION_COLORS.result, `[bridge:verbose] ─── output ${id} ───\n${formatPayload(out)}\n[bridge:verbose] ─── end output ───`) + '\n');
408
+ process.stderr.write(paint(bodyColor, `[bridge:verbose] ─── output ${name}${errFlag} ───\n${formatPayload(out)}\n[bridge:verbose] ─── end output ───`) + '\n');
402
409
  }
403
410
  }
404
411
  }
@@ -415,7 +422,7 @@ export function spawnClaude(params) {
415
422
  const detail = joinErrorDetail(event['result'], event['errors']);
416
423
  apiErrorText = joinErrorDetail(apiErrorText, detail) || apiErrorText;
417
424
  const subtype = event['subtype'] ? ` subtype=${event['subtype']}` : '';
418
- 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');
419
426
  if (!detail) {
420
427
  let raw;
421
428
  try {
@@ -424,7 +431,7 @@ export function spawnClaude(params) {
424
431
  catch {
425
432
  raw = String(event);
426
433
  }
427
- 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');
428
435
  }
429
436
  }
430
437
  }
@@ -499,11 +506,11 @@ export function spawnClaude(params) {
499
506
  return;
500
507
  mcpReconnecting = true;
501
508
  lastMcpReconnectAt = now;
502
- process.stderr.write(paint(SECTION_COLORS.result, '[bridge] remote MCP connection lost — reconnecting and retrying') + '\n');
509
+ process.stderr.write(paint(SECTION_COLORS.error, '[bridge] remote MCP connection lost — reconnecting and retrying') + '\n');
503
510
  onNotice?.('Reconnecting to 1Presence tools…');
504
511
  void q.reconnectMcpServer(REMOTE_MCP_SERVER_NAME)
505
512
  .then(() => process.stderr.write(paint(SECTION_COLORS.result, '[bridge] remote MCP reconnected') + '\n'))
506
- .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'))
507
514
  .finally(() => { mcpReconnecting = false; });
508
515
  };
509
516
  try {
@@ -522,7 +529,7 @@ export function spawnClaude(params) {
522
529
  }
523
530
  else if (subtype === 'api_retry') {
524
531
  const r = m;
525
- 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');
526
533
  const attempt = r.attempt ?? 0;
527
534
  if (attempt >= 2 || (r.retry_delay_ms ?? 0) >= 2000) {
528
535
  onNotice?.(formatRetryNotice(attempt, r.max_retries ?? 0, r.retry_delay_ms));
@@ -547,7 +554,7 @@ export function spawnClaude(params) {
547
554
  const full = joinErrorDetail(`API Error: ${am.error}`, msgText, rid);
548
555
  if (!apiErrorText || /^API Error: \w+$/.test(apiErrorText))
549
556
  apiErrorText = full;
550
- 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');
551
558
  break;
552
559
  }
553
560
  const event = { type: 'assistant', message: am.message, error: am.error };
@@ -642,7 +649,7 @@ export function spawnClaude(params) {
642
649
  return;
643
650
  const message = err?.message ?? String(err);
644
651
  const stack = err?.stack;
645
- 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');
646
653
  if (/40[13]\b|unauthor|invalid (api key|authentication)|please run \/login/i.test(message)) {
647
654
  sawAuthFailure = true;
648
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.79.0",
3
+ "version": "0.81.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",