@apmantza/greedysearch-pi 1.0.24 → 1.1.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/extractors/gemini.mjs +38 -4
- package/index.ts +7 -2
- package/package.json +1 -1
- package/search.mjs +3 -0
- package/skills/greedy-search/SKILL.md +72 -38
package/extractors/gemini.mjs
CHANGED
|
@@ -108,15 +108,49 @@ async function extractAnswer(tab) {
|
|
|
108
108
|
const answer = await cdp(['eval', tab, `window.__geminiClipboard || ''`]);
|
|
109
109
|
if (!answer) throw new Error('Clipboard interceptor returned empty text');
|
|
110
110
|
|
|
111
|
-
// Sources
|
|
111
|
+
// Click "Sources" button to open the sidebar with proper source cards
|
|
112
|
+
await cdp(['eval', tab, `
|
|
113
|
+
(function() {
|
|
114
|
+
var btn = document.querySelector('button.legacy-sources-sidebar-button, button.mdс-button--outline');
|
|
115
|
+
if (!btn) btn = Array.from(document.querySelectorAll('button')).find(b => b.innerText?.trim() === 'Sources');
|
|
116
|
+
if (btn) { btn.click(); return 'clicked'; }
|
|
117
|
+
return 'not-found';
|
|
118
|
+
})()
|
|
119
|
+
`]).catch(() => 'not-found');
|
|
120
|
+
|
|
121
|
+
// Wait for the sources sidebar to populate
|
|
122
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
123
|
+
|
|
124
|
+
// Extract sources from the sidebar panel (has proper URLs + titles)
|
|
112
125
|
const raw = await cdp(['eval', tab, `
|
|
113
126
|
(function() {
|
|
114
|
-
|
|
127
|
+
// Find the Sources sidebar container by heading
|
|
128
|
+
var headings = Array.from(document.querySelectorAll('h1, h2, h3, [class*="header"]'));
|
|
129
|
+
var sourceHeading = headings.find(h => h.innerText?.trim() === 'Sources');
|
|
130
|
+
if (sourceHeading) {
|
|
131
|
+
var container = sourceHeading.closest('.container') || sourceHeading.parentElement;
|
|
132
|
+
var links = Array.from(container.querySelectorAll('a[href^="http"]'))
|
|
133
|
+
.map(a => ({ url: a.href.split('#')[0], title: a.innerText?.trim().split('\\n')[0] || '' }))
|
|
134
|
+
.filter(s => s.url && !s.url.includes('gemini.google') && !s.url.includes('gstatic') && !s.url.includes('google.com/search'))
|
|
135
|
+
.filter((v, i, arr) => arr.findIndex(x => x.url === v.url) === i)
|
|
136
|
+
.slice(0, 8);
|
|
137
|
+
return JSON.stringify(links);
|
|
138
|
+
}
|
|
139
|
+
// Fallback: inline source cards with aria-labels
|
|
140
|
+
var cards = Array.from(document.querySelectorAll('button[aria-label*="citation from"]'));
|
|
141
|
+
if (cards.length) {
|
|
142
|
+
return JSON.stringify(cards.map(b => {
|
|
143
|
+
var label = b.getAttribute('aria-label') || '';
|
|
144
|
+
var name = label.match(/from\\s+(.+?)\\.\\s/)?.[1] || label;
|
|
145
|
+
return { url: '', title: name };
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
// Last resort: page-wide links (may include footer junk)
|
|
149
|
+
return JSON.stringify(Array.from(document.querySelectorAll('a[href^="http"]'))
|
|
115
150
|
.map(a => ({ url: a.href.split('#')[0], title: a.innerText?.trim().split('\\n')[0] || '' }))
|
|
116
151
|
.filter(s => s.url && !s.url.includes('gemini.google') && !s.url.includes('gstatic') && !s.url.includes('google.com/search'))
|
|
117
152
|
.filter((v, i, arr) => arr.findIndex(x => x.url === v.url) === i)
|
|
118
|
-
.slice(0, 8);
|
|
119
|
-
return JSON.stringify(sources);
|
|
153
|
+
.slice(0, 8));
|
|
120
154
|
})()
|
|
121
155
|
`]).catch(() => '[]');
|
|
122
156
|
const sources = JSON.parse(raw);
|
package/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ function cdpAvailable(): boolean {
|
|
|
22
22
|
|
|
23
23
|
function runSearch(engine: string, query: string, flags: string[] = []): Promise<Record<string, unknown>> {
|
|
24
24
|
return new Promise((resolve, reject) => {
|
|
25
|
-
const proc = spawn("node", [__dir + "/search.mjs", engine, ...flags, query], {
|
|
25
|
+
const proc = spawn("node", [__dir + "/search.mjs", engine, "--inline", ...flags, query], {
|
|
26
26
|
stdio: ["ignore", "pipe", "pipe"],
|
|
27
27
|
});
|
|
28
28
|
let out = "";
|
|
@@ -137,9 +137,13 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
|
137
137
|
description: 'When true and engine is "all", deduplicates sources across engines and feeds them to Gemini for a single grounded synthesis. Adds ~30s but saves tokens and improves answer quality.',
|
|
138
138
|
default: false,
|
|
139
139
|
})),
|
|
140
|
+
fullAnswer: Type.Optional(Type.Boolean({
|
|
141
|
+
description: 'When true, returns the complete answer instead of a truncated preview (default: false, answers are shortened to ~300 chars to save tokens).',
|
|
142
|
+
default: false,
|
|
143
|
+
})),
|
|
140
144
|
}),
|
|
141
145
|
execute: async (_toolCallId, params) => {
|
|
142
|
-
const { query, engine = "all", synthesize = false } = params as { query: string; engine: string; synthesize?: boolean };
|
|
146
|
+
const { query, engine = "all", synthesize = false, fullAnswer = false } = params as { query: string; engine: string; synthesize?: boolean; fullAnswer?: boolean };
|
|
143
147
|
|
|
144
148
|
if (!cdpAvailable()) {
|
|
145
149
|
return {
|
|
@@ -150,6 +154,7 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
|
150
154
|
|
|
151
155
|
const flags: string[] = [];
|
|
152
156
|
if (synthesize && engine === "all") flags.push("--synthesize");
|
|
157
|
+
if (fullAnswer) flags.push("--full");
|
|
153
158
|
|
|
154
159
|
let data: Record<string, unknown>;
|
|
155
160
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apmantza/greedysearch-pi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Pi extension: search Perplexity, Bing Copilot, and Google AI in parallel with optional Gemini synthesis — grounded AI answers, not just links",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
package/search.mjs
CHANGED
|
@@ -41,6 +41,8 @@ const ENGINES = {
|
|
|
41
41
|
b: 'bing-copilot.mjs',
|
|
42
42
|
google: 'google-ai.mjs',
|
|
43
43
|
g: 'google-ai.mjs',
|
|
44
|
+
gemini: 'gemini.mjs',
|
|
45
|
+
gem: 'gemini.mjs',
|
|
44
46
|
stackoverflow: 'stackoverflow-ai.mjs',
|
|
45
47
|
so: 'stackoverflow-ai.mjs',
|
|
46
48
|
stack: 'stackoverflow-ai.mjs',
|
|
@@ -52,6 +54,7 @@ const ENGINE_DOMAINS = {
|
|
|
52
54
|
perplexity: 'perplexity.ai',
|
|
53
55
|
bing: 'copilot.microsoft.com',
|
|
54
56
|
google: 'google.com',
|
|
57
|
+
gemini: 'gemini.google.com',
|
|
55
58
|
stackoverflow: 'stackoverflow.com',
|
|
56
59
|
};
|
|
57
60
|
|
|
@@ -1,38 +1,72 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: greedy-search
|
|
3
|
-
description: Multi-engine AI web search — Perplexity, Bing Copilot, Google AI in parallel
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Greedy Search
|
|
7
|
-
|
|
8
|
-
Use
|
|
9
|
-
|
|
10
|
-
##
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
---
|
|
2
|
+
name: greedy-search
|
|
3
|
+
description: Multi-engine AI web search — Perplexity, Bing Copilot, Google AI in parallel with optional Gemini synthesis. Use for high-quality research where training data may be stale or single-engine results are insufficient.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Greedy Search
|
|
7
|
+
|
|
8
|
+
Use `greedy_search` when you need high-quality, multi-perspective answers from the web.
|
|
9
|
+
|
|
10
|
+
## Greedy Search vs Built-in Web Search
|
|
11
|
+
|
|
12
|
+
| | `web_search` | `greedy_search` |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| Speed | Instant (~2s) | 15-60s (one engine) / 30-90s (all engines) |
|
|
15
|
+
| Quality | Good for simple lookups | Higher — 3 AI engines cross-verify |
|
|
16
|
+
| Synthesis | Single engine answer | Optional Gemini synthesis (cleanest answer) |
|
|
17
|
+
| Use for | Quick facts, simple questions | Research, decisions, complex topics |
|
|
18
|
+
|
|
19
|
+
**Rule of thumb:** Use `web_search` for quick facts. Use `greedy_search` when the answer matters — architecture decisions, comparing libraries, understanding new releases, debugging tricky errors.
|
|
20
|
+
|
|
21
|
+
## When to Use
|
|
22
|
+
|
|
23
|
+
- **Version-specific changes** — "What changed in React 19?" / "Breaking changes in FastAPI 0.100"
|
|
24
|
+
- **Choosing between tools** — "Prisma vs Drizzle in 2026" / "Best auth library for Next.js 15"
|
|
25
|
+
- **Debugging** — User pastes an error message or stack trace
|
|
26
|
+
- **Research tasks** — When you need to synthesize information from multiple sources
|
|
27
|
+
- **Best practices** — "How to structure a monorepo" / "Auth patterns for SaaS"
|
|
28
|
+
- **Anything where training data might be stale** — 2025+, 2026+, "latest", "current", "still maintained"
|
|
29
|
+
|
|
30
|
+
## Engine Selection
|
|
31
|
+
|
|
32
|
+
```greedy_search({ query: "what changed in React 19", engine: "all" })```
|
|
33
|
+
|
|
34
|
+
| Engine | Latency | Best for |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `all` (default) | 30-90s | Highest confidence — all 3 engines in parallel |
|
|
37
|
+
| `perplexity` | 15-30s | Technical Q&A, code explanations, documentation |
|
|
38
|
+
| `bing` | 15-30s | Recent news, Microsoft ecosystem |
|
|
39
|
+
| `google` | 15-30s | Broad coverage, multiple perspectives |
|
|
40
|
+
| `gemini` | 15-30s | Google's perspective, different training data |
|
|
41
|
+
|
|
42
|
+
Use a single engine when speed matters and the question isn't contentious.
|
|
43
|
+
|
|
44
|
+
## Synthesis Mode
|
|
45
|
+
|
|
46
|
+
For complex research questions, use `synthesize: true` with `engine: "all"`:
|
|
47
|
+
|
|
48
|
+
```greedy_search({ query: "best auth patterns for SaaS in 2026", engine: "all", synthesize: true })```
|
|
49
|
+
|
|
50
|
+
This deduplicates sources across engines and feeds them to Gemini for one clean, synthesized answer. Adds ~30s but produces the highest quality output — ideal for research tasks where you'd otherwise need to parse 3 separate answers.
|
|
51
|
+
|
|
52
|
+
Use synthesis when:
|
|
53
|
+
- You need one definitive answer, not multiple perspectives
|
|
54
|
+
- You're researching a topic to write about or make a decision
|
|
55
|
+
- The question has a lot of noise and you want the signal
|
|
56
|
+
|
|
57
|
+
Skip synthesis when:
|
|
58
|
+
- You want to see where engines disagree (useful for controversial topics)
|
|
59
|
+
- Speed matters
|
|
60
|
+
|
|
61
|
+
## Full vs Short Answers
|
|
62
|
+
|
|
63
|
+
Default mode returns ~300 char summaries to save tokens. Use `fullAnswer: true` when you need the complete response:
|
|
64
|
+
|
|
65
|
+
```greedy_search({ query: "explain the React compiler", engine: "perplexity", fullAnswer: true })```
|
|
66
|
+
|
|
67
|
+
## Interpreting Results
|
|
68
|
+
|
|
69
|
+
- **All 3 agree** → High confidence, present as fact
|
|
70
|
+
- **2 agree, 1 differs** → Likely correct but note the dissent
|
|
71
|
+
- **All differ** → Present the different perspectives to the user
|
|
72
|
+
- **Sources with `[3/3]` or `[2/3]`** → Cited by multiple engines, higher confidence
|