@1presence/bridge 0.39.0 → 0.42.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/config.js CHANGED
@@ -1,9 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ensureModelChoice = ensureModelChoice;
4
- exports.getBridgeModel = getBridgeModel;
5
- const readline_1 = require("readline");
6
- const child_process_1 = require("child_process");
1
+ import { emitKeypressEvents } from 'readline';
2
+ import { spawn } from 'child_process';
7
3
  // ─── In-memory model choice ───────────────────────────────────────────────────
8
4
  //
9
5
  // The bridge prompts for a model on every interactive startup. The choice is
@@ -27,7 +23,7 @@ function detectClaudeDefaultModel() {
27
23
  } };
28
24
  let proc;
29
25
  try {
30
- proc = (0, child_process_1.spawn)('claude', [
26
+ proc = spawn('claude', [
31
27
  '-p',
32
28
  '--input-format', 'stream-json',
33
29
  '--output-format', 'stream-json',
@@ -111,7 +107,7 @@ function promptForModel(defaultModel) {
111
107
  }
112
108
  };
113
109
  render();
114
- (0, readline_1.emitKeypressEvents)(process.stdin);
110
+ emitKeypressEvents(process.stdin);
115
111
  const wasRaw = process.stdin.isRaw;
116
112
  if (process.stdin.isTTY)
117
113
  process.stdin.setRawMode(true);
@@ -172,7 +168,7 @@ function promptForModel(defaultModel) {
172
168
  * in memory only — every startup re-prompts. In a non-TTY environment the
173
169
  * prompt is skipped and Claude Code's own default is used.
174
170
  */
175
- async function ensureModelChoice() {
171
+ export async function ensureModelChoice() {
176
172
  if (!process.stdin.isTTY) {
177
173
  selectedModel = null;
178
174
  return;
@@ -187,6 +183,6 @@ async function ensureModelChoice() {
187
183
  }
188
184
  }
189
185
  /** Returns the model id chosen for this session, or null to defer to Claude Code's own default. */
190
- function getBridgeModel() {
186
+ export function getBridgeModel() {
191
187
  return selectedModel;
192
188
  }
package/dist/index.js CHANGED
@@ -1,29 +1,29 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const ws_1 = __importDefault(require("ws"));
8
- const fs_1 = require("fs");
9
- const os_1 = require("os");
10
- const path_1 = require("path");
11
- const auth_1 = require("./auth");
12
- const claude_1 = require("./claude");
13
- const config_1 = require("./config");
14
- const update_1 = require("./update");
15
- const accumulator_1 = require("./accumulator");
16
- const outbox_1 = require("./outbox");
17
- const timer_1 = require("./timer");
18
- const package_json_1 = require("../package.json");
2
+ import WebSocket from 'ws';
3
+ import { writeFileSync, chmodSync, existsSync, statSync, readdirSync } from 'fs';
4
+ import { tmpdir } from 'os';
5
+ import { join, dirname } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { createRequire } from 'module';
8
+ import { getValidAuth, ensureFreshToken, isTokenValid, AuthCancelledError } from './auth.js';
9
+ import { spawnClaude, killAll, cancelConversation, setVerbose, setDebug, paint, SECTION_COLORS } from './claude.js';
10
+ import { ensureModelChoice } from './config.js';
11
+ import { checkAndUpdate } from './update.js';
12
+ import { makeBridgeAccumulator, postSaveTurn } from './accumulator.js';
13
+ import { writeSpool, deleteSpool, listSpool } from './outbox.js';
14
+ import { startTurnTimer, stopTurnTimer, formatElapsed } from './timer.js';
15
+ // ESM has no __dirname; derive it. JSON version is read via createRequire to
16
+ // avoid version-sensitive import assertions on a published CLI bin.
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const { version } = createRequire(import.meta.url)('../package.json');
19
19
  // Published tarballs don't ship src/, so this fires only when running the
20
20
  // dist build from a live workspace checkout. Catches the trap where editing
21
21
  // src/ without re-running tsc leaves you executing stale dist code — banner
22
22
  // version matches package.json but behavior doesn't match the source.
23
23
  if (__dirname.endsWith('dist')) {
24
- const srcDir = (0, path_1.join)(__dirname, '..', 'src');
25
- if ((0, fs_1.existsSync)(srcDir)) {
26
- const newest = (dir) => Math.max(...(0, fs_1.readdirSync)(dir).map(f => (0, fs_1.statSync)((0, path_1.join)(dir, f)).mtimeMs));
24
+ const srcDir = join(__dirname, '..', 'src');
25
+ if (existsSync(srcDir)) {
26
+ const newest = (dir) => Math.max(...readdirSync(dir).map(f => statSync(join(dir, f)).mtimeMs));
27
27
  if (newest(srcDir) > newest(__dirname)) {
28
28
  console.error('Bridge dist is stale (src/ has been edited since last build). Run: npm run build');
29
29
  process.exit(1);
@@ -45,6 +45,52 @@ const PWA_URL = process.env.BRIDGE_PWA_URL ?? GATEWAY_HTTP.replace('://api.', ':
45
45
  // ─── In-memory state ──────────────────────────────────────────────────────────
46
46
  let currentAuth = null;
47
47
  let currentWs = null;
48
+ // Running cost across all turns this process has handled, for the cost segment
49
+ // of the per-turn status line. On a pure subscription the CLI often reports a
50
+ // per-turn cost of 0, in which case this stays at 0 and reads as "plan usage".
51
+ let sessionCostUsd = 0;
52
+ // ─── Status line ──────────────────────────────────────────────────────────────
53
+ //
54
+ // A compact line printed after each completed turn echoing the segments local
55
+ // Claude Code shows in its own status bar: model, context fill, and cost. The
56
+ // 5h/7d subscription rate-limit windows it also shows are deliberately absent —
57
+ // those ride in the API's rate-limit response HEADERS, which the bridge (a
58
+ // consumer of the CLI's stream-json stdout only) never sees. Display only.
59
+ // Raw model id (claude-opus-4-7, claude-sonnet-4-6-20250101) to friendly "Opus
60
+ // 4.7". Regex-based so new dated snapshots format without a table edit; an
61
+ // unrecognised shape falls back to the raw id rather than guessing.
62
+ function friendlyModelName(model) {
63
+ if (!model)
64
+ return 'unknown';
65
+ const m = /claude-(opus|sonnet|haiku)-(\d+)-(\d+)/i.exec(model);
66
+ if (!m)
67
+ return model;
68
+ return `${m[1].charAt(0).toUpperCase()}${m[1].slice(1)} ${m[2]}.${m[3]}`;
69
+ }
70
+ // Context window (tokens) per model, for the context-fill estimate. Keyed by a
71
+ // family regex against the raw model id; first match wins, and an unrecognised
72
+ // id falls back to the Claude 4.x baseline rather than guessing high.
73
+ //
74
+ // Every model the bridge can currently run is 200k: Opus/Sonnet/Haiku 4.x are
75
+ // 200k on the standard path, and the 1M-context window is an API beta that the
76
+ // bridge's subscription print mode never opts into — so it does not apply here.
77
+ // When a model ships with a different standard window, add a row above the
78
+ // baseline; that one line keeps the estimate honest without touching anything
79
+ // else. (The percentage is of the raw window — local Claude's own gauge also
80
+ // reserves output headroom, so its reading runs a few points higher near full.)
81
+ const CONTEXT_WINDOWS = [
82
+ { match: /claude-(opus|sonnet|haiku)-4/i, tokens: 200_000 },
83
+ ];
84
+ const DEFAULT_CONTEXT_WINDOW = 200_000;
85
+ function contextWindowFor(model) {
86
+ if (model) {
87
+ for (const { match, tokens } of CONTEXT_WINDOWS) {
88
+ if (match.test(model))
89
+ return tokens;
90
+ }
91
+ }
92
+ return DEFAULT_CONTEXT_WINDOW;
93
+ }
48
94
  // ─── System prompt fetch ──────────────────────────────────────────────────────
49
95
  // Pulls the fully-built system prompt from agent-api (via gateway proxy).
50
96
  // This MUST match the hosted runtime exactly — STATIC_SYSTEM_PROMPT + dynamic
@@ -111,7 +157,7 @@ async function fetchSystemPrompt(token, agentSlug) {
111
157
  }
112
158
  // ─── Setup files ──────────────────────────────────────────────────────────────
113
159
  function tmpFile(name) {
114
- return (0, path_1.join)((0, os_1.tmpdir)(), name);
160
+ return join(tmpdir(), name);
115
161
  }
116
162
  // Fetch the system prompt and write it to /tmp/agent-${uid}.md. The hosted
117
163
  // runtime rebuilds buildSystemBlocks() per turn (dynamic context: vault state,
@@ -123,9 +169,9 @@ async function writeSystemPrompt(auth, agentSlug) {
123
169
  const systemPrompt = await fetchSystemPrompt(token, agentSlug);
124
170
  writeRestricted(tmpFile(`agent-${uid}.md`), systemPrompt);
125
171
  if (VERBOSE) {
126
- console.log((0, claude_1.paint)(claude_1.SECTION_COLORS.system, '\n[bridge:verbose] ─── system prompt ───────────────────────'));
127
- console.log((0, claude_1.paint)(claude_1.SECTION_COLORS.system, systemPrompt));
128
- console.log((0, claude_1.paint)(claude_1.SECTION_COLORS.system, '[bridge:verbose] ─── end system prompt ───────────────────\n'));
172
+ console.log(paint(SECTION_COLORS.system, '\n[bridge:verbose] ─── system prompt ───────────────────────'));
173
+ console.log(paint(SECTION_COLORS.system, systemPrompt));
174
+ console.log(paint(SECTION_COLORS.system, '[bridge:verbose] ─── end system prompt ───────────────────\n'));
129
175
  }
130
176
  }
131
177
  function writeMcpConfig(auth) {
@@ -149,8 +195,8 @@ async function writeSetupFiles(auth, agentSlug) {
149
195
  // state. writeFileSync's mode only takes effect on file creation — chmodSync
150
196
  // covers the overwrite case so a legacy 0644 file gets tightened on next run.
151
197
  function writeRestricted(path, data) {
152
- (0, fs_1.writeFileSync)(path, data, { mode: 0o600 });
153
- (0, fs_1.chmodSync)(path, 0o600);
198
+ writeFileSync(path, data, { mode: 0o600 });
199
+ chmodSync(path, 0o600);
154
200
  }
155
201
  // ─── Helpers ──────────────────────────────────────────────────────────────────
156
202
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -162,7 +208,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
162
208
  // Refresh JWT if <10 min remaining before spawning Claude
163
209
  let activeAuth = auth;
164
210
  try {
165
- const freshAuth = await (0, auth_1.ensureFreshToken)(auth);
211
+ const freshAuth = await ensureFreshToken(auth);
166
212
  if (freshAuth.token !== auth.token) {
167
213
  currentAuth = freshAuth;
168
214
  activeAuth = freshAuth;
@@ -172,11 +218,11 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
172
218
  catch (err) {
173
219
  // If the cached token still has time, proceed — refresh was preemptive.
174
220
  // If it's already invalid, MCP calls will 401 mid-turn — fail fast instead.
175
- if (!(0, auth_1.isTokenValid)(auth.token)) {
221
+ if (!isTokenValid(auth.token)) {
176
222
  const message = 'Authentication expired and refresh failed — please restart the bridge to sign in again.';
177
- (0, timer_1.stopTurnTimer)();
223
+ stopTurnTimer();
178
224
  console.error(`[bridge] ${message} (${err.message})`);
179
- if (currentWs?.readyState === ws_1.default.OPEN) {
225
+ if (currentWs?.readyState === WebSocket.OPEN) {
180
226
  currentWs.send(JSON.stringify({ type: 'error', conversationId, message }));
181
227
  }
182
228
  return;
@@ -195,15 +241,15 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
195
241
  }
196
242
  catch (err) {
197
243
  const message = `System prompt refresh failed: ${err.message}`;
198
- (0, timer_1.stopTurnTimer)();
244
+ stopTurnTimer();
199
245
  console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message}`);
200
- if (currentWs?.readyState === ws_1.default.OPEN) {
246
+ if (currentWs?.readyState === WebSocket.OPEN) {
201
247
  currentWs.send(JSON.stringify({ type: 'error', conversationId, message }));
202
248
  }
203
249
  return;
204
250
  }
205
251
  let responding = false;
206
- const accumulator = (0, accumulator_1.makeBridgeAccumulator)();
252
+ const accumulator = makeBridgeAccumulator();
207
253
  const startedAt = Date.now();
208
254
  const turnSessionId = sessionId ?? conversationId; // gateway always supplies one; defensive fallback
209
255
  // The CLI's `--session-id` is treated as a "claim this new session ID"
@@ -237,14 +283,14 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
237
283
  // ack is recoverable by drain-on-startup. The gateway dedupes on
238
284
  // conversationId, so a replay is idempotent.
239
285
  try {
240
- (0, outbox_1.writeSpool)(record);
286
+ writeSpool(record);
241
287
  }
242
288
  catch (err) {
243
289
  console.warn(`[bridge] spool write failed: ${err.message}`);
244
290
  }
245
- const result = await (0, accumulator_1.postSaveTurn)(GATEWAY_HTTP, activeAuth.token, record);
291
+ const result = await postSaveTurn(GATEWAY_HTTP, activeAuth.token, record);
246
292
  if (result.ok) {
247
- (0, outbox_1.deleteSpool)(record.conversationId);
293
+ deleteSpool(record.conversationId);
248
294
  }
249
295
  else {
250
296
  // Leave the spool file in place — next startup or next successful
@@ -252,7 +298,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
252
298
  console.warn(`[bridge] save-turn POST failed (${result.status}): ${result.error ?? 'unknown'} — kept on disk for retry`);
253
299
  }
254
300
  }
255
- (0, claude_1.spawnClaude)({
301
+ spawnClaude({
256
302
  conversationId,
257
303
  presenceSessionId: claudePinnedSessionId,
258
304
  text,
@@ -267,7 +313,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
267
313
  responding = true;
268
314
  console.log(`[${new Date().toLocaleTimeString()}] ◐ responding…`);
269
315
  }
270
- if (currentWs?.readyState === ws_1.default.OPEN) {
316
+ if (currentWs?.readyState === WebSocket.OPEN) {
271
317
  currentWs.send(JSON.stringify({ type: 'stream', conversationId, event }));
272
318
  }
273
319
  },
@@ -275,21 +321,29 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
275
321
  // Ephemeral, non-persisted thread notice (admin-only Local Mode). Relayed
276
322
  // by the gateway to the PWA SSE stream as a `notice` AgentEvent; it does
277
323
  // NOT go through the turn accumulator, so it never lands in history.
278
- if (currentWs?.readyState === ws_1.default.OPEN) {
324
+ if (currentWs?.readyState === WebSocket.OPEN) {
279
325
  currentWs.send(JSON.stringify({ type: 'notice', conversationId, message }));
280
326
  }
281
327
  },
282
- onDone: (messageCount, costUsd, usage, model) => {
283
- const elapsed = (0, timer_1.stopTurnTimer)();
284
- const parts = [(0, timer_1.formatElapsed)(elapsed)];
328
+ onDone: (messageCount, costUsd, usage, model, contextTokens) => {
329
+ const elapsed = stopTurnTimer();
330
+ const parts = [formatElapsed(elapsed)];
285
331
  if (usage)
286
332
  parts.push(`in:${usage.input_tokens} out:${usage.output_tokens}`);
287
333
  const costStr = costUsd === 0 ? '$0.0000 (plan usage)' : `$${costUsd.toFixed(4)}`;
288
334
  parts.push(costStr);
289
335
  const suffix = ` ${parts.join(' ')}`;
290
336
  console.log(`[${new Date().toLocaleTimeString()}] ✓ done${suffix}`);
337
+ // Status-bar line, mirroring local Claude Code: model · context fill ·
338
+ // session cost. Dimmed and indented so it groups under the done line
339
+ // without competing with it. The cost segment falls back to "plan usage"
340
+ // whenever the running total is 0 (the subscription case).
341
+ sessionCostUsd += costUsd;
342
+ const ctxPct = Math.max(0, Math.min(100, Math.round((contextTokens / contextWindowFor(model)) * 100)));
343
+ const costSeg = sessionCostUsd > 0 ? `$${sessionCostUsd.toFixed(2)} session` : 'plan usage';
344
+ console.log(paint('90', ` 🤖 ${friendlyModelName(model)} · 🧠 ${ctxPct}% · 💰 ${costSeg}`));
291
345
  const mapped = toBridgeUsage(usage);
292
- if (currentWs?.readyState === ws_1.default.OPEN) {
346
+ if (currentWs?.readyState === WebSocket.OPEN) {
293
347
  currentWs.send(JSON.stringify({
294
348
  type: 'done',
295
349
  conversationId,
@@ -303,10 +357,10 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
303
357
  void finalizeAndPost(buildSpoolRecord(mapped, model));
304
358
  },
305
359
  onError: (message, usage, model) => {
306
- const elapsed = (0, timer_1.stopTurnTimer)();
307
- console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message} (${(0, timer_1.formatElapsed)(elapsed)})`);
360
+ const elapsed = stopTurnTimer();
361
+ console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message} (${formatElapsed(elapsed)})`);
308
362
  const mapped = toBridgeUsage(usage);
309
- if (currentWs?.readyState === ws_1.default.OPEN) {
363
+ if (currentWs?.readyState === WebSocket.OPEN) {
310
364
  currentWs.send(JSON.stringify({
311
365
  type: 'error',
312
366
  conversationId,
@@ -337,14 +391,14 @@ function toBridgeUsage(usage) {
337
391
  // dedupes on conversationId — if it already saved via the WS path, the
338
392
  // reply is finalized=false and we still delete the spool.
339
393
  async function drainOutbox(auth) {
340
- const records = (0, outbox_1.listSpool)();
394
+ const records = listSpool();
341
395
  if (records.length === 0)
342
396
  return;
343
397
  console.log(`[bridge] draining ${records.length} pending save record${records.length === 1 ? '' : 's'}…`);
344
398
  for (const record of records) {
345
- const result = await (0, accumulator_1.postSaveTurn)(GATEWAY_HTTP, auth.token, record);
399
+ const result = await postSaveTurn(GATEWAY_HTTP, auth.token, record);
346
400
  if (result.ok) {
347
- (0, outbox_1.deleteSpool)(record.conversationId);
401
+ deleteSpool(record.conversationId);
348
402
  }
349
403
  else {
350
404
  console.warn(`[bridge] drain POST failed (${result.status}): ${result.error ?? 'unknown'} — leaving on disk`);
@@ -357,14 +411,14 @@ async function drainOutbox(auth) {
357
411
  const PING_INTERVAL_MS = 30_000;
358
412
  const PONG_TIMEOUT_MS = 10_000;
359
413
  function connect(auth, retryDelay = 1000) {
360
- const ws = new ws_1.default(GATEWAY_WS, {
414
+ const ws = new WebSocket(GATEWAY_WS, {
361
415
  headers: { Authorization: `Bearer ${auth.token}` },
362
416
  });
363
417
  let pingTimer = null;
364
418
  let pongTimer = null;
365
419
  function startPing() {
366
420
  pingTimer = setInterval(() => {
367
- if (ws.readyState !== ws_1.default.OPEN)
421
+ if (ws.readyState !== WebSocket.OPEN)
368
422
  return;
369
423
  ws.send(JSON.stringify({ type: 'ping', ts: Date.now() }));
370
424
  pongTimer = setTimeout(() => {
@@ -419,7 +473,7 @@ function connect(auth, retryDelay = 1000) {
419
473
  // (PWA→gateway connection dropped). Kill the local Claude Code process for
420
474
  // this conversation so it stops generating instead of running to the end.
421
475
  if (msg.type === 'cancel' && msg.conversationId) {
422
- const cancelled = (0, claude_1.cancelConversation)(msg.conversationId);
476
+ const cancelled = cancelConversation(msg.conversationId);
423
477
  if (cancelled)
424
478
  console.log(`[bridge] ✕ stopped conversation ${msg.conversationId}`);
425
479
  return;
@@ -430,9 +484,9 @@ function connect(auth, retryDelay = 1000) {
430
484
  const ts = new Date().toLocaleTimeString();
431
485
  const hist = Array.isArray(history) ? history : [];
432
486
  console.log(`[${ts}] ▶ ${text}${hist.length ? ` (history: ${hist.length} turn${hist.length === 1 ? '' : 's'})` : ''}`);
433
- (0, timer_1.startTurnTimer)();
487
+ startTurnTimer();
434
488
  handleMessage(conversationId, text, sessionId ?? null, hist, auth, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug).catch((err) => {
435
- (0, timer_1.stopTurnTimer)();
489
+ stopTurnTimer();
436
490
  console.error(`[bridge] handleMessage error: ${err.message}`);
437
491
  });
438
492
  });
@@ -475,24 +529,24 @@ function connect(auth, retryDelay = 1000) {
475
529
  }
476
530
  // ─── Main ─────────────────────────────────────────────────────────────────────
477
531
  async function main() {
478
- console.log(`1Presence Bridge v${package_json_1.version}\n`);
532
+ console.log(`1Presence Bridge v${version}\n`);
479
533
  if (VERBOSE) {
480
- (0, claude_1.setVerbose)(true);
534
+ setVerbose(true);
481
535
  console.log('[bridge:verbose] verbose logging enabled — system prompts (magenta), user prompts (blue), assistant text (green), tool inputs (cyan), and tool outputs (yellow) will be printed, colour-coded by kind.\n');
482
536
  }
483
537
  if (DEBUG) {
484
- (0, claude_1.setDebug)(true);
538
+ setDebug(true);
485
539
  console.log('[bridge:debug] debug transcript enabled — user prompts, assistant text, tool inputs, and tool outputs will be printed (system prompt omitted; use --verbose for that).\n');
486
540
  }
487
- if (await (0, update_1.checkAndUpdate)())
541
+ if (await checkAndUpdate())
488
542
  return;
489
543
  // Auth
490
- const auth = await (0, auth_1.getValidAuth)(GATEWAY_HTTP, PWA_URL);
544
+ const auth = await getValidAuth(GATEWAY_HTTP, PWA_URL);
491
545
  currentAuth = auth;
492
546
  // One-time interactive model choice (only prompts on first run; saved to
493
547
  // ~/.1presence/config.json). In a non-TTY environment this is a no-op and
494
548
  // Claude Code's own default is used.
495
- await (0, config_1.ensureModelChoice)();
549
+ await ensureModelChoice();
496
550
  // Write system prompt + MCP config. If this fails the bridge is dead in the
497
551
  // water — surface the underlying error rather than letting it bubble up as
498
552
  // a generic "Fatal:" with no context.
@@ -512,7 +566,7 @@ async function main() {
512
566
  // Graceful shutdown
513
567
  const shutdown = () => {
514
568
  console.log('\nShutting down…');
515
- (0, claude_1.killAll)();
569
+ killAll();
516
570
  process.exit(0);
517
571
  };
518
572
  process.on('SIGINT', shutdown);
@@ -534,7 +588,7 @@ async function main() {
534
588
  });
535
589
  }
536
590
  main().catch((err) => {
537
- if (err instanceof auth_1.AuthCancelledError) {
591
+ if (err instanceof AuthCancelledError) {
538
592
  console.error(`\n${err.message}`);
539
593
  console.error('Run `npx @1presence/bridge` again when you are ready to sign in.');
540
594
  process.exit(0);
package/dist/outbox.js CHANGED
@@ -1,11 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writeSpool = writeSpool;
4
- exports.deleteSpool = deleteSpool;
5
- exports.listSpool = listSpool;
6
- const fs_1 = require("fs");
7
- const os_1 = require("os");
8
- const path_1 = require("path");
1
+ import { mkdirSync, writeFileSync, readdirSync, readFileSync, unlinkSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
9
4
  // ─── On-disk turn spool ───────────────────────────────────────────────────────
10
5
  //
11
6
  // Each in-flight bridge turn writes a record to ~/.1presence/outbox/. The file
@@ -20,31 +15,31 @@ const path_1 = require("path");
20
15
  //
21
16
  // Payload mode 0600 — the file contains the user's assistant transcript and
22
17
  // tool inputs. Tightened on every write to handle legacy world-readable files.
23
- const OUTBOX_DIR = (0, path_1.join)((0, os_1.homedir)(), '.1presence', 'outbox');
18
+ const OUTBOX_DIR = join(homedir(), '.1presence', 'outbox');
24
19
  function ensureDir() {
25
- (0, fs_1.mkdirSync)(OUTBOX_DIR, { recursive: true });
20
+ mkdirSync(OUTBOX_DIR, { recursive: true });
26
21
  }
27
22
  function pathFor(conversationId) {
28
- return (0, path_1.join)(OUTBOX_DIR, `${conversationId}.json`);
23
+ return join(OUTBOX_DIR, `${conversationId}.json`);
29
24
  }
30
- function writeSpool(record) {
25
+ export function writeSpool(record) {
31
26
  ensureDir();
32
- (0, fs_1.writeFileSync)(pathFor(record.conversationId), JSON.stringify(record), { mode: 0o600 });
27
+ writeFileSync(pathFor(record.conversationId), JSON.stringify(record), { mode: 0o600 });
33
28
  }
34
- function deleteSpool(conversationId) {
29
+ export function deleteSpool(conversationId) {
35
30
  try {
36
- (0, fs_1.unlinkSync)(pathFor(conversationId));
31
+ unlinkSync(pathFor(conversationId));
37
32
  }
38
33
  catch { /* already gone — fine */ }
39
34
  }
40
- function listSpool() {
35
+ export function listSpool() {
41
36
  ensureDir();
42
37
  const out = [];
43
- for (const file of (0, fs_1.readdirSync)(OUTBOX_DIR)) {
38
+ for (const file of readdirSync(OUTBOX_DIR)) {
44
39
  if (!file.endsWith('.json'))
45
40
  continue;
46
41
  try {
47
- const raw = (0, fs_1.readFileSync)((0, path_1.join)(OUTBOX_DIR, file), 'utf-8');
42
+ const raw = readFileSync(join(OUTBOX_DIR, file), 'utf-8');
48
43
  out.push(JSON.parse(raw));
49
44
  }
50
45
  catch {
package/dist/timer.js CHANGED
@@ -1,12 +1,7 @@
1
- "use strict";
2
1
  // Live elapsed-time indicator for the active turn. Writes `\r\x1b[K⏱ Xs`
3
2
  // once per second; wraps console.log/error/warn so that any other output
4
3
  // clears the timer line before printing, then redraws the timer on the new
5
4
  // bottom line. Idempotent — stop is safe to call multiple times.
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.formatElapsed = formatElapsed;
8
- exports.startTurnTimer = startTurnTimer;
9
- exports.stopTurnTimer = stopTurnTimer;
10
5
  let intervalId = null;
11
6
  let startedAt = 0;
12
7
  let originalLog = null;
@@ -19,14 +14,14 @@ function draw() {
19
14
  const elapsedSec = Math.floor((Date.now() - startedAt) / 1000);
20
15
  process.stdout.write(`\r\x1b[K⏱ ${formatElapsed(elapsedSec)}`);
21
16
  }
22
- function formatElapsed(seconds) {
17
+ export function formatElapsed(seconds) {
23
18
  if (seconds < 60)
24
19
  return `${seconds}s`;
25
20
  const m = Math.floor(seconds / 60);
26
21
  const s = seconds % 60;
27
22
  return `${m}m ${s.toString().padStart(2, '0')}s`;
28
23
  }
29
- function startTurnTimer() {
24
+ export function startTurnTimer() {
30
25
  if (intervalId !== null)
31
26
  return;
32
27
  startedAt = Date.now();
@@ -39,7 +34,7 @@ function startTurnTimer() {
39
34
  draw();
40
35
  intervalId = setInterval(draw, 1000);
41
36
  }
42
- function stopTurnTimer() {
37
+ export function stopTurnTimer() {
43
38
  if (intervalId === null)
44
39
  return 0;
45
40
  clearInterval(intervalId);
package/dist/update.js CHANGED
@@ -1,8 +1,8 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.checkAndUpdate = checkAndUpdate;
4
- const child_process_1 = require("child_process");
5
- const package_json_1 = require("../package.json");
1
+ import { spawn } from 'child_process';
2
+ import { createRequire } from 'module';
3
+ // ESM JSON imports need version-sensitive import assertions; createRequire reads
4
+ // the manifest synchronously on every Node 18+ without that fragility.
5
+ const { version } = createRequire(import.meta.url)('../package.json');
6
6
  function isNewer(a, b) {
7
7
  const pa = a.split('.').map(Number);
8
8
  const pb = b.split('.').map(Number);
@@ -14,7 +14,7 @@ function isNewer(a, b) {
14
14
  }
15
15
  return false;
16
16
  }
17
- async function checkAndUpdate() {
17
+ export async function checkAndUpdate() {
18
18
  try {
19
19
  const res = await fetch('https://registry.npmjs.org/@1presence/bridge/latest', {
20
20
  signal: AbortSignal.timeout(3000),
@@ -23,10 +23,10 @@ async function checkAndUpdate() {
23
23
  return false;
24
24
  const data = await res.json();
25
25
  const latest = data.version;
26
- if (!isNewer(latest, package_json_1.version))
26
+ if (!isNewer(latest, version))
27
27
  return false;
28
28
  console.log(`Updating to v${latest}…\n`);
29
- const child = (0, child_process_1.spawn)('npx', ['--yes', `@1presence/bridge@${latest}`], {
29
+ const child = spawn('npx', ['--yes', `@1presence/bridge@${latest}`], {
30
30
  stdio: 'inherit',
31
31
  env: process.env,
32
32
  });
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@1presence/bridge",
3
- "version": "0.39.0",
3
+ "version": "0.42.0",
4
4
  "description": "Run 1Presence on your Mac and use your Claude.ai Pro subscription from any device",
5
+ "type": "module",
5
6
  "bin": {
6
7
  "1presence-bridge": "dist/index.js"
7
8
  },
@@ -18,6 +19,7 @@
18
19
  "start": "node dist/index.js"
19
20
  },
20
21
  "dependencies": {
22
+ "@anthropic-ai/claude-agent-sdk": "^0.3.153",
21
23
  "ws": "^8.20.0"
22
24
  },
23
25
  "devDependencies": {