diff --git a/src/onboarding.ts b/src/onboarding.ts index f96262607..28b4f2a06 100644 --- a/src/onboarding.ts +++ b/src/onboarding.ts @@ -37,18 +37,6 @@ type PicoModule = { // ─── Constants ──────────────────────────────────────────────────────────────── const TOOL_KEYS: ToolKeyConfig[] = [ - { - provider: 'brave', - envVar: 'BRAVE_API_KEY', - label: 'Brave Search', - hint: 'web search + search_and_read tools', - }, - { - provider: 'brave_answers', - envVar: 'BRAVE_ANSWERS_KEY', - label: 'Brave Answers', - hint: 'AI-summarised search answers', - }, { provider: 'context7', envVar: 'CONTEXT7_API_KEY', @@ -207,6 +195,18 @@ export async function runOnboarding(authStorage: AuthStorage): Promise { p.log.info('You can configure your LLM provider later with /login inside GSD.') } + // ── Web Search Provider ────────────────────────────────────────────────── + let searchConfigured: string | null = null + try { + searchConfigured = await runWebSearchStep(p, pc, authStorage, llmConfigured) + } catch (err) { + if (isCancelError(p, err)) { + p.cancel('Setup cancelled.') + return + } + p.log.warn(`Web search setup failed: ${err instanceof Error ? err.message : String(err)}`) + } + // ── Tool API Keys ───────────────────────────────────────────────────────── let toolKeyCount = 0 try { @@ -234,6 +234,12 @@ export async function runOnboarding(authStorage: AuthStorage): Promise { summaryLines.push(`${pc.yellow('↷')} LLM provider: skipped — use /login inside GSD`) } + if (searchConfigured) { + summaryLines.push(`${pc.green('✓')} Web search: ${searchConfigured}`) + } else { + summaryLines.push(`${pc.dim('↷')} Web search: not configured — use /search-provider inside GSD`) + } + if (toolKeyCount > 0) { summaryLines.push(`${pc.green('✓')} ${toolKeyCount} tool key${toolKeyCount > 1 ? 's' : ''} saved`) } else { @@ -398,6 +404,77 @@ async function runApiKeyFlow( return true } +// ─── Web Search Provider Step ───────────────────────────────────────────────── + +async function runWebSearchStep( + p: ClackModule, + pc: PicoModule, + authStorage: AuthStorage, + isAnthropicAuth: boolean, +): Promise { + // Check which LLM provider was configured + const authed = authStorage.list().filter(id => LLM_PROVIDER_IDS.includes(id)) + const isAnthropic = isAnthropicAuth && authed.includes('anthropic') + + // Build options based on what's available + type SearchOption = { value: string; label: string; hint?: string } + const options: SearchOption[] = [] + + if (isAnthropic) { + options.push({ + value: 'anthropic-native', + label: 'Anthropic built-in web search', + hint: 'no API key needed — already included with Claude', + }) + } + + options.push( + { value: 'brave', label: 'Brave Search', hint: 'requires API key — brave.com/search/api' }, + { value: 'tavily', label: 'Tavily', hint: 'requires API key — tavily.com' }, + { value: 'skip', label: 'Skip for now', hint: 'use /search-provider inside GSD later' }, + ) + + const choice = await p.select({ + message: 'How do you want to search the web?', + options, + }) + + if (p.isCancel(choice) || choice === 'skip') return null + + if (choice === 'anthropic-native') { + p.log.success(`Web search: ${pc.green('Anthropic built-in')} — works out of the box`) + return 'Anthropic built-in' + } + + if (choice === 'brave') { + const key = await p.password({ + message: `Paste your Brave Search API key ${pc.dim('(brave.com/search/api)')}:`, + mask: '●', + }) + if (p.isCancel(key) || !(key as string)?.trim()) return null + const trimmed = (key as string).trim() + authStorage.set('brave', { type: 'api_key', key: trimmed }) + process.env.BRAVE_API_KEY = trimmed + p.log.success(`Web search: ${pc.green('Brave Search')} configured`) + return 'Brave Search' + } + + if (choice === 'tavily') { + const key = await p.password({ + message: `Paste your Tavily API key ${pc.dim('(tavily.com)')}:`, + mask: '●', + }) + if (p.isCancel(key) || !(key as string)?.trim()) return null + const trimmed = (key as string).trim() + authStorage.set('tavily', { type: 'api_key', key: trimmed }) + process.env.TAVILY_API_KEY = trimmed + p.log.success(`Web search: ${pc.green('Tavily')} configured`) + return 'Tavily' + } + + return null +} + // ─── Tool API Keys Step ─────────────────────────────────────────────────────── async function runToolKeysStep(