feat: add Brave Search integration for researchers
- Add `websearch` command to gsd-tools.js for Brave API - Detect BRAVE_API_KEY env var or ~/.gsd/brave_api_key file - Persist brave_search setting to config.json on project init - Update researcher agents to check config before calling Graceful degradation: if brave_search is false, agents use built-in WebSearch without wasted Bash calls. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,22 @@ When researching "best library for X": find what the ecosystem actually uses, do
|
||||
|
||||
**WebSearch tips:** Always include current year. Use multiple query variations. Cross-verify with authoritative sources.
|
||||
|
||||
## Enhanced Web Search (Brave API)
|
||||
|
||||
Check `brave_search` from init context. If `true`, use Brave Search for higher quality results:
|
||||
|
||||
```bash
|
||||
node ~/.claude/get-shit-done/bin/gsd-tools.js websearch "your query" --limit 10
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--limit N` — Number of results (default: 10)
|
||||
- `--freshness day|week|month` — Restrict to recent content
|
||||
|
||||
If `brave_search: false` (or not set), use built-in WebSearch tool instead.
|
||||
|
||||
Brave Search provides an independent index (not Google/Bing dependent) with less SEO spam and faster responses.
|
||||
|
||||
## Verification Protocol
|
||||
|
||||
**WebSearch findings MUST be verified:**
|
||||
|
||||
@@ -91,6 +91,22 @@ Problems: "[tech] common mistakes", "[tech] gotchas"
|
||||
|
||||
Always include current year. Use multiple query variations. Mark WebSearch-only findings as LOW confidence.
|
||||
|
||||
### Enhanced Web Search (Brave API)
|
||||
|
||||
Check `brave_search` from orchestrator context. If `true`, use Brave Search for higher quality results:
|
||||
|
||||
```bash
|
||||
node ~/.claude/get-shit-done/bin/gsd-tools.js websearch "your query" --limit 10
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--limit N` — Number of results (default: 10)
|
||||
- `--freshness day|week|month` — Restrict to recent content
|
||||
|
||||
If `brave_search: false` (or not set), use built-in WebSearch tool instead.
|
||||
|
||||
Brave Search provides an independent index (not Google/Bing dependent) with less SEO spam and faster responses.
|
||||
|
||||
## Verification Protocol
|
||||
|
||||
**WebSearch findings must be verified:**
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
* summary-extract <path> [--fields] Extract structured data from SUMMARY.md
|
||||
* state-snapshot Structured parse of STATE.md
|
||||
* phase-plan-index <phase> Index plans with waves and status
|
||||
* websearch <query> Search web via Brave API (if configured)
|
||||
* [--limit N] [--freshness day|week|month]
|
||||
*
|
||||
* Phase Operations:
|
||||
* phase next-decimal <phase> Calculate next decimal phase number
|
||||
@@ -165,6 +167,7 @@ function loadConfig(cwd) {
|
||||
plan_checker: true,
|
||||
verifier: true,
|
||||
parallelization: true,
|
||||
brave_search: false,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -197,6 +200,7 @@ function loadConfig(cwd) {
|
||||
plan_checker: get('plan_checker', { section: 'workflow', field: 'plan_check' }) ?? defaults.plan_checker,
|
||||
verifier: get('verifier', { section: 'workflow', field: 'verifier' }) ?? defaults.verifier,
|
||||
parallelization,
|
||||
brave_search: get('brave_search') ?? defaults.brave_search,
|
||||
};
|
||||
} catch {
|
||||
return defaults;
|
||||
@@ -584,6 +588,11 @@ function cmdConfigEnsureSection(cwd, raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect Brave Search API key availability
|
||||
const homedir = require('os').homedir();
|
||||
const braveKeyFile = path.join(homedir, '.gsd', 'brave_api_key');
|
||||
const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile));
|
||||
|
||||
// Create default config
|
||||
const defaults = {
|
||||
model_profile: 'balanced',
|
||||
@@ -598,6 +607,7 @@ function cmdConfigEnsureSection(cwd, raw) {
|
||||
verifier: true,
|
||||
},
|
||||
parallelization: true,
|
||||
brave_search: hasBraveSearch,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1987,6 +1997,70 @@ function cmdSummaryExtract(cwd, summaryPath, fields, raw) {
|
||||
output(fullResult, raw);
|
||||
}
|
||||
|
||||
// ─── Web Search (Brave API) ──────────────────────────────────────────────────
|
||||
|
||||
async function cmdWebsearch(query, options, raw) {
|
||||
const apiKey = process.env.BRAVE_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
// No key = silent skip, agent falls back to built-in WebSearch
|
||||
output({ available: false, reason: 'BRAVE_API_KEY not set' }, raw, '');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!query) {
|
||||
output({ available: false, error: 'Query required' }, raw, '');
|
||||
return;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
q: query,
|
||||
count: String(options.limit || 10),
|
||||
country: 'us',
|
||||
search_lang: 'en',
|
||||
text_decorations: 'false'
|
||||
});
|
||||
|
||||
if (options.freshness) {
|
||||
params.set('freshness', options.freshness);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://api.search.brave.com/res/v1/web/search?${params}`,
|
||||
{
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'X-Subscription-Token': apiKey
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
output({ available: false, error: `API error: ${response.status}` }, raw, '');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const results = (data.web?.results || []).map(r => ({
|
||||
title: r.title,
|
||||
url: r.url,
|
||||
description: r.description,
|
||||
age: r.age || null
|
||||
}));
|
||||
|
||||
output({
|
||||
available: true,
|
||||
query,
|
||||
count: results.length,
|
||||
results
|
||||
}, raw, results.map(r => `${r.title}\n${r.url}\n${r.description}`).join('\n\n'));
|
||||
} catch (err) {
|
||||
output({ available: false, error: err.message }, raw, '');
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Frontmatter CRUD ────────────────────────────────────────────────────────
|
||||
|
||||
function cmdFrontmatterGet(cwd, filePath, field, raw) {
|
||||
@@ -3659,6 +3733,11 @@ function cmdInitPlanPhase(cwd, phase, includes, raw) {
|
||||
function cmdInitNewProject(cwd, raw) {
|
||||
const config = loadConfig(cwd);
|
||||
|
||||
// Detect Brave Search API key availability
|
||||
const homedir = require('os').homedir();
|
||||
const braveKeyFile = path.join(homedir, '.gsd', 'brave_api_key');
|
||||
const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile));
|
||||
|
||||
// Detect existing code
|
||||
let hasCode = false;
|
||||
let hasPackageFile = false;
|
||||
@@ -3699,6 +3778,9 @@ function cmdInitNewProject(cwd, raw) {
|
||||
|
||||
// Git state
|
||||
has_git: pathExistsInternal(cwd, '.git'),
|
||||
|
||||
// Enhanced search
|
||||
brave_search_available: hasBraveSearch,
|
||||
};
|
||||
|
||||
output(result, raw);
|
||||
@@ -3841,6 +3923,7 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
||||
const result = {
|
||||
// Config
|
||||
commit_docs: config.commit_docs,
|
||||
brave_search: config.brave_search,
|
||||
|
||||
// Phase info
|
||||
phase_found: !!phaseInfo,
|
||||
@@ -4128,7 +4211,7 @@ function cmdInitProgress(cwd, includes, raw) {
|
||||
|
||||
// ─── CLI Router ───────────────────────────────────────────────────────────────
|
||||
|
||||
function main() {
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const rawIndex = args.indexOf('--raw');
|
||||
const raw = rawIndex !== -1;
|
||||
@@ -4495,6 +4578,17 @@ function main() {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'websearch': {
|
||||
const query = args[1];
|
||||
const limitIdx = args.indexOf('--limit');
|
||||
const freshnessIdx = args.indexOf('--freshness');
|
||||
await cmdWebsearch(query, {
|
||||
limit: limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) : 10,
|
||||
freshness: freshnessIdx !== -1 ? args[freshnessIdx + 1] : null,
|
||||
}, raw);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
error(`Unknown command: ${command}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user