fix(FN-668): add macOS keychain support for Claude CLI credentials

- Add readClaudeKeychainCredentials() to read from macOS keychain using security CLI
- Fall back to keychain when legacy credential files don't exist
- Support both subscriptionType and rateLimitTier for plan detection
- Add comprehensive tests for keychain credential reading
- Add changeset for patch release
This commit is contained in:
gsxdsm
2026-04-01 07:02:54 -07:00
parent d2d32acce8
commit e133025798
3 changed files with 173 additions and 1 deletions

View File

@@ -1,6 +1,8 @@
import * as fs from "node:fs";
import * as path from "node:path";
import * as https from "node:https";
import * as child_process from "node:child_process";
import { promisify } from "node:util";
/**
* Pace information for weekly usage windows
@@ -209,6 +211,23 @@ function decodeJwtPayload(token: string): any {
// ── Claude fetcher ─────────────────────────────────────────────────────────
/**
* Read Claude credentials from macOS keychain.
* Returns the parsed credentials object or null if not found/error.
*/
function readClaudeKeychainCredentials(): any | null {
try {
const result = child_process.execFileSync(
"security",
["find-generic-password", "-s", "Claude Code-credentials", "-w"],
{ encoding: "utf-8", timeout: 5000 }
);
return JSON.parse(result.trim());
} catch {
return null;
}
}
async function fetchClaudeUsage(): Promise<ProviderUsage> {
const usage: ProviderUsage = {
name: "Claude",
@@ -231,6 +250,11 @@ async function fetchClaudeUsage(): Promise<ProviderUsage> {
} catch {}
}
// Fallback to macOS keychain if file credentials not found
if (!creds) {
creds = readClaudeKeychainCredentials();
}
const oauthCreds = creds?.claudeAiOauth || creds;
if (!oauthCreds?.accessToken) {
usage.error = "No Claude CLI credentials — run 'claude' to login";