Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/lib/claude-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { isImageFile } from '@/types';
import { registerPendingPermission } from './permission-registry';
import { registerConversation, unregisterConversation } from './conversation-registry';
import { getSetting, getActiveProvider, updateSdkSessionId, createPermissionRequest } from './db';
import { findClaudeBinary, findGitBash, getExpandedPath } from './platform';
import { findClaudeBinary, findGitBash, getExpandedPath, getClaudeOAuthTokenFromKeychain } from './platform';
import { notifyPermissionRequest, notifyGeneric } from './telegram-bot';
import os from 'os';
import fs from 'fs';
Expand Down Expand Up @@ -358,9 +358,16 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
if (appBaseUrl) {
sdkEnv.ANTHROPIC_BASE_URL = appBaseUrl;
}
// If neither legacy settings nor env vars provide a key, log a warning
// If neither legacy settings nor env vars provide a key,
// try to read the OAuth token from macOS Keychain (claude auth login / subscription).
if (!appToken && !sdkEnv.ANTHROPIC_API_KEY && !sdkEnv.ANTHROPIC_AUTH_TOKEN) {
console.warn('[claude-client] No API key found: no active provider, no legacy settings, and no ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN in environment');
const keychainToken = getClaudeOAuthTokenFromKeychain();
if (keychainToken) {
sdkEnv.ANTHROPIC_AUTH_TOKEN = keychainToken;
console.log('[claude-client] Using OAuth token from macOS Keychain');
} else {
console.warn('[claude-client] No API key found: no active provider, no legacy settings, no ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN in environment, and no Keychain token');
}
}
}

Expand Down
25 changes: 25 additions & 0 deletions src/lib/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,28 @@ export function findGitBash(): string | null {

return null;
}

/**
* Read Claude Code's OAuth access token from macOS Keychain.
* Claude Code stores credentials under service "Claude Code-credentials",
* account = current username, as a JSON blob with shape:
* { claudeAiOauth: { accessToken, refreshToken, expiresAt, ... } }
*
* Returns the accessToken string, or null if unavailable / not macOS.
*/
export function getClaudeOAuthTokenFromKeychain(): string | null {
if (process.platform !== 'darwin') return null;
try {
const raw = execFileSync(
'/usr/bin/security',
['find-generic-password', '-a', process.env.USER || os.userInfo().username, '-w', '-s', 'Claude Code-credentials'],
{ timeout: 3000, stdio: 'pipe' },
).toString().trim();
if (!raw) return null;
const parsed = JSON.parse(raw);
const token = parsed?.claudeAiOauth?.accessToken;
return typeof token === 'string' && token.length > 0 ? token : null;
} catch {
return null;
}
}