build: compile dist/ [auto]

This commit is contained in:
github-actions[bot]
2026-03-06 07:05:40 +00:00
parent 67ddceb38a
commit d0e294c2ba
4 changed files with 128 additions and 20 deletions

142
dist/usage-api.js vendored
View File

@@ -13,14 +13,31 @@ const LEGACY_KEYCHAIN_SERVICE_NAME = 'Claude Code-credentials';
// File-based cache (HUD runs as new process each render, so in-memory cache won't persist)
const CACHE_TTL_MS = 60_000; // 60 seconds
const CACHE_FAILURE_TTL_MS = 15_000; // 15 seconds for failed requests
const CACHE_LOCK_STALE_MS = 30_000;
const CACHE_LOCK_WAIT_MS = 2_000;
const CACHE_LOCK_POLL_MS = 50;
const KEYCHAIN_TIMEOUT_MS = 3000;
const KEYCHAIN_BACKOFF_MS = 60_000; // Backoff on keychain failures to avoid re-prompting
const USAGE_API_TIMEOUT_MS_DEFAULT = 15_000;
export const USAGE_API_USER_AGENT = 'claude-code/2.1';
export const USAGE_API_USER_AGENT = 'claude-hud';
function getCachePath(homeDir) {
return path.join(getHudPluginDir(homeDir), '.usage-cache.json');
}
function readCache(homeDir, now) {
function getCacheLockPath(homeDir) {
return path.join(getHudPluginDir(homeDir), '.usage-cache.lock');
}
function hydrateCacheData(data) {
// JSON.stringify converts Date to ISO string, so we need to reconvert on read.
// new Date() handles both Date objects and ISO strings safely.
if (data.fiveHourResetAt) {
data.fiveHourResetAt = new Date(data.fiveHourResetAt);
}
if (data.sevenDayResetAt) {
data.sevenDayResetAt = new Date(data.sevenDayResetAt);
}
return data;
}
function readCacheState(homeDir, now) {
try {
const cachePath = getCachePath(homeDir);
if (!fs.existsSync(cachePath))
@@ -29,23 +46,20 @@ function readCache(homeDir, now) {
const cache = JSON.parse(content);
// Check TTL - use shorter TTL for failure results
const ttl = cache.data.apiUnavailable ? CACHE_FAILURE_TTL_MS : CACHE_TTL_MS;
if (now - cache.timestamp >= ttl)
return null;
// JSON.stringify converts Date to ISO string, so we need to reconvert on read.
// new Date() handles both Date objects and ISO strings safely.
const data = cache.data;
if (data.fiveHourResetAt) {
data.fiveHourResetAt = new Date(data.fiveHourResetAt);
}
if (data.sevenDayResetAt) {
data.sevenDayResetAt = new Date(data.sevenDayResetAt);
}
return data;
return {
data: hydrateCacheData(cache.data),
timestamp: cache.timestamp,
isFresh: now - cache.timestamp < ttl,
};
}
catch {
return null;
}
}
function readCache(homeDir, now) {
const cache = readCacheState(homeDir, now);
return cache?.isFresh ? cache.data : null;
}
function writeCache(homeDir, data, timestamp) {
try {
const cachePath = getCachePath(homeDir);
@@ -60,6 +74,78 @@ function writeCache(homeDir, data, timestamp) {
// Ignore cache write failures
}
}
function readLockTimestamp(lockPath) {
try {
if (!fs.existsSync(lockPath))
return null;
const raw = fs.readFileSync(lockPath, 'utf8').trim();
const parsed = Number.parseInt(raw, 10);
return Number.isFinite(parsed) ? parsed : null;
}
catch {
return null;
}
}
function tryAcquireCacheLock(homeDir) {
const lockPath = getCacheLockPath(homeDir);
const cacheDir = path.dirname(lockPath);
try {
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true });
}
const fd = fs.openSync(lockPath, 'wx');
try {
fs.writeFileSync(fd, String(Date.now()), 'utf8');
}
finally {
fs.closeSync(fd);
}
return 'acquired';
}
catch (error) {
const maybeError = error;
if (maybeError.code !== 'EEXIST') {
debug('Usage cache lock unavailable, continuing without coordination:', maybeError.message);
return 'unsupported';
}
}
const lockTimestamp = readLockTimestamp(lockPath);
if (lockTimestamp != null && Date.now() - lockTimestamp > CACHE_LOCK_STALE_MS) {
try {
fs.unlinkSync(lockPath);
}
catch {
return 'busy';
}
return tryAcquireCacheLock(homeDir);
}
return 'busy';
}
function releaseCacheLock(homeDir) {
try {
const lockPath = getCacheLockPath(homeDir);
if (fs.existsSync(lockPath)) {
fs.unlinkSync(lockPath);
}
}
catch {
// Ignore lock cleanup failures
}
}
async function waitForFreshCache(homeDir, now, timeoutMs = CACHE_LOCK_WAIT_MS) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, CACHE_LOCK_POLL_MS));
const cached = readCache(homeDir, now());
if (cached) {
return cached;
}
if (!fs.existsSync(getCacheLockPath(homeDir))) {
break;
}
}
return readCache(homeDir, now());
}
const defaultDeps = {
homeDir: () => os.homedir(),
fetchApi: fetchUsageApi,
@@ -79,11 +165,24 @@ export async function getUsage(overrides = {}) {
const now = deps.now();
const homeDir = deps.homeDir();
// Check file-based cache first
const cached = readCache(homeDir, now);
if (cached) {
return cached;
const cacheState = readCacheState(homeDir, now);
if (cacheState?.isFresh) {
return cacheState.data;
}
let holdsCacheLock = false;
const lockStatus = tryAcquireCacheLock(homeDir);
if (lockStatus === 'busy') {
if (cacheState) {
return cacheState.data;
}
return await waitForFreshCache(homeDir, deps.now);
}
holdsCacheLock = lockStatus === 'acquired';
try {
const refreshedCache = readCache(homeDir, deps.now());
if (refreshedCache) {
return refreshedCache;
}
const credentials = readCredentials(homeDir, now, deps.readKeychain);
if (!credentials) {
return null;
@@ -132,6 +231,11 @@ export async function getUsage(overrides = {}) {
debug('getUsage failed:', error);
return null;
}
finally {
if (holdsCacheLock) {
releaseCacheLock(homeDir);
}
}
}
/**
* Get path for keychain failure backoff cache.
@@ -592,6 +696,10 @@ export function clearCache(homeDir) {
if (fs.existsSync(cachePath)) {
fs.unlinkSync(cachePath);
}
const lockPath = getCacheLockPath(homeDir);
if (fs.existsSync(lockPath)) {
fs.unlinkSync(lockPath);
}
}
catch {
// Ignore