@apmantza/greedysearch-pi 1.9.0 → 1.9.1
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 +29 -0
- package/bin/launch-visible.mjs +65 -0
- package/bin/launch.mjs +440 -417
- package/bin/search.mjs +7 -12
- package/extractors/common.mjs +49 -0
- package/extractors/selectors.mjs +55 -54
- package/index.ts +175 -177
- package/package.json +3 -2
- package/skills/greedy-search/skill.md +2 -7
- package/src/fetcher.mjs +666 -652
- package/src/formatters/synthesis.ts +1 -5
- package/src/search/output.mjs +23 -1
- package/src/search/sources.mjs +466 -466
- package/src/tools/greedy-search-handler.ts +226 -124
package/bin/search.mjs
CHANGED
|
@@ -25,7 +25,6 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
25
25
|
import { homedir } from "node:os";
|
|
26
26
|
import { join } from "node:path";
|
|
27
27
|
import {
|
|
28
|
-
activateTab,
|
|
29
28
|
cdp,
|
|
30
29
|
closeTab,
|
|
31
30
|
closeTabs,
|
|
@@ -388,11 +387,13 @@ async function main() {
|
|
|
388
387
|
// in the shared profile and future headless runs will reuse them.
|
|
389
388
|
}
|
|
390
389
|
} finally {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
//
|
|
390
|
+
if (keepVisibleForHuman) {
|
|
391
|
+
// User must interact — keep visible Chrome open but out of the way
|
|
392
|
+
minimizeChrome().catch(() => {});
|
|
393
|
+
} else {
|
|
394
|
+
// Switch back to headless for synthesis + source fetch.
|
|
395
|
+
// killHeadlessChrome() sends Browser.close first so Chrome flushes
|
|
396
|
+
// its cookie database before the force-kill — cookies are preserved.
|
|
396
397
|
await closeTabs(retryTabs);
|
|
397
398
|
process.stderr.write(
|
|
398
399
|
"[greedysearch] Switching back to headless Chrome...\n",
|
|
@@ -405,11 +406,6 @@ async function main() {
|
|
|
405
406
|
}
|
|
406
407
|
}
|
|
407
408
|
|
|
408
|
-
// Minimize visible Chrome if it was kept alive (recovery succeeded or needs-human)
|
|
409
|
-
if (keepVisibleForHuman || recovered > 0) {
|
|
410
|
-
minimizeChrome().catch(() => {});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
409
|
// Clear engineTabs — finally{} closeTabs handles empty arrays gracefully
|
|
414
410
|
engineTabs.length = 0;
|
|
415
411
|
}
|
|
@@ -422,7 +418,6 @@ async function main() {
|
|
|
422
418
|
let geminiTabPromise = null;
|
|
423
419
|
if (depth !== "fast") {
|
|
424
420
|
geminiTabPromise = openNewTab("https://gemini.google.com/app")
|
|
425
|
-
.then((tab) => { activateTab(tab).catch(() => {}); return tab; })
|
|
426
421
|
.catch(() => null);
|
|
427
422
|
}
|
|
428
423
|
|
package/extractors/common.mjs
CHANGED
|
@@ -195,6 +195,55 @@ export async function injectHeadlessStealth(tab) {
|
|
|
195
195
|
};
|
|
196
196
|
} catch(_) {}
|
|
197
197
|
|
|
198
|
+
// ── window outer dimensions ──────────────────────────
|
|
199
|
+
// outerWidth/Height = 0 in headless — a well-known bot signal.
|
|
200
|
+
// Mirror innerWidth/Height (set by --window-size flag) so the ratio is sane.
|
|
201
|
+
try {
|
|
202
|
+
if (!window.outerWidth) Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth || 1920, configurable: true });
|
|
203
|
+
if (!window.outerHeight) Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight || 1080, configurable: true });
|
|
204
|
+
} catch(_) {}
|
|
205
|
+
|
|
206
|
+
// ── screen properties ─────────────────────────────────
|
|
207
|
+
try {
|
|
208
|
+
if (!screen.colorDepth) Object.defineProperty(screen, 'colorDepth', { get: () => 24, configurable: true });
|
|
209
|
+
if (!screen.pixelDepth) Object.defineProperty(screen, 'pixelDepth', { get: () => 24, configurable: true });
|
|
210
|
+
} catch(_) {}
|
|
211
|
+
|
|
212
|
+
// ── navigator.userAgentData (UA Client Hints) ─────────
|
|
213
|
+
// Derive version from the UA string already set by --user-agent flag so the
|
|
214
|
+
// two APIs are always consistent. Removes any "HeadlessChrome" brand entry.
|
|
215
|
+
try {
|
|
216
|
+
var _uaMajor = (navigator.userAgent.match(/Chrome\/(\d+)/) || [])[1] || '136';
|
|
217
|
+
var _uaFull = (navigator.userAgent.match(/Chrome\/([\d.]+)/) || [])[1] || (_uaMajor + '.0.0.0');
|
|
218
|
+
var _brands = [
|
|
219
|
+
{ brand: 'Not)A;Brand', version: '99' },
|
|
220
|
+
{ brand: 'Google Chrome', version: _uaMajor },
|
|
221
|
+
{ brand: 'Chromium', version: _uaMajor },
|
|
222
|
+
];
|
|
223
|
+
Object.defineProperty(navigator, 'userAgentData', {
|
|
224
|
+
get: function() {
|
|
225
|
+
return {
|
|
226
|
+
brands: _brands, mobile: false, platform: 'Windows',
|
|
227
|
+
getHighEntropyValues: function() {
|
|
228
|
+
return Promise.resolve({
|
|
229
|
+
architecture: 'x86', bitness: '64',
|
|
230
|
+
brands: _brands,
|
|
231
|
+
fullVersionList: [
|
|
232
|
+
{ brand: 'Not)A;Brand', version: '99.0.0.0' },
|
|
233
|
+
{ brand: 'Google Chrome', version: _uaFull },
|
|
234
|
+
{ brand: 'Chromium', version: _uaFull },
|
|
235
|
+
],
|
|
236
|
+
mobile: false, model: '', platform: 'Windows',
|
|
237
|
+
platformVersion: '15.0.0', uaFullVersion: _uaFull, wow64: false,
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
toJSON: function() { return { brands: _brands, mobile: false, platform: 'Windows' }; },
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
configurable: true,
|
|
244
|
+
});
|
|
245
|
+
} catch(_) {}
|
|
246
|
+
|
|
198
247
|
// ── CDP Runtime serialization guard ──────────────────
|
|
199
248
|
// Sites detect CDP by putting a getter on Error.prototype.stack
|
|
200
249
|
// and checking if console.log triggers it (only happens when
|
package/extractors/selectors.mjs
CHANGED
|
@@ -1,54 +1,55 @@
|
|
|
1
|
-
// extractors/selectors.mjs
|
|
2
|
-
// Centralized CSS selectors for all engines.
|
|
3
|
-
// Update selectors here when a site changes its UI.
|
|
4
|
-
|
|
5
|
-
export const SELECTORS = {
|
|
6
|
-
// ──────────────────────────────────────────────
|
|
7
|
-
// Perplexity (perplexity.ai)
|
|
8
|
-
// ──────────────────────────────────────────────
|
|
9
|
-
perplexity: {
|
|
10
|
-
input: "#ask-input",
|
|
11
|
-
// Note: copy button found via JS in extractor (language-agnostic)
|
|
12
|
-
copyButton: null,
|
|
13
|
-
sourceItem: "[data-pplx-citation-url]",
|
|
14
|
-
sourceLink: "a",
|
|
15
|
-
consent: "#onetrust-accept-btn-handler",
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
// ──────────────────────────────────────────────
|
|
19
|
-
// Bing Copilot (copilot.microsoft.com)
|
|
20
|
-
// ──────────────────────────────────────────────
|
|
21
|
-
bing: {
|
|
22
|
-
input: "#userInput",
|
|
23
|
-
copyButton: 'button[data-testid="copy-ai-message-button"]',
|
|
24
|
-
sourceLink: 'a[href^="http"][target="_blank"]',
|
|
25
|
-
sourceExclude: "copilot.microsoft.com",
|
|
26
|
-
consent: "#onetrust-accept-btn-handler",
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
// ──────────────────────────────────────────────
|
|
30
|
-
// Google AI Mode (google.com/search?udm=50)
|
|
31
|
-
// ──────────────────────────────────────────────
|
|
32
|
-
google: {
|
|
33
|
-
answerContainer: ".pWvJNd",
|
|
34
|
-
sourceLink: 'a[href^="http"]',
|
|
35
|
-
sourceExclude: ["google.", "gstatic", "googleapis"],
|
|
36
|
-
sourceHeadingParent: "[data-snhf]",
|
|
37
|
-
consent: '#L2AGLb, button[jsname="b3VHJd"], .tHlp8d',
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
// ──────────────────────────────────────────────
|
|
41
|
-
// Gemini (gemini.google.com/app)
|
|
42
|
-
// ──────────────────────────────────────────────
|
|
43
|
-
gemini: {
|
|
44
|
-
input: "rich-textarea .ql-editor",
|
|
45
|
-
// Language-agnostic: use Material icon data attributes (work across locales)
|
|
46
|
-
copyButton: 'button:has(mat-icon[data-mat-icon-name="content_copy"])',
|
|
47
|
-
sendButton: 'button:has(mat-icon[data-mat-icon-name="send"]), .send-button',
|
|
48
|
-
sourcesSidebarButton: "button.legacy-sources-sidebar-button",
|
|
49
|
-
sourcesExclude: ["gemini.google", "gstatic", "google.com/search"],
|
|
50
|
-
citationButtonPattern: 'button[aria-label*="citation from"]',
|
|
51
|
-
// For parsing citation aria-labels: "View source details for citation from {name}. Opens side panel."
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
1
|
+
// extractors/selectors.mjs
|
|
2
|
+
// Centralized CSS selectors for all engines.
|
|
3
|
+
// Update selectors here when a site changes its UI.
|
|
4
|
+
|
|
5
|
+
export const SELECTORS = {
|
|
6
|
+
// ──────────────────────────────────────────────
|
|
7
|
+
// Perplexity (perplexity.ai)
|
|
8
|
+
// ──────────────────────────────────────────────
|
|
9
|
+
perplexity: {
|
|
10
|
+
input: "#ask-input",
|
|
11
|
+
// Note: copy button found via JS in extractor (language-agnostic)
|
|
12
|
+
copyButton: null,
|
|
13
|
+
sourceItem: "[data-pplx-citation-url]",
|
|
14
|
+
sourceLink: "a",
|
|
15
|
+
consent: "#onetrust-accept-btn-handler",
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
// ──────────────────────────────────────────────
|
|
19
|
+
// Bing Copilot (copilot.microsoft.com)
|
|
20
|
+
// ──────────────────────────────────────────────
|
|
21
|
+
bing: {
|
|
22
|
+
input: "#userInput",
|
|
23
|
+
copyButton: 'button[data-testid="copy-ai-message-button"]',
|
|
24
|
+
sourceLink: 'a[href^="http"][target="_blank"]',
|
|
25
|
+
sourceExclude: "copilot.microsoft.com",
|
|
26
|
+
consent: "#onetrust-accept-btn-handler",
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// ──────────────────────────────────────────────
|
|
30
|
+
// Google AI Mode (google.com/search?udm=50)
|
|
31
|
+
// ──────────────────────────────────────────────
|
|
32
|
+
google: {
|
|
33
|
+
answerContainer: ".pWvJNd",
|
|
34
|
+
sourceLink: 'a[href^="http"]',
|
|
35
|
+
sourceExclude: ["google.", "gstatic", "googleapis"],
|
|
36
|
+
sourceHeadingParent: "[data-snhf]",
|
|
37
|
+
consent: '#L2AGLb, button[jsname="b3VHJd"], .tHlp8d',
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// ──────────────────────────────────────────────
|
|
41
|
+
// Gemini (gemini.google.com/app)
|
|
42
|
+
// ──────────────────────────────────────────────
|
|
43
|
+
gemini: {
|
|
44
|
+
input: "rich-textarea .ql-editor",
|
|
45
|
+
// Language-agnostic: use Material icon data attributes (work across locales)
|
|
46
|
+
copyButton: 'button:has(mat-icon[data-mat-icon-name="content_copy"])',
|
|
47
|
+
sendButton: 'button:has(mat-icon[data-mat-icon-name="send"]), .send-button',
|
|
48
|
+
sourcesSidebarButton: "button.legacy-sources-sidebar-button",
|
|
49
|
+
sourcesExclude: ["gemini.google", "gstatic", "google.com/search"],
|
|
50
|
+
citationButtonPattern: 'button[aria-label*="citation from"]',
|
|
51
|
+
// For parsing citation aria-labels: "View source details for citation from {name}. Opens side panel."
|
|
52
|
+
// Bounded + non-overlapping character classes to prevent ReDoS
|
|
53
|
+
citationNameRegex: /from\s{1,20}([^.]{1,200})\.\s/,
|
|
54
|
+
},
|
|
55
|
+
};
|
package/index.ts
CHANGED
|
@@ -1,177 +1,175 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GreedySearch Pi Extension
|
|
3
|
-
*
|
|
4
|
-
* Adds `greedy_search` tool to Pi.
|
|
5
|
-
* Use depth: "deep" for deep research (source fetching + synthesis + confidence).
|
|
6
|
-
*
|
|
7
|
-
* Reports streaming progress as each engine completes.
|
|
8
|
-
* Requires Chrome to be running (or it auto-launches a dedicated instance).
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { spawn } from "node:child_process";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
ctx.ui.notify(
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
proc.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
ctx.ui.notify(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
writeFileSync(USER_CONFIG_FILE, JSON.stringify(config, null, 2), "utf8");
|
|
177
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* GreedySearch Pi Extension
|
|
3
|
+
*
|
|
4
|
+
* Adds `greedy_search` tool to Pi.
|
|
5
|
+
* Use depth: "deep" for deep research (source fetching + synthesis + confidence).
|
|
6
|
+
*
|
|
7
|
+
* Reports streaming progress as each engine completes.
|
|
8
|
+
* Requires Chrome to be running (or it auto-launches a dedicated instance).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
|
|
18
|
+
import { registerGreedySearchTool } from "./src/tools/greedy-search-handler.js";
|
|
19
|
+
import { cdpAvailable } from "./src/tools/shared.js";
|
|
20
|
+
|
|
21
|
+
const __dir = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
|
|
23
|
+
export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
24
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
25
|
+
if (!cdpAvailable(__dir)) {
|
|
26
|
+
ctx.ui.notify(
|
|
27
|
+
"GreedySearch: cdp.mjs missing from package directory — try reinstalling: pi install git:github.com/apmantza/GreedySearch-pi",
|
|
28
|
+
"warning",
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ─── greedy_search ────────────────────────────────────────────────────────
|
|
34
|
+
registerGreedySearchTool(pi, __dir);
|
|
35
|
+
|
|
36
|
+
// ─── GreedySearch Chrome commands ─────────────────────────────────────────
|
|
37
|
+
pi.registerCommand("greedy-visible", {
|
|
38
|
+
description:
|
|
39
|
+
"Launch GreedySearch Chrome in visible mode for captcha/login/cookie setup.",
|
|
40
|
+
handler: async (_args, ctx) => {
|
|
41
|
+
await runChromeCommand([], ctx, "Visible GreedySearch Chrome launched.");
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
pi.registerCommand("greedy-status", {
|
|
46
|
+
description: "Show GreedySearch Chrome status.",
|
|
47
|
+
handler: async (_args, ctx) => {
|
|
48
|
+
await runChromeCommand(["--status"], ctx);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
pi.registerCommand("greedy-kill", {
|
|
53
|
+
description: "Stop GreedySearch Chrome.",
|
|
54
|
+
handler: async (_args, ctx) => {
|
|
55
|
+
await runChromeCommand(["--kill"], ctx, "GreedySearch Chrome stopped.");
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// ─── /set-greedy-locale command ───────────────────────────────────────────
|
|
60
|
+
pi.registerCommand("set-greedy-locale", {
|
|
61
|
+
description:
|
|
62
|
+
"Set default locale for GreedySearch results (e.g., /set-greedy-locale de, /set-greedy-locale --clear, /set-greedy-locale --show)",
|
|
63
|
+
handler: async (args, ctx) => {
|
|
64
|
+
const arg = args.trim() || "--show";
|
|
65
|
+
|
|
66
|
+
if (arg === "--show") {
|
|
67
|
+
const config = loadUserConfig();
|
|
68
|
+
if (config.locale) {
|
|
69
|
+
ctx.ui.notify(`Default locale: ${config.locale}`, "info");
|
|
70
|
+
} else {
|
|
71
|
+
ctx.ui.notify("No default locale (uses: en)", "info");
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (arg === "--clear") {
|
|
77
|
+
const config = loadUserConfig();
|
|
78
|
+
delete config.locale;
|
|
79
|
+
saveUserConfig(config);
|
|
80
|
+
ctx.ui.notify("Default locale cleared (now uses: en).", "info");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Set locale
|
|
85
|
+
const locale = arg.toLowerCase();
|
|
86
|
+
const VALID_LOCALES = [
|
|
87
|
+
"en",
|
|
88
|
+
"de",
|
|
89
|
+
"fr",
|
|
90
|
+
"es",
|
|
91
|
+
"it",
|
|
92
|
+
"pt",
|
|
93
|
+
"nl",
|
|
94
|
+
"pl",
|
|
95
|
+
"ru",
|
|
96
|
+
"ja",
|
|
97
|
+
"ko",
|
|
98
|
+
"zh",
|
|
99
|
+
"ar",
|
|
100
|
+
"hi",
|
|
101
|
+
"tr",
|
|
102
|
+
"sv",
|
|
103
|
+
"da",
|
|
104
|
+
"no",
|
|
105
|
+
"fi",
|
|
106
|
+
"cs",
|
|
107
|
+
"hu",
|
|
108
|
+
"ro",
|
|
109
|
+
"el",
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
if (!VALID_LOCALES.includes(locale)) {
|
|
113
|
+
ctx.ui.notify(
|
|
114
|
+
`Invalid locale "${locale}". Valid: ${VALID_LOCALES.join(", ")}`,
|
|
115
|
+
"error",
|
|
116
|
+
);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const config = loadUserConfig();
|
|
121
|
+
config.locale = locale;
|
|
122
|
+
saveUserConfig(config);
|
|
123
|
+
ctx.ui.notify(`Default locale set to: ${locale}`, "info");
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const USER_CONFIG_DIR = join(homedir(), ".config", "greedysearch");
|
|
129
|
+
const USER_CONFIG_FILE = join(USER_CONFIG_DIR, "config.json");
|
|
130
|
+
|
|
131
|
+
async function runChromeCommand(
|
|
132
|
+
args: string[],
|
|
133
|
+
ctx: any,
|
|
134
|
+
successMessage?: string,
|
|
135
|
+
): Promise<void> {
|
|
136
|
+
const visibleBin = join(__dir, "bin", "visible.mjs");
|
|
137
|
+
const { code, output } = await new Promise<{
|
|
138
|
+
code: number | null;
|
|
139
|
+
output: string;
|
|
140
|
+
}>((resolve) => {
|
|
141
|
+
const proc = spawn(process.execPath, [visibleBin, ...args], {
|
|
142
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
143
|
+
env: { ...process.env, GREEDY_SEARCH_VISIBLE: "1" },
|
|
144
|
+
});
|
|
145
|
+
let output = "";
|
|
146
|
+
proc.stdout.on("data", (d: Buffer) => (output += d.toString()));
|
|
147
|
+
proc.stderr.on("data", (d: Buffer) => (output += d.toString()));
|
|
148
|
+
proc.on("close", (code: number | null) => resolve({ code, output }));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (code === 0) {
|
|
152
|
+
ctx.ui.notify((successMessage || output.trim() || "Done.").trim(), "info");
|
|
153
|
+
} else {
|
|
154
|
+
ctx.ui.notify(
|
|
155
|
+
output.trim() || `GreedySearch Chrome command failed (${code})`,
|
|
156
|
+
"error",
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function loadUserConfig(): Record<string, string> {
|
|
162
|
+
try {
|
|
163
|
+
if (existsSync(USER_CONFIG_FILE)) {
|
|
164
|
+
return JSON.parse(readFileSync(USER_CONFIG_FILE, "utf8"));
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
// Ignore parse errors
|
|
168
|
+
}
|
|
169
|
+
return {};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function saveUserConfig(config: Record<string, string>): void {
|
|
173
|
+
mkdirSync(USER_CONFIG_DIR, { recursive: true });
|
|
174
|
+
writeFileSync(USER_CONFIG_FILE, JSON.stringify(config, null, 2), "utf8");
|
|
175
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apmantza/greedysearch-pi",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "Headless multi-engine AI search (Perplexity, Bing Copilot, Google AI) via browser automation -- NO API KEYS needed. Extracts answers with sources, optional synthesis. Grounded AI answers from real browser interactions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
"turndown": "^7.1.2"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@
|
|
52
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
53
|
+
"@earendil-works/pi-tui": "*",
|
|
53
54
|
"@sinclair/typebox": "*"
|
|
54
55
|
}
|
|
55
56
|
}
|
|
@@ -9,18 +9,13 @@ Use `greedy_search` for live web answers.
|
|
|
9
9
|
greedy_search({ query: "React 19 changes", depth: "standard" });
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
**Params:** `query` (required), `engine`: `all`|`perplexity`|`bing`|`google`|`gemini`, `depth`: `fast`|`standard`|`deep
|
|
12
|
+
**Params:** `query` (required), `engine`: `all`|`perplexity`|`bing`|`google`|`gemini`, `depth`: `fast`|`standard`|`deep`
|
|
13
13
|
|
|
14
14
|
**Depths:**
|
|
15
|
-
|
|
16
15
|
- `fast`: ~15-30s, single engine, no synthesis
|
|
17
16
|
- `standard`: ~30-90s, all engines + Gemini synthesis + sources
|
|
18
17
|
- `deep`: ~60-180s, stronger grounding + confidence metadata
|
|
19
18
|
|
|
20
|
-
**
|
|
21
|
-
|
|
22
|
-
**Pi commands:** `/greedy-visible`, `/greedy-status`, `/greedy-kill`, `/set-greedy-locale`
|
|
19
|
+
**Blocks:** Headless by default; auto-retries in visible mode. If human verification is needed, visible Chrome stays open — tell the user to solve it and rerun.
|
|
23
20
|
|
|
24
21
|
**CDP safety:** Never call raw `bin/cdp.mjs`. Use `bin/cdp-greedy.mjs`, `bin/cdp-visible.mjs`, or `bin/cdp-headless.mjs`.
|
|
25
|
-
|
|
26
|
-
Old `coding_task`/`deep_research` folded into `greedy_search`. Use `engine: "gemini"` for one-off opinion, `depth: "deep"` for research.
|