@apmantza/greedysearch-pi 1.7.0 → 1.7.2

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.
@@ -1,288 +1,288 @@
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
- // search.mjs passes CDP_PROFILE_DIR so cdp.mjs targets this dedicated Chrome
9
- // without ever touching the user's main Chrome DevToolsActivePort file.
10
- //
11
- // Usage:
12
- // node launch.mjs — launch (or report if already running)
13
- // node launch.mjs --kill — stop and restore original DevToolsActivePort
14
- // node launch.mjs --status — check if running
15
-
16
- import { execSync, spawn } from "node:child_process";
17
- import {
18
- existsSync,
19
- mkdirSync,
20
- readFileSync,
21
- unlinkSync,
22
- writeFileSync,
23
- } from "node:fs";
24
- import http from "node:http";
25
- import { platform, tmpdir } from "node:os";
26
- import { join } from "node:path";
27
-
28
- const PORT = 9222;
29
- const PROFILE_DIR = join(tmpdir(), "greedysearch-chrome-profile");
30
- const ACTIVE_PORT = join(PROFILE_DIR, "DevToolsActivePort");
31
- const PID_FILE = join(tmpdir(), "greedysearch-chrome.pid");
32
-
33
- function findChrome() {
34
- const os = platform();
35
- const candidates =
36
- os === "win32"
37
- ? [
38
- "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
39
- "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
40
- ]
41
- : os === "darwin"
42
- ? [
43
- "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
44
- "/Applications/Chromium.app/Contents/MacOS/Chromium",
45
- ]
46
- : [
47
- "/usr/bin/google-chrome",
48
- "/usr/bin/google-chrome-stable",
49
- "/usr/bin/chromium-browser",
50
- "/usr/bin/chromium",
51
- "/snap/bin/chromium",
52
- ];
53
- return candidates.find(existsSync) || null;
54
- }
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
- function isRunning() {
70
- if (!existsSync(PID_FILE)) return false;
71
- const pid = parseInt(readFileSync(PID_FILE, "utf8").trim(), 10);
72
- if (!pid) return false;
73
- try {
74
- process.kill(pid, 0);
75
- return pid;
76
- } catch {
77
- return false;
78
- }
79
- }
80
-
81
- // Get the PID of the process listening on a port (Windows + Unix)
82
- function getPortPid(port) {
83
- try {
84
- const os = platform();
85
- if (os === "win32") {
86
- // Windows: netstat -ano returns PID in last column
87
- const out = execSync(`netstat -ano -p TCP 2>nul`, { encoding: "utf8" });
88
- // Match lines like: TCP 127.0.0.1:9222 0.0.0.0:0 LISTENING 12345
89
- const regex = new RegExp(
90
- `TCP\\s+[^\\s]*:${port}\\s+[^\\s]*:0\\s+LISTENING\\s+(\\d+)`,
91
- "i",
92
- );
93
- const match = out.match(regex);
94
- return match ? parseInt(match[1], 10) : null;
95
- } else {
96
- // Unix: use lsof or ss
97
- try {
98
- const out = execSync(`lsof -i :${port} -t 2>/dev/null`, {
99
- encoding: "utf8",
100
- }).trim();
101
- return out ? parseInt(out.split("\n")[0], 10) : null;
102
- } catch {
103
- const out = execSync(`ss -tlnp 2>/dev/null | grep :${port}`, {
104
- encoding: "utf8",
105
- });
106
- const match = out.match(/pid=(\d+)/);
107
- return match ? parseInt(match[1], 10) : null;
108
- }
109
- }
110
- } catch {
111
- return null;
112
- }
113
- }
114
-
115
- // Kill a process by PID (with Windows/Unix compatibility)
116
- function killProcess(pid) {
117
- try {
118
- if (platform() === "win32") {
119
- execSync(`taskkill //F //PID ${pid}`, { stdio: "ignore" });
120
- } else {
121
- process.kill(pid, "SIGTERM");
122
- }
123
- return true;
124
- } catch {
125
- return false;
126
- }
127
- }
128
-
129
- // Clean up ghost Chrome on port 9222 that isn't tracked by our PID file
130
- function cleanupGhostChrome() {
131
- const portPid = getPortPid(PORT);
132
- if (!portPid) return; // Nothing on port 9222, all good
133
-
134
- const trackedPid = isRunning();
135
-
136
- if (trackedPid && portPid === trackedPid) {
137
- return; // Port 9222 is our Chrome, all good
138
- }
139
-
140
- // Ghost Chrome detected — something on 9222 that isn't ours
141
- if (trackedPid && portPid !== trackedPid) {
142
- console.log(
143
- `Ghost Chrome detected: port ${PORT} has pid ${portPid}, but our PID file says ${trackedPid}.`,
144
- );
145
- } else if (!trackedPid) {
146
- console.log(
147
- `Ghost Chrome detected: unknown process ${portPid} on port ${PORT} (no PID file).`,
148
- );
149
- }
150
-
151
- console.log(`Killing ghost Chrome (pid ${portPid})...`);
152
- killProcess(portPid);
153
-
154
- // Clean up stale files
155
- try {
156
- unlinkSync(PID_FILE);
157
- } catch {}
158
- try {
159
- unlinkSync(ACTIVE_PORT);
160
- } catch {}
161
- console.log("Cleaned up stale Chrome files.");
162
- }
163
-
164
- function httpGet(url, timeoutMs = 1000) {
165
- return new Promise((resolve) => {
166
- const req = http.get(url, (res) => {
167
- let body = "";
168
- res.on("data", (d) => (body += d));
169
- res.on("end", () => resolve({ ok: res.statusCode === 200, body }));
170
- });
171
- req.on("error", () => resolve({ ok: false }));
172
- req.setTimeout(timeoutMs, () => {
173
- req.destroy();
174
- resolve({ ok: false });
175
- });
176
- });
177
- }
178
-
179
- async function writePortFile(timeoutMs = 15000) {
180
- // Chrome on Windows doesn't write DevToolsActivePort — we build it from the HTTP API.
181
- const deadline = Date.now() + timeoutMs;
182
- while (Date.now() < deadline) {
183
- const { ok, body } = await httpGet(
184
- `http://localhost:${PORT}/json/version`,
185
- 1500,
186
- );
187
- if (ok) {
188
- try {
189
- const { webSocketDebuggerUrl } = JSON.parse(body);
190
- // webSocketDebuggerUrl = "ws://localhost:9223/devtools/browser/..."
191
- const wsPath = new URL(webSocketDebuggerUrl).pathname;
192
- // Write in DevToolsActivePort format: port on line 1, path on line 2
193
- const content = `${PORT}\n${wsPath}`;
194
- writeFileSync(ACTIVE_PORT, content, "utf8");
195
- return true;
196
- } catch {
197
- /* malformed response, retry */
198
- }
199
- }
200
- await new Promise((r) => setTimeout(r, 400));
201
- }
202
- return false;
203
- }
204
-
205
- // ---------------------------------------------------------------------------
206
-
207
- async function main() {
208
- const arg = process.argv[2];
209
-
210
- // Clean up any ghost Chrome on port 9222 before doing anything else
211
- cleanupGhostChrome();
212
-
213
- if (arg === "--kill") {
214
- const pid = isRunning();
215
- if (pid) {
216
- const ok = killProcess(pid);
217
- if (ok) console.log(`Stopped Chrome (pid ${pid}).`);
218
- else console.error(`Failed to stop pid ${pid}.`);
219
- } else {
220
- console.log("GreedySearch Chrome is not running.");
221
- }
222
- return;
223
- }
224
-
225
- if (arg === "--status") {
226
- const pid = isRunning();
227
- if (pid)
228
- console.log(
229
- `Running — pid ${pid}, port ${PORT}, DevToolsActivePort redirected.`,
230
- );
231
- else console.log("Not running.");
232
- return;
233
- }
234
-
235
- // Already running?
236
- const existing = isRunning();
237
- if (existing) {
238
- const ready = await writePortFile(5000);
239
- if (ready) {
240
- console.log(
241
- `GreedySearch Chrome already running (pid ${existing}, port ${PORT}).`,
242
- );
243
- console.log("Dedicated GreedySearch DevToolsActivePort is ready.");
244
- return;
245
- }
246
- // Stale PID — process alive but not Chrome on port 9223. Fall through to fresh launch.
247
- console.log(
248
- `Stale PID ${existing} detected (not Chrome on port ${PORT}) — launching fresh.`,
249
- );
250
- try {
251
- unlinkSync(PID_FILE);
252
- } catch {}
253
- }
254
-
255
- const CHROME_EXE = process.env.CHROME_PATH || findChrome();
256
- if (!CHROME_EXE) {
257
- console.error("Chrome not found. Tried standard paths for your OS.");
258
- console.error(
259
- "Set the CHROME_PATH environment variable to point to your Chrome binary.",
260
- );
261
- process.exit(1);
262
- }
263
-
264
- mkdirSync(PROFILE_DIR, { recursive: true });
265
-
266
- console.log(`Launching GreedySearch Chrome on port ${PORT}...`);
267
- const proc = spawn(CHROME_EXE, CHROME_FLAGS, {
268
- detached: true,
269
- stdio: "ignore",
270
- windowsHide: false,
271
- });
272
- proc.unref();
273
- writeFileSync(PID_FILE, String(proc.pid));
274
-
275
- // Wait for Chrome HTTP endpoint and build the dedicated DevToolsActivePort file
276
- const portFileReady = await writePortFile();
277
- if (!portFileReady) {
278
- console.error("Chrome did not become ready within 15s.");
279
- process.exit(1);
280
- }
281
-
282
- console.log(`Ready. No more "Allow remote debugging?" dialogs.`);
283
- console.log(
284
- "GreedySearch now uses its own isolated DevToolsActivePort file.",
285
- );
286
- }
287
-
288
- 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
+ // search.mjs passes CDP_PROFILE_DIR so cdp.mjs targets this dedicated Chrome
9
+ // without ever touching the user's main Chrome DevToolsActivePort file.
10
+ //
11
+ // Usage:
12
+ // node launch.mjs — launch (or report if already running)
13
+ // node launch.mjs --kill — stop and restore original DevToolsActivePort
14
+ // node launch.mjs --status — check if running
15
+
16
+ import { execSync, spawn } from "node:child_process";
17
+ import {
18
+ existsSync,
19
+ mkdirSync,
20
+ readFileSync,
21
+ unlinkSync,
22
+ writeFileSync,
23
+ } from "node:fs";
24
+ import http from "node:http";
25
+ import { platform, tmpdir } from "node:os";
26
+ import { join } from "node:path";
27
+
28
+ const PORT = 9222;
29
+ const PROFILE_DIR = join(tmpdir(), "greedysearch-chrome-profile");
30
+ const ACTIVE_PORT = join(PROFILE_DIR, "DevToolsActivePort");
31
+ const PID_FILE = join(tmpdir(), "greedysearch-chrome.pid");
32
+
33
+ function findChrome() {
34
+ const os = platform();
35
+ const candidates =
36
+ os === "win32"
37
+ ? [
38
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
39
+ "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
40
+ ]
41
+ : os === "darwin"
42
+ ? [
43
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
44
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
45
+ ]
46
+ : [
47
+ "/usr/bin/google-chrome",
48
+ "/usr/bin/google-chrome-stable",
49
+ "/usr/bin/chromium-browser",
50
+ "/usr/bin/chromium",
51
+ "/snap/bin/chromium",
52
+ ];
53
+ return candidates.find(existsSync) || null;
54
+ }
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
+ function isRunning() {
70
+ if (!existsSync(PID_FILE)) return false;
71
+ const pid = parseInt(readFileSync(PID_FILE, "utf8").trim(), 10);
72
+ if (!pid) return false;
73
+ try {
74
+ process.kill(pid, 0);
75
+ return pid;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+
81
+ // Get the PID of the process listening on a port (Windows + Unix)
82
+ function getPortPid(port) {
83
+ try {
84
+ const os = platform();
85
+ if (os === "win32") {
86
+ // Windows: netstat -ano returns PID in last column
87
+ const out = execSync(`netstat -ano -p TCP 2>nul`, { encoding: "utf8" });
88
+ // Match lines like: TCP 127.0.0.1:9222 0.0.0.0:0 LISTENING 12345
89
+ const regex = new RegExp(
90
+ `TCP\\s+[^\\s]*:${port}\\s+[^\\s]*:0\\s+LISTENING\\s+(\\d+)`,
91
+ "i",
92
+ );
93
+ const match = out.match(regex);
94
+ return match ? parseInt(match[1], 10) : null;
95
+ } else {
96
+ // Unix: use lsof or ss
97
+ try {
98
+ const out = execSync(`lsof -i :${port} -t 2>/dev/null`, {
99
+ encoding: "utf8",
100
+ }).trim();
101
+ return out ? parseInt(out.split("\n")[0], 10) : null;
102
+ } catch {
103
+ const out = execSync(`ss -tlnp 2>/dev/null | grep :${port}`, {
104
+ encoding: "utf8",
105
+ });
106
+ const match = out.match(/pid=(\d+)/);
107
+ return match ? parseInt(match[1], 10) : null;
108
+ }
109
+ }
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ // Kill a process by PID (with Windows/Unix compatibility)
116
+ function killProcess(pid) {
117
+ try {
118
+ if (platform() === "win32") {
119
+ execSync(`taskkill //F //PID ${pid}`, { stdio: "ignore" });
120
+ } else {
121
+ process.kill(pid, "SIGTERM");
122
+ }
123
+ return true;
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+
129
+ // Clean up ghost Chrome on port 9222 that isn't tracked by our PID file
130
+ function cleanupGhostChrome() {
131
+ const portPid = getPortPid(PORT);
132
+ if (!portPid) return; // Nothing on port 9222, all good
133
+
134
+ const trackedPid = isRunning();
135
+
136
+ if (trackedPid && portPid === trackedPid) {
137
+ return; // Port 9222 is our Chrome, all good
138
+ }
139
+
140
+ // Ghost Chrome detected — something on 9222 that isn't ours
141
+ if (trackedPid && portPid !== trackedPid) {
142
+ console.log(
143
+ `Ghost Chrome detected: port ${PORT} has pid ${portPid}, but our PID file says ${trackedPid}.`,
144
+ );
145
+ } else if (!trackedPid) {
146
+ console.log(
147
+ `Ghost Chrome detected: unknown process ${portPid} on port ${PORT} (no PID file).`,
148
+ );
149
+ }
150
+
151
+ console.log(`Killing ghost Chrome (pid ${portPid})...`);
152
+ killProcess(portPid);
153
+
154
+ // Clean up stale files
155
+ try {
156
+ unlinkSync(PID_FILE);
157
+ } catch {}
158
+ try {
159
+ unlinkSync(ACTIVE_PORT);
160
+ } catch {}
161
+ console.log("Cleaned up stale Chrome files.");
162
+ }
163
+
164
+ function httpGet(url, timeoutMs = 1000) {
165
+ return new Promise((resolve) => {
166
+ const req = http.get(url, (res) => {
167
+ let body = "";
168
+ res.on("data", (d) => (body += d));
169
+ res.on("end", () => resolve({ ok: res.statusCode === 200, body }));
170
+ });
171
+ req.on("error", () => resolve({ ok: false }));
172
+ req.setTimeout(timeoutMs, () => {
173
+ req.destroy();
174
+ resolve({ ok: false });
175
+ });
176
+ });
177
+ }
178
+
179
+ async function writePortFile(timeoutMs = 15000) {
180
+ // Chrome on Windows doesn't write DevToolsActivePort — we build it from the HTTP API.
181
+ const deadline = Date.now() + timeoutMs;
182
+ while (Date.now() < deadline) {
183
+ const { ok, body } = await httpGet(
184
+ `http://localhost:${PORT}/json/version`,
185
+ 1500,
186
+ );
187
+ if (ok) {
188
+ try {
189
+ const { webSocketDebuggerUrl } = JSON.parse(body);
190
+ // webSocketDebuggerUrl = "ws://localhost:9223/devtools/browser/..."
191
+ const wsPath = new URL(webSocketDebuggerUrl).pathname;
192
+ // Write in DevToolsActivePort format: port on line 1, path on line 2
193
+ const content = `${PORT}\n${wsPath}`;
194
+ writeFileSync(ACTIVE_PORT, content, "utf8");
195
+ return true;
196
+ } catch {
197
+ /* malformed response, retry */
198
+ }
199
+ }
200
+ await new Promise((r) => setTimeout(r, 400));
201
+ }
202
+ return false;
203
+ }
204
+
205
+ // ---------------------------------------------------------------------------
206
+
207
+ async function main() {
208
+ const arg = process.argv[2];
209
+
210
+ // Clean up any ghost Chrome on port 9222 before doing anything else
211
+ cleanupGhostChrome();
212
+
213
+ if (arg === "--kill") {
214
+ const pid = isRunning();
215
+ if (pid) {
216
+ const ok = killProcess(pid);
217
+ if (ok) console.log(`Stopped Chrome (pid ${pid}).`);
218
+ else console.error(`Failed to stop pid ${pid}.`);
219
+ } else {
220
+ console.log("GreedySearch Chrome is not running.");
221
+ }
222
+ return;
223
+ }
224
+
225
+ if (arg === "--status") {
226
+ const pid = isRunning();
227
+ if (pid)
228
+ console.log(
229
+ `Running — pid ${pid}, port ${PORT}, DevToolsActivePort redirected.`,
230
+ );
231
+ else console.log("Not running.");
232
+ return;
233
+ }
234
+
235
+ // Already running?
236
+ const existing = isRunning();
237
+ if (existing) {
238
+ const ready = await writePortFile(5000);
239
+ if (ready) {
240
+ console.log(
241
+ `GreedySearch Chrome already running (pid ${existing}, port ${PORT}).`,
242
+ );
243
+ console.log("Dedicated GreedySearch DevToolsActivePort is ready.");
244
+ return;
245
+ }
246
+ // Stale PID — process alive but not Chrome on port 9223. Fall through to fresh launch.
247
+ console.log(
248
+ `Stale PID ${existing} detected (not Chrome on port ${PORT}) — launching fresh.`,
249
+ );
250
+ try {
251
+ unlinkSync(PID_FILE);
252
+ } catch {}
253
+ }
254
+
255
+ const CHROME_EXE = process.env.CHROME_PATH || findChrome();
256
+ if (!CHROME_EXE) {
257
+ console.error("Chrome not found. Tried standard paths for your OS.");
258
+ console.error(
259
+ "Set the CHROME_PATH environment variable to point to your Chrome binary.",
260
+ );
261
+ process.exit(1);
262
+ }
263
+
264
+ mkdirSync(PROFILE_DIR, { recursive: true });
265
+
266
+ console.log(`Launching GreedySearch Chrome on port ${PORT}...`);
267
+ const proc = spawn(CHROME_EXE, CHROME_FLAGS, {
268
+ detached: true,
269
+ stdio: "ignore",
270
+ windowsHide: false,
271
+ });
272
+ proc.unref();
273
+ writeFileSync(PID_FILE, String(proc.pid));
274
+
275
+ // Wait for Chrome HTTP endpoint and build the dedicated DevToolsActivePort file
276
+ const portFileReady = await writePortFile();
277
+ if (!portFileReady) {
278
+ console.error("Chrome did not become ready within 15s.");
279
+ process.exit(1);
280
+ }
281
+
282
+ console.log(`Ready. No more "Allow remote debugging?" dialogs.`);
283
+ console.log(
284
+ "GreedySearch now uses its own isolated DevToolsActivePort file.",
285
+ );
286
+ }
287
+
288
+ main();