项目文件夹

文件
2026-07-12 23:57:31 +00:00

114 行
4.0 KiB
JavaScript

import * as fs from 'node:fs';
import * as os from 'node:os';
import { getClaudeConfigJsonPath } from './claude-config-dir.js';
import { sanitizeDisplayText } from './utils/sanitize.js';
const EMPTY_AUTH_INFO = { method: null, user: null };
const API_KEY_AUTH_INFO = { method: 'API Key', user: null };
function hasApiKey(env) {
return typeof env.ANTHROPIC_API_KEY === 'string' && env.ANTHROPIC_API_KEY.trim().length > 0;
}
// Strip ANSI sequences and control/bidi characters so values from
// claude.json can never smuggle escape sequences into the terminal.
function sanitizeValue(value) {
return sanitizeDisplayText(value).trim();
}
function readString(obj, key) {
const value = obj[key];
if (typeof value !== 'string') {
return null;
}
const sanitized = sanitizeValue(value);
return sanitized.length > 0 ? sanitized : null;
}
/**
* Formats an organizationType value into a display label:
* "claude_max" → "Claude Max", "claude_pro" → "Claude Pro".
*/
function formatOrgType(orgType) {
return orgType
.split('_')
.filter(Boolean)
.map((word) => word[0].toUpperCase() + word.slice(1))
.join(' ');
}
/**
* Extracts a multiplier suffix from a rate-limit tier value:
* "default_claude_max_20x" → "20x". Returns null when no tier is encoded.
*/
function extractTierSuffix(rateLimitTier) {
const match = /_(\d+x)$/i.exec(rateLimitTier);
return match ? match[1] : null;
}
/**
* Derives auth info from the parsed contents of {CLAUDE_CONFIG_DIR}.json.
* Pure so it can be tested without touching the filesystem.
*/
export function deriveAuthInfo(claudeJson, env = process.env) {
// ANTHROPIC_API_KEY takes precedence at runtime. oauthAccount can remain in
// claude.json after a user switches to API-key authentication.
if (hasApiKey(env)) {
return API_KEY_AUTH_INFO;
}
const root = (claudeJson && typeof claudeJson === 'object')
? claudeJson
: null;
const account = (root?.oauthAccount && typeof root.oauthAccount === 'object')
? root.oauthAccount
: null;
if (!account) {
return EMPTY_AUTH_INFO;
}
let method = null;
const orgType = readString(account, 'organizationType');
if (orgType) {
method = formatOrgType(orgType);
const rateLimitTier = readString(account, 'organizationRateLimitTier');
const tier = rateLimitTier ? extractTierSuffix(rateLimitTier) : null;
if (tier && !method.toLowerCase().includes(tier.toLowerCase())) {
method += ` ${tier}`;
}
}
const email = readString(account, 'emailAddress');
const user = email ? email.split('@')[0] : readString(account, 'displayName');
return { method, user };
}
/** Reads auth info for the current login. Never throws. */
export function readAuthInfo() {
// Avoid reading a stale OAuth profile when the active source is an API key.
if (hasApiKey(process.env)) {
return API_KEY_AUTH_INFO;
}
try {
const configJsonPath = getClaudeConfigJsonPath(os.homedir());
const content = fs.readFileSync(configJsonPath, 'utf-8');
return deriveAuthInfo(JSON.parse(content));
}
catch {
return EMPTY_AUTH_INFO;
}
}
export function truncateUser(user, maxLength) {
if (maxLength <= 0 || user.length <= maxLength) {
return user;
}
return `${user.slice(0, maxLength)}…`;
}
/**
* Builds the standalone auth segment for the end of the first HUD line,
* honoring the showAuth / showAuthUser / authUserLength display settings.
* Returns e.g. "Claude Max 20x · yukinosh…", or null when nothing to show.
*/
export function formatAuthSegment(info, display) {
if (!info) {
return null;
}
const parts = [];
if (display?.showAuth && info.method) {
parts.push(info.method);
}
if (display?.showAuthUser && info.user) {
parts.push(truncateUser(info.user, display?.authUserLength ?? 8));
}
return parts.length > 0 ? parts.join(' · ') : null;
}
//# sourceMappingURL=auth.js.map