@1presence/bridge 0.1.11 → 0.1.13

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/auth.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureFreshToken = ensureFreshToken;
3
4
  exports.getValidAuth = getValidAuth;
4
5
  exports.refreshAuth = refreshAuth;
5
6
  exports.clearAuth = clearAuth;
@@ -45,7 +46,7 @@ function loadCachedAuth() {
45
46
  const data = JSON.parse(raw);
46
47
  if (!data.token || !isTokenValid(data.token))
47
48
  return null;
48
- return { token: data.token, uid: data.uid, email: data.email };
49
+ return { token: data.token, uid: data.uid, email: data.email, refreshToken: data.refreshToken };
49
50
  }
50
51
  catch {
51
52
  return null;
@@ -88,7 +89,7 @@ function runBrowserAuthFlow(gatewayUrl, pwaUrl) {
88
89
  req.on('data', (chunk) => { body += chunk.toString(); });
89
90
  req.on('end', () => {
90
91
  try {
91
- const { token } = JSON.parse(body);
92
+ const { token, refreshToken } = JSON.parse(body);
92
93
  if (!token) {
93
94
  res.writeHead(400);
94
95
  res.end();
@@ -103,7 +104,7 @@ function runBrowserAuthFlow(gatewayUrl, pwaUrl) {
103
104
  reject(new Error('Invalid token — missing uid'));
104
105
  return;
105
106
  }
106
- resolve({ token, uid, email });
107
+ resolve({ token, uid, email, refreshToken: refreshToken ?? undefined });
107
108
  }
108
109
  catch (err) {
109
110
  res.writeHead(400);
@@ -125,6 +126,35 @@ function runBrowserAuthFlow(gatewayUrl, pwaUrl) {
125
126
  }, 5 * 60 * 1000);
126
127
  });
127
128
  }
129
+ // ─── Token refresh ────────────────────────────────────────────────────────────
130
+ // Firebase web API key — public, safe to embed
131
+ const FIREBASE_API_KEY = 'AIzaSyAz16A3eRIMhdLGF9ptsVsWZx9LjKeZwi8';
132
+ async function refreshIdToken(refreshToken) {
133
+ const res = await fetch(`https://securetoken.googleapis.com/v1/token?key=${FIREBASE_API_KEY}`, {
134
+ method: 'POST',
135
+ headers: { 'Content-Type': 'application/json' },
136
+ body: JSON.stringify({ grant_type: 'refresh_token', refresh_token: refreshToken }),
137
+ });
138
+ if (!res.ok)
139
+ throw new Error(`Token refresh failed: ${res.status}`);
140
+ const data = await res.json();
141
+ if (!data.id_token)
142
+ throw new Error('Token refresh returned no id_token');
143
+ return data.id_token;
144
+ }
145
+ /** Returns auth with a fresh ID token. Refreshes if <10 minutes remain. */
146
+ async function ensureFreshToken(auth) {
147
+ const { exp } = parseJwt(auth.token);
148
+ const tenMinutes = 10 * 60;
149
+ if (exp && exp > Math.floor(Date.now() / 1000) + tenMinutes)
150
+ return auth;
151
+ if (!auth.refreshToken)
152
+ return auth; // no refresh token, use as-is
153
+ const newToken = await refreshIdToken(auth.refreshToken);
154
+ const updated = { ...auth, token: newToken };
155
+ saveCachedAuth(updated);
156
+ return updated;
157
+ }
128
158
  // ─── Public API ───────────────────────────────────────────────────────────────
129
159
  async function getValidAuth(gatewayUrl, pwaUrl) {
130
160
  const cached = loadCachedAuth();
package/dist/claude.js CHANGED
@@ -21,12 +21,16 @@ function spawnClaude(params) {
21
21
  '--allowedTools', 'mcp__1presence__*',
22
22
  '--system-prompt', systemPromptPath,
23
23
  '--mcp-config', mcpConfigPath,
24
+ '--strict-mcp-config',
24
25
  ];
25
26
  if (claudeSessionId) {
26
27
  args.push('--resume', claudeSessionId);
27
28
  }
29
+ // Strip API key so Claude Code uses the user's claude.ai Pro subscription
30
+ // (OAuth credentials), not an API key that would bill to a separate account.
31
+ const { ANTHROPIC_API_KEY: _stripped, ...safeEnv } = process.env;
28
32
  const proc = (0, child_process_1.spawn)('claude', args, {
29
- env: { ...process.env },
33
+ env: safeEnv,
30
34
  stdio: ['ignore', 'pipe', 'pipe'],
31
35
  });
32
36
  active.set(conversationId, proc);
@@ -57,6 +61,10 @@ function spawnClaude(params) {
57
61
  if (sid && presenceSessionId) {
58
62
  (0, sessions_1.saveClaudeSession)(presenceSessionId, sid);
59
63
  }
64
+ const keySource = event['apiKeySource'];
65
+ const model = event['model'];
66
+ const authLabel = keySource === 'oauth' || !keySource ? 'claude.ai plan' : `api key`;
67
+ process.stderr.write(`[bridge] model: ${model ?? 'unknown'} auth: ${authLabel}\n`);
60
68
  sessionIdExtracted = true;
61
69
  }
62
70
  // Count complete assistant turns + accumulate token usage
package/dist/index.js CHANGED
@@ -57,6 +57,56 @@ async function writeSetupFiles(auth) {
57
57
  };
58
58
  (0, fs_1.writeFileSync)(tmpFile(`mcp-${uid}.json`), JSON.stringify(mcpConfig, null, 2), 'utf-8');
59
59
  }
60
+ // ─── Handle a single incoming message (token refresh + spawn) ─────────────────
61
+ async function handleMessage(conversationId, text, sessionId, ws, auth) {
62
+ // Refresh JWT if <10 min remaining before spawning Claude
63
+ let activeAuth = auth;
64
+ try {
65
+ const freshAuth = await (0, auth_1.ensureFreshToken)(auth);
66
+ if (freshAuth.token !== auth.token) {
67
+ currentAuth = freshAuth;
68
+ activeAuth = freshAuth;
69
+ await writeSetupFiles(freshAuth);
70
+ }
71
+ }
72
+ catch (err) {
73
+ console.warn(`[bridge] token refresh failed: ${err.message}`);
74
+ }
75
+ let responding = false;
76
+ (0, claude_1.spawnClaude)({
77
+ conversationId,
78
+ presenceSessionId: sessionId,
79
+ text,
80
+ uid: activeAuth.uid,
81
+ onEvent: (event) => {
82
+ if (!responding && event['type'] === 'assistant') {
83
+ responding = true;
84
+ console.log(`[${new Date().toLocaleTimeString()}] ◐ responding…`);
85
+ }
86
+ if (ws.readyState === ws_1.default.OPEN) {
87
+ ws.send(JSON.stringify({ type: 'stream', conversationId, event }));
88
+ }
89
+ },
90
+ onDone: (messageCount, costUsd, usage) => {
91
+ const parts = [];
92
+ if (usage)
93
+ parts.push(`in:${usage.input_tokens} out:${usage.output_tokens}`);
94
+ const costStr = costUsd === 0 ? '$0.0000 (plan usage)' : `$${costUsd.toFixed(4)}`;
95
+ parts.push(costStr);
96
+ const suffix = parts.length ? ` ${parts.join(' ')}` : '';
97
+ console.log(`[${new Date().toLocaleTimeString()}] ✓ done${suffix}`);
98
+ if (ws.readyState === ws_1.default.OPEN) {
99
+ ws.send(JSON.stringify({ type: 'done', conversationId, messageCount, costUsd }));
100
+ }
101
+ },
102
+ onError: (message) => {
103
+ console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message}`);
104
+ if (ws.readyState === ws_1.default.OPEN) {
105
+ ws.send(JSON.stringify({ type: 'error', conversationId, message }));
106
+ }
107
+ },
108
+ });
109
+ }
60
110
  // ─── WebSocket connection ─────────────────────────────────────────────────────
61
111
  function connect(auth, retryDelay = 1000) {
62
112
  const ws = new ws_1.default(GATEWAY_WS, {
@@ -79,42 +129,11 @@ function connect(auth, retryDelay = 1000) {
79
129
  const ts = new Date().toLocaleTimeString();
80
130
  const preview = text.length > 80 ? text.slice(0, 80) + '…' : text;
81
131
  console.log(`[${ts}] ▶ ${preview}`);
82
- let responding = false;
83
- (0, claude_1.spawnClaude)({
84
- conversationId,
85
- presenceSessionId: sessionId ?? null,
86
- text,
87
- uid: auth.uid,
88
- onEvent: (event) => {
89
- if (!responding && event['type'] === 'assistant') {
90
- responding = true;
91
- console.log(`[${new Date().toLocaleTimeString()}] ◐ responding…`);
92
- }
93
- if (ws.readyState === ws_1.default.OPEN) {
94
- ws.send(JSON.stringify({ type: 'stream', conversationId, event }));
95
- }
96
- },
97
- onDone: (messageCount, costUsd, usage) => {
98
- const parts = [];
99
- if (usage)
100
- parts.push(`in:${usage.input_tokens} out:${usage.output_tokens}`);
101
- const costStr = costUsd === 0 ? '$0.0000 (plan usage)' : `$${costUsd.toFixed(4)}`;
102
- parts.push(costStr);
103
- const suffix = parts.length ? ` ${parts.join(' ')}` : '';
104
- console.log(`[${new Date().toLocaleTimeString()}] ✓ done${suffix}`);
105
- if (ws.readyState === ws_1.default.OPEN) {
106
- ws.send(JSON.stringify({ type: 'done', conversationId, messageCount, costUsd }));
107
- }
108
- },
109
- onError: (message) => {
110
- console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message}`);
111
- if (ws.readyState === ws_1.default.OPEN) {
112
- ws.send(JSON.stringify({ type: 'error', conversationId, message }));
113
- }
114
- },
132
+ handleMessage(conversationId, text, sessionId ?? null, ws, auth).catch((err) => {
133
+ console.error(`[bridge] handleMessage error: ${err.message}`);
115
134
  });
116
135
  });
117
- ws.on('close', (code, reason) => {
136
+ ws.on('close', (code) => {
118
137
  if (code === 4001) {
119
138
  console.error('Authentication failed — clearing cached credentials. Please restart the bridge.');
120
139
  (0, auth_1.clearAuth)();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1presence/bridge",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Run 1Presence on your Mac and use your Claude.ai Pro subscription from any device",
5
5
  "bin": {
6
6
  "1presence-bridge": "dist/index.js"