promethios-bridge 1.7.3 → 1.7.4

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.3",
3
+ "version": "1.7.4",
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": {
package/src/bridge.js CHANGED
@@ -189,6 +189,126 @@ async function startBridge({ setupToken, apiBase, port, dev }) {
189
189
  res.sendStatus(204);
190
190
  });
191
191
 
192
+ // ── Overlay route: serves the floating chat UI in the default browser ────
193
+ // This is the fallback when Electron is not available (e.g. running via npx).
194
+ // The overlay HTML is stored inline here so no file path resolution is needed.
195
+ let overlayAuthToken = null; // set after authentication
196
+ app.get('/overlay', (req, res) => {
197
+ const token = overlayAuthToken || '';
198
+ const apiBase_ = apiBase;
199
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
200
+ res.send(`<!DOCTYPE html>
201
+ <html lang="en">
202
+ <head>
203
+ <meta charset="UTF-8">
204
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
205
+ <title>Promethios</title>
206
+ <style>
207
+ * { box-sizing: border-box; margin: 0; padding: 0; }
208
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
209
+ background: #0f0f11; color: #e4e4e7; height: 100vh; display: flex;
210
+ flex-direction: column; overflow: hidden; }
211
+ #header { background: #18181b; border-bottom: 1px solid #27272a;
212
+ padding: 10px 14px; display: flex; align-items: center; gap: 8px;
213
+ flex-shrink: 0; }
214
+ #header .dot { width: 8px; height: 8px; border-radius: 50%;
215
+ background: #22c55e; flex-shrink: 0; }
216
+ #header .title { font-size: 13px; font-weight: 600; color: #a1a1aa; }
217
+ #header .status { font-size: 11px; color: #22c55e; margin-left: auto; }
218
+ #messages { flex: 1; overflow-y: auto; padding: 12px; display: flex;
219
+ flex-direction: column; gap: 8px; }
220
+ .msg { max-width: 85%; padding: 8px 12px; border-radius: 12px;
221
+ font-size: 13px; line-height: 1.5; word-break: break-word; }
222
+ .msg.user { background: #3f3f46; align-self: flex-end; color: #e4e4e7; }
223
+ .msg.ai { background: #1e1e2e; border: 1px solid #27272a;
224
+ align-self: flex-start; color: #c4b5fd; }
225
+ .msg.system { background: transparent; border: none;
226
+ color: #52525b; font-size: 11px; align-self: center; font-style: italic; }
227
+ #input-row { padding: 10px 12px; background: #18181b;
228
+ border-top: 1px solid #27272a; display: flex; gap: 8px; flex-shrink: 0; }
229
+ #input { flex: 1; background: #27272a; border: 1px solid #3f3f46;
230
+ border-radius: 8px; padding: 8px 12px; color: #e4e4e7; font-size: 13px;
231
+ outline: none; resize: none; height: 36px; font-family: inherit; }
232
+ #input:focus { border-color: #6d28d9; }
233
+ #send { background: #6d28d9; color: white; border: none; border-radius: 8px;
234
+ padding: 0 14px; font-size: 13px; cursor: pointer; height: 36px;
235
+ flex-shrink: 0; }
236
+ #send:hover { background: #7c3aed; }
237
+ #send:disabled { background: #3f3f46; cursor: not-allowed; }
238
+ ::-webkit-scrollbar { width: 4px; }
239
+ ::-webkit-scrollbar-track { background: transparent; }
240
+ ::-webkit-scrollbar-thumb { background: #3f3f46; border-radius: 2px; }
241
+ </style>
242
+ </head>
243
+ <body>
244
+ <div id="header">
245
+ <div class="dot"></div>
246
+ <span class="title">Promethios</span>
247
+ <span class="status" id="status">Connected</span>
248
+ </div>
249
+ <div id="messages">
250
+ <div class="msg system">Bridge connected — ready to help</div>
251
+ </div>
252
+ <div id="input-row">
253
+ <textarea id="input" placeholder="Ask Promethios..." rows="1"></textarea>
254
+ <button id="send">Send</button>
255
+ </div>
256
+ <script>
257
+ const AUTH_TOKEN = '${token}';
258
+ const API_BASE = '${apiBase_}';
259
+ const messagesEl = document.getElementById('messages');
260
+ const inputEl = document.getElementById('input');
261
+ const sendBtn = document.getElementById('send');
262
+ const statusEl = document.getElementById('status');
263
+
264
+ function addMsg(role, text) {
265
+ const d = document.createElement('div');
266
+ d.className = 'msg ' + role;
267
+ d.textContent = text;
268
+ messagesEl.appendChild(d);
269
+ messagesEl.scrollTop = messagesEl.scrollHeight;
270
+ }
271
+
272
+ async function sendMessage() {
273
+ const text = inputEl.value.trim();
274
+ if (!text) return;
275
+ inputEl.value = '';
276
+ addMsg('user', text);
277
+ sendBtn.disabled = true;
278
+ statusEl.textContent = 'Thinking...';
279
+ try {
280
+ const res = await fetch(API_BASE + '/api/chat/quick', {
281
+ method: 'POST',
282
+ headers: { 'Content-Type': 'application/json',
283
+ 'Authorization': 'Bearer ' + AUTH_TOKEN },
284
+ body: JSON.stringify({ message: text, source: 'overlay' }),
285
+ });
286
+ if (res.ok) {
287
+ const data = await res.json();
288
+ addMsg('ai', data.reply || data.message || JSON.stringify(data));
289
+ } else {
290
+ addMsg('system', 'Error: ' + res.status);
291
+ }
292
+ } catch (e) {
293
+ addMsg('system', 'Could not reach Promethios: ' + e.message);
294
+ }
295
+ sendBtn.disabled = false;
296
+ statusEl.textContent = 'Connected';
297
+ }
298
+
299
+ sendBtn.addEventListener('click', sendMessage);
300
+ inputEl.addEventListener('keydown', e => {
301
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
302
+ });
303
+ inputEl.addEventListener('input', () => {
304
+ inputEl.style.height = 'auto';
305
+ inputEl.style.height = Math.min(inputEl.scrollHeight, 100) + 'px';
306
+ });
307
+ <\/script>
308
+ </body>
309
+ </html>`);
310
+ });
311
+
192
312
  await new Promise((resolve, reject) => {
193
313
  const server = app.listen(port, '127.0.0.1', () => resolve(server));
194
314
  server.on('error', reject);
@@ -221,24 +341,41 @@ async function startBridge({ setupToken, apiBase, port, dev }) {
221
341
  console.log(chalk.gray(' Press Ctrl+C to disconnect.'));
222
342
  console.log('');
223
343
 
224
- // ── Step 4c: Launch Electron overlay window (if available) ──────────────────
225
- // The overlay is an always-on-top floating chat pill that works across all
226
- // apps on Windows, Mac, and Linux. It auto-launches alongside the bridge.
227
- // Users can toggle watching on/off from the pill without restarting the bridge.
344
+ // ── Step 4c: Launch overlay ──────────────────────────────────────────────
345
+ // Set the auth token so the /overlay route can embed it in the HTML.
346
+ overlayAuthToken = authToken;
347
+
348
+ // Try Electron first (available when installed globally or via postinstall).
349
+ // Fall back to opening the browser-based overlay at http://localhost:<port>/overlay.
350
+ let overlayLaunched = false;
228
351
  if (launchOverlay) {
229
352
  try {
230
353
  const overlayChild = launchOverlay({ authToken, apiBase, dev });
231
354
  if (overlayChild) {
232
- // Give the child process a moment to start — if it fails immediately
233
- // (e.g. ENOENT because electron is not installed), the error handler
234
- // in launcher.js will silently absorb it. We print the success message
235
- // optimistically but it's non-critical if the overlay doesn't appear.
355
+ overlayLaunched = true;
236
356
  console.log(chalk.cyan(' ⬡ Promethios overlay launched — floating chat is ready'));
237
357
  console.log(chalk.gray(' Hotkey: Ctrl+Shift+P (Win/Linux) or Cmd+Shift+P (Mac)'));
238
358
  console.log('');
239
359
  }
240
360
  } catch (err) {
241
- log('Overlay launch failed (non-critical):', err.message);
361
+ log('Electron overlay launch failed (non-critical):', err.message);
362
+ }
363
+ }
364
+
365
+ if (!overlayLaunched) {
366
+ // Electron not available — open the lightweight browser overlay instead.
367
+ // This is a small chat UI served by the bridge's own Express server.
368
+ const overlayUrl = `http://127.0.0.1:${port}/overlay`;
369
+ try {
370
+ const openModule = require('open');
371
+ await openModule(overlayUrl);
372
+ console.log(chalk.cyan(' ⬡ Promethios overlay opened in your browser'));
373
+ 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('');
242
379
  }
243
380
  }
244
381
 
@@ -231,25 +231,36 @@ function extractUrlFromBrowserTitle(title, process) {
231
231
 
232
232
  // Try to get URL from Chrome/Edge via PowerShell UIAutomation (Windows only)
233
233
  // This reads the address bar text directly — works even for SPAs
234
+ // NOTE: Must use -File (temp script) not -Command to preserve multi-line PS syntax.
234
235
  if (proc.includes('chrome') || proc.includes('msedge')) {
235
236
  try {
236
- const psScript = `
237
- Add-Type -AssemblyName UIAutomationClient
238
- Add-Type -AssemblyName UIAutomationTypes
239
- $proc = Get-Process -Name "${proc.includes('msedge') ? 'msedge' : 'chrome'}" -ErrorAction SilentlyContinue | Select-Object -First 1
240
- if ($proc) {
241
- $root = [System.Windows.Automation.AutomationElement]::FromHandle($proc.MainWindowHandle)
242
- $cond = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::NameProperty, "Address and search bar")
243
- $bar = $root.FindFirst([System.Windows.Automation.TreeScope]::Descendants, $cond)
244
- if ($bar) {
245
- $vp = $bar.GetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern)
246
- Write-Output $vp.Current.Value
247
- }
248
- }`.trim();
237
+ const os = require('os');
238
+ const path = require('path');
239
+ const fs = require('fs');
240
+ const browserName = proc.includes('msedge') ? 'msedge' : 'chrome';
241
+ const tmpFile = path.join(os.tmpdir(), `promethios_url_${process.pid}.ps1`);
242
+ const psLines = [
243
+ 'Add-Type -AssemblyName UIAutomationClient',
244
+ 'Add-Type -AssemblyName UIAutomationTypes',
245
+ `$browserProc = Get-Process -Name "${browserName}" -ErrorAction SilentlyContinue | Select-Object -First 1`,
246
+ 'if ($browserProc) {',
247
+ ' $root = [System.Windows.Automation.AutomationElement]::FromHandle($browserProc.MainWindowHandle)',
248
+ ' $cond = New-Object System.Windows.Automation.PropertyCondition(',
249
+ ' [System.Windows.Automation.AutomationElement]::NameProperty,',
250
+ ' "Address and search bar")',
251
+ ' $bar = $root.FindFirst([System.Windows.Automation.TreeScope]::Descendants, $cond)',
252
+ ' if ($bar) {',
253
+ ' $vp = $bar.GetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern)',
254
+ ' Write-Output $vp.Current.Value',
255
+ ' }',
256
+ '}',
257
+ ];
258
+ fs.writeFileSync(tmpFile, psLines.join('\r\n'), 'utf8');
249
259
  const url = execSync(
250
- `powershell -NoProfile -NonInteractive -Command "${psScript.replace(/"/g, '\\"').replace(/\n/g, ' ')}"`,
260
+ `powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${tmpFile}"`,
251
261
  { encoding: 'utf8', timeout: 5000, windowsHide: true }
252
262
  ).trim();
263
+ try { fs.unlinkSync(tmpFile); } catch { /* cleanup best-effort */ }
253
264
  if (url && (url.startsWith('http') || url.startsWith('chrome'))) {
254
265
  return url;
255
266
  }