@apmantza/greedysearch-pi 1.8.0 → 1.8.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.
- package/CHANGELOG.md +10 -0
- package/README.md +17 -1
- package/bin/launch.mjs +366 -288
- package/bin/search.mjs +148 -20
- package/extractors/common.mjs +291 -279
- package/extractors/gemini.mjs +146 -145
- package/extractors/google-ai.mjs +125 -124
- package/extractors/perplexity.mjs +145 -141
- package/extractors/selectors.mjs +54 -52
- package/index.ts +179 -35
- package/package.json +53 -46
- package/src/github.mjs +237 -237
- package/src/search/chrome.mjs +222 -222
- package/src/search/constants.mjs +37 -37
- package/src/search/defaults.mjs +14 -14
- package/src/search/engines.mjs +6 -2
- package/src/search/fetch-source.mjs +229 -229
- package/src/search/output.mjs +58 -58
- package/src/search/sources.mjs +445 -445
- package/src/search/synthesis-runner.mjs +63 -63
- package/src/search/synthesis.mjs +51 -40
- package/src/tools/deep-research-handler.ts +36 -36
- package/src/tools/greedy-search-handler.ts +57 -57
- package/src/tools/shared.ts +130 -130
- package/src/types.ts +103 -103
- package/test.mjs +377 -0
package/bin/search.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
// search.mjs - unified CLI for GreedySearch extractors
|
|
3
4
|
//
|
|
4
5
|
// Usage:
|
|
5
6
|
// node search.mjs <engine> "<query>"
|
|
@@ -10,7 +11,7 @@
|
|
|
10
11
|
// bing | copilot | b
|
|
11
12
|
// google | g
|
|
12
13
|
// gemini | gem
|
|
13
|
-
// all
|
|
14
|
+
// all - fan-out to all engines in parallel
|
|
14
15
|
//
|
|
15
16
|
// Output: JSON to stdout, errors to stderr
|
|
16
17
|
//
|
|
@@ -19,30 +20,45 @@
|
|
|
19
20
|
// node search.mjs gem "latest React features"
|
|
20
21
|
// node search.mjs all "how does TCP congestion control work"
|
|
21
22
|
|
|
23
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
24
|
+
// Config file for user defaults
|
|
25
|
+
import { homedir } from "node:os";
|
|
26
|
+
import { join } from "node:path";
|
|
22
27
|
import {
|
|
23
|
-
ALL_ENGINES,
|
|
24
|
-
ENGINES,
|
|
25
|
-
} from "../src/search/constants.mjs";
|
|
26
|
-
import {
|
|
27
|
-
buildSourceRegistry,
|
|
28
|
-
mergeFetchDataIntoSources,
|
|
29
|
-
} from "../src/search/sources.mjs";
|
|
30
|
-
import { buildConfidence } from "../src/search/synthesis.mjs";
|
|
31
|
-
import { synthesizeWithGemini } from "../src/search/synthesis-runner.mjs";
|
|
32
|
-
import {
|
|
33
|
-
cdp,
|
|
34
|
-
ensureChrome,
|
|
35
|
-
openNewTab,
|
|
36
28
|
activateTab,
|
|
29
|
+
cdp,
|
|
37
30
|
closeTab,
|
|
38
31
|
closeTabs,
|
|
32
|
+
ensureChrome,
|
|
33
|
+
openNewTab,
|
|
39
34
|
} from "../src/search/chrome.mjs";
|
|
35
|
+
import { ALL_ENGINES, ENGINES } from "../src/search/constants.mjs";
|
|
40
36
|
import { runExtractor } from "../src/search/engines.mjs";
|
|
41
37
|
import {
|
|
42
38
|
fetchMultipleSources,
|
|
43
39
|
fetchTopSource,
|
|
44
40
|
} from "../src/search/fetch-source.mjs";
|
|
45
41
|
import { writeOutput } from "../src/search/output.mjs";
|
|
42
|
+
import {
|
|
43
|
+
buildSourceRegistry,
|
|
44
|
+
mergeFetchDataIntoSources,
|
|
45
|
+
} from "../src/search/sources.mjs";
|
|
46
|
+
import { buildConfidence } from "../src/search/synthesis.mjs";
|
|
47
|
+
import { synthesizeWithGemini } from "../src/search/synthesis-runner.mjs";
|
|
48
|
+
|
|
49
|
+
const CONFIG_DIR = join(homedir(), ".config", "greedysearch");
|
|
50
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
51
|
+
|
|
52
|
+
function loadUserConfig() {
|
|
53
|
+
try {
|
|
54
|
+
if (existsSync(CONFIG_FILE)) {
|
|
55
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Ignore errors
|
|
59
|
+
}
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
46
62
|
|
|
47
63
|
// ─── Main ──────────────────────────────────────────────────────────────────
|
|
48
64
|
|
|
@@ -61,6 +77,11 @@ async function main() {
|
|
|
61
77
|
" --deep-research Deprecated: source fetching is now default",
|
|
62
78
|
" --fetch-top-source Fetch content from top source",
|
|
63
79
|
" --inline Output JSON to stdout (for piping)",
|
|
80
|
+
" --locale <lang> Force results language (en, de, fr, etc.)",
|
|
81
|
+
"",
|
|
82
|
+
"Environment:",
|
|
83
|
+
" GREEDY_SEARCH_LOCALE Default locale (default: en)",
|
|
84
|
+
" GREEDY_SEARCH_VISIBLE Set to 1 to show Chrome window",
|
|
64
85
|
"",
|
|
65
86
|
"Examples:",
|
|
66
87
|
' node search.mjs all "Node.js streams" # Default: sources + synthesis',
|
|
@@ -92,13 +113,17 @@ async function main() {
|
|
|
92
113
|
// --deep-research / --deep flags map to deep mode (backward compat)
|
|
93
114
|
if (args.includes("--deep-research")) {
|
|
94
115
|
depth = "standard";
|
|
95
|
-
process.stderr.write(
|
|
116
|
+
process.stderr.write(
|
|
117
|
+
"[greedysearch] --deep-research is deprecated; use --depth standard (now default)\n",
|
|
118
|
+
);
|
|
96
119
|
}
|
|
97
120
|
if (args.includes("--deep")) {
|
|
98
121
|
depth = "deep";
|
|
99
122
|
}
|
|
100
123
|
if (args.includes("--synthesize")) {
|
|
101
|
-
process.stderr.write(
|
|
124
|
+
process.stderr.write(
|
|
125
|
+
"[greedysearch] --synthesize is deprecated; synthesis is now default for multi-engine\n",
|
|
126
|
+
);
|
|
102
127
|
}
|
|
103
128
|
|
|
104
129
|
const full = args.includes("--full");
|
|
@@ -107,6 +132,20 @@ async function main() {
|
|
|
107
132
|
const inline = args.includes("--inline");
|
|
108
133
|
const outIdx = args.indexOf("--out");
|
|
109
134
|
const outFile = outIdx !== -1 ? args[outIdx + 1] : null;
|
|
135
|
+
|
|
136
|
+
// Locale handling: CLI flag > env var > config file > default (en)
|
|
137
|
+
const localeIdx = args.indexOf("--locale");
|
|
138
|
+
const envLocale = process.env.GREEDY_SEARCH_LOCALE;
|
|
139
|
+
const userConfig = loadUserConfig();
|
|
140
|
+
let locale = "en"; // Default to English
|
|
141
|
+
|
|
142
|
+
if (localeIdx !== -1 && args[localeIdx + 1]) {
|
|
143
|
+
locale = args[localeIdx + 1];
|
|
144
|
+
} else if (envLocale) {
|
|
145
|
+
locale = envLocale;
|
|
146
|
+
} else if (userConfig.locale) {
|
|
147
|
+
locale = userConfig.locale;
|
|
148
|
+
}
|
|
110
149
|
const rest = args.filter(
|
|
111
150
|
(a, i) =>
|
|
112
151
|
a !== "--full" &&
|
|
@@ -140,7 +179,7 @@ async function main() {
|
|
|
140
179
|
try {
|
|
141
180
|
const results = await Promise.allSettled(
|
|
142
181
|
ALL_ENGINES.map((e, i) =>
|
|
143
|
-
runExtractor(ENGINES[e], query, engineTabs[i], short)
|
|
182
|
+
runExtractor(ENGINES[e], query, engineTabs[i], short, null, locale)
|
|
144
183
|
.then((r) => {
|
|
145
184
|
process.stderr.write(`PROGRESS:${e}:done\n`);
|
|
146
185
|
return { engine: e, ...r };
|
|
@@ -236,7 +275,7 @@ async function main() {
|
|
|
236
275
|
}
|
|
237
276
|
|
|
238
277
|
try {
|
|
239
|
-
const result = await runExtractor(script, query, null, short);
|
|
278
|
+
const result = await runExtractor(script, query, null, short, null, locale);
|
|
240
279
|
if (fetchSource && result.sources?.length > 0) {
|
|
241
280
|
result.topSource = await fetchTopSource(result.sources[0].url);
|
|
242
281
|
}
|
|
@@ -257,4 +296,93 @@ function pickTopSource(out) {
|
|
|
257
296
|
return null;
|
|
258
297
|
}
|
|
259
298
|
|
|
260
|
-
|
|
299
|
+
/**
|
|
300
|
+
* Minimize Chrome window via CDP after search completes.
|
|
301
|
+
* Called at the end of search to keep window minimized.
|
|
302
|
+
*/
|
|
303
|
+
async function minimizeChrome() {
|
|
304
|
+
if (process.env.GREEDY_SEARCH_VISIBLE === "1") return;
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const http = await import("node:http");
|
|
308
|
+
const version = await new Promise((resolve, reject) => {
|
|
309
|
+
http
|
|
310
|
+
.get(`http://localhost:9222/json/version`, (res) => {
|
|
311
|
+
let body = "";
|
|
312
|
+
res.on("data", (d) => (body += d));
|
|
313
|
+
res.on("end", () => resolve(JSON.parse(body)));
|
|
314
|
+
})
|
|
315
|
+
.on("error", reject);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const wsUrl = version.webSocketDebuggerUrl;
|
|
319
|
+
const WebSocket = globalThis.WebSocket;
|
|
320
|
+
if (!WebSocket) return;
|
|
321
|
+
|
|
322
|
+
const ws = new WebSocket(wsUrl);
|
|
323
|
+
let requestId = 0;
|
|
324
|
+
const pending = new Map();
|
|
325
|
+
|
|
326
|
+
ws.onopen = () => {
|
|
327
|
+
const id = ++requestId;
|
|
328
|
+
pending.set(id, {
|
|
329
|
+
resolve: (result) => {
|
|
330
|
+
const targets = result.targetInfos || [];
|
|
331
|
+
const pageTarget = targets.find((t) => t.type === "page");
|
|
332
|
+
if (!pageTarget) {
|
|
333
|
+
ws.close();
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const winId = ++requestId;
|
|
338
|
+
pending.set(winId, {
|
|
339
|
+
resolve: (winResult) => {
|
|
340
|
+
const windowId = winResult.windowId;
|
|
341
|
+
const minId = ++requestId;
|
|
342
|
+
pending.set(minId, { resolve: () => {}, reject: () => {} });
|
|
343
|
+
ws.send(
|
|
344
|
+
JSON.stringify({
|
|
345
|
+
id: minId,
|
|
346
|
+
method: "Browser.setWindowBounds",
|
|
347
|
+
params: { windowId, bounds: { windowState: "minimized" } },
|
|
348
|
+
}),
|
|
349
|
+
);
|
|
350
|
+
setTimeout(() => ws.close(), 500);
|
|
351
|
+
},
|
|
352
|
+
reject: () => ws.close(),
|
|
353
|
+
});
|
|
354
|
+
ws.send(
|
|
355
|
+
JSON.stringify({
|
|
356
|
+
id: winId,
|
|
357
|
+
method: "Browser.getWindowForTarget",
|
|
358
|
+
params: { targetId: pageTarget.targetId },
|
|
359
|
+
}),
|
|
360
|
+
);
|
|
361
|
+
},
|
|
362
|
+
reject: () => ws.close(),
|
|
363
|
+
});
|
|
364
|
+
ws.send(JSON.stringify({ id, method: "Target.getTargets", params: {} }));
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
ws.onmessage = (event) => {
|
|
368
|
+
const msg = JSON.parse(event.data);
|
|
369
|
+
if (msg.id && pending.has(msg.id)) {
|
|
370
|
+
const { resolve, reject } = pending.get(msg.id);
|
|
371
|
+
pending.delete(msg.id);
|
|
372
|
+
if (msg.error) reject?.(msg.error);
|
|
373
|
+
else resolve?.(msg.result);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
setTimeout(() => ws.close(), 3000);
|
|
378
|
+
} catch {
|
|
379
|
+
// Best-effort
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
main().finally(async () => {
|
|
384
|
+
// Ensure window is minimized after search completes
|
|
385
|
+
await minimizeChrome();
|
|
386
|
+
// Give minimize time to complete before exit
|
|
387
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
388
|
+
});
|