jarrodwatts--claude-hud
114 行
4.0 KiB
JavaScript
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
|