@apmantza/greedysearch-pi 1.0.14 → 1.0.16

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/cdp.mjs CHANGED
@@ -38,6 +38,16 @@ function getDevToolsActivePortPath() {
38
38
  }
39
39
 
40
40
  function getWsUrl() {
41
+ // If CDP_PROFILE_DIR is set (by search.mjs), prefer that profile's port file
42
+ // so GreedySearch targets its own Chrome, not the user's main session.
43
+ const profileDir = process.env.CDP_PROFILE_DIR;
44
+ if (profileDir) {
45
+ const p = profileDir.replace(/\\/g, '/') + '/DevToolsActivePort';
46
+ if (existsSync(p)) {
47
+ const lines = readFileSync(p, 'utf8').trim().split('\n');
48
+ return `ws://127.0.0.1:${lines[0]}${lines[1]}`;
49
+ }
50
+ }
41
51
  const portFile = getDevToolsActivePortPath();
42
52
  const lines = readFileSync(portFile, 'utf8').trim().split('\n');
43
53
  return `ws://127.0.0.1:${lines[0]}${lines[1]}`;
@@ -10,19 +10,17 @@
10
10
 
11
11
  import { readFileSync, existsSync } from 'fs';
12
12
  import { spawn } from 'child_process';
13
- import { tmpdir } from 'os';
13
+ import { tmpdir, homedir } from 'os';
14
14
  import { join, dirname } from 'path';
15
15
  import { fileURLToPath } from 'url';
16
16
  import { dismissConsent, handleVerification } from './consent.mjs';
17
17
 
18
18
  const __dir = dirname(fileURLToPath(import.meta.url));
19
- const CDP = join(__dir, '..', 'cdp.mjs');
19
+ const CDP = join(homedir(), '.claude', 'skills', 'chrome-cdp', 'scripts', 'cdp.mjs');
20
20
  const PAGES_CACHE = `${tmpdir().replace(/\\/g, '/')}/cdp-pages.json`;
21
21
 
22
- const STREAM_POLL_INTERVAL = 600;
23
- const STREAM_STABLE_ROUNDS = 3;
24
- const STREAM_TIMEOUT = 60000;
25
- const MIN_ANSWER_LENGTH = 20;
22
+ const COPY_POLL_INTERVAL = 600;
23
+ const COPY_TIMEOUT = 120000; // wait up to 2 min for copy button to appear
26
24
 
27
25
  // ---------------------------------------------------------------------------
28
26
 
@@ -64,58 +62,66 @@ async function typeIntoGemini(tab, text) {
64
62
  `]);
65
63
  }
66
64
 
67
- async function waitForStreamComplete(tab) {
68
- // Wait for Stop button to appear (streaming started), then disappear (streaming done)
69
- const deadline = Date.now() + STREAM_TIMEOUT;
70
- let streamingStarted = false;
71
- let stableCount = 0;
72
- let lastLen = -1;
65
+ async function injectClipboardInterceptor(tab) {
66
+ // Override both clipboard APIs Gemini uses clipboard.write(ClipboardItem) for rich copy.
67
+ await cdp(['eval', tab, `
68
+ window.__geminiClipboard = null;
69
+ const _origWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
70
+ navigator.clipboard.writeText = function(text) {
71
+ window.__geminiClipboard = text;
72
+ return _origWriteText(text);
73
+ };
74
+ const _origWrite = navigator.clipboard.write.bind(navigator.clipboard);
75
+ navigator.clipboard.write = async function(items) {
76
+ try {
77
+ for (const item of items) {
78
+ if (item.types && item.types.includes('text/plain')) {
79
+ const blob = await item.getType('text/plain');
80
+ window.__geminiClipboard = await blob.text();
81
+ break;
82
+ }
83
+ }
84
+ } catch(e) {}
85
+ return _origWrite(items);
86
+ };
87
+ `]);
88
+ }
73
89
 
90
+ async function waitForCopyButton(tab) {
91
+ // The "Copy response" button appears only after streaming is complete.
92
+ const deadline = Date.now() + COPY_TIMEOUT;
74
93
  while (Date.now() < deadline) {
75
- await new Promise(r => setTimeout(r, STREAM_POLL_INTERVAL));
76
-
77
- const stopVisible = await cdp(['eval', tab,
78
- `!!document.querySelector('button[aria-label*="Stop"]')`
94
+ await new Promise(r => setTimeout(r, COPY_POLL_INTERVAL));
95
+ const found = await cdp(['eval', tab,
96
+ `!!document.querySelector('button[aria-label="Copy"]')`
79
97
  ]).catch(() => 'false');
80
-
81
- if (stopVisible === 'true') {
82
- streamingStarted = true;
83
- } else if (streamingStarted) {
84
- // Stop button gone — streaming finished. Confirm with stable text length.
85
- const lenStr = await cdp(['eval', tab,
86
- `(function(){var els=document.querySelectorAll('model-response .markdown');var last=els[els.length-1];return (last?.innerText?.length||0)+''})()`
87
- ]).catch(() => '0');
88
- const len = parseInt(lenStr) || 0;
89
- if (len >= MIN_ANSWER_LENGTH && len === lastLen) {
90
- stableCount++;
91
- if (stableCount >= STREAM_STABLE_ROUNDS) return len;
92
- } else {
93
- stableCount = 0;
94
- lastLen = len;
95
- }
96
- }
98
+ if (found === 'true') return;
97
99
  }
98
-
99
- if (lastLen >= MIN_ANSWER_LENGTH) return lastLen;
100
- throw new Error(`Gemini answer did not stabilise within ${STREAM_TIMEOUT}ms`);
100
+ throw new Error(`Gemini copy button did not appear within ${COPY_TIMEOUT}ms`);
101
101
  }
102
102
 
103
103
  async function extractAnswer(tab) {
104
+ // Click copy button → our interceptor captures the text.
105
+ await cdp(['eval', tab, `document.querySelector('button[aria-label="Copy"]')?.click()`]);
106
+ await new Promise(r => setTimeout(r, 400));
107
+
108
+ const answer = await cdp(['eval', tab, `window.__geminiClipboard || ''`]);
109
+ if (!answer) throw new Error('Clipboard interceptor returned empty text');
110
+
111
+ // Sources: links rendered in the page (best-effort; Shadow DOM may hide some)
104
112
  const raw = await cdp(['eval', tab, `
105
113
  (function() {
106
- var els = document.querySelectorAll('model-response .markdown');
107
- var last = els[els.length - 1];
108
- if (!last) return JSON.stringify({ answer: '', sources: [] });
109
- var answer = last.innerText.trim();
110
- var sources = Array.from(document.querySelectorAll('model-response a[href^="http"]'))
114
+ var sources = Array.from(document.querySelectorAll('a[href^="http"]'))
111
115
  .map(a => ({ url: a.href.split('#')[0], title: a.innerText?.trim().split('\\n')[0] || '' }))
112
- .filter(s => s.url && !s.url.includes('gemini.google') && !s.url.includes('gstatic'))
116
+ .filter(s => s.url && !s.url.includes('gemini.google') && !s.url.includes('gstatic') && !s.url.includes('google.com/search'))
113
117
  .filter((v, i, arr) => arr.findIndex(x => x.url === v.url) === i)
114
118
  .slice(0, 8);
115
- return JSON.stringify({ answer, sources });
119
+ return JSON.stringify(sources);
116
120
  })()
117
- `]);
118
- return JSON.parse(raw);
121
+ `]).catch(() => '[]');
122
+ const sources = JSON.parse(raw);
123
+
124
+ return { answer: answer.trim(), sources };
119
125
  }
120
126
 
121
127
  // ---------------------------------------------------------------------------
@@ -154,15 +160,16 @@ async function main() {
154
160
  }
155
161
  await new Promise(r => setTimeout(r, 300));
156
162
 
163
+ await injectClipboardInterceptor(tab);
157
164
  await typeIntoGemini(tab, query);
158
165
  await new Promise(r => setTimeout(r, 400));
159
166
 
160
167
  await cdp(['eval', tab, `document.querySelector('button[aria-label*="Send"]')?.click()`]);
161
168
 
162
- await waitForStreamComplete(tab);
169
+ await waitForCopyButton(tab);
163
170
 
164
171
  const { answer, sources } = await extractAnswer(tab);
165
- if (!answer) throw new Error('No answer extracted from Gemini');
172
+ if (!answer) throw new Error('No answer captured from Gemini clipboard');
166
173
  const out = short ? answer.slice(0, 300).replace(/\s+\S*$/, '') + '…' : answer;
167
174
 
168
175
  const finalUrl = await cdp(['eval', tab, 'document.location.href']).catch(() => 'https://gemini.google.com/app');
package/launch.mjs CHANGED
@@ -1,199 +1,199 @@
1
- #!/usr/bin/env node
2
- // launch.mjs — start a dedicated Chrome instance for GreedySearch
3
- //
4
- // This Chrome instance uses --disable-features=DevToolsPrivacyUI which suppresses
5
- // the "Allow remote debugging?" dialog entirely. It runs on port 9223 so it doesn't
6
- // conflict with your main Chrome session.
7
- //
8
- // On launch, it overwrites the DevToolsActivePort file that cdp.mjs reads so all
9
- // extractors automatically target the GreedySearch Chrome, with no code changes.
10
- // The original file is restored on --kill.
11
- //
12
- // Usage:
13
- // node launch.mjs — launch (or report if already running)
14
- // node launch.mjs --kill — stop and restore original DevToolsActivePort
15
- // node launch.mjs --status — check if running
16
-
17
- import { spawn } from 'child_process';
18
- import { existsSync, writeFileSync, readFileSync, copyFileSync, mkdirSync, unlinkSync } from 'fs';
19
- import { tmpdir, homedir, platform } from 'os';
20
- import { join } from 'path';
21
- import http from 'http';
22
-
23
- const PORT = 9223;
24
- const PROFILE_DIR = join(tmpdir(), 'greedysearch-chrome-profile');
25
- const ACTIVE_PORT = join(PROFILE_DIR, 'DevToolsActivePort');
26
- const PID_FILE = join(tmpdir(), 'greedysearch-chrome.pid');
27
-
28
- function findChrome() {
29
- const os = platform();
30
- const candidates = os === 'win32' ? [
31
- 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
32
- 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
33
- ] : os === 'darwin' ? [
34
- '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
35
- '/Applications/Chromium.app/Contents/MacOS/Chromium',
36
- ] : [
37
- '/usr/bin/google-chrome',
38
- '/usr/bin/google-chrome-stable',
39
- '/usr/bin/chromium-browser',
40
- '/usr/bin/chromium',
41
- '/snap/bin/chromium',
42
- ];
43
- return candidates.find(existsSync) || null;
44
- }
45
-
46
- function systemPortPath() {
47
- const os = platform();
48
- if (os === 'win32') return join(homedir(), 'AppData', 'Local', 'Google', 'Chrome', 'User Data', 'DevToolsActivePort');
49
- if (os === 'darwin') return join(homedir(), 'Library', 'Application Support', 'Google', 'Chrome', 'DevToolsActivePort');
50
- return join(homedir(), '.config', 'google-chrome', 'DevToolsActivePort');
51
- }
52
-
53
- const SYSTEM_PORT = systemPortPath();
54
- const SYSTEM_BACKUP = SYSTEM_PORT + '.bak';
55
-
56
- const CHROME_FLAGS = [
57
- `--remote-debugging-port=${PORT}`,
58
- '--disable-features=DevToolsPrivacyUI', // suppresses "Allow remote debugging?" dialog
59
- '--no-first-run',
60
- '--no-default-browser-check',
61
- '--disable-default-apps',
62
- `--user-data-dir=${PROFILE_DIR}`,
63
- '--profile-directory=Default',
64
- 'about:blank',
65
- ];
66
-
67
- // ---------------------------------------------------------------------------
68
-
69
-
70
- function isRunning() {
71
- if (!existsSync(PID_FILE)) return false;
72
- const pid = parseInt(readFileSync(PID_FILE, 'utf8').trim());
73
- if (!pid) return false;
74
- try { process.kill(pid, 0); return pid; } catch { return false; }
75
- }
76
-
77
- function httpGet(url, timeoutMs = 1000) {
78
- return new Promise(resolve => {
79
- const req = http.get(url, res => {
80
- let body = '';
81
- res.on('data', d => body += d);
82
- res.on('end', () => resolve({ ok: res.statusCode === 200, body }));
83
- });
84
- req.on('error', () => resolve({ ok: false }));
85
- req.setTimeout(timeoutMs, () => { req.destroy(); resolve({ ok: false }); });
86
- });
87
- }
88
-
89
-
90
- async function writePortFile(timeoutMs = 15000) {
91
- // Chrome on Windows doesn't write DevToolsActivePort — we build it from the HTTP API.
92
- const deadline = Date.now() + timeoutMs;
93
- while (Date.now() < deadline) {
94
- const { ok, body } = await httpGet(`http://localhost:${PORT}/json/version`, 1500);
95
- if (ok) {
96
- try {
97
- const { webSocketDebuggerUrl } = JSON.parse(body);
98
- // webSocketDebuggerUrl = "ws://localhost:9223/devtools/browser/..."
99
- const wsPath = new URL(webSocketDebuggerUrl).pathname;
100
- // Write in DevToolsActivePort format: port on line 1, path on line 2
101
- const content = `${PORT}\n${wsPath}`;
102
- writeFileSync(ACTIVE_PORT, content, 'utf8');
103
- return true;
104
- } catch { /* malformed response, retry */ }
105
- }
106
- await new Promise(r => setTimeout(r, 400));
107
- }
108
- return false;
109
- }
110
-
111
- function redirectCdpToGreedySearch() {
112
- // Back up system DevToolsActivePort (user's main Chrome)
113
- if (existsSync(SYSTEM_PORT) && !existsSync(SYSTEM_BACKUP)) {
114
- copyFileSync(SYSTEM_PORT, SYSTEM_BACKUP);
115
- }
116
- // Point cdp.mjs to our dedicated Chrome's port
117
- copyFileSync(ACTIVE_PORT, SYSTEM_PORT);
118
- }
119
-
120
- function restoreCdpToMainChrome() {
121
- if (existsSync(SYSTEM_BACKUP)) {
122
- copyFileSync(SYSTEM_BACKUP, SYSTEM_PORT);
123
- console.log('Restored DevToolsActivePort to main Chrome.');
124
- } else if (existsSync(SYSTEM_PORT)) {
125
- // No backup means main Chrome wasn't using CDP — remove our file
126
- try { unlinkSync(SYSTEM_PORT); } catch {}
127
- }
128
- }
129
-
130
- // ---------------------------------------------------------------------------
131
-
132
- async function main() {
133
- const arg = process.argv[2];
134
-
135
- if (arg === '--kill') {
136
- const pid = isRunning();
137
- if (pid) {
138
- try { process.kill(pid, 'SIGTERM'); console.log(`Stopped Chrome (pid ${pid}).`); }
139
- catch (e) { console.error(`Failed: ${e.message}`); }
140
- } else {
141
- console.log('GreedySearch Chrome is not running.');
142
- }
143
- restoreCdpToMainChrome();
144
- return;
145
- }
146
-
147
- if (arg === '--status') {
148
- const pid = isRunning();
149
- if (pid) console.log(`Running — pid ${pid}, port ${PORT}, DevToolsActivePort redirected.`);
150
- else console.log('Not running.');
151
- return;
152
- }
153
-
154
- // Already running?
155
- const existing = isRunning();
156
- if (existing) {
157
- const ready = await writePortFile(5000);
158
- if (ready) {
159
- console.log(`GreedySearch Chrome already running (pid ${existing}, port ${PORT}).`);
160
- redirectCdpToGreedySearch();
161
- console.log('DevToolsActivePort redirected.');
162
- return;
163
- }
164
- // Stale PID — process alive but not Chrome on port 9223. Fall through to fresh launch.
165
- console.log(`Stale PID ${existing} detected (not Chrome on port ${PORT}) — launching fresh.`);
166
- try { unlinkSync(PID_FILE); } catch {}
167
- }
168
-
169
- const CHROME_EXE = process.env.CHROME_PATH || findChrome();
170
- if (!CHROME_EXE) {
171
- console.error('Chrome not found. Tried standard paths for your OS.');
172
- console.error('Set the CHROME_PATH environment variable to point to your Chrome binary.');
173
- process.exit(1);
174
- }
175
-
176
- mkdirSync(PROFILE_DIR, { recursive: true });
177
-
178
- console.log(`Launching GreedySearch Chrome on port ${PORT}...`);
179
- const proc = spawn(CHROME_EXE, CHROME_FLAGS, {
180
- detached: true,
181
- stdio: 'ignore',
182
- windowsHide: false,
183
- });
184
- proc.unref();
185
- writeFileSync(PID_FILE, String(proc.pid));
186
-
187
- // Wait for Chrome HTTP endpoint, build DevToolsActivePort file, redirect cdp.mjs
188
- const portFileReady = await writePortFile();
189
- if (!portFileReady) {
190
- console.error('Chrome did not become ready within 15s.');
191
- process.exit(1);
192
- }
193
- redirectCdpToGreedySearch();
194
-
195
- console.log(`Ready. No more "Allow remote debugging?" dialogs.`);
196
- console.log(`Run "node launch.mjs --kill" when done to restore your main Chrome's CDP.`);
197
- }
198
-
199
- main();
1
+ #!/usr/bin/env node
2
+ // launch.mjs — start a dedicated Chrome instance for GreedySearch
3
+ //
4
+ // This Chrome instance uses --disable-features=DevToolsPrivacyUI which suppresses
5
+ // the "Allow remote debugging?" dialog entirely. It runs on port 9222 so it doesn't
6
+ // conflict with your main Chrome session (which may use port 9223).
7
+ //
8
+ // On launch, it overwrites the DevToolsActivePort file that cdp.mjs reads so all
9
+ // extractors automatically target the GreedySearch Chrome, with no code changes.
10
+ // The original file is restored on --kill.
11
+ //
12
+ // Usage:
13
+ // node launch.mjs — launch (or report if already running)
14
+ // node launch.mjs --kill — stop and restore original DevToolsActivePort
15
+ // node launch.mjs --status — check if running
16
+
17
+ import { spawn } from 'child_process';
18
+ import { existsSync, writeFileSync, readFileSync, copyFileSync, mkdirSync, unlinkSync } from 'fs';
19
+ import { tmpdir, homedir, platform } from 'os';
20
+ import { join } from 'path';
21
+ import http from 'http';
22
+
23
+ const PORT = 9222;
24
+ const PROFILE_DIR = join(tmpdir(), 'greedysearch-chrome-profile');
25
+ const ACTIVE_PORT = join(PROFILE_DIR, 'DevToolsActivePort');
26
+ const PID_FILE = join(tmpdir(), 'greedysearch-chrome.pid');
27
+
28
+ function findChrome() {
29
+ const os = platform();
30
+ const candidates = os === 'win32' ? [
31
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
32
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
33
+ ] : os === 'darwin' ? [
34
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
35
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
36
+ ] : [
37
+ '/usr/bin/google-chrome',
38
+ '/usr/bin/google-chrome-stable',
39
+ '/usr/bin/chromium-browser',
40
+ '/usr/bin/chromium',
41
+ '/snap/bin/chromium',
42
+ ];
43
+ return candidates.find(existsSync) || null;
44
+ }
45
+
46
+ function systemPortPath() {
47
+ const os = platform();
48
+ if (os === 'win32') return join(homedir(), 'AppData', 'Local', 'Google', 'Chrome', 'User Data', 'DevToolsActivePort');
49
+ if (os === 'darwin') return join(homedir(), 'Library', 'Application Support', 'Google', 'Chrome', 'DevToolsActivePort');
50
+ return join(homedir(), '.config', 'google-chrome', 'DevToolsActivePort');
51
+ }
52
+
53
+ const SYSTEM_PORT = systemPortPath();
54
+ const SYSTEM_BACKUP = SYSTEM_PORT + '.bak';
55
+
56
+ const CHROME_FLAGS = [
57
+ `--remote-debugging-port=${PORT}`,
58
+ '--disable-features=DevToolsPrivacyUI', // suppresses "Allow remote debugging?" dialog
59
+ '--no-first-run',
60
+ '--no-default-browser-check',
61
+ '--disable-default-apps',
62
+ `--user-data-dir=${PROFILE_DIR}`,
63
+ '--profile-directory=Default',
64
+ 'about:blank',
65
+ ];
66
+
67
+ // ---------------------------------------------------------------------------
68
+
69
+
70
+ function isRunning() {
71
+ if (!existsSync(PID_FILE)) return false;
72
+ const pid = parseInt(readFileSync(PID_FILE, 'utf8').trim());
73
+ if (!pid) return false;
74
+ try { process.kill(pid, 0); return pid; } catch { return false; }
75
+ }
76
+
77
+ function httpGet(url, timeoutMs = 1000) {
78
+ return new Promise(resolve => {
79
+ const req = http.get(url, res => {
80
+ let body = '';
81
+ res.on('data', d => body += d);
82
+ res.on('end', () => resolve({ ok: res.statusCode === 200, body }));
83
+ });
84
+ req.on('error', () => resolve({ ok: false }));
85
+ req.setTimeout(timeoutMs, () => { req.destroy(); resolve({ ok: false }); });
86
+ });
87
+ }
88
+
89
+
90
+ async function writePortFile(timeoutMs = 15000) {
91
+ // Chrome on Windows doesn't write DevToolsActivePort — we build it from the HTTP API.
92
+ const deadline = Date.now() + timeoutMs;
93
+ while (Date.now() < deadline) {
94
+ const { ok, body } = await httpGet(`http://localhost:${PORT}/json/version`, 1500);
95
+ if (ok) {
96
+ try {
97
+ const { webSocketDebuggerUrl } = JSON.parse(body);
98
+ // webSocketDebuggerUrl = "ws://localhost:9223/devtools/browser/..."
99
+ const wsPath = new URL(webSocketDebuggerUrl).pathname;
100
+ // Write in DevToolsActivePort format: port on line 1, path on line 2
101
+ const content = `${PORT}\n${wsPath}`;
102
+ writeFileSync(ACTIVE_PORT, content, 'utf8');
103
+ return true;
104
+ } catch { /* malformed response, retry */ }
105
+ }
106
+ await new Promise(r => setTimeout(r, 400));
107
+ }
108
+ return false;
109
+ }
110
+
111
+ function redirectCdpToGreedySearch() {
112
+ // Back up system DevToolsActivePort (user's main Chrome)
113
+ if (existsSync(SYSTEM_PORT) && !existsSync(SYSTEM_BACKUP)) {
114
+ copyFileSync(SYSTEM_PORT, SYSTEM_BACKUP);
115
+ }
116
+ // Point cdp.mjs to our dedicated Chrome's port
117
+ copyFileSync(ACTIVE_PORT, SYSTEM_PORT);
118
+ }
119
+
120
+ function restoreCdpToMainChrome() {
121
+ if (existsSync(SYSTEM_BACKUP)) {
122
+ copyFileSync(SYSTEM_BACKUP, SYSTEM_PORT);
123
+ console.log('Restored DevToolsActivePort to main Chrome.');
124
+ } else if (existsSync(SYSTEM_PORT)) {
125
+ // No backup means main Chrome wasn't using CDP — remove our file
126
+ try { unlinkSync(SYSTEM_PORT); } catch {}
127
+ }
128
+ }
129
+
130
+ // ---------------------------------------------------------------------------
131
+
132
+ async function main() {
133
+ const arg = process.argv[2];
134
+
135
+ if (arg === '--kill') {
136
+ const pid = isRunning();
137
+ if (pid) {
138
+ try { process.kill(pid, 'SIGTERM'); console.log(`Stopped Chrome (pid ${pid}).`); }
139
+ catch (e) { console.error(`Failed: ${e.message}`); }
140
+ } else {
141
+ console.log('GreedySearch Chrome is not running.');
142
+ }
143
+ restoreCdpToMainChrome();
144
+ return;
145
+ }
146
+
147
+ if (arg === '--status') {
148
+ const pid = isRunning();
149
+ if (pid) console.log(`Running — pid ${pid}, port ${PORT}, DevToolsActivePort redirected.`);
150
+ else console.log('Not running.');
151
+ return;
152
+ }
153
+
154
+ // Already running?
155
+ const existing = isRunning();
156
+ if (existing) {
157
+ const ready = await writePortFile(5000);
158
+ if (ready) {
159
+ console.log(`GreedySearch Chrome already running (pid ${existing}, port ${PORT}).`);
160
+ redirectCdpToGreedySearch();
161
+ console.log('DevToolsActivePort redirected.');
162
+ return;
163
+ }
164
+ // Stale PID — process alive but not Chrome on port 9223. Fall through to fresh launch.
165
+ console.log(`Stale PID ${existing} detected (not Chrome on port ${PORT}) — launching fresh.`);
166
+ try { unlinkSync(PID_FILE); } catch {}
167
+ }
168
+
169
+ const CHROME_EXE = process.env.CHROME_PATH || findChrome();
170
+ if (!CHROME_EXE) {
171
+ console.error('Chrome not found. Tried standard paths for your OS.');
172
+ console.error('Set the CHROME_PATH environment variable to point to your Chrome binary.');
173
+ process.exit(1);
174
+ }
175
+
176
+ mkdirSync(PROFILE_DIR, { recursive: true });
177
+
178
+ console.log(`Launching GreedySearch Chrome on port ${PORT}...`);
179
+ const proc = spawn(CHROME_EXE, CHROME_FLAGS, {
180
+ detached: true,
181
+ stdio: 'ignore',
182
+ windowsHide: false,
183
+ });
184
+ proc.unref();
185
+ writeFileSync(PID_FILE, String(proc.pid));
186
+
187
+ // Wait for Chrome HTTP endpoint, build DevToolsActivePort file, redirect cdp.mjs
188
+ const portFileReady = await writePortFile();
189
+ if (!portFileReady) {
190
+ console.error('Chrome did not become ready within 15s.');
191
+ process.exit(1);
192
+ }
193
+ redirectCdpToGreedySearch();
194
+
195
+ console.log(`Ready. No more "Allow remote debugging?" dialogs.`);
196
+ console.log(`Run "node launch.mjs --kill" when done to restore your main Chrome's CDP.`);
197
+ }
198
+
199
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apmantza/greedysearch-pi",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Pi extension: search Perplexity, Bing Copilot, and Google AI simultaneously — synthesized AI answers, not just links",
5
5
  "type": "module",
6
6
  "keywords": [
package/search.mjs CHANGED
@@ -23,13 +23,15 @@ import { spawn } from 'child_process';
23
23
  import { fileURLToPath } from 'url';
24
24
  import { join, dirname } from 'path';
25
25
  import { readFileSync, existsSync, writeFileSync } from 'fs';
26
- import { tmpdir } from 'os';
26
+ import { tmpdir, homedir } from 'os';
27
27
  import http from 'http';
28
28
 
29
29
  const __dir = dirname(fileURLToPath(import.meta.url));
30
- const CDP = join(__dir, 'cdp.mjs');
30
+ const CDP = join(homedir(), '.claude', 'skills', 'chrome-cdp', 'scripts', 'cdp.mjs');
31
31
  const PAGES_CACHE = `${tmpdir().replace(/\\/g, '/')}/cdp-pages.json`;
32
32
 
33
+ const GREEDY_PORT = 9222;
34
+
33
35
  const ENGINES = {
34
36
  perplexity: 'perplexity.mjs',
35
37
  pplx: 'perplexity.mjs',
@@ -182,9 +184,16 @@ function writeOutput(data, outFile) {
182
184
  }
183
185
  }
184
186
 
185
- function probePort9223(timeoutMs = 3000) {
187
+ const GREEDY_PROFILE_DIR = `${tmpdir().replace(/\\/g, '/')}/greedysearch-chrome-profile`;
188
+ const ACTIVE_PORT_FILE = `${GREEDY_PROFILE_DIR}/DevToolsActivePort`;
189
+
190
+ // Tell cdp.mjs to prefer the GreedySearch Chrome profile's DevToolsActivePort,
191
+ // so searches never accidentally attach to the user's main Chrome session.
192
+ process.env.CDP_PROFILE_DIR = GREEDY_PROFILE_DIR;
193
+
194
+ function probeGreedyChrome(timeoutMs = 3000) {
186
195
  return new Promise(resolve => {
187
- const req = http.get('http://localhost:9223/json/version', res => {
196
+ const req = http.get(`http://localhost:${GREEDY_PORT}/json/version`, res => {
188
197
  res.resume();
189
198
  resolve(res.statusCode === 200);
190
199
  });
@@ -193,14 +202,37 @@ function probePort9223(timeoutMs = 3000) {
193
202
  });
194
203
  }
195
204
 
205
+ // Write (or refresh) the DevToolsActivePort file for the GreedySearch Chrome so
206
+ // cdp.mjs always connects to the right port rather than the user's main Chrome.
207
+ async function refreshPortFile() {
208
+ try {
209
+ const body = await new Promise((res, rej) => {
210
+ const req = http.get(`http://localhost:${GREEDY_PORT}/json/version`, r => {
211
+ let b = '';
212
+ r.on('data', d => b += d);
213
+ r.on('end', () => res(b));
214
+ });
215
+ req.on('error', rej);
216
+ req.setTimeout(3000, () => { req.destroy(); rej(new Error('timeout')); });
217
+ });
218
+ const { webSocketDebuggerUrl } = JSON.parse(body);
219
+ const wsPath = new URL(webSocketDebuggerUrl).pathname;
220
+ writeFileSync(ACTIVE_PORT_FILE, `${GREEDY_PORT}\n${wsPath}`, 'utf8');
221
+ } catch { /* best-effort — launch.mjs already wrote the file on first start */ }
222
+ }
223
+
196
224
  async function ensureChrome() {
197
- const ready = await probePort9223();
225
+ const ready = await probeGreedyChrome();
198
226
  if (!ready) {
199
- process.stderr.write('GreedySearch Chrome not running on port 9223 — auto-launching...\n');
227
+ process.stderr.write(`GreedySearch Chrome not running on port ${GREEDY_PORT} — auto-launching...\n`);
200
228
  await new Promise((resolve, reject) => {
201
229
  const proc = spawn('node', [join(__dir, 'launch.mjs')], { stdio: ['ignore', process.stderr, process.stderr] });
202
230
  proc.on('close', code => code === 0 ? resolve() : reject(new Error('launch.mjs failed')));
203
231
  });
232
+ } else {
233
+ // Chrome already running — refresh the port file so cdp.mjs always picks
234
+ // up the right port, even if the file was stale from a previous session.
235
+ await refreshPortFile();
204
236
  }
205
237
  }
206
238