From d06a8351637aa54d9c4d3fe8011ee369bc5bbdc8 Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Sat, 14 Mar 2026 00:50:29 +0900 Subject: [PATCH] fix: usage dashboard - remove title, use system uptime, handle null limitName, drop seconds from timestamp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove "📊 Usage ëģīęģ " title line - Use os.uptime() instead of process.uptime() for actual server uptime - Handle null limitName in Codex rate limits (fallback to limitId or 'Codex') - Fix Codex usage parsing: extract from rateLimitsByLimitId object - Fix formatResetKST to handle unix timestamps (number type) - Remove seconds from update timestamp display --- src/index.ts | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2bb4023..f698543 100644 --- a/src/index.ts +++ b/src/index.ts @@ -416,9 +416,12 @@ function usageEmoji(pct: number): string { return 'ðŸŸĒ'; } -function formatResetKST(iso: string): string { +function formatResetKST(value: string | number): string { try { - return new Date(iso).toLocaleString('ko-KR', { + // Handle unix timestamp (seconds) or ISO string + const date = + typeof value === 'number' ? new Date(value * 1000) : new Date(value); + return date.toLocaleString('ko-KR', { timeZone: 'Asia/Seoul', month: 'short', day: 'numeric', @@ -426,7 +429,7 @@ function formatResetKST(iso: string): string { minute: '2-digit', }); } catch { - return iso; + return String(value); } } @@ -544,9 +547,10 @@ interface ClaudeUsageData { } interface CodexRateLimit { - limitName: string; - primary: { usedPercent: number; resetsAt: string }; - secondary: { usedPercent: number; resetsAt: string }; + limitId?: string; + limitName: string | null; + primary: { usedPercent: number; resetsAt: string | number }; + secondary: { usedPercent: number; resetsAt: string | number }; } async function fetchClaudeUsage(): Promise { @@ -635,7 +639,13 @@ async function fetchCodexUsage(): Promise { }) + '\n', ); } else if (msg.id === 2 && msg.result) { - finish(msg.result); + // Extract rate limits from rateLimitsByLimitId object + const byId = msg.result.rateLimitsByLimitId; + if (byId && typeof byId === 'object') { + finish(Object.values(byId) as CodexRateLimit[]); + } else { + finish(null); + } } } catch { /* non-JSON line, skip */ @@ -659,8 +669,6 @@ async function fetchCodexUsage(): Promise { async function buildUsageContent(): Promise { const lines: string[] = []; - lines.push(`📊 **Usage ëģīęģ ** (${ASSISTANT_NAME})`); - lines.push(''); // Fetch API usage in parallel const [claudeUsage, codexUsage] = await Promise.all([ @@ -695,11 +703,12 @@ async function buildUsageContent(): Promise { for (const limit of codexUsage) { const p = Math.round(limit.primary.usedPercent); const s = Math.round(limit.secondary.usedPercent); + const name = limit.limitName || limit.limitId || 'Codex'; lines.push( - `â€Ē ${limit.limitName} 5ė‹œę°„: ${usageEmoji(p)} ${p}% (ëĶŽė…‹: ${formatResetKST(limit.primary.resetsAt)})`, + `â€Ē ${name} 5ė‹œę°„: ${usageEmoji(p)} ${p}% (ëĶŽė…‹: ${formatResetKST(limit.primary.resetsAt)})`, ); lines.push( - `â€Ē ${limit.limitName} 7ėž: ${usageEmoji(s)} ${s}% (ëĶŽė…‹: ${formatResetKST(limit.secondary.resetsAt)})`, + `â€Ē ${name} 7ėž: ${usageEmoji(s)} ${s}% (ëĶŽė…‹: ${formatResetKST(limit.secondary.resetsAt)})`, ); } lines.push(''); @@ -745,9 +754,9 @@ async function buildUsageContent(): Promise { } lines.push(diskLine); - lines.push(`â€Ē ė—…íƒ€ėž„: ${formatElapsed(process.uptime() * 1000)}`); + lines.push(`â€Ē ė—…íƒ€ėž„: ${formatElapsed(os.uptime() * 1000)}`); - return lines.join('\n') + `\n\n_${new Date().toLocaleTimeString('ko-KR')}_`; + return lines.join('\n') + `\n\n_${new Date().toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' })}_`; } // ── Dashboard Lifecycle ─────────────────────────────────────────