promethios-bridge 1.7.4 → 1.7.6
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 +1 -1
- package/src/contextCapture.js +296 -102
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "promethios-bridge",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.6",
|
|
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/contextCapture.js
CHANGED
|
@@ -3,12 +3,24 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Captures what the user is currently working on:
|
|
5
5
|
* - Active window title + process name
|
|
6
|
-
* - Active browser URL (
|
|
6
|
+
* - Active browser URL (all major browsers on all platforms)
|
|
7
7
|
* - Selected / clipboard text (last copied text)
|
|
8
|
-
* - Active form field content (via clipboard snapshot)
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Browser URL capture strategy (layered, best-effort):
|
|
10
|
+
*
|
|
11
|
+
* Windows:
|
|
12
|
+
* 1. UIAutomation address bar read (Chromium-based: Chrome, Edge, Brave, Opera, Arc)
|
|
13
|
+
* 2. Firefox: accessibility via PowerShell COM (MozillaAccessible) — best-effort
|
|
14
|
+
* 3. Title-based URL extraction (all browsers — works when URL is in title)
|
|
15
|
+
*
|
|
16
|
+
* macOS:
|
|
17
|
+
* 1. AppleScript (Chrome, Safari, Edge, Firefox, Brave, Opera, Arc, Vivaldi)
|
|
18
|
+
* 2. Title-based URL extraction fallback
|
|
19
|
+
*
|
|
20
|
+
* Linux:
|
|
21
|
+
* 1. xdotool + wmctrl for active window
|
|
22
|
+
* 2. xdg-open / qdbus for Chromium-based browsers (best-effort)
|
|
23
|
+
* 3. Title-based URL extraction fallback
|
|
12
24
|
*
|
|
13
25
|
* Platform support:
|
|
14
26
|
* - Windows: PowerShell Get-Process / UIAutomation / Get-Clipboard
|
|
@@ -16,7 +28,47 @@
|
|
|
16
28
|
* - Linux: xdotool / xclip (if available)
|
|
17
29
|
*/
|
|
18
30
|
|
|
19
|
-
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
const { execSync } = require('child_process');
|
|
34
|
+
const os = require('os');
|
|
35
|
+
const path = require('path');
|
|
36
|
+
const fs = require('fs');
|
|
37
|
+
|
|
38
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
39
|
+
// Browser process name maps
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
// Chromium-based browsers: UIAutomation works on all of them (Windows)
|
|
43
|
+
// Maps lowercase process name fragment → actual process name for Get-Process
|
|
44
|
+
const CHROMIUM_PROC_MAP = {
|
|
45
|
+
chrome: 'chrome',
|
|
46
|
+
msedge: 'msedge',
|
|
47
|
+
brave: 'brave',
|
|
48
|
+
opera: 'opera',
|
|
49
|
+
// Arc on Windows runs as "Arc"
|
|
50
|
+
arc: 'Arc',
|
|
51
|
+
// Vivaldi
|
|
52
|
+
vivaldi: 'vivaldi',
|
|
53
|
+
// Chromium itself
|
|
54
|
+
chromium: 'chromium',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// AppleScript app names for macOS (keyed by lowercase process name fragment)
|
|
58
|
+
const MAC_BROWSER_APPLESCRIPT = {
|
|
59
|
+
'google chrome': 'tell application "Google Chrome" to return URL of active tab of front window',
|
|
60
|
+
chrome: 'tell application "Google Chrome" to return URL of active tab of front window',
|
|
61
|
+
safari: 'tell application "Safari" to return URL of current tab of front window',
|
|
62
|
+
firefox: 'tell application "Firefox" to return URL of active tab of front window',
|
|
63
|
+
'microsoft edge': 'tell application "Microsoft Edge" to return URL of active tab of front window',
|
|
64
|
+
msedge: 'tell application "Microsoft Edge" to return URL of active tab of front window',
|
|
65
|
+
edge: 'tell application "Microsoft Edge" to return URL of active tab of front window',
|
|
66
|
+
brave: 'tell application "Brave Browser" to return URL of active tab of front window',
|
|
67
|
+
opera: 'tell application "Opera" to return URL of active tab of front window',
|
|
68
|
+
arc: 'tell application "Arc" to return URL of active tab of front window',
|
|
69
|
+
vivaldi: 'tell application "Vivaldi" to return URL of active tab of front window',
|
|
70
|
+
chromium: 'tell application "Chromium" to return URL of active tab of front window',
|
|
71
|
+
};
|
|
20
72
|
|
|
21
73
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
74
|
// Main capture function — returns a context snapshot object
|
|
@@ -26,11 +78,11 @@ async function captureContext(platform, dev) {
|
|
|
26
78
|
|
|
27
79
|
const snapshot = {
|
|
28
80
|
captured_at: new Date().toISOString(),
|
|
29
|
-
active_window: null,
|
|
30
|
-
active_url: null,
|
|
31
|
-
active_app: null,
|
|
32
|
-
clipboard_text: null,
|
|
33
|
-
browser_tab_title: null,
|
|
81
|
+
active_window: null, // { title, process }
|
|
82
|
+
active_url: null, // string — browser URL if active
|
|
83
|
+
active_app: null, // friendly app name
|
|
84
|
+
clipboard_text: null, // last copied text (truncated to 500 chars)
|
|
85
|
+
browser_tab_title: null, // browser tab title if browser is active
|
|
34
86
|
};
|
|
35
87
|
|
|
36
88
|
try {
|
|
@@ -52,12 +104,11 @@ async function captureContext(platform, dev) {
|
|
|
52
104
|
// Windows capture via PowerShell
|
|
53
105
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
54
106
|
async function captureWindows(snapshot, log) {
|
|
55
|
-
// Active window title + process name
|
|
107
|
+
// ── Active window title + process name ────────────────────────────────────
|
|
56
108
|
// NOTE: PowerShell here-strings (@"..."@) cannot be passed via -Command because
|
|
57
109
|
// newlines get collapsed. We write a temp .ps1 file and run it with -File instead.
|
|
58
110
|
try {
|
|
59
|
-
const
|
|
60
|
-
const tmpFile = require('path').join(os.tmpdir(), `promethios_ctx_${process.pid}.ps1`);
|
|
111
|
+
const tmpFile = path.join(os.tmpdir(), `promethios_ctx_${process.pid}.ps1`);
|
|
61
112
|
const psScript = [
|
|
62
113
|
'Add-Type @"',
|
|
63
114
|
'using System;',
|
|
@@ -81,12 +132,12 @@ async function captureWindows(snapshot, log) {
|
|
|
81
132
|
'} | ConvertTo-Json -Compress',
|
|
82
133
|
'Write-Output $result',
|
|
83
134
|
].join('\r\n');
|
|
84
|
-
|
|
135
|
+
fs.writeFileSync(tmpFile, psScript, 'utf8');
|
|
85
136
|
const output = execSync(
|
|
86
137
|
`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${tmpFile}"`,
|
|
87
138
|
{ encoding: 'utf8', timeout: 4000, windowsHide: true }
|
|
88
139
|
).trim();
|
|
89
|
-
try {
|
|
140
|
+
try { fs.unlinkSync(tmpFile); } catch { /* cleanup best-effort */ }
|
|
90
141
|
const parsed = JSON.parse(output);
|
|
91
142
|
snapshot.active_window = { title: parsed.title, process: parsed.procName };
|
|
92
143
|
snapshot.active_app = friendlyAppName(parsed.procName, parsed.title);
|
|
@@ -95,18 +146,43 @@ async function captureWindows(snapshot, log) {
|
|
|
95
146
|
log('Windows active window capture failed:', err.message);
|
|
96
147
|
}
|
|
97
148
|
|
|
98
|
-
//
|
|
99
|
-
// Chrome/Edge format: "<page title> - <browser name>"
|
|
100
|
-
// Firefox format: "<page title> — Mozilla Firefox"
|
|
149
|
+
// ── Browser URL capture ───────────────────────────────────────────────────
|
|
101
150
|
if (snapshot.active_window) {
|
|
102
|
-
const
|
|
151
|
+
const procName = (snapshot.active_window.process || '').toLowerCase();
|
|
152
|
+
const winTitle = snapshot.active_window.title || '';
|
|
153
|
+
|
|
154
|
+
// Determine which browser family this is
|
|
155
|
+
const chromiumKey = Object.keys(CHROMIUM_PROC_MAP).find(k => procName.includes(k));
|
|
156
|
+
const isFirefox = procName.includes('firefox');
|
|
157
|
+
|
|
158
|
+
let url = null;
|
|
159
|
+
|
|
160
|
+
if (chromiumKey) {
|
|
161
|
+
// ── Chromium-based: UIAutomation address bar read ──────────────────────
|
|
162
|
+
url = await captureChromiumUrlWindows(CHROMIUM_PROC_MAP[chromiumKey], log);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!url && isFirefox) {
|
|
166
|
+
// ── Firefox on Windows: title-based extraction ─────────────────────────
|
|
167
|
+
// Firefox doesn't expose the address bar via UIAutomation without an extension.
|
|
168
|
+
// Best we can do without an extension is title parsing.
|
|
169
|
+
url = extractUrlFromTitle(winTitle, 'firefox');
|
|
170
|
+
log('Firefox URL (title-based):', url);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!url && chromiumKey && !url) {
|
|
174
|
+
// UIAutomation failed — fall back to title parsing
|
|
175
|
+
url = extractUrlFromTitle(winTitle, procName);
|
|
176
|
+
}
|
|
177
|
+
|
|
103
178
|
if (url) {
|
|
104
179
|
snapshot.active_url = url;
|
|
105
|
-
snapshot.browser_tab_title = extractTabTitle(
|
|
180
|
+
snapshot.browser_tab_title = extractTabTitle(winTitle, procName);
|
|
181
|
+
log('Captured URL:', url);
|
|
106
182
|
}
|
|
107
183
|
}
|
|
108
184
|
|
|
109
|
-
// Clipboard text (last copied)
|
|
185
|
+
// ── Clipboard text (last copied) ──────────────────────────────────────────
|
|
110
186
|
try {
|
|
111
187
|
const clip = execSync(
|
|
112
188
|
'powershell -NoProfile -NonInteractive -Command "Get-Clipboard -ErrorAction SilentlyContinue"',
|
|
@@ -121,14 +197,71 @@ async function captureWindows(snapshot, log) {
|
|
|
121
197
|
}
|
|
122
198
|
}
|
|
123
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Read the URL from a Chromium-based browser's address bar on Windows
|
|
202
|
+
* using PowerShell UIAutomation. Works for Chrome, Edge, Brave, Opera, Arc, Vivaldi.
|
|
203
|
+
*
|
|
204
|
+
* @param {string} browserProcName - exact process name for Get-Process (e.g. "chrome", "brave")
|
|
205
|
+
* @param {Function} log
|
|
206
|
+
* @returns {string|null}
|
|
207
|
+
*/
|
|
208
|
+
function captureChromiumUrlWindows(browserProcName, log) {
|
|
209
|
+
try {
|
|
210
|
+
const tmpFile = path.join(os.tmpdir(), `promethios_url_${process.pid}.ps1`);
|
|
211
|
+
const psLines = [
|
|
212
|
+
'Add-Type -AssemblyName UIAutomationClient',
|
|
213
|
+
'Add-Type -AssemblyName UIAutomationTypes',
|
|
214
|
+
// Filter to processes with a real (non-zero) window handle
|
|
215
|
+
`$procs = Get-Process -Name "${browserProcName}" -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowHandle -ne 0 }`,
|
|
216
|
+
'if ($procs) {',
|
|
217
|
+
' $browserProc = $procs | Select-Object -First 1',
|
|
218
|
+
' try {',
|
|
219
|
+
' $root = [System.Windows.Automation.AutomationElement]::FromHandle($browserProc.MainWindowHandle)',
|
|
220
|
+
' if ($root) {',
|
|
221
|
+
// Try "Address and search bar" (Chrome, Edge, Brave, Opera, Vivaldi)
|
|
222
|
+
' $cond = New-Object System.Windows.Automation.PropertyCondition(',
|
|
223
|
+
' [System.Windows.Automation.AutomationElement]::NameProperty,',
|
|
224
|
+
' "Address and search bar")',
|
|
225
|
+
' $bar = $root.FindFirst([System.Windows.Automation.TreeScope]::Descendants, $cond)',
|
|
226
|
+
// Arc uses "Address Bar" instead
|
|
227
|
+
' if (-not $bar) {',
|
|
228
|
+
' $cond2 = New-Object System.Windows.Automation.PropertyCondition(',
|
|
229
|
+
' [System.Windows.Automation.AutomationElement]::NameProperty,',
|
|
230
|
+
' "Address Bar")',
|
|
231
|
+
' $bar = $root.FindFirst([System.Windows.Automation.TreeScope]::Descendants, $cond2)',
|
|
232
|
+
' }',
|
|
233
|
+
' if ($bar) {',
|
|
234
|
+
' $vp = $bar.GetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern)',
|
|
235
|
+
' Write-Output $vp.Current.Value',
|
|
236
|
+
' }',
|
|
237
|
+
' }',
|
|
238
|
+
' } catch {',
|
|
239
|
+
' # Silently ignore UIAutomation errors',
|
|
240
|
+
' }',
|
|
241
|
+
'}',
|
|
242
|
+
];
|
|
243
|
+
fs.writeFileSync(tmpFile, psLines.join('\r\n'), 'utf8');
|
|
244
|
+
const url = execSync(
|
|
245
|
+
`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${tmpFile}"`,
|
|
246
|
+
{ encoding: 'utf8', timeout: 5000, windowsHide: true }
|
|
247
|
+
).trim();
|
|
248
|
+
try { fs.unlinkSync(tmpFile); } catch { /* cleanup best-effort */ }
|
|
249
|
+
if (url && (url.startsWith('http') || url.startsWith('chrome') || url.startsWith('edge') || url.startsWith('brave'))) {
|
|
250
|
+
return url;
|
|
251
|
+
}
|
|
252
|
+
} catch (err) {
|
|
253
|
+
log('Chromium UIAutomation failed:', err.message);
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
124
258
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
125
259
|
// macOS capture via AppleScript
|
|
126
260
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
127
261
|
async function captureMac(snapshot, log) {
|
|
128
|
-
// Active app + window title
|
|
262
|
+
// ── Active app + window title ──────────────────────────────────────────────
|
|
129
263
|
try {
|
|
130
|
-
const script = `
|
|
131
|
-
tell application "System Events"
|
|
264
|
+
const script = `tell application "System Events"
|
|
132
265
|
set frontApp to first application process whose frontmost is true
|
|
133
266
|
set appName to name of frontApp
|
|
134
267
|
set windowTitle to ""
|
|
@@ -147,33 +280,39 @@ end tell`;
|
|
|
147
280
|
log('macOS active window capture failed:', err.message);
|
|
148
281
|
}
|
|
149
282
|
|
|
150
|
-
// Browser URL via AppleScript
|
|
283
|
+
// ── Browser URL via AppleScript ────────────────────────────────────────────
|
|
284
|
+
// Covers: Chrome, Safari, Edge, Firefox, Brave, Opera, Arc, Vivaldi, Chromium
|
|
151
285
|
if (snapshot.active_window) {
|
|
152
286
|
const proc = (snapshot.active_window.process || '').toLowerCase();
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
urlScript = 'tell application "Microsoft Edge" to return URL of active tab of front window';
|
|
163
|
-
}
|
|
164
|
-
if (urlScript) {
|
|
165
|
-
const url = execSync(`osascript -e '${urlScript}'`,
|
|
287
|
+
const winTitle = snapshot.active_window.title || '';
|
|
288
|
+
|
|
289
|
+
// Find the best matching AppleScript command
|
|
290
|
+
const scriptKey = Object.keys(MAC_BROWSER_APPLESCRIPT).find(k => proc.includes(k));
|
|
291
|
+
let url = null;
|
|
292
|
+
|
|
293
|
+
if (scriptKey) {
|
|
294
|
+
try {
|
|
295
|
+
url = execSync(`osascript -e '${MAC_BROWSER_APPLESCRIPT[scriptKey]}'`,
|
|
166
296
|
{ encoding: 'utf8', timeout: 3000 }).trim();
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
log('
|
|
297
|
+
if (url && !url.startsWith('http') && !url.startsWith('file')) url = null;
|
|
298
|
+
} catch (err) {
|
|
299
|
+
log('macOS AppleScript URL capture failed for', scriptKey, ':', err.message);
|
|
170
300
|
}
|
|
171
|
-
}
|
|
172
|
-
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Fallback: title-based extraction
|
|
304
|
+
if (!url) {
|
|
305
|
+
url = extractUrlFromTitle(winTitle, proc);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (url) {
|
|
309
|
+
snapshot.active_url = url;
|
|
310
|
+
snapshot.browser_tab_title = extractTabTitle(winTitle, proc);
|
|
311
|
+
log('Browser URL:', url);
|
|
173
312
|
}
|
|
174
313
|
}
|
|
175
314
|
|
|
176
|
-
// Clipboard
|
|
315
|
+
// ── Clipboard ──────────────────────────────────────────────────────────────
|
|
177
316
|
try {
|
|
178
317
|
const clip = execSync('pbpaste', { encoding: 'utf8', timeout: 2000 }).trim();
|
|
179
318
|
if (clip && clip.length > 0 && clip.length < 5000) {
|
|
@@ -188,17 +327,64 @@ end tell`;
|
|
|
188
327
|
// Linux capture via xdotool / xclip (best-effort)
|
|
189
328
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
190
329
|
async function captureLinux(snapshot, log) {
|
|
330
|
+
// ── Active window title + process ─────────────────────────────────────────
|
|
331
|
+
let winTitle = '';
|
|
332
|
+
let procName = '';
|
|
333
|
+
|
|
191
334
|
try {
|
|
192
|
-
|
|
335
|
+
winTitle = execSync('xdotool getactivewindow getwindowname 2>/dev/null',
|
|
193
336
|
{ encoding: 'utf8', timeout: 2000 }).trim();
|
|
194
|
-
snapshot.active_window = { title, process: 'unknown' };
|
|
195
|
-
snapshot.active_app =
|
|
337
|
+
snapshot.active_window = { title: winTitle, process: 'unknown' };
|
|
338
|
+
snapshot.active_app = winTitle;
|
|
196
339
|
} catch (err) {
|
|
197
340
|
log('Linux active window capture failed:', err.message);
|
|
198
341
|
}
|
|
199
342
|
|
|
343
|
+
// Try to get the process name via xdotool + /proc
|
|
344
|
+
try {
|
|
345
|
+
const wid = execSync('xdotool getactivewindow 2>/dev/null',
|
|
346
|
+
{ encoding: 'utf8', timeout: 1000 }).trim();
|
|
347
|
+
if (wid) {
|
|
348
|
+
const pid = execSync(`xdotool getwindowpid ${wid} 2>/dev/null`,
|
|
349
|
+
{ encoding: 'utf8', timeout: 1000 }).trim();
|
|
350
|
+
if (pid) {
|
|
351
|
+
procName = execSync(`cat /proc/${pid}/comm 2>/dev/null`,
|
|
352
|
+
{ encoding: 'utf8', timeout: 500 }).trim().toLowerCase();
|
|
353
|
+
if (snapshot.active_window) snapshot.active_window.process = procName;
|
|
354
|
+
snapshot.active_app = friendlyAppName(procName, winTitle);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
} catch (err) {
|
|
358
|
+
log('Linux process name capture failed:', err.message);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ── Browser URL capture ───────────────────────────────────────────────────
|
|
362
|
+
if (winTitle || procName) {
|
|
363
|
+
const proc = procName || winTitle.toLowerCase();
|
|
364
|
+
let url = null;
|
|
365
|
+
|
|
366
|
+
// Try qdbus for Chromium-based browsers (works on some Linux desktops)
|
|
367
|
+
const chromiumKey = Object.keys(CHROMIUM_PROC_MAP).find(k => proc.includes(k));
|
|
368
|
+
if (chromiumKey) {
|
|
369
|
+
url = captureChromiumUrlLinux(chromiumKey, log);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Fallback: title-based extraction
|
|
373
|
+
if (!url) {
|
|
374
|
+
url = extractUrlFromTitle(winTitle, proc);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (url) {
|
|
378
|
+
snapshot.active_url = url;
|
|
379
|
+
snapshot.browser_tab_title = extractTabTitle(winTitle, proc);
|
|
380
|
+
log('Browser URL:', url);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// ── Clipboard ──────────────────────────────────────────────────────────────
|
|
200
385
|
try {
|
|
201
|
-
const clip = execSync(
|
|
386
|
+
const clip = execSync(
|
|
387
|
+
'xclip -selection clipboard -o 2>/dev/null || xsel --clipboard --output 2>/dev/null',
|
|
202
388
|
{ encoding: 'utf8', timeout: 2000 }).trim();
|
|
203
389
|
if (clip && clip.length > 0 && clip.length < 5000) {
|
|
204
390
|
snapshot.clipboard_text = clip.slice(0, 500);
|
|
@@ -208,88 +394,93 @@ async function captureLinux(snapshot, log) {
|
|
|
208
394
|
}
|
|
209
395
|
}
|
|
210
396
|
|
|
397
|
+
/**
|
|
398
|
+
* Try to get the active URL from a Chromium browser on Linux via qdbus
|
|
399
|
+
* (works on KDE/GNOME with accessibility enabled).
|
|
400
|
+
*/
|
|
401
|
+
function captureChromiumUrlLinux(browserKey, log) {
|
|
402
|
+
try {
|
|
403
|
+
// xdotool + xprop approach: read _NET_WM_NAME of the active window
|
|
404
|
+
// and parse if it looks like a URL (some browsers put URL in title in certain modes)
|
|
405
|
+
// More reliable: use wmctrl to get window title and parse
|
|
406
|
+
const title = execSync('xdotool getactivewindow getwindowname 2>/dev/null',
|
|
407
|
+
{ encoding: 'utf8', timeout: 1000 }).trim();
|
|
408
|
+
return extractUrlFromTitle(title, browserKey);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
log('Linux Chromium URL capture failed:', err.message);
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
211
415
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
212
416
|
// Helpers
|
|
213
417
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
214
418
|
|
|
215
419
|
/**
|
|
216
420
|
* Try to extract a URL from a browser window title.
|
|
217
|
-
* Chrome/Edge show: "<page title> - Google Chrome"
|
|
218
|
-
* Firefox shows: "<page title> — Mozilla Firefox"
|
|
219
|
-
* We can't get the actual URL from the title alone, but we can detect
|
|
220
|
-
* that a browser is active and parse the tab title.
|
|
221
421
|
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
422
|
+
* Some browsers (especially in developer/kiosk mode, or when the URL bar
|
|
423
|
+
* is focused) include the URL in the window title. This is a best-effort
|
|
424
|
+
* fallback when native API access is unavailable.
|
|
425
|
+
*
|
|
426
|
+
* Title patterns:
|
|
427
|
+
* Chrome/Edge/Brave: "<page title> - Google Chrome"
|
|
428
|
+
* Firefox: "<page title> — Mozilla Firefox"
|
|
429
|
+
* Safari: "<page title>"
|
|
430
|
+
* Arc: "<page title> - Arc"
|
|
431
|
+
*
|
|
432
|
+
* We cannot reliably extract the URL from the title alone for most pages,
|
|
433
|
+
* but we can detect that a browser is active and return the tab title.
|
|
434
|
+
* The function returns null if no URL can be determined.
|
|
225
435
|
*/
|
|
226
|
-
function
|
|
227
|
-
const proc = (
|
|
436
|
+
function extractUrlFromTitle(windowTitle, processName) {
|
|
437
|
+
const proc = (processName || '').toLowerCase();
|
|
228
438
|
const isBrowser = proc.includes('chrome') || proc.includes('msedge') ||
|
|
229
|
-
proc.includes('firefox') || proc.includes('opera') || proc.includes('brave')
|
|
439
|
+
proc.includes('firefox') || proc.includes('opera') || proc.includes('brave') ||
|
|
440
|
+
proc.includes('arc') || proc.includes('safari') || proc.includes('vivaldi') ||
|
|
441
|
+
proc.includes('chromium') || proc.includes('edge');
|
|
230
442
|
if (!isBrowser) return null;
|
|
231
443
|
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
if (
|
|
236
|
-
|
|
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');
|
|
259
|
-
const url = execSync(
|
|
260
|
-
`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${tmpFile}"`,
|
|
261
|
-
{ encoding: 'utf8', timeout: 5000, windowsHide: true }
|
|
262
|
-
).trim();
|
|
263
|
-
try { fs.unlinkSync(tmpFile); } catch { /* cleanup best-effort */ }
|
|
264
|
-
if (url && (url.startsWith('http') || url.startsWith('chrome'))) {
|
|
265
|
-
return url;
|
|
266
|
-
}
|
|
267
|
-
} catch (err) {
|
|
268
|
-
// UIAutomation not available — fall back to title parsing
|
|
269
|
-
}
|
|
444
|
+
// Some browsers show the URL directly in the title when the address bar is focused
|
|
445
|
+
// or in certain modes (e.g. "http://localhost:3000 - Google Chrome")
|
|
446
|
+
const urlMatch = windowTitle.match(/https?:\/\/[^\s"'<>]+/);
|
|
447
|
+
if (urlMatch) {
|
|
448
|
+
return urlMatch[0].replace(/\s*[-—|].*$/, '').trim();
|
|
270
449
|
}
|
|
271
450
|
|
|
272
|
-
return null; // URL not
|
|
451
|
+
return null; // URL not determinable from title alone
|
|
273
452
|
}
|
|
274
453
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
454
|
+
/**
|
|
455
|
+
* Strip the browser name suffix from a window title to get the tab title.
|
|
456
|
+
*/
|
|
457
|
+
function extractTabTitle(windowTitle, processName) {
|
|
458
|
+
return (windowTitle || '')
|
|
279
459
|
.replace(/ - Google Chrome$/, '')
|
|
280
460
|
.replace(/ - Microsoft Edge$/, '')
|
|
281
461
|
.replace(/ — Mozilla Firefox$/, '')
|
|
462
|
+
.replace(/ - Mozilla Firefox$/, '')
|
|
282
463
|
.replace(/ - Brave$/, '')
|
|
464
|
+
.replace(/ - Brave Browser$/, '')
|
|
283
465
|
.replace(/ - Opera$/, '')
|
|
466
|
+
.replace(/ - Arc$/, '')
|
|
467
|
+
.replace(/ - Vivaldi$/, '')
|
|
468
|
+
.replace(/ - Safari$/, '')
|
|
469
|
+
.replace(/ \| .+$/, '') // some browsers use " | Site Name" format
|
|
284
470
|
.trim();
|
|
285
471
|
}
|
|
286
472
|
|
|
287
473
|
function friendlyAppName(processName, windowTitle) {
|
|
288
474
|
const proc = (processName || '').toLowerCase();
|
|
289
475
|
if (proc.includes('chrome')) return 'Google Chrome';
|
|
290
|
-
if (proc.includes('msedge')) return 'Microsoft Edge';
|
|
476
|
+
if (proc.includes('msedge') || proc.includes('edge')) return 'Microsoft Edge';
|
|
291
477
|
if (proc.includes('firefox')) return 'Firefox';
|
|
292
478
|
if (proc.includes('brave')) return 'Brave Browser';
|
|
479
|
+
if (proc.includes('opera')) return 'Opera';
|
|
480
|
+
if (proc.includes('arc')) return 'Arc';
|
|
481
|
+
if (proc.includes('vivaldi')) return 'Vivaldi';
|
|
482
|
+
if (proc.includes('safari')) return 'Safari';
|
|
483
|
+
if (proc.includes('chromium')) return 'Chromium';
|
|
293
484
|
if (proc.includes('code')) return 'VS Code';
|
|
294
485
|
if (proc.includes('excel')) return 'Microsoft Excel';
|
|
295
486
|
if (proc.includes('word')) return 'Microsoft Word';
|
|
@@ -299,7 +490,10 @@ function friendlyAppName(processName, windowTitle) {
|
|
|
299
490
|
if (proc.includes('zoom')) return 'Zoom';
|
|
300
491
|
if (proc.includes('notepad')) return 'Notepad';
|
|
301
492
|
if (proc.includes('explorer')) return 'File Explorer';
|
|
493
|
+
if (proc.includes('finder')) return 'Finder';
|
|
302
494
|
if (proc.includes('cmd') || proc.includes('powershell') || proc.includes('windowsterminal')) return 'Terminal';
|
|
495
|
+
if (proc.includes('iterm')) return 'iTerm2';
|
|
496
|
+
if (proc.includes('terminal')) return 'Terminal';
|
|
303
497
|
return processName || 'Unknown';
|
|
304
498
|
}
|
|
305
499
|
|