@1presence/bridge 0.2.0 → 0.3.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/auth.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuthCancelledError = void 0;
3
4
  exports.isTokenValid = isTokenValid;
4
5
  exports.ensureFreshToken = ensureFreshToken;
5
6
  exports.getValidAuth = getValidAuth;
@@ -69,8 +70,13 @@ function openBrowser(url) {
69
70
  console.error('Could not open browser automatically. Please open this URL manually:\n' + url);
70
71
  });
71
72
  }
73
+ class AuthCancelledError extends Error {
74
+ constructor() { super('Sign-in cancelled — the browser tab was closed.'); }
75
+ }
76
+ exports.AuthCancelledError = AuthCancelledError;
72
77
  function runBrowserAuthFlow(gatewayUrl, pwaUrl) {
73
78
  return new Promise((resolve, reject) => {
79
+ let resolved = false;
74
80
  const server = (0, http_1.createServer)((req, res) => {
75
81
  // CORS headers so the PWA (https) can POST to http://localhost
76
82
  res.setHeader('Access-Control-Allow-Origin', '*');
@@ -86,6 +92,21 @@ function runBrowserAuthFlow(gatewayUrl, pwaUrl) {
86
92
  res.end();
87
93
  return;
88
94
  }
95
+ // Status beacon from the PWA — currently used so we exit early when
96
+ // the user closes the auth tab before signing in (sendBeacon path).
97
+ const path = (req.url ?? '/').split('?')[0];
98
+ if (path === '/status') {
99
+ const params = new URL(req.url ?? '/', 'http://localhost').searchParams;
100
+ const event = params.get('event');
101
+ res.writeHead(204);
102
+ res.end();
103
+ if (event === 'closed' && !resolved) {
104
+ resolved = true;
105
+ server.close();
106
+ reject(new AuthCancelledError());
107
+ }
108
+ return;
109
+ }
89
110
  let body = '';
90
111
  req.on('data', (chunk) => { body += chunk.toString(); });
91
112
  req.on('end', () => {
@@ -98,6 +119,7 @@ function runBrowserAuthFlow(gatewayUrl, pwaUrl) {
98
119
  }
99
120
  res.writeHead(200, { 'Content-Type': 'application/json' });
100
121
  res.end(JSON.stringify({ ok: true }));
122
+ resolved = true;
101
123
  server.close();
102
124
  const uid = uidFromToken(token);
103
125
  const email = emailFromToken(token);
@@ -122,6 +144,9 @@ function runBrowserAuthFlow(gatewayUrl, pwaUrl) {
122
144
  openBrowser(authUrl);
123
145
  });
124
146
  setTimeout(() => {
147
+ if (resolved)
148
+ return;
149
+ resolved = true;
125
150
  server.close();
126
151
  reject(new Error('Sign-in timed out after 5 minutes. Please try again.'));
127
152
  }, 5 * 60 * 1000);
package/dist/claude.js CHANGED
@@ -166,6 +166,7 @@ function spawnClaude(params) {
166
166
  let usage = null;
167
167
  let extractedModel = null;
168
168
  let buffer = '';
169
+ let killedForViolation = false;
169
170
  proc.stdout.on('data', (chunk) => {
170
171
  buffer += chunk.toString('utf-8');
171
172
  const lines = buffer.split('\n');
@@ -231,6 +232,19 @@ function spawnClaude(params) {
231
232
  const toolName = block['name'];
232
233
  const prefix = toolName.startsWith('mcp__') ? '[mcp]' : '[tool]';
233
234
  process.stderr.write(`[bridge] ${prefix} ${toolName}\n`);
235
+ // Defense-in-depth: CLI flags (--tools "", --allowedTools, --strict-mcp-config,
236
+ // --setting-sources "") are supposed to make this unreachable. If we see a
237
+ // non-1Presence tool here anyway, something has bypassed those guards — kill
238
+ // immediately so any side effect already in flight is the only damage done.
239
+ if (!toolName.startsWith('mcp__1presence__')) {
240
+ killedForViolation = true;
241
+ const violation = `bridge tool violation: ${toolName} is not allowed in Local Mode`;
242
+ process.stderr.write(`[bridge] FATAL ${violation} — killing\n`);
243
+ active.delete(conversationId);
244
+ proc.kill('SIGKILL');
245
+ onError(violation, usage, extractedModel);
246
+ return;
247
+ }
234
248
  }
235
249
  else if (block['type'] === 'text') {
236
250
  const text = block['text'];
@@ -261,6 +275,9 @@ function spawnClaude(params) {
261
275
  });
262
276
  proc.on('close', (code) => {
263
277
  active.delete(conversationId);
278
+ // Violation path already called onError + killed — don't double-fire.
279
+ if (killedForViolation)
280
+ return;
264
281
  // Flush any remaining buffer
265
282
  if (buffer.trim()) {
266
283
  try {
package/dist/index.js CHANGED
@@ -290,6 +290,11 @@ async function main() {
290
290
  process.on('SIGTERM', shutdown);
291
291
  }
292
292
  main().catch((err) => {
293
+ if (err instanceof auth_1.AuthCancelledError) {
294
+ console.error(`\n${err.message}`);
295
+ console.error('Run `npx @1presence/bridge` again when you are ready to sign in.');
296
+ process.exit(0);
297
+ }
293
298
  console.error('Fatal:', err.message);
294
299
  process.exit(1);
295
300
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1presence/bridge",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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"