@apmantza/greedysearch-pi 1.2.1 → 1.3.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/index.ts +285 -0
- package/package.json +1 -1
- package/search.mjs +115 -12
- package/skills/greedy-search/SKILL.md +76 -3
package/index.ts
CHANGED
|
@@ -125,6 +125,124 @@ function formatResults(engine: string, data: Record<string, unknown>): string {
|
|
|
125
125
|
return lines.join("\n").trim();
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
function formatDeepResearch(data: Record<string, unknown>): string {
|
|
129
|
+
const lines: string[] = [];
|
|
130
|
+
const confidence = data._confidence as Record<string, unknown> | undefined;
|
|
131
|
+
const fetchedSources = data._fetchedSources as Array<Record<string, unknown>> | undefined;
|
|
132
|
+
const dedupedSources = data._sources as Array<Record<string, unknown>> | undefined;
|
|
133
|
+
|
|
134
|
+
lines.push("# Deep Research Report\n");
|
|
135
|
+
|
|
136
|
+
// Confidence summary
|
|
137
|
+
if (confidence) {
|
|
138
|
+
const enginesResponded = (confidence.enginesResponded as string[]) || [];
|
|
139
|
+
const enginesFailed = (confidence.enginesFailed as string[]) || [];
|
|
140
|
+
const consensusScore = confidence.consensusScore || 0;
|
|
141
|
+
|
|
142
|
+
lines.push("## Confidence\n");
|
|
143
|
+
lines.push(`- **Engines responded:** ${enginesResponded.join(", ") || "none"}`);
|
|
144
|
+
if (enginesFailed.length > 0) {
|
|
145
|
+
lines.push(`- **Engines failed:** ${enginesFailed.join(", ")}`);
|
|
146
|
+
}
|
|
147
|
+
lines.push(`- **Top source consensus:** ${consensusScore}/3 engines`);
|
|
148
|
+
lines.push(`- **Total unique sources:** ${confidence.sourcesCount || 0}`);
|
|
149
|
+
lines.push("");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Per-engine answers
|
|
153
|
+
lines.push("## Findings\n");
|
|
154
|
+
for (const engine of ["perplexity", "bing", "google"]) {
|
|
155
|
+
const r = data[engine] as Record<string, unknown> | undefined;
|
|
156
|
+
if (!r) continue;
|
|
157
|
+
lines.push(`### ${engine.charAt(0).toUpperCase() + engine.slice(1)}`);
|
|
158
|
+
if (r.error) {
|
|
159
|
+
lines.push(`⚠️ Error: ${r.error}`);
|
|
160
|
+
} else if (r.answer) {
|
|
161
|
+
lines.push(String(r.answer).slice(0, 2000));
|
|
162
|
+
}
|
|
163
|
+
lines.push("");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Synthesis
|
|
167
|
+
const synthesis = data._synthesis as Record<string, unknown> | undefined;
|
|
168
|
+
if (synthesis?.answer) {
|
|
169
|
+
lines.push("## Synthesized Answer\n");
|
|
170
|
+
lines.push(String(synthesis.answer));
|
|
171
|
+
lines.push("");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Deduplicated sources by consensus
|
|
175
|
+
if (dedupedSources && dedupedSources.length > 0) {
|
|
176
|
+
lines.push("## Sources (Ranked by Consensus)\n");
|
|
177
|
+
for (const s of dedupedSources) {
|
|
178
|
+
const engines = (s.engines as string[]) || [];
|
|
179
|
+
const consensus = engines.length;
|
|
180
|
+
lines.push(`- **[${consensus}/3]** [${s.title || "Untitled"}](${s.url})`);
|
|
181
|
+
}
|
|
182
|
+
lines.push("");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Fetched source content
|
|
186
|
+
if (fetchedSources && fetchedSources.length > 0) {
|
|
187
|
+
lines.push("## Source Content (Top Matches)\n");
|
|
188
|
+
for (const fs of fetchedSources) {
|
|
189
|
+
lines.push(`### ${fs.title || fs.url}`);
|
|
190
|
+
lines.push(`*Source: ${fs.url}*`);
|
|
191
|
+
lines.push("");
|
|
192
|
+
if (fs.content) {
|
|
193
|
+
lines.push(String(fs.content).slice(0, 3000));
|
|
194
|
+
} else if (fs.error) {
|
|
195
|
+
lines.push(`⚠️ Could not fetch: ${fs.error}`);
|
|
196
|
+
}
|
|
197
|
+
lines.push("\n---\n");
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return lines.join("\n").trim();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function formatCodingTask(data: Record<string, unknown> | Record<string, Record<string, unknown>>): string {
|
|
205
|
+
const lines: string[] = [];
|
|
206
|
+
|
|
207
|
+
// Check if it's multi-engine result
|
|
208
|
+
const hasMultipleEngines = "gemini" in data || "copilot" in data;
|
|
209
|
+
|
|
210
|
+
if (hasMultipleEngines) {
|
|
211
|
+
// Multi-engine result
|
|
212
|
+
for (const [engineName, result] of Object.entries(data)) {
|
|
213
|
+
const r = result as Record<string, unknown>;
|
|
214
|
+
lines.push(`## ${engineName.charAt(0).toUpperCase() + engineName.slice(1)}\n`);
|
|
215
|
+
|
|
216
|
+
if (r.error) {
|
|
217
|
+
lines.push(`⚠️ Error: ${r.error}\n`);
|
|
218
|
+
} else {
|
|
219
|
+
if (r.explanation) lines.push(String(r.explanation));
|
|
220
|
+
if (Array.isArray(r.code) && r.code.length > 0) {
|
|
221
|
+
for (const block of r.code) {
|
|
222
|
+
const b = block as { language: string; code: string };
|
|
223
|
+
lines.push(`\n\`\`\`${b.language}\n${b.code}\n\`\`\`\n`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (r.url) lines.push(`*Source: ${r.url}*`);
|
|
227
|
+
}
|
|
228
|
+
lines.push("");
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
// Single engine result
|
|
232
|
+
const r = data as Record<string, unknown>;
|
|
233
|
+
if (r.explanation) lines.push(String(r.explanation));
|
|
234
|
+
if (Array.isArray(r.code) && r.code.length > 0) {
|
|
235
|
+
for (const block of r.code) {
|
|
236
|
+
const b = block as { language: string; code: string };
|
|
237
|
+
lines.push(`\n\`\`\`${b.language}\n${b.code}\n\`\`\`\n`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (r.url) lines.push(`*Source: ${r.url}*`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return lines.join("\n").trim();
|
|
244
|
+
}
|
|
245
|
+
|
|
128
246
|
export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
129
247
|
pi.on("session_start", async (_event, ctx) => {
|
|
130
248
|
if (!cdpAvailable()) {
|
|
@@ -219,4 +337,171 @@ export default function greedySearchExtension(pi: ExtensionAPI) {
|
|
|
219
337
|
}
|
|
220
338
|
},
|
|
221
339
|
});
|
|
340
|
+
|
|
341
|
+
// ─── deep_research ─────────────────────────────────────────────────────────
|
|
342
|
+
pi.registerTool({
|
|
343
|
+
name: "deep_research",
|
|
344
|
+
label: "Deep Research",
|
|
345
|
+
description:
|
|
346
|
+
"Comprehensive multi-engine research with source fetching and synthesis. " +
|
|
347
|
+
"Runs Perplexity, Bing Copilot, and Google AI in parallel with full answers, " +
|
|
348
|
+
"deduplicates and ranks sources by consensus, fetches content from top sources, " +
|
|
349
|
+
"and synthesizes via Gemini. Returns a structured research document with confidence scores. " +
|
|
350
|
+
"Use for architecture decisions, library comparisons, best practices, or any research where the answer matters.",
|
|
351
|
+
promptSnippet: "Deep multi-engine research with source deduplication and synthesis",
|
|
352
|
+
parameters: Type.Object({
|
|
353
|
+
query: Type.String({ description: "The research question" }),
|
|
354
|
+
}),
|
|
355
|
+
execute: async (_toolCallId, params, signal, onUpdate) => {
|
|
356
|
+
const { query } = params as { query: string };
|
|
357
|
+
|
|
358
|
+
if (!cdpAvailable()) {
|
|
359
|
+
return {
|
|
360
|
+
content: [{ type: "text", text: "cdp.mjs missing — try reinstalling." }],
|
|
361
|
+
details: {} as { raw?: Record<string, unknown> },
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const completed = new Set<string>();
|
|
366
|
+
|
|
367
|
+
const onProgress = (eng: string, status: "done" | "error") => {
|
|
368
|
+
completed.add(eng);
|
|
369
|
+
const parts: string[] = [];
|
|
370
|
+
for (const e of ALL_ENGINES) {
|
|
371
|
+
if (completed.has(e)) parts.push(`✅ ${e}`);
|
|
372
|
+
else parts.push(`⏳ ${e}`);
|
|
373
|
+
}
|
|
374
|
+
if (completed.size >= 3) parts.push("🔄 synthesizing");
|
|
375
|
+
|
|
376
|
+
onUpdate?.({
|
|
377
|
+
content: [{ type: "text", text: `**Researching...** ${parts.join(" · ")}` }],
|
|
378
|
+
details: { _progress: true },
|
|
379
|
+
} as any);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
// Run deep research (includes full answers, synthesis, and source fetching)
|
|
384
|
+
const data = await runSearch("all", query, ["--deep-research"], signal, onProgress);
|
|
385
|
+
const text = formatDeepResearch(data);
|
|
386
|
+
return {
|
|
387
|
+
content: [{ type: "text", text: text || "No results returned." }],
|
|
388
|
+
details: { raw: data },
|
|
389
|
+
};
|
|
390
|
+
} catch (e) {
|
|
391
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
392
|
+
return {
|
|
393
|
+
content: [{ type: "text", text: `Deep research failed: ${msg}` }],
|
|
394
|
+
details: {} as { raw?: Record<string, unknown> },
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// ─── coding_task ───────────────────────────────────────────────────────────
|
|
401
|
+
pi.registerTool({
|
|
402
|
+
name: "coding_task",
|
|
403
|
+
label: "Coding Task",
|
|
404
|
+
description:
|
|
405
|
+
"Delegate a coding task to Gemini and/or Copilot via browser automation. " +
|
|
406
|
+
"Returns extracted code blocks and explanations. Supports multiple modes: " +
|
|
407
|
+
"'code' (write/modify code), 'review' (senior engineer code review), " +
|
|
408
|
+
"'plan' (architect risk assessment), 'test' (edge case testing), " +
|
|
409
|
+
"'debug' (fresh-eyes root cause analysis). " +
|
|
410
|
+
"Best for getting a 'second opinion' on hard problems, debugging tricky issues, " +
|
|
411
|
+
"or risk-assessing major refactors. Use engine 'all' for both perspectives.",
|
|
412
|
+
promptSnippet: "Browser-based coding assistant with Gemini and Copilot",
|
|
413
|
+
parameters: Type.Object({
|
|
414
|
+
task: Type.String({ description: "The coding task or question" }),
|
|
415
|
+
engine: Type.Union(
|
|
416
|
+
[
|
|
417
|
+
Type.Literal("all"),
|
|
418
|
+
Type.Literal("gemini"),
|
|
419
|
+
Type.Literal("copilot"),
|
|
420
|
+
],
|
|
421
|
+
{
|
|
422
|
+
description: 'Engine to use. "all" runs both Gemini and Copilot in parallel.',
|
|
423
|
+
default: "gemini",
|
|
424
|
+
},
|
|
425
|
+
),
|
|
426
|
+
mode: Type.Union(
|
|
427
|
+
[
|
|
428
|
+
Type.Literal("code"),
|
|
429
|
+
Type.Literal("review"),
|
|
430
|
+
Type.Literal("plan"),
|
|
431
|
+
Type.Literal("test"),
|
|
432
|
+
Type.Literal("debug"),
|
|
433
|
+
],
|
|
434
|
+
{
|
|
435
|
+
description: "Task mode: code (default), review (code review), plan (architect review), test (write tests), debug (root cause analysis)",
|
|
436
|
+
default: "code",
|
|
437
|
+
},
|
|
438
|
+
),
|
|
439
|
+
context: Type.Optional(Type.String({
|
|
440
|
+
description: "Optional code context/snippet to include with the task",
|
|
441
|
+
})),
|
|
442
|
+
}),
|
|
443
|
+
execute: async (_toolCallId, params, signal, onUpdate) => {
|
|
444
|
+
const { task, engine = "gemini", mode = "code", context } = params as {
|
|
445
|
+
task: string; engine: string; mode: string; context?: string;
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
if (!cdpAvailable()) {
|
|
449
|
+
return {
|
|
450
|
+
content: [{ type: "text", text: "cdp.mjs missing — try reinstalling." }],
|
|
451
|
+
details: {} as { raw?: Record<string, unknown> },
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const flags: string[] = ["--engine", engine, "--mode", mode];
|
|
456
|
+
if (context) flags.push("--context", context);
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
onUpdate?.({
|
|
460
|
+
content: [{ type: "text", text: `**Coding task...** 🔄 ${engine === "all" ? "Gemini + Copilot" : engine} (${mode} mode)` }],
|
|
461
|
+
details: { _progress: true },
|
|
462
|
+
} as any);
|
|
463
|
+
|
|
464
|
+
const data = await new Promise<Record<string, unknown>>((resolve, reject) => {
|
|
465
|
+
const proc = spawn("node", [__dir + "/coding-task.mjs", task, ...flags], {
|
|
466
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
467
|
+
});
|
|
468
|
+
let out = "";
|
|
469
|
+
let err = "";
|
|
470
|
+
|
|
471
|
+
const onAbort = () => { proc.kill("SIGTERM"); reject(new Error("Aborted")); };
|
|
472
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
473
|
+
|
|
474
|
+
proc.stdout.on("data", (d: Buffer) => (out += d));
|
|
475
|
+
proc.stderr.on("data", (d: Buffer) => { err += d; });
|
|
476
|
+
proc.on("close", (code: number) => {
|
|
477
|
+
signal?.removeEventListener("abort", onAbort);
|
|
478
|
+
if (code !== 0) {
|
|
479
|
+
reject(new Error(err.trim() || `coding-task.mjs exited with code ${code}`));
|
|
480
|
+
} else {
|
|
481
|
+
try {
|
|
482
|
+
resolve(JSON.parse(out.trim()));
|
|
483
|
+
} catch {
|
|
484
|
+
reject(new Error(`Invalid JSON from coding-task.mjs: ${out.slice(0, 200)}`));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Timeout after 3 minutes
|
|
490
|
+
setTimeout(() => { proc.kill("SIGTERM"); reject(new Error("Coding task timed out after 180s")); }, 180000);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const text = formatCodingTask(data);
|
|
494
|
+
return {
|
|
495
|
+
content: [{ type: "text", text: text || "No response." }],
|
|
496
|
+
details: { raw: data },
|
|
497
|
+
};
|
|
498
|
+
} catch (e) {
|
|
499
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
500
|
+
return {
|
|
501
|
+
content: [{ type: "text", text: `Coding task failed: ${msg}` }],
|
|
502
|
+
details: {} as { raw?: Record<string, unknown> },
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
},
|
|
506
|
+
});
|
|
222
507
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apmantza/greedysearch-pi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Pi extension: browser-automation tool that searches Perplexity, Bing Copilot, and Google AI in parallel, extracts answers and sources via CDP, with optional Gemini synthesis — grounded AI answers from real browser interactions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
package/search.mjs
CHANGED
|
@@ -165,6 +165,75 @@ async function fetchTopSource(url) {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
async function fetchSourceContent(url, maxChars = 5000) {
|
|
169
|
+
try {
|
|
170
|
+
const controller = new AbortController();
|
|
171
|
+
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
172
|
+
|
|
173
|
+
const res = await fetch(url, {
|
|
174
|
+
signal: controller.signal,
|
|
175
|
+
headers: {
|
|
176
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
177
|
+
'Accept': 'text/html,application/xhtml+xml',
|
|
178
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
clearTimeout(timeout);
|
|
182
|
+
|
|
183
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
184
|
+
|
|
185
|
+
const html = await res.text();
|
|
186
|
+
|
|
187
|
+
// Simple HTML extraction - remove tags and extract text
|
|
188
|
+
const content = html
|
|
189
|
+
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
190
|
+
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
191
|
+
.replace(/<nav[\s\S]*?<\/nav>/gi, '')
|
|
192
|
+
.replace(/<header[\s\S]*?<\/header>/gi, '')
|
|
193
|
+
.replace(/<footer[\s\S]*?<\/footer>/gi, '')
|
|
194
|
+
.replace(/<[^>]+>/g, ' ')
|
|
195
|
+
.replace(/&[a-z]+;/gi, ' ')
|
|
196
|
+
.replace(/\s+/g, ' ')
|
|
197
|
+
.trim()
|
|
198
|
+
.slice(0, maxChars);
|
|
199
|
+
|
|
200
|
+
// Extract title
|
|
201
|
+
const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
202
|
+
const title = titleMatch ? titleMatch[1].trim() : '';
|
|
203
|
+
|
|
204
|
+
return { url, title, content };
|
|
205
|
+
} catch (e) {
|
|
206
|
+
return { url, title: '', content: null, error: e.message };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function fetchMultipleSources(sources, maxSources = 5, maxChars = 5000) {
|
|
211
|
+
process.stderr.write(`[greedysearch] Fetching content from ${Math.min(sources.length, maxSources)} sources...\n`);
|
|
212
|
+
|
|
213
|
+
// Fetch sources sequentially (CDP doesn't handle parallel tab operations well)
|
|
214
|
+
const toFetch = sources.slice(0, maxSources);
|
|
215
|
+
const fetched = [];
|
|
216
|
+
|
|
217
|
+
for (let i = 0; i < toFetch.length; i++) {
|
|
218
|
+
const s = toFetch[i];
|
|
219
|
+
process.stderr.write(`[greedysearch] Fetching ${i + 1}/${toFetch.length}: ${s.url.slice(0, 60)}...\n`);
|
|
220
|
+
try {
|
|
221
|
+
const result = await fetchSourceContent(s.url, maxChars);
|
|
222
|
+
if (result.content && result.content.length > 100) {
|
|
223
|
+
fetched.push(result);
|
|
224
|
+
process.stderr.write(`[greedysearch] ✓ Got ${result.content.length} chars\n`);
|
|
225
|
+
} else {
|
|
226
|
+
process.stderr.write(`[greedysearch] ✗ Empty or too short\n`);
|
|
227
|
+
}
|
|
228
|
+
} catch (e) {
|
|
229
|
+
process.stderr.write(`[greedysearch] ✗ Failed: ${e.message.slice(0, 80)}\n`);
|
|
230
|
+
}
|
|
231
|
+
process.stderr.write(`PROGRESS:fetch:${i + 1}/${toFetch.length}\n`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return fetched;
|
|
235
|
+
}
|
|
236
|
+
|
|
168
237
|
function pickTopSource(out) {
|
|
169
238
|
for (const engine of ['perplexity', 'google', 'bing']) {
|
|
170
239
|
const r = out[engine];
|
|
@@ -234,8 +303,8 @@ async function synthesizeWithGemini(query, results) {
|
|
|
234
303
|
proc.stderr.on('data', d => err += d);
|
|
235
304
|
const t = setTimeout(() => {
|
|
236
305
|
proc.kill();
|
|
237
|
-
reject(new Error('Gemini synthesis timed out after
|
|
238
|
-
},
|
|
306
|
+
reject(new Error('Gemini synthesis timed out after 180s'));
|
|
307
|
+
}, 180000);
|
|
239
308
|
proc.on('close', code => {
|
|
240
309
|
clearTimeout(t);
|
|
241
310
|
if (code !== 0) reject(new Error(err.trim() || 'gemini extractor failed'));
|
|
@@ -388,28 +457,37 @@ async function main() {
|
|
|
388
457
|
'',
|
|
389
458
|
'Engines: perplexity (p), bing (b), google (g), gemini (gem), all',
|
|
390
459
|
'',
|
|
460
|
+
'Flags:',
|
|
461
|
+
' --full Return complete answers (~3000+ chars)',
|
|
462
|
+
' --synthesize Synthesize results via Gemini (adds ~30s)',
|
|
463
|
+
' --deep-research Full research: full answers + source fetching + synthesis',
|
|
464
|
+
' --fetch-top-source Fetch content from top source',
|
|
465
|
+
' --inline Output JSON to stdout (for piping)',
|
|
466
|
+
'',
|
|
391
467
|
'Examples:',
|
|
392
468
|
' node search.mjs p "what is memoization"',
|
|
393
|
-
' node search.mjs so "node.js event loop explained"',
|
|
394
469
|
' node search.mjs all "TCP congestion control"',
|
|
470
|
+
' node search.mjs all "RAG vs fine-tuning" --deep-research',
|
|
395
471
|
].join('\n') + '\n');
|
|
396
472
|
process.exit(1);
|
|
397
473
|
}
|
|
398
474
|
|
|
399
475
|
await ensureChrome();
|
|
400
476
|
|
|
401
|
-
const full
|
|
402
|
-
const short
|
|
403
|
-
const fetchSource
|
|
404
|
-
const synthesize
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
const
|
|
408
|
-
const
|
|
477
|
+
const full = args.includes('--full') || args.includes('--deep-research');
|
|
478
|
+
const short = !full;
|
|
479
|
+
const fetchSource = args.includes('--fetch-top-source');
|
|
480
|
+
const synthesize = args.includes('--synthesize') || args.includes('--deep-research');
|
|
481
|
+
const deepResearch = args.includes('--deep-research');
|
|
482
|
+
const inline = args.includes('--inline');
|
|
483
|
+
const outIdx = args.indexOf('--out');
|
|
484
|
+
const outFile = outIdx !== -1 ? args[outIdx + 1] : null;
|
|
485
|
+
const rest = args.filter((a, i) =>
|
|
409
486
|
a !== '--full' &&
|
|
410
|
-
a !== '--short' &&
|
|
487
|
+
a !== '--short' &&
|
|
411
488
|
a !== '--fetch-top-source' &&
|
|
412
489
|
a !== '--synthesize' &&
|
|
490
|
+
a !== '--deep-research' &&
|
|
413
491
|
a !== '--inline' &&
|
|
414
492
|
a !== '--out' &&
|
|
415
493
|
(outIdx === -1 || i !== outIdx + 1)
|
|
@@ -481,6 +559,31 @@ async function main() {
|
|
|
481
559
|
if (top) out._topSource = await fetchTopSource(top.url);
|
|
482
560
|
}
|
|
483
561
|
|
|
562
|
+
// Deep research mode: fetch top sources and return structured document
|
|
563
|
+
if (deepResearch) {
|
|
564
|
+
process.stderr.write('PROGRESS:deep-research:start\n');
|
|
565
|
+
|
|
566
|
+
// Get top sources by consensus
|
|
567
|
+
const topSources = out._sources || [];
|
|
568
|
+
|
|
569
|
+
if (topSources.length > 0) {
|
|
570
|
+
// Fetch content from top sources
|
|
571
|
+
out._fetchedSources = await fetchMultipleSources(topSources, 5, 8000);
|
|
572
|
+
process.stderr.write('PROGRESS:deep-research:done\n');
|
|
573
|
+
} else {
|
|
574
|
+
out._fetchedSources = [];
|
|
575
|
+
process.stderr.write('PROGRESS:deep-research:no-sources\n');
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Build confidence scores
|
|
579
|
+
out._confidence = {
|
|
580
|
+
sourcesCount: topSources.length,
|
|
581
|
+
consensusScore: topSources.length > 0 ? topSources[0]?.engines?.length || 0 : 0,
|
|
582
|
+
enginesResponded: ALL_ENGINES.filter(e => out[e]?.answer && !out[e]?.error),
|
|
583
|
+
enginesFailed: ALL_ENGINES.filter(e => out[e]?.error),
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
484
587
|
writeOutput(out, outFile, { inline, synthesize, query });
|
|
485
588
|
return;
|
|
486
589
|
}
|
|
@@ -1,11 +1,84 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: greedy-search
|
|
3
|
-
description: Multi-engine AI web search —
|
|
3
|
+
description: Multi-engine AI web search — greedy_search, deep_research, and coding_task. Use for high-quality research where training data may be stale or single-engine results are insufficient.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# GreedySearch Tools
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## Tool Overview
|
|
9
|
+
|
|
10
|
+
| Tool | Speed | Use for |
|
|
11
|
+
|------|-------|---------|
|
|
12
|
+
| `greedy_search` | 15-90s | Quick lookups, comparisons, debugging errors |
|
|
13
|
+
| `deep_research` | 60-120s | Architecture decisions, thorough research, source-backed answers |
|
|
14
|
+
| `coding_task` | 60-180s | Second opinions on code, reviews, debugging tricky issues |
|
|
15
|
+
|
|
16
|
+
## When to Use Which
|
|
17
|
+
|
|
18
|
+
- **`greedy_search`** — Default. Fast enough for most things. Use when you need current info.
|
|
19
|
+
- **`deep_research`** — When the answer *matters*. Gives you a structured document with confidence scores, deduplicated sources ranked by consensus, Gemini synthesis, AND actual content from top sources.
|
|
20
|
+
- **`coding_task`** — When you need a "second opinion" on hard problems. Best for `debug` and `plan` modes on tricky issues.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# greedy_search
|
|
25
|
+
|
|
26
|
+
Multi-engine AI web search with streaming progress.
|
|
27
|
+
|
|
28
|
+
```greedy_search({ query: "what changed in React 19", engine: "all" })```
|
|
29
|
+
|
|
30
|
+
| Parameter | Type | Default | Description |
|
|
31
|
+
|-----------|------|---------|-------------|
|
|
32
|
+
| `query` | string | required | The search question |
|
|
33
|
+
| `engine` | string | `"all"` | `all`, `perplexity`, `bing`, `google`, `gemini` |
|
|
34
|
+
| `synthesize` | boolean | `false` | Synthesize via Gemini |
|
|
35
|
+
| `fullAnswer` | boolean | `false` | Complete answer vs ~300 char summary |
|
|
36
|
+
|
|
37
|
+
**When to use:** Quick lookups, error messages, comparing tools, "what's new in X".
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
# deep_research
|
|
42
|
+
|
|
43
|
+
Comprehensive research with source fetching and synthesis. Returns a structured document.
|
|
44
|
+
|
|
45
|
+
```deep_research({ query: "RAG vs fine-tuning for production" })```
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
- Full answers from all 3 engines (Perplexity, Bing, Google)
|
|
49
|
+
- Gemini synthesis combining all perspectives
|
|
50
|
+
- Deduplicated sources ranked by consensus (3/3 > 2/3 > 1/3)
|
|
51
|
+
- Fetched content from top 5 sources (no CDP — uses native fetch)
|
|
52
|
+
- Confidence metadata (which engines responded, consensus score)
|
|
53
|
+
|
|
54
|
+
**When to use:** Architecture decisions, "which library should I use", research for a writeup, anything where you need source-backed confidence.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
# coding_task
|
|
59
|
+
|
|
60
|
+
Browser-based coding assistant using Gemini and/or Copilot.
|
|
61
|
+
|
|
62
|
+
```coding_task({ task: "debug this race condition", mode: "debug", engine: "all" })```
|
|
63
|
+
|
|
64
|
+
| Parameter | Type | Default | Description |
|
|
65
|
+
|-----------|------|---------|-------------|
|
|
66
|
+
| `task` | string | required | The coding task/question |
|
|
67
|
+
| `engine` | string | `"gemini"` | `gemini`, `copilot`, or `all` |
|
|
68
|
+
| `mode` | string | `"code"` | See modes below |
|
|
69
|
+
| `context` | string | — | Code snippet to include |
|
|
70
|
+
|
|
71
|
+
**Modes:**
|
|
72
|
+
|
|
73
|
+
| Mode | Use when |
|
|
74
|
+
|------|----------|
|
|
75
|
+
| `debug` | Stuck on a tricky bug. Fresh eyes catch different failure modes. |
|
|
76
|
+
| `plan` | About to refactor something big. Gemini plays devil's advocate. |
|
|
77
|
+
| `review` | Code review before merge. High-stakes code benefits from second opinion. |
|
|
78
|
+
| `test` | Need edge cases the author missed. |
|
|
79
|
+
| `code` | Just need the code written (but you can probably do this yourself faster). |
|
|
80
|
+
|
|
81
|
+
**When to use:** Debugging tricky issues, planning major refactors, security-critical reviews. **Skip for** simple code generation — you're faster.
|
|
9
82
|
|
|
10
83
|
## Greedy Search vs Built-in Web Search
|
|
11
84
|
|