@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/bin/search.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- // search.mjs — unified CLI for GreedySearch extractors
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 fan-out to all engines in parallel
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("[greedysearch] --deep-research is deprecated; use --depth standard (now default)\n");
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("[greedysearch] --synthesize is deprecated; synthesis is now default for multi-engine\n");
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
- main();
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
+ });