@duckmind/dm-windows-x64 0.62.3 → 0.63.0
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/dm.exe +0 -0
- package/extensions/.dm-extensions.json +43 -183
- package/extensions/dm-context/src/context.js +1 -2
- package/extensions/dm-context/src/index.js +1 -2
- package/extensions/{greedysearch-dm/bin/cdp.mjs → dm-cua/bin/browser-cdp.mjs} +3 -3
- package/extensions/dm-cua/bin/browser-cua.mjs +1 -1
- package/extensions/{greedysearch-dm/bin/launch.mjs → dm-cua/bin/browser-launch.mjs} +12 -11
- package/extensions/dm-cua/index.js +1 -1
- package/extensions/dm-cua/src/browser-cua-lib.mjs +17 -12
- package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/constants.mjs +11 -11
- package/extensions/{greedysearch-dm/src/utils → dm-cua/src/browser-helpers}/node-runtime.mjs +1 -1
- package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/port-pid.mjs +1 -1
- package/extensions/dm-ponytail/extensions/ponytail.mjs +186 -0
- package/extensions/dm-ponytail/hooks/claude-codex-hooks.json +41 -0
- package/extensions/dm-ponytail/hooks/copilot-hooks.json +21 -0
- package/extensions/dm-ponytail/hooks/ponytail-activate.js +59 -0
- package/extensions/dm-ponytail/hooks/ponytail-config.js +116 -0
- package/extensions/dm-ponytail/hooks/ponytail-instructions.js +81 -0
- package/extensions/dm-ponytail/hooks/ponytail-mode-tracker.js +96 -0
- package/extensions/dm-ponytail/hooks/ponytail-runtime.js +79 -0
- package/extensions/dm-ponytail/hooks/ponytail-statusline.ps1 +24 -0
- package/extensions/dm-ponytail/hooks/ponytail-statusline.sh +18 -0
- package/extensions/dm-ponytail/hooks/ponytail-subagent.js +50 -0
- package/extensions/dm-ponytail/hooks/qoder-hooks.json +26 -0
- package/extensions/dm-ponytail/package.json +19 -0
- package/extensions/dm-ponytail/pi-extension/index.js +186 -0
- package/extensions/dm-ponytail/pi-extension/package.json +8 -0
- package/extensions/dm-ponytail/skills/ponytail/SKILL.md +120 -0
- package/extensions/dm-ponytail/skills/ponytail-audit/SKILL.md +41 -0
- package/extensions/dm-ponytail/skills/ponytail-debt/SKILL.md +44 -0
- package/extensions/dm-ponytail/skills/ponytail-gain/SKILL.md +50 -0
- package/extensions/dm-ponytail/skills/ponytail-help/SKILL.md +71 -0
- package/extensions/dm-ponytail/skills/ponytail-review/SKILL.md +57 -0
- package/package.json +2 -6
- package/extensions/dm-caveman/extensions/caveman.js +0 -300
- package/extensions/dm-caveman/package.json +0 -20
- package/extensions/dm-goal/package.json +0 -23
- package/extensions/dm-goal/src/goal.js +0 -914
- package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +0 -40
- package/extensions/greedysearch-dm/bin/cdp-headless.mjs +0 -5
- package/extensions/greedysearch-dm/bin/cdp-visible.mjs +0 -5
- package/extensions/greedysearch-dm/bin/gschrome.mjs +0 -30
- package/extensions/greedysearch-dm/bin/kill-visible.mjs +0 -7
- package/extensions/greedysearch-dm/bin/launch-visible.mjs +0 -13
- package/extensions/greedysearch-dm/bin/mcp.mjs +0 -400
- package/extensions/greedysearch-dm/bin/search.mjs +0 -635
- package/extensions/greedysearch-dm/bin/visible.mjs +0 -22
- package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +0 -344
- package/extensions/greedysearch-dm/extractors/chatgpt.mjs +0 -354
- package/extensions/greedysearch-dm/extractors/common.mjs +0 -794
- package/extensions/greedysearch-dm/extractors/consensus.mjs +0 -527
- package/extensions/greedysearch-dm/extractors/consent.mjs +0 -449
- package/extensions/greedysearch-dm/extractors/gemini.mjs +0 -291
- package/extensions/greedysearch-dm/extractors/google-ai.mjs +0 -86
- package/extensions/greedysearch-dm/extractors/logically.mjs +0 -589
- package/extensions/greedysearch-dm/extractors/perplexity.mjs +0 -370
- package/extensions/greedysearch-dm/extractors/selectors.mjs +0 -32
- package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +0 -199
- package/extensions/greedysearch-dm/index.js +0 -123
- package/extensions/greedysearch-dm/package.json +0 -27
- package/extensions/greedysearch-dm/skills/greedy-search/skill.md +0 -18
- package/extensions/greedysearch-dm/src/fetcher.mjs +0 -577
- package/extensions/greedysearch-dm/src/formatters/results.js +0 -95
- package/extensions/greedysearch-dm/src/formatters/sources.js +0 -57
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +0 -49
- package/extensions/greedysearch-dm/src/github.mjs +0 -228
- package/extensions/greedysearch-dm/src/reddit.mjs +0 -154
- package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +0 -340
- package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +0 -156
- package/extensions/greedysearch-dm/src/search/chrome.mjs +0 -510
- package/extensions/greedysearch-dm/src/search/defaults.mjs +0 -10
- package/extensions/greedysearch-dm/src/search/engines.mjs +0 -79
- package/extensions/greedysearch-dm/src/search/fetch-source.mjs +0 -470
- package/extensions/greedysearch-dm/src/search/file-sources.mjs +0 -31
- package/extensions/greedysearch-dm/src/search/output.mjs +0 -51
- package/extensions/greedysearch-dm/src/search/paths.mjs +0 -48
- package/extensions/greedysearch-dm/src/search/pdf.mjs +0 -64
- package/extensions/greedysearch-dm/src/search/progress.mjs +0 -109
- package/extensions/greedysearch-dm/src/search/query.mjs +0 -21
- package/extensions/greedysearch-dm/src/search/recovery.mjs +0 -49
- package/extensions/greedysearch-dm/src/search/research.mjs +0 -2255
- package/extensions/greedysearch-dm/src/search/scale-aware.mjs +0 -61
- package/extensions/greedysearch-dm/src/search/simple-research.mjs +0 -418
- package/extensions/greedysearch-dm/src/search/sources.mjs +0 -412
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +0 -127
- package/extensions/greedysearch-dm/src/search/synthesis.mjs +0 -202
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +0 -209
- package/extensions/greedysearch-dm/src/tools/shared.js +0 -226
- package/extensions/greedysearch-dm/src/types.js +0 -0
- package/extensions/greedysearch-dm/src/utils/content.mjs +0 -38
- package/extensions/greedysearch-dm/src/utils/helpers.js +0 -22
- /package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/minimize.mjs +0 -0
- /package/extensions/{greedysearch-dm/src/utils → dm-cua/src/browser-helpers}/system-cmds.mjs +0 -0
|
@@ -1,510 +0,0 @@
|
|
|
1
|
-
import { spawn, execFileSync, execSync } from "node:child_process";
|
|
2
|
-
import { nodeRuntimeCommand } from "../utils/node-runtime.mjs";
|
|
3
|
-
import {
|
|
4
|
-
existsSync,
|
|
5
|
-
readFileSync,
|
|
6
|
-
renameSync,
|
|
7
|
-
unlinkSync,
|
|
8
|
-
writeFileSync
|
|
9
|
-
} from "node:fs";
|
|
10
|
-
import http from "node:http";
|
|
11
|
-
import { platform } from "node:os";
|
|
12
|
-
import { join } from "node:path";
|
|
13
|
-
import {
|
|
14
|
-
cdp as _cdp,
|
|
15
|
-
injectHeadlessStealth
|
|
16
|
-
} from "../../extractors/common.mjs";
|
|
17
|
-
import { resolveSystemCmd } from "../utils/system-cmds.mjs";
|
|
18
|
-
import { findListeningProcessPid } from "./port-pid.mjs";
|
|
19
|
-
import {
|
|
20
|
-
moduleDirectory,
|
|
21
|
-
resolveGreedySearchLaunchScript
|
|
22
|
-
} from "./paths.mjs";
|
|
23
|
-
import {
|
|
24
|
-
ACTIVE_PORT_FILE,
|
|
25
|
-
CHROME_MODE_FILE,
|
|
26
|
-
GREEDY_ACTIVITY_FILE,
|
|
27
|
-
GREEDY_PID_FILE,
|
|
28
|
-
GREEDY_PORT,
|
|
29
|
-
GREEDY_PROFILE_DIR,
|
|
30
|
-
PAGES_CACHE
|
|
31
|
-
} from "./constants.mjs";
|
|
32
|
-
import {
|
|
33
|
-
readMetadata,
|
|
34
|
-
touchActivity as touchActivityBL,
|
|
35
|
-
acquireLaunchLock,
|
|
36
|
-
cleanupStaleSessions,
|
|
37
|
-
registerClient
|
|
38
|
-
} from "./browser-lifecycle.mjs";
|
|
39
|
-
const __dir = moduleDirectory(import.meta.url);
|
|
40
|
-
const PID_FILE = GREEDY_PID_FILE;
|
|
41
|
-
const ACTIVITY_FILE = GREEDY_ACTIVITY_FILE;
|
|
42
|
-
const HEADLESS_IDLE_TIMEOUT_MINUTES = Number.parseInt(process.env.GREEDY_SEARCH_IDLE_TIMEOUT_MINUTES || "5", 10) || 5;
|
|
43
|
-
const VISIBLE_IDLE_TIMEOUT_MINUTES = Number.parseInt(process.env.GREEDY_SEARCH_VISIBLE_IDLE_TIMEOUT_MINUTES || "60", 10) || 60;
|
|
44
|
-
export function detectHeadlessFromChromeCommandLine(cmdLine, debugPort = GREEDY_PORT) {
|
|
45
|
-
const normalized = String(cmdLine || "").toLowerCase();
|
|
46
|
-
if (!normalized.includes(`--remote-debugging-port=${debugPort}`) || normalized.includes("--type=")) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
return normalized.includes("--headless");
|
|
50
|
-
}
|
|
51
|
-
export function isChromeHeadless() {
|
|
52
|
-
try {
|
|
53
|
-
const portPid = getPortPid();
|
|
54
|
-
const cmdLine = portPid ? getProcessCommandLine(portPid) : null;
|
|
55
|
-
const headless = detectHeadlessFromChromeCommandLine(cmdLine);
|
|
56
|
-
if (headless !== null) {
|
|
57
|
-
try {
|
|
58
|
-
writeFileSync(CHROME_MODE_FILE, headless ? "headless" : "visible", "utf8");
|
|
59
|
-
} catch {}
|
|
60
|
-
return headless;
|
|
61
|
-
}
|
|
62
|
-
} catch {}
|
|
63
|
-
try {
|
|
64
|
-
if (!existsSync(CHROME_MODE_FILE))
|
|
65
|
-
return true;
|
|
66
|
-
return readFileSync(CHROME_MODE_FILE, "utf8").trim() === "headless";
|
|
67
|
-
} catch {
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
export function touchActivity() {
|
|
72
|
-
try {
|
|
73
|
-
writeFileSync(ACTIVITY_FILE, String(Date.now()), "utf8");
|
|
74
|
-
} catch {}
|
|
75
|
-
try {
|
|
76
|
-
const md = readMetadata();
|
|
77
|
-
if (md)
|
|
78
|
-
touchActivityBL(md);
|
|
79
|
-
} catch {}
|
|
80
|
-
}
|
|
81
|
-
function getProcessCommandLine(pid) {
|
|
82
|
-
try {
|
|
83
|
-
if (platform() === "win32") {
|
|
84
|
-
const output = execFileSync(resolveSystemCmd("powershell"), [
|
|
85
|
-
"-NoProfile",
|
|
86
|
-
"-NonInteractive",
|
|
87
|
-
"-Command",
|
|
88
|
-
`(Get-CimInstance Win32_Process -Filter "ProcessId = ${pid}").CommandLine`
|
|
89
|
-
], { encoding: "utf8", windowsHide: true, timeout: 5000 });
|
|
90
|
-
return output.trim() || null;
|
|
91
|
-
}
|
|
92
|
-
const output = execFileSync(resolveSystemCmd("ps"), ["-p", String(pid), "-o", "command="], { encoding: "utf8", timeout: 5000 });
|
|
93
|
-
return output.trim() || null;
|
|
94
|
-
} catch {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
function commandLineMatchesGreedyBrowser(commandLine) {
|
|
99
|
-
const normalized = String(commandLine || "").replaceAll("\\", "/");
|
|
100
|
-
const profile = GREEDY_PROFILE_DIR.replaceAll("\\", "/");
|
|
101
|
-
return normalized.includes(`--remote-debugging-port=${GREEDY_PORT}`) && normalized.includes(`--user-data-dir=${profile}`) && !normalized.includes("--type=");
|
|
102
|
-
}
|
|
103
|
-
function getPortPid() {
|
|
104
|
-
return findListeningProcessPid(GREEDY_PORT);
|
|
105
|
-
}
|
|
106
|
-
function getOwnedBrowserPid() {
|
|
107
|
-
const pid = getPortPid();
|
|
108
|
-
return pid && commandLineMatchesGreedyBrowser(getProcessCommandLine(pid)) ? pid : null;
|
|
109
|
-
}
|
|
110
|
-
async function gracefulCloseChrome(graceMs = 1500) {
|
|
111
|
-
const ownedPid = getOwnedBrowserPid();
|
|
112
|
-
if (!ownedPid) {
|
|
113
|
-
process.stderr.write(`[greedysearch] Refusing to close an unverified listener.
|
|
114
|
-
`);
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
const version = await new Promise((resolve, reject) => {
|
|
119
|
-
const req = http.get(`http://localhost:${GREEDY_PORT}/json/version`, (res) => {
|
|
120
|
-
let body = "";
|
|
121
|
-
res.on("data", (d) => body += d);
|
|
122
|
-
res.on("end", () => {
|
|
123
|
-
try {
|
|
124
|
-
resolve(JSON.parse(body));
|
|
125
|
-
} catch {
|
|
126
|
-
reject(new Error("bad JSON"));
|
|
127
|
-
}
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
req.on("error", reject);
|
|
131
|
-
req.setTimeout(1000, () => {
|
|
132
|
-
req.destroy();
|
|
133
|
-
reject(new Error("timeout"));
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
const ws = new globalThis.WebSocket(version.webSocketDebuggerUrl);
|
|
137
|
-
await new Promise((resolve) => {
|
|
138
|
-
ws.onopen = () => {
|
|
139
|
-
if (getOwnedBrowserPid() !== ownedPid) {
|
|
140
|
-
ws.close();
|
|
141
|
-
resolve();
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
ws.send(JSON.stringify({ id: 1, method: "Browser.close" }));
|
|
145
|
-
setTimeout(() => {
|
|
146
|
-
ws.close();
|
|
147
|
-
resolve();
|
|
148
|
-
}, 200);
|
|
149
|
-
};
|
|
150
|
-
ws.onerror = () => resolve();
|
|
151
|
-
setTimeout(resolve, 1000);
|
|
152
|
-
});
|
|
153
|
-
} catch {}
|
|
154
|
-
const deadline = Date.now() + graceMs;
|
|
155
|
-
while (Date.now() < deadline) {
|
|
156
|
-
const pid = getPortPid();
|
|
157
|
-
if (!pid)
|
|
158
|
-
return true;
|
|
159
|
-
if (pid !== ownedPid)
|
|
160
|
-
return false;
|
|
161
|
-
await new Promise((r) => setTimeout(r, 150));
|
|
162
|
-
}
|
|
163
|
-
return killProcessOnPort(ownedPid);
|
|
164
|
-
}
|
|
165
|
-
function killProcessOnPort(expectedPid = null) {
|
|
166
|
-
try {
|
|
167
|
-
const pid = getOwnedBrowserPid();
|
|
168
|
-
if (!pid || expectedPid && pid !== expectedPid)
|
|
169
|
-
return false;
|
|
170
|
-
if (platform() === "win32") {
|
|
171
|
-
execFileSync(resolveSystemCmd("taskkill"), ["/F", "/PID", String(pid)], {
|
|
172
|
-
stdio: "ignore",
|
|
173
|
-
windowsHide: true
|
|
174
|
-
});
|
|
175
|
-
} else {
|
|
176
|
-
process.kill(pid, "SIGKILL");
|
|
177
|
-
}
|
|
178
|
-
return true;
|
|
179
|
-
} catch {
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
export async function killChrome() {
|
|
184
|
-
if (!getOwnedBrowserPid()) {
|
|
185
|
-
if (getPortPid()) {
|
|
186
|
-
process.stderr.write(`[greedysearch] Refusing to kill an unverified listener.
|
|
187
|
-
`);
|
|
188
|
-
}
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
const ready = await probeGreedyChrome(500);
|
|
192
|
-
if (!ready) {
|
|
193
|
-
try {
|
|
194
|
-
unlinkSync(PID_FILE);
|
|
195
|
-
} catch {}
|
|
196
|
-
try {
|
|
197
|
-
unlinkSync(ACTIVITY_FILE);
|
|
198
|
-
} catch {}
|
|
199
|
-
try {
|
|
200
|
-
unlinkSync(CHROME_MODE_FILE);
|
|
201
|
-
} catch {}
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
const killed = await gracefulCloseChrome(1500);
|
|
205
|
-
try {
|
|
206
|
-
unlinkSync(PID_FILE);
|
|
207
|
-
} catch {}
|
|
208
|
-
try {
|
|
209
|
-
unlinkSync(ACTIVITY_FILE);
|
|
210
|
-
} catch {}
|
|
211
|
-
try {
|
|
212
|
-
unlinkSync(CHROME_MODE_FILE);
|
|
213
|
-
} catch {}
|
|
214
|
-
if (killed) {
|
|
215
|
-
process.stderr.write(`[greedysearch] Killed Chrome on port ${GREEDY_PORT}.
|
|
216
|
-
`);
|
|
217
|
-
}
|
|
218
|
-
return killed;
|
|
219
|
-
}
|
|
220
|
-
export const killHeadlessChrome = killChrome;
|
|
221
|
-
export async function checkAndKillIdle() {
|
|
222
|
-
const headless = isChromeHeadless();
|
|
223
|
-
const timeoutMinutes = headless ? HEADLESS_IDLE_TIMEOUT_MINUTES : VISIBLE_IDLE_TIMEOUT_MINUTES;
|
|
224
|
-
if (timeoutMinutes <= 0)
|
|
225
|
-
return false;
|
|
226
|
-
if (!existsSync(ACTIVITY_FILE)) {
|
|
227
|
-
touchActivity();
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
try {
|
|
231
|
-
const lastActivity = Number.parseInt(readFileSync(ACTIVITY_FILE, "utf8").trim(), 10);
|
|
232
|
-
if (!lastActivity)
|
|
233
|
-
return false;
|
|
234
|
-
const idleMs = Date.now() - lastActivity;
|
|
235
|
-
const idleMinutes = idleMs / 60000;
|
|
236
|
-
if (idleMinutes >= timeoutMinutes) {
|
|
237
|
-
return killChrome();
|
|
238
|
-
}
|
|
239
|
-
} catch {}
|
|
240
|
-
return false;
|
|
241
|
-
}
|
|
242
|
-
export const cdp = _cdp;
|
|
243
|
-
export async function getAnyTab() {
|
|
244
|
-
const list = await cdp(["list"]);
|
|
245
|
-
const first = list.split(`
|
|
246
|
-
`)[0];
|
|
247
|
-
if (!first)
|
|
248
|
-
throw new Error("No Chrome tabs found");
|
|
249
|
-
return first.slice(0, 8);
|
|
250
|
-
}
|
|
251
|
-
export async function openNewTab(url = "about:blank") {
|
|
252
|
-
const anchor = await getAnyTab();
|
|
253
|
-
const hostname = new URL(url).hostname;
|
|
254
|
-
const needsStealth = hostname === "copilot.microsoft.com" || hostname === "www.perplexity.ai" || hostname === "perplexity.ai" || hostname.endsWith(".perplexity.ai");
|
|
255
|
-
if (needsStealth) {
|
|
256
|
-
const raw = await cdp([
|
|
257
|
-
"evalraw",
|
|
258
|
-
anchor,
|
|
259
|
-
"Target.createTarget",
|
|
260
|
-
JSON.stringify({ url: "about:blank" })
|
|
261
|
-
]);
|
|
262
|
-
const { targetId } = JSON.parse(raw);
|
|
263
|
-
const tid = targetId.slice(0, 8);
|
|
264
|
-
await cdp(["list"]).catch(() => null);
|
|
265
|
-
if (hostname === "copilot.microsoft.com") {
|
|
266
|
-
await injectHeadlessStealth(tid);
|
|
267
|
-
} else {
|
|
268
|
-
injectHeadlessStealth(tid).catch(() => {});
|
|
269
|
-
}
|
|
270
|
-
await cdp(["list"]).catch(() => null);
|
|
271
|
-
return targetId;
|
|
272
|
-
}
|
|
273
|
-
const raw = await cdp([
|
|
274
|
-
"evalraw",
|
|
275
|
-
anchor,
|
|
276
|
-
"Target.createTarget",
|
|
277
|
-
JSON.stringify({ url })
|
|
278
|
-
]);
|
|
279
|
-
const { targetId } = JSON.parse(raw);
|
|
280
|
-
await cdp(["list"]).catch(() => null);
|
|
281
|
-
return targetId;
|
|
282
|
-
}
|
|
283
|
-
export async function activateTab(targetId) {
|
|
284
|
-
try {
|
|
285
|
-
const anchor = await getAnyTab();
|
|
286
|
-
await cdp([
|
|
287
|
-
"evalraw",
|
|
288
|
-
anchor,
|
|
289
|
-
"Target.activateTarget",
|
|
290
|
-
JSON.stringify({ targetId })
|
|
291
|
-
]);
|
|
292
|
-
} catch {}
|
|
293
|
-
}
|
|
294
|
-
export async function closeTab(targetId) {
|
|
295
|
-
try {
|
|
296
|
-
const anchor = await getAnyTab();
|
|
297
|
-
await cdp([
|
|
298
|
-
"evalraw",
|
|
299
|
-
anchor,
|
|
300
|
-
"Target.closeTarget",
|
|
301
|
-
JSON.stringify({ targetId })
|
|
302
|
-
]);
|
|
303
|
-
} catch {}
|
|
304
|
-
}
|
|
305
|
-
export async function closeTabs(targetIds = []) {
|
|
306
|
-
await Promise.all(targetIds.filter(Boolean).map((tid) => closeTab(tid).catch(() => {})));
|
|
307
|
-
if (targetIds.length > 0) {
|
|
308
|
-
await cdp(["list"]).catch(() => null);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
export function getFullTabFromCache(engine, engineDomains) {
|
|
312
|
-
try {
|
|
313
|
-
if (!existsSync(PAGES_CACHE))
|
|
314
|
-
return null;
|
|
315
|
-
const pages = JSON.parse(readFileSync(PAGES_CACHE, "utf8"));
|
|
316
|
-
const found = pages.find((p) => p.url.includes(engineDomains[engine]));
|
|
317
|
-
return found ? found.targetId : null;
|
|
318
|
-
} catch {
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
export function probeGreedyChrome(timeoutMs = 3000) {
|
|
323
|
-
return new Promise((resolve) => {
|
|
324
|
-
const req = http.get(`http://localhost:${GREEDY_PORT}/json/version`, (res) => {
|
|
325
|
-
res.resume();
|
|
326
|
-
resolve(res.statusCode === 200);
|
|
327
|
-
});
|
|
328
|
-
req.on("error", () => resolve(false));
|
|
329
|
-
req.setTimeout(timeoutMs, () => {
|
|
330
|
-
req.destroy();
|
|
331
|
-
resolve(false);
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
async function waitForPortDown(timeoutMs = 3000, intervalMs = 200) {
|
|
336
|
-
const deadline = Date.now() + timeoutMs;
|
|
337
|
-
while (Date.now() < deadline) {
|
|
338
|
-
if (!await probeGreedyChrome(300))
|
|
339
|
-
return true;
|
|
340
|
-
await new Promise((r) => setTimeout(r, intervalMs));
|
|
341
|
-
}
|
|
342
|
-
return !await probeGreedyChrome(300);
|
|
343
|
-
}
|
|
344
|
-
export async function refreshPortFile() {
|
|
345
|
-
const LOCK_FILE = `${ACTIVE_PORT_FILE}.lock`;
|
|
346
|
-
const TEMP_FILE = `${ACTIVE_PORT_FILE}.tmp`;
|
|
347
|
-
const LOCK_STALE_MS = 5000;
|
|
348
|
-
const LOCK_WAIT_MS = 1000;
|
|
349
|
-
const lockAcquired = await new Promise((resolve) => {
|
|
350
|
-
const start = Date.now();
|
|
351
|
-
const tryLock = () => {
|
|
352
|
-
try {
|
|
353
|
-
const payload = JSON.stringify({ pid: process.pid, ts: Date.now() });
|
|
354
|
-
writeFileSync(LOCK_FILE, payload, { encoding: "utf8", flag: "wx" });
|
|
355
|
-
resolve(true);
|
|
356
|
-
} catch (e) {
|
|
357
|
-
if (e?.code !== "EEXIST") {
|
|
358
|
-
if (Date.now() - start < LOCK_WAIT_MS) {
|
|
359
|
-
setTimeout(tryLock, 50);
|
|
360
|
-
} else {
|
|
361
|
-
resolve(false);
|
|
362
|
-
}
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
try {
|
|
366
|
-
const lockRaw = readFileSync(LOCK_FILE, "utf8").trim();
|
|
367
|
-
const parsed = lockRaw.startsWith("{") ? JSON.parse(lockRaw) : { ts: Number(lockRaw) };
|
|
368
|
-
const lockTime = Number(parsed?.ts) || 0;
|
|
369
|
-
if (lockTime > 0 && Date.now() - lockTime > LOCK_STALE_MS) {
|
|
370
|
-
try {
|
|
371
|
-
unlinkSync(LOCK_FILE);
|
|
372
|
-
} catch {}
|
|
373
|
-
}
|
|
374
|
-
if (Date.now() - start < LOCK_WAIT_MS) {
|
|
375
|
-
setTimeout(tryLock, 50);
|
|
376
|
-
} else {
|
|
377
|
-
resolve(false);
|
|
378
|
-
}
|
|
379
|
-
} catch {
|
|
380
|
-
if (Date.now() - start < LOCK_WAIT_MS) {
|
|
381
|
-
setTimeout(tryLock, 50);
|
|
382
|
-
} else {
|
|
383
|
-
resolve(false);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
tryLock();
|
|
389
|
-
});
|
|
390
|
-
try {
|
|
391
|
-
const body = await new Promise((res, rej) => {
|
|
392
|
-
const req = http.get(`http://localhost:${GREEDY_PORT}/json/version`, (r) => {
|
|
393
|
-
let b = "";
|
|
394
|
-
r.on("data", (d) => b += d);
|
|
395
|
-
r.on("end", () => res(b));
|
|
396
|
-
});
|
|
397
|
-
req.on("error", rej);
|
|
398
|
-
req.setTimeout(3000, () => {
|
|
399
|
-
req.destroy();
|
|
400
|
-
rej(new Error("timeout"));
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
const { webSocketDebuggerUrl } = JSON.parse(body);
|
|
404
|
-
const wsPath = new URL(webSocketDebuggerUrl).pathname;
|
|
405
|
-
if (lockAcquired) {
|
|
406
|
-
writeFileSync(TEMP_FILE, `${GREEDY_PORT}
|
|
407
|
-
${wsPath}`, "utf8");
|
|
408
|
-
try {
|
|
409
|
-
unlinkSync(ACTIVE_PORT_FILE);
|
|
410
|
-
} catch {}
|
|
411
|
-
renameSync(TEMP_FILE, ACTIVE_PORT_FILE);
|
|
412
|
-
}
|
|
413
|
-
} catch {} finally {
|
|
414
|
-
if (lockAcquired) {
|
|
415
|
-
try {
|
|
416
|
-
unlinkSync(LOCK_FILE);
|
|
417
|
-
} catch {}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
export async function ensureChrome() {
|
|
422
|
-
if (process.env.GREEDY_SEARCH_RESEARCH_CHILD && await probeGreedyChrome()) {
|
|
423
|
-
await refreshPortFile();
|
|
424
|
-
try {
|
|
425
|
-
const md = readMetadata();
|
|
426
|
-
if (md) {
|
|
427
|
-
touchActivityBL(md);
|
|
428
|
-
registerClient(md);
|
|
429
|
-
}
|
|
430
|
-
} catch {}
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
cleanupStaleSessions();
|
|
434
|
-
const wasKilled = await checkAndKillIdle();
|
|
435
|
-
let ready = wasKilled ? false : await probeGreedyChrome();
|
|
436
|
-
if (!ready && !wasKilled) {
|
|
437
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
438
|
-
ready = await probeGreedyChrome();
|
|
439
|
-
}
|
|
440
|
-
let forceRelaunch = false;
|
|
441
|
-
if (ready) {
|
|
442
|
-
const headless = isChromeHeadless();
|
|
443
|
-
const wantsVisible = process.env.GREEDY_SEARCH_VISIBLE === "1";
|
|
444
|
-
if (!wantsVisible && !headless) {
|
|
445
|
-
process.stderr.write(`[greedysearch] Visible Chrome detected — switching to headless mode...
|
|
446
|
-
`);
|
|
447
|
-
await killHeadlessChrome();
|
|
448
|
-
await waitForPortDown();
|
|
449
|
-
forceRelaunch = true;
|
|
450
|
-
} else if (wantsVisible && headless) {
|
|
451
|
-
process.stderr.write(`[greedysearch] Headless Chrome detected — switching to visible mode...
|
|
452
|
-
`);
|
|
453
|
-
await killHeadlessChrome();
|
|
454
|
-
await waitForPortDown();
|
|
455
|
-
forceRelaunch = true;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
const readyAfterModeCheck = forceRelaunch ? false : await probeGreedyChrome();
|
|
459
|
-
if (readyAfterModeCheck) {
|
|
460
|
-
await refreshPortFile();
|
|
461
|
-
try {
|
|
462
|
-
const md = readMetadata();
|
|
463
|
-
if (md) {
|
|
464
|
-
touchActivityBL(md);
|
|
465
|
-
registerClient(md);
|
|
466
|
-
}
|
|
467
|
-
} catch {}
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
const lock = acquireLaunchLock();
|
|
471
|
-
if (!lock.acquired) {
|
|
472
|
-
const deadline = Date.now() + 12000;
|
|
473
|
-
let reReady = false;
|
|
474
|
-
while (Date.now() < deadline) {
|
|
475
|
-
reReady = await probeGreedyChrome(500);
|
|
476
|
-
if (reReady)
|
|
477
|
-
break;
|
|
478
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
479
|
-
}
|
|
480
|
-
if (reReady) {
|
|
481
|
-
await refreshPortFile();
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
try {
|
|
486
|
-
const reCheck = await probeGreedyChrome(1000);
|
|
487
|
-
if (reCheck) {
|
|
488
|
-
await refreshPortFile();
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
process.stderr.write(`GreedySearch Chrome not running on port ${GREEDY_PORT} — auto-launching...
|
|
492
|
-
`);
|
|
493
|
-
const launchScript = resolveGreedySearchLaunchScript({
|
|
494
|
-
moduleDir: __dir,
|
|
495
|
-
entrypoint: process.argv[1],
|
|
496
|
-
env: process.env
|
|
497
|
-
});
|
|
498
|
-
const launchArgs = [launchScript];
|
|
499
|
-
if (process.env.GREEDY_SEARCH_VISIBLE !== "1")
|
|
500
|
-
launchArgs.push("--headless");
|
|
501
|
-
await new Promise((resolve, reject) => {
|
|
502
|
-
const proc = spawn(nodeRuntimeCommand(), launchArgs, {
|
|
503
|
-
stdio: ["ignore", process.stderr, process.stderr]
|
|
504
|
-
});
|
|
505
|
-
proc.on("close", (code) => code === 0 ? resolve() : reject(new Error("launch.mjs failed")));
|
|
506
|
-
});
|
|
507
|
-
} finally {
|
|
508
|
-
lock.release();
|
|
509
|
-
}
|
|
510
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { nodeRuntimeCommand } from "../utils/node-runtime.mjs";
|
|
3
|
-
import { ENGINES, GREEDY_PROFILE_DIR } from "./constants.mjs";
|
|
4
|
-
import {
|
|
5
|
-
moduleDirectory,
|
|
6
|
-
resolveGreedySearchExtractorScript
|
|
7
|
-
} from "./paths.mjs";
|
|
8
|
-
|
|
9
|
-
export { ENGINES };
|
|
10
|
-
const __dir = moduleDirectory(import.meta.url);
|
|
11
|
-
export function runExtractor(script, query, tabPrefix = null, short = false, timeoutMs = null, locale = null) {
|
|
12
|
-
if (timeoutMs === null) {
|
|
13
|
-
timeoutMs = script.includes("logically") ? 120000 : script.includes("chatgpt") ? 80000 : script.includes("gemini") ? 70000 : 60000;
|
|
14
|
-
}
|
|
15
|
-
const extraArgs = [
|
|
16
|
-
...tabPrefix ? ["--tab", tabPrefix] : [],
|
|
17
|
-
...short ? ["--short"] : [],
|
|
18
|
-
...locale ? ["--locale", locale] : []
|
|
19
|
-
];
|
|
20
|
-
return new Promise((resolve, reject) => {
|
|
21
|
-
const extractorScript = resolveGreedySearchExtractorScript(script, {
|
|
22
|
-
moduleDir: __dir
|
|
23
|
-
});
|
|
24
|
-
const proc = spawn(nodeRuntimeCommand(), [extractorScript, "--stdin", ...extraArgs], {
|
|
25
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
26
|
-
env: { ...process.env, CDP_PROFILE_DIR: GREEDY_PROFILE_DIR }
|
|
27
|
-
});
|
|
28
|
-
proc.stdin.write(query);
|
|
29
|
-
proc.stdin.end();
|
|
30
|
-
let out = "";
|
|
31
|
-
let err = "";
|
|
32
|
-
proc.stdout.on("data", (d) => out += d);
|
|
33
|
-
proc.stderr.on("data", (d) => {
|
|
34
|
-
err += d;
|
|
35
|
-
if (process.env.GREEDY_SEARCH_CHILD_STDERR !== "0") {
|
|
36
|
-
process.stderr.write(d);
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
const t = setTimeout(() => {
|
|
40
|
-
proc.kill();
|
|
41
|
-
const tailLines = (s, n = 20) => String(s ?? "").split(/\r?\n/).filter(Boolean).slice(-n).join(`
|
|
42
|
-
`);
|
|
43
|
-
let envelope = null;
|
|
44
|
-
try {
|
|
45
|
-
const parsed = JSON.parse(out.trim());
|
|
46
|
-
if (parsed._envelope)
|
|
47
|
-
envelope = parsed._envelope;
|
|
48
|
-
} catch {}
|
|
49
|
-
const errObj = new Error(`${script} timed out after ${timeoutMs / 1000}s` + (envelope?.lastStage ? ` (last stage: ${envelope.lastStage})` : ""));
|
|
50
|
-
errObj.engineScript = script;
|
|
51
|
-
errObj.lastStage = envelope?.lastStage || null;
|
|
52
|
-
errObj.partialErr = tailLines(err);
|
|
53
|
-
errObj.partialOut = tailLines(out);
|
|
54
|
-
reject(errObj);
|
|
55
|
-
}, timeoutMs);
|
|
56
|
-
proc.on("close", (code) => {
|
|
57
|
-
clearTimeout(t);
|
|
58
|
-
if (code === 0) {
|
|
59
|
-
try {
|
|
60
|
-
resolve(JSON.parse(out.trim()));
|
|
61
|
-
} catch {
|
|
62
|
-
reject(new Error(`bad JSON from ${script}: ${out.slice(0, 100)}`));
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
let envelope = null;
|
|
66
|
-
try {
|
|
67
|
-
const parsed = JSON.parse(out.trim());
|
|
68
|
-
if (parsed._envelope)
|
|
69
|
-
envelope = parsed._envelope;
|
|
70
|
-
} catch {}
|
|
71
|
-
const msg = err.trim() || `extractor exit ${code}`;
|
|
72
|
-
const errObj = new Error(msg);
|
|
73
|
-
if (envelope)
|
|
74
|
-
errObj.envelope = envelope;
|
|
75
|
-
reject(errObj);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
}
|