diff --git a/docs/websearch.md b/docs/websearch.md index 623d1b2c..184588df 100644 --- a/docs/websearch.md +++ b/docs/websearch.md @@ -57,21 +57,19 @@ The **ultimate solution** for third-party WebSearch. Uses `gemini` CLI with OAut ### Requirements -- `gemini` CLI installed and authenticated (`gemini auth login`) +- `gemini` CLI installed and authenticated (run `gemini` to authenticate via browser) - OAuth authentication (no GEMINI_API_KEY needed) ### Installation -The Gemini CLI is typically installed via: +The Gemini CLI is installed via npm: ```bash -pip install google-generativeai -# or -pipx install google-generativeai +npm install -g @google/gemini-cli ``` -Then authenticate: +Then authenticate by running gemini once (opens browser): ```bash -gemini auth login +gemini ``` ## MCP Providers @@ -176,8 +174,8 @@ CCS writes MCP configuration to `~/.claude/.mcp.json`. Example: ### Gemini CLI Issues -1. **Not installed**: Install with `pip install google-generativeai` -2. **Not authenticated**: Run `gemini auth login` +1. **Not installed**: Install with `npm install -g @google/gemini-cli` +2. **Not authenticated**: Run `gemini` to open browser for OAuth login 3. **Timeout**: Increase timeout in config or via `CCS_GEMINI_TIMEOUT=90` 4. **Skip Gemini**: Set `CCS_GEMINI_SKIP=1` to use MCP fallback only diff --git a/lib/hooks/websearch-transformer.cjs b/lib/hooks/websearch-transformer.cjs index 84d9f8d7..b0270543 100644 --- a/lib/hooks/websearch-transformer.cjs +++ b/lib/hooks/websearch-transformer.cjs @@ -513,8 +513,8 @@ function outputError(query, error, providerName) { `Query: "${query}"`, '', 'Troubleshooting:', - ' - Check if Gemini CLI is authenticated: gemini auth status', - ' - Re-authenticate if needed: gemini auth login', + ' - Check ~/.gemini/oauth_creds.json exists', + ' - Re-authenticate: run `gemini` (opens browser)', ].join('\n'); const output = { @@ -546,7 +546,7 @@ function outputNoProvidersEnabled(query) { '', '1. Gemini CLI (FREE, 1000 req/day):', ' npm install -g @google/gemini-cli', - ' gemini auth login', + ' gemini # opens browser to authenticate', '', '2. OpenCode (FREE via Zen):', ' curl -fsSL https://opencode.ai/install | bash', @@ -595,7 +595,7 @@ function outputAllFailedMessage(query, errors) { `Query: "${query}"`, '', 'Troubleshooting:', - ' - Gemini: gemini auth status / gemini auth login', + ' - Gemini: run `gemini` to authenticate (opens browser)', ' - OpenCode: opencode --version', ' - Grok: Check XAI_API_KEY environment variable', ].join('\n'); diff --git a/src/utils/websearch-manager.ts b/src/utils/websearch-manager.ts index f4a7721c..7ae85d89 100644 --- a/src/utils/websearch-manager.ts +++ b/src/utils/websearch-manager.ts @@ -114,6 +114,32 @@ export function hasGeminiCli(): boolean { return getGeminiCliStatus().installed; } +/** + * Check if Gemini CLI is authenticated + * + * Gemini CLI stores OAuth credentials in ~/.gemini/oauth_creds.json + * Authentication is done by running `gemini` which opens browser for OAuth. + * Note: There is NO `gemini auth login` command - just run `gemini` to authenticate. + * + * @returns true if oauth_creds.json exists with access_token + */ +export function isGeminiAuthenticated(): boolean { + const oauthPath = path.join(os.homedir(), '.gemini', 'oauth_creds.json'); + + if (!fs.existsSync(oauthPath)) { + return false; + } + + try { + const content = fs.readFileSync(oauthPath, 'utf8'); + const creds = JSON.parse(content); + // Check if access_token exists (doesn't validate expiry - Gemini CLI handles refresh) + return !!creds.access_token; + } catch { + return false; + } +} + /** * Clear Gemini CLI cache (for testing or after installation) */ @@ -692,7 +718,7 @@ export function getWebSearchHookEnv(): Record { /** * WebSearch availability status for third-party profiles */ -export type WebSearchReadiness = 'ready' | 'unavailable'; +export type WebSearchReadiness = 'ready' | 'needs_auth' | 'unavailable'; /** * WebSearch status for display @@ -700,6 +726,7 @@ export type WebSearchReadiness = 'ready' | 'unavailable'; export interface WebSearchStatus { readiness: WebSearchReadiness; geminiCli: boolean; + geminiAuthenticated: boolean; grokCli: boolean; opencodeCli: boolean; message: string; @@ -709,6 +736,7 @@ export interface WebSearchStatus { * Get WebSearch readiness status for display * * Called on third-party profile startup to inform user. + * Checks both installation AND authentication status for Gemini CLI. */ export function getWebSearchReadiness(): WebSearchStatus { const wsConfig = getWebSearchConfig(); @@ -718,6 +746,7 @@ export function getWebSearchReadiness(): WebSearchStatus { return { readiness: 'unavailable', geminiCli: false, + geminiAuthenticated: false, grokCli: false, opencodeCli: false, message: 'Disabled in config', @@ -726,28 +755,56 @@ export function getWebSearchReadiness(): WebSearchStatus { // Check all CLIs const geminiInstalled = hasGeminiCli(); + const geminiAuthed = geminiInstalled && isGeminiAuthenticated(); const grokInstalled = hasGrokCli(); const opencodeInstalled = hasOpenCodeCli(); - // Build message based on installed CLIs - const installedClis: string[] = []; - if (geminiInstalled) installedClis.push('Gemini'); - if (grokInstalled) installedClis.push('Grok'); - if (opencodeInstalled) installedClis.push('OpenCode'); + // Build message based on installed + authenticated CLIs + const readyClis: string[] = []; + const needsAuthClis: string[] = []; - if (installedClis.length > 0) { + // Gemini requires auth check + if (geminiInstalled) { + if (geminiAuthed) { + readyClis.push('Gemini'); + } else { + needsAuthClis.push('Gemini'); + } + } + + // Other CLIs don't require auth check (for now) + if (grokInstalled) readyClis.push('Grok'); + if (opencodeInstalled) readyClis.push('OpenCode'); + + // Determine overall status + if (readyClis.length > 0) { + // At least one CLI is ready return { readiness: 'ready', geminiCli: geminiInstalled, + geminiAuthenticated: geminiAuthed, grokCli: grokInstalled, opencodeCli: opencodeInstalled, - message: `Ready (${installedClis.join(' + ')})`, + message: `Ready (${readyClis.join(' + ')})`, + }; + } + + if (needsAuthClis.length > 0) { + // CLIs installed but need auth + return { + readiness: 'needs_auth', + geminiCli: geminiInstalled, + geminiAuthenticated: false, + grokCli: grokInstalled, + opencodeCli: opencodeInstalled, + message: `Gemini: run 'gemini' to login`, }; } return { readiness: 'unavailable', geminiCli: false, + geminiAuthenticated: false, grokCli: false, opencodeCli: false, message: 'Install: npm i -g @google/gemini-cli', @@ -767,6 +824,10 @@ export function displayWebSearchStatus(): void { case 'ready': console.error(ok(`WebSearch: ${status.message}`)); break; + case 'needs_auth': + // CLI installed but not authenticated - show warning + console.error(warn(`WebSearch: ${status.message}`)); + break; case 'unavailable': console.error(fail(`WebSearch: ${status.message}`)); // Show install hints for CLI-only users