promethios-bridge 1.7.6 → 1.7.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promethios-bridge",
3
- "version": "1.7.6",
3
+ "version": "1.7.7",
4
4
  "description": "Run Promethios agent frameworks locally on your computer with full file, terminal, browser access, ambient context capture, and the always-on-top floating chat overlay. Native Framework Mode supports OpenClaw and other frameworks via the bridge.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -49,8 +49,7 @@
49
49
  "express": "^4.18.2",
50
50
  "open": "^8.4.2",
51
51
  "ora": "^5.4.1",
52
- "node-fetch": "^2.7.0",
53
- "playwright": "^1.42.0"
52
+ "node-fetch": "^2.7.0"
54
53
  },
55
54
  "optionalDependencies": {
56
55
  "playwright": "^1.42.0",
package/src/bridge.js CHANGED
@@ -72,6 +72,52 @@ async function checkForUpdates(currentVersion, log) {
72
72
  }
73
73
  }
74
74
 
75
+ /**
76
+ * Open a URL in the user's default browser.
77
+ * Uses platform-native commands for maximum reliability:
78
+ * Windows: cmd /c start "" "<url>"
79
+ * macOS: open <url>
80
+ * Linux: xdg-open <url>
81
+ * Falls back to the `open` npm package if the native command fails.
82
+ * Returns true if the browser was successfully opened, false otherwise.
83
+ */
84
+ async function openInBrowser(url, log) {
85
+ const { exec } = require('child_process');
86
+ const platform = process.platform;
87
+
88
+ return new Promise((resolve) => {
89
+ let cmd;
90
+ if (platform === 'win32') {
91
+ // On Windows, `start` requires an empty title arg before the URL
92
+ // to prevent issues with URLs containing special characters.
93
+ cmd = `cmd /c start "" "${url}"`;
94
+ } else if (platform === 'darwin') {
95
+ cmd = `open "${url}"`;
96
+ } else {
97
+ cmd = `xdg-open "${url}"`;
98
+ }
99
+
100
+ exec(cmd, { timeout: 5000 }, (err) => {
101
+ if (!err) {
102
+ resolve(true);
103
+ return;
104
+ }
105
+ // Native command failed — try the `open` npm package as fallback
106
+ log('Native browser open failed, trying open package:', err.message);
107
+ try {
108
+ const openModule = require('open');
109
+ openModule(url).then(() => resolve(true)).catch((e) => {
110
+ log('open package also failed:', e.message);
111
+ resolve(false);
112
+ });
113
+ } catch (e) {
114
+ log('open package not available:', e.message);
115
+ resolve(false);
116
+ }
117
+ });
118
+ });
119
+ }
120
+
75
121
  async function startBridge({ setupToken, apiBase, port, dev }) {
76
122
  const log = dev
77
123
  ? (...args) => console.log(chalk.gray(' [debug]'), ...args)
@@ -366,17 +412,15 @@ async function startBridge({ setupToken, apiBase, port, dev }) {
366
412
  // Electron not available — open the lightweight browser overlay instead.
367
413
  // This is a small chat UI served by the bridge's own Express server.
368
414
  const overlayUrl = `http://127.0.0.1:${port}/overlay`;
369
- try {
370
- const openModule = require('open');
371
- await openModule(overlayUrl);
415
+ const openedInBrowser = await openInBrowser(overlayUrl, log);
416
+ if (openedInBrowser) {
372
417
  console.log(chalk.cyan(' ⬡ Promethios overlay opened in your browser'));
373
418
  console.log(chalk.gray(` URL: ${overlayUrl}`));
374
- console.log('');
375
- } catch (err) {
376
- log('Browser overlay launch failed (non-critical):', err.message);
377
- console.log(chalk.gray(` ℹ Open ${overlayUrl} in your browser for the chat overlay`));
378
- console.log('');
419
+ } else {
420
+ console.log(chalk.gray(` ℹ Open this URL in your browser for the chat overlay:`));
421
+ console.log(chalk.cyan(` ${overlayUrl}`));
379
422
  }
423
+ console.log('');
380
424
  }
381
425
 
382
426
  // Heartbeat loop
@@ -2,56 +2,74 @@
2
2
  * launcher.js
3
3
  *
4
4
  * Called by the bridge CLI after successful authentication.
5
- * Spawns the Electron overlay window as a detached child process.
6
- * Bundled inside promethios-bridge no separate install required.
5
+ * Attempts to launch the Electron overlay window. Returns null if Electron
6
+ * is not available (which is the case when running via `npx`, since optional
7
+ * dependencies are not installed in the npx cache).
7
8
  *
8
- * Usage (from bridge CLI):
9
- * const { launchOverlay } = require('./overlay/launcher');
10
- * launchOverlay({ authToken, apiBase, threadId, dev });
9
+ * IMPORTANT: We check for the actual Electron binary on disk, NOT just whether
10
+ * the `electron` npm package is present. When running via `npx`, the electron
11
+ * package IS downloaded (it's a listed optionalDependency), but its postinstall
12
+ * script downloads the real binary separately — that binary may not exist yet.
13
+ * Using require.resolve('electron') would return true even without the binary,
14
+ * causing spawn() to fail silently and the browser fallback to never be reached.
11
15
  */
12
16
 
17
+ 'use strict';
18
+
13
19
  const { spawn } = require('child_process');
14
20
  const path = require('path');
15
21
  const fs = require('fs');
16
22
 
17
23
  /**
18
- * Launch the Promethios overlay Electron window.
19
- * Returns the child process (or null if Electron is not available).
20
- *
21
- * IMPORTANT: When running via `npx`, optional dependencies (including electron)
22
- * are NOT installed in the npx cache. The spawn() call may therefore fail with
23
- * ENOENT. We attach an 'error' handler before unref() to silently absorb this
24
- * instead of crashing the bridge process with an unhandled error event.
24
+ * Find the actual Electron binary on disk.
25
+ * Returns the path string if found and executable, or null if not available.
25
26
  */
26
- function launchOverlay({ authToken, apiBase = 'https://api.promethios.ai', threadId = '', dev = false } = {}) {
27
- // Find electron binary try local node_modules first, then global
28
- let electronBin = null;
27
+ function findElectronBinary() {
28
+ const isWin = process.platform === 'win32';
29
29
 
30
+ // Candidate paths for the real Electron binary (not just the npm package stub)
30
31
  const candidates = [
31
- // Installed inside promethios-bridge's own node_modules (postinstall puts it here)
32
- path.join(__dirname, '..', '..', 'node_modules', '.bin', 'electron'),
33
- path.join(__dirname, '..', '..', 'node_modules', '.bin', 'electron.cmd'),
34
- // Global electron fallback
35
- 'electron',
32
+ // 1. Installed inside promethios-bridge's own node_modules (full npm install, not npx)
33
+ path.join(__dirname, '..', '..', 'node_modules', 'electron', 'dist',
34
+ isWin ? 'electron.exe' : process.platform === 'darwin' ? 'Electron.app/Contents/MacOS/Electron' : 'electron'),
35
+ // 2. .bin shim (only works if the real binary was downloaded by postinstall)
36
+ path.join(__dirname, '..', '..', 'node_modules', '.bin', isWin ? 'electron.cmd' : 'electron'),
37
+ // 3. Global electron install
38
+ path.join(require('os').homedir(), '.npm', '_npx', '**', 'node_modules', 'electron', 'dist', isWin ? 'electron.exe' : 'electron'),
36
39
  ];
37
40
 
38
41
  for (const candidate of candidates) {
42
+ // Skip glob-style paths (we can't expand them here)
43
+ if (candidate.includes('**')) continue;
39
44
  try {
40
- if (candidate === 'electron') {
41
- // Try to resolve globally — throws if not installed
42
- require.resolve('electron');
43
- electronBin = candidate;
44
- break;
45
- }
46
45
  if (fs.existsSync(candidate)) {
47
- electronBin = candidate;
48
- break;
46
+ // Extra check: the .bin shim always exists even without the real binary.
47
+ // For the .bin shim, also verify the actual dist binary exists.
48
+ if (candidate.includes('.bin')) {
49
+ const distBin = path.join(
50
+ path.dirname(candidate), '..', 'electron', 'dist',
51
+ isWin ? 'electron.exe' : process.platform === 'darwin'
52
+ ? 'Electron.app/Contents/MacOS/Electron' : 'electron'
53
+ );
54
+ if (!fs.existsSync(distBin)) continue;
55
+ }
56
+ return candidate;
49
57
  }
50
- } catch { /* not found */ }
58
+ } catch { /* ignore */ }
51
59
  }
52
60
 
61
+ return null;
62
+ }
63
+
64
+ /**
65
+ * Launch the Promethios overlay Electron window.
66
+ * Returns the child process if Electron was found and launched, or null otherwise.
67
+ */
68
+ function launchOverlay({ authToken, apiBase = 'https://api.promethios.ai', threadId = '', dev = false } = {}) {
69
+ const electronBin = findElectronBinary();
70
+
53
71
  if (!electronBin) {
54
- if (dev) console.log('[overlay] Electron not found — overlay will not launch.');
72
+ if (dev) console.log('[overlay] Electron binary not found on disk skipping Electron overlay.');
55
73
  return null;
56
74
  }
57
75
 
@@ -70,16 +88,13 @@ function launchOverlay({ authToken, apiBase = 'https://api.promethios.ai', threa
70
88
  });
71
89
 
72
90
  // Attach error handler BEFORE unref() to prevent unhandled 'error' event crashes.
73
- // When running via npx, optional deps like electron are not installed, so spawn
74
- // may emit ENOENT. We catch it silently — the bridge works fine without the overlay.
75
91
  child.on('error', (err) => {
76
92
  if (dev) console.log(`[overlay] Spawn error (non-critical): ${err.message}`);
77
- // No-op: overlay is optional, bridge continues running normally
78
93
  });
79
94
 
80
- child.unref(); // Don't keep bridge CLI alive waiting for overlay
95
+ child.unref();
81
96
 
82
- if (dev) console.log(`[overlay] Launched (pid ${child.pid})`);
97
+ if (dev) console.log(`[overlay] Launched Electron (pid ${child.pid})`);
83
98
 
84
99
  return child;
85
100
  }