diff --git a/.claude/settings.json b/.claude/settings.json index bf6be2f..120f104 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -6,7 +6,7 @@ "hooks": [ { "type": "command", - "command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -qE '\\.(ts|tsx|js|jsx|json)$'; then bun biome check --write \"$file_path\" 2>/dev/null || true; fi; }" + "command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -qE '\\.(ts|tsx|js|jsx|json)$'; then bun biome check --write \"$file_path\" 2>/dev/null || true; bun run build 2>/dev/null || true; fi; }" } ] } diff --git a/CLAUDE.md b/CLAUDE.md index 21e80e9..04de5ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,6 +17,7 @@ cc-statusline/ │ ├── format/ │ │ ├── index.ts # barrel re-export │ │ ├── formatNumber.ts # formatNumber +│ │ ├── formatResetDate.ts # formatResetDate │ │ ├── formatTime.ts # formatTime │ │ └── getTimeUntilReset.ts # getTimeUntilReset │ ├── git/ @@ -56,7 +57,8 @@ cc-statusline/ | Git diff | `git diff --shortstat` | | PR URL | `gh pr view` | | 블록 사용량 | Anthropic Usage API (`/api/oauth/usage`, OAuth 토큰) | -| 리셋 타이머 | Anthropic Usage API `resets_at` | +| 리셋 타이머 | Anthropic Usage API `five_hour.resets_at` | +| 주간 리셋 시간 | Anthropic Usage API `seven_day.resets_at` | ## WHY @@ -65,7 +67,8 @@ Claude Code 기본 statusbar에 다음 정보를 추가로 표시: - Context window 토큰 사용량 및 사용률 (%) - Git diff 통계 (파일 수, +insertions, -deletions) - PR URL (클릭 가능한 OSC 8 하이퍼링크) -- 리셋 타이머 (5시간 사용량 리셋까지 남은 시간) +- 리셋 시각 (5시간 사용량 리셋 시각, HH:MM) +- 주간 리셋 시간 (7일 사용량 리셋 시각, MM/DD HH:MM) - 블록 사용량 (서버 API 기반 5시간/7일 utilization %) - TrueColor 동적 색상 (임계값 기반 경고) @@ -114,7 +117,7 @@ bun test --coverage ``` **테스트 구조**: -- `pure.test.ts`: 순수 함수 (getUsageColor, formatNumber, formatTime, getTimeUntilReset) +- `pure.test.ts`: 순수 함수 (getUsageColor, formatNumber, formatTime, formatResetDate, getTimeUntilReset) - `cached.test.ts`: 캐시 TTL 및 메커니즘 - `cli.test.ts`: CLI 인자 파싱 (--show-usage) - `main.test.ts`: renderStatusLine 순수 함수 (의존성 주입 방식) diff --git a/README.md b/README.md index b403bef..e5ab7ac 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,9 @@ Add the following to `~/.claude/settings.json`: - **Git Diff**: File count, insertions, deletions - **PR URL**: Clickable OSC 8 hyperlink - **TrueColor**: Dynamic colors based on thresholds -- **Limit Reset Timer**: Countdown to usage limit reset +- **Limit Reset Time**: Reset time display (HH:MM) - **Block Usage**: 5-hour utilization percentage (from server API) +- **Weekly Reset Timer**: Weekly limit reset time (MM/DD HH:MM) - **Weekly Usage**: 7-day utilization percentage (from server API) ## Emoji Guide @@ -62,8 +63,9 @@ Add the following to `~/.claude/settings.json`: | ⏱️ | Session elapsed time | | 💰 | Session cost in USD | | 🧠 | Context window usage | -| ⏳ | Limit reset countdown | +| ⏳ | Limit reset time | | 📊 | 5-hour utilization % | +| ⏰ | Weekly limit reset time | | 📅 | 7-day utilization % | | ✏️ | Uncommitted changes | | 📎 | Pull request link | @@ -84,7 +86,8 @@ Calls the Anthropic Usage API (`/api/oauth/usage`) using the OAuth access token 1. **5-hour utilization** - Server-calculated usage percentage for the current billing block 2. **7-day utilization** - Server-calculated weekly usage percentage -3. **Reset timer** - Exact reset time from the server (`resets_at`) +3. **Reset timer** - Exact reset time from the server (`five_hour.resets_at`) +4. **Weekly reset timer** - Weekly limit reset time (`seven_day.resets_at`), shown as `MM/DD HH:MM` (e.g., `02/15 17:00`) ### Enable diff --git a/bun.lock b/bun.lock index 7b06208..cea2647 100644 --- a/bun.lock +++ b/bun.lock @@ -3,9 +3,6 @@ "workspaces": { "": { "name": "cc-statusline", - "dependencies": { - "ccusage": "^18.0.5", - }, "devDependencies": { "@biomejs/biome": "^2.3.9", "@types/bun": "latest", @@ -38,8 +35,6 @@ "bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="], - "ccusage": ["ccusage@18.0.5", "", { "bin": { "ccusage": "dist/index.js" } }, "sha512-bnZrVbGm5h7hIIOH3FZFaDxKgJLLHhWDWIv7/9M6/O373YM6RAwsG9hxZXA1ep+C2ISiNEX6MYVoXgkoSuFf9Q=="], - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], diff --git a/docs/README.es.md b/docs/README.es.md index 0532679..26ed1d0 100644 --- a/docs/README.es.md +++ b/docs/README.es.md @@ -50,8 +50,9 @@ Agrega lo siguiente a `~/.claude/settings.json`: - **Git Diff**: Cantidad de archivos, inserciones, eliminaciones - **PR URL**: Hipervínculo OSC 8 clickeable - **TrueColor**: Colores dinámicos basados en umbrales -- **Temporizador de reinicio**: Tiempo restante hasta el reinicio del límite +- **Hora de reinicio**: Hora de reinicio del límite de 5 horas (HH:MM) - **Uso del bloque**: Porcentaje de utilización de 5 horas (desde API del servidor) +- **Temporizador de reinicio semanal**: Tiempo de reinicio del límite semanal (MM/DD HH:MM) - **Uso semanal**: Porcentaje de utilización de 7 días (desde API del servidor) ## Guía de Emojis @@ -63,8 +64,9 @@ Agrega lo siguiente a `~/.claude/settings.json`: | ⏱️ | Tiempo transcurrido de sesión | | 💰 | Costo de sesión en USD | | 🧠 | Uso de ventana de contexto | -| ⏳ | Cuenta regresiva de reinicio | +| ⏳ | Hora de reinicio | | 📊 | Utilización de 5 horas % | +| ⏰ | Tiempo de reinicio semanal | | 📅 | Utilización de 7 días % | | ✏️ | Cambios sin confirmar | | 📎 | Enlace de Pull Request | @@ -85,7 +87,8 @@ Llama a la API de Uso de Anthropic (`/api/oauth/usage`) usando el token de acces 1. **Utilización de 5 horas** - Porcentaje de uso calculado por el servidor para el bloque de facturación actual 2. **Utilización de 7 días** - Porcentaje de uso semanal calculado por el servidor -3. **Temporizador de reinicio** - Tiempo exacto de reinicio desde el servidor (`resets_at`) +3. **Temporizador de reinicio** - Tiempo exacto de reinicio desde el servidor (`five_hour.resets_at`) +4. **Temporizador de reinicio semanal** - Tiempo de reinicio del límite semanal (`seven_day.resets_at`), formato `MM/DD HH:MM` (ej., `02/15 17:00`) ### Activar diff --git a/docs/README.ja.md b/docs/README.ja.md index b5f8346..e12659f 100644 --- a/docs/README.ja.md +++ b/docs/README.ja.md @@ -50,8 +50,9 @@ Claude Code用カスタムステータスライン。 - **Git Diff**: ファイル数、追加、削除 - **PR URL**: クリック可能なOSC 8ハイパーリンク - **TrueColor**: しきい値に基づく動的カラー -- **制限リセットタイマー**: 使用量制限リセットまでの残り時間 +- **リセット時刻**: 5時間使用量リセット時刻(HH:MM) - **ブロック使用量**: 5時間使用率(サーバーAPI基準) +- **週間リセットタイマー**: 7日使用量リセット時刻(MM/DD HH:MM) - **週間使用量**: 7日使用率(サーバーAPI基準) ## 絵文字ガイド @@ -63,8 +64,9 @@ Claude Code用カスタムステータスライン。 | ⏱️ | セッション経過時間 | | 💰 | セッションコスト(USD) | | 🧠 | コンテキストウィンドウ使用量 | -| ⏳ | 制限リセットカウントダウン | +| ⏳ | リセット時刻 | | 📊 | 5時間使用率 % | +| ⏰ | 週間制限リセット時間 | | 📅 | 7日使用率 % | | ✏️ | コミットされていない変更 | | 📎 | Pull Requestリンク | @@ -85,7 +87,8 @@ macOS KeychainのOAuthアクセストークンを使用してAnthropic Usage API 1. **5時間使用率** - 現在のビリングブロックのサーバー計算使用パーセンテージ 2. **7日使用率** - サーバー計算の週間使用パーセンテージ -3. **リセットタイマー** - サーバーから提供される正確なリセット時間(`resets_at`) +3. **リセットタイマー** - サーバーから提供される正確なリセット時間(`five_hour.resets_at`) +4. **週間リセットタイマー** - 7日使用量リセット時刻(`seven_day.resets_at`)、`MM/DD HH:MM`形式(例:`02/15 17:00`) ### 有効化 diff --git a/docs/README.ko.md b/docs/README.ko.md index b722a31..74aa88c 100644 --- a/docs/README.ko.md +++ b/docs/README.ko.md @@ -50,8 +50,9 @@ Claude Code를 위한 커스텀 상태표시줄. - **Git Diff**: 파일 수, 추가, 삭제 - **PR URL**: 클릭 가능한 OSC 8 하이퍼링크 - **TrueColor**: 임계값에 따른 동적 색상 -- **제한 리셋 타이머**: 사용량 제한 리셋까지 남은 시간 +- **리셋 시각**: 5시간 사용량 리셋 시각 (HH:MM) - **블록 사용량**: 5시간 사용률 (서버 API 기반) +- **주간 리셋 타이머**: 7일 사용량 리셋 시각 (MM/DD HH:MM) - **주간 사용량**: 7일 사용률 (서버 API 기반) ## Emoji 가이드 @@ -63,8 +64,9 @@ Claude Code를 위한 커스텀 상태표시줄. | ⏱️ | 세션 경과 시간 | | 💰 | 세션 비용 (USD) | | 🧠 | 컨텍스트 창 사용량 | -| ⏳ | 제한 리셋 카운트다운 | +| ⏳ | 리셋 시각 | | 📊 | 5시간 사용률 % | +| ⏰ | 주간 제한 리셋 시간 | | 📅 | 7일 사용률 % | | ✏️ | 커밋되지 않은 변경사항 | | 📎 | Pull Request 링크 | @@ -85,7 +87,8 @@ macOS Keychain의 OAuth 액세스 토큰을 사용하여 Anthropic Usage API(`/a 1. **5시간 사용률** - 현재 빌링 블록의 서버 계산 사용 백분율 2. **7일 사용률** - 서버 계산 주간 사용 백분율 -3. **리셋 타이머** - 서버에서 제공하는 정확한 리셋 시간 (`resets_at`) +3. **리셋 타이머** - 서버에서 제공하는 정확한 리셋 시간 (`five_hour.resets_at`) +4. **주간 리셋 타이머** - 7일 사용량 리셋 시각 (`seven_day.resets_at`), `MM/DD HH:MM` 포맷 (예: `02/15 17:00`) ### 활성화 diff --git a/docs/README.zh.md b/docs/README.zh.md index e8181f8..bde7f9d 100644 --- a/docs/README.zh.md +++ b/docs/README.zh.md @@ -50,8 +50,9 @@ Claude Code 自定义状态栏。 - **Git Diff**: 文件数、新增、删除 - **PR URL**: 可点击的 OSC 8 超链接 - **TrueColor**: 基于阈值的动态颜色 -- **限制重置计时器**: 使用量限制重置剩余时间 +- **重置时间**: 5小时使用量重置时间(HH:MM) - **块使用量**: 5小时使用率(来自服务器 API) +- **每周重置计时器**: 7天使用量重置时间(MM/DD HH:MM) - **周使用量**: 7天使用率(来自服务器 API) ## 表情符号指南 @@ -63,8 +64,9 @@ Claude Code 自定义状态栏。 | ⏱️ | 会话经过时间 | | 💰 | 会话费用(美元) | | 🧠 | 上下文窗口使用量 | -| ⏳ | 限制重置倒计时 | +| ⏳ | 重置时间 | | 📊 | 5小时使用率 % | +| ⏰ | 每周限制重置时间 | | 📅 | 7天使用率 % | | ✏️ | 未提交的更改 | | 📎 | Pull Request 链接 | @@ -85,7 +87,8 @@ Claude Code 自定义状态栏。 1. **5小时使用率** - 当前计费块的服务器计算使用百分比 2. **7天使用率** - 服务器计算的周使用百分比 -3. **重置计时器** - 服务器提供的精确重置时间(`resets_at`) +3. **重置计时器** - 服务器提供的精确重置时间(`five_hour.resets_at`) +4. **每周重置计时器** - 7天使用量重置时间(`seven_day.resets_at`),`MM/DD HH:MM` 格式(如 `02/15 17:00`) ### 启用 diff --git a/docs/usage_metrics.png b/docs/usage_metrics.png index b353d42..4ac67f1 100644 Binary files a/docs/usage_metrics.png and b/docs/usage_metrics.png differ diff --git a/package.json b/package.json index 878b44b..27d0f4f 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,5 @@ "lint": "biome check src/", "typecheck": "tsc --noEmit" }, - "type": "module", - "dependencies": {} + "type": "module" } diff --git a/src/__tests__/async.test.ts b/src/__tests__/async.test.ts index bca11f6..e452252 100644 --- a/src/__tests__/async.test.ts +++ b/src/__tests__/async.test.ts @@ -140,6 +140,7 @@ describe("async functions (integration)", () => { resetTime: null, utilization: 56, sevenDayUtilization: 37, + sevenDayResetTime: null, }, timestamp: Date.now(), }; diff --git a/src/__tests__/cached.test.ts b/src/__tests__/cached.test.ts index 307b6c1..04ca90f 100644 --- a/src/__tests__/cached.test.ts +++ b/src/__tests__/cached.test.ts @@ -96,6 +96,7 @@ describe("cache mechanism", () => { resetTime: new Date("2024-01-01T15:00:00Z"), utilization: 56, sevenDayUtilization: 37, + sevenDayResetTime: null, }; cache.blockUsage = { value: blockUsageValue, timestamp: Date.now() }; @@ -114,6 +115,7 @@ describe("cache mechanism", () => { resetTime: null, utilization: 0, sevenDayUtilization: null, + sevenDayResetTime: null, }, timestamp: now - CACHE_TTL.blockUsage - 1000, }; @@ -168,6 +170,7 @@ describe("cache mechanism", () => { resetTime: new Date(), utilization: 56, sevenDayUtilization: 37, + sevenDayResetTime: null, }, timestamp: Date.now(), }; diff --git a/src/__tests__/main.test.ts b/src/__tests__/main.test.ts index 925eed6..67d0188 100644 --- a/src/__tests__/main.test.ts +++ b/src/__tests__/main.test.ts @@ -188,6 +188,7 @@ describe("renderStatusLine", () => { resetTime: new Date(), utilization: 56, sevenDayUtilization: 37, + sevenDayResetTime: null, }, }); const lines = renderStatusLine(ctx); @@ -207,6 +208,7 @@ describe("renderStatusLine", () => { resetTime: new Date(now + 3600000), // 1 hour later utilization: 56, sevenDayUtilization: 37, + sevenDayResetTime: null, }, }); const lines = renderStatusLine(ctx); @@ -218,22 +220,22 @@ describe("renderStatusLine", () => { }); describe("block usage display", () => { - test("shows reset timer when resetTime is set", () => { - const now = Date.now(); - setSystemTime(now); + test("shows reset time as absolute HH:MM", () => { + const resetTime = new Date(2024, 1, 15, 14, 30); // 14:30 const ctx = createRenderContext({ showUsage: true, blockUsage: { - resetTime: new Date(now + 2 * 3600000 + 30 * 60000), // 2h 30m later + resetTime, utilization: 30, sevenDayUtilization: null, + sevenDayResetTime: null, }, }); const lines = renderStatusLine(ctx); expect(lines[2]).toContain("⏳"); - expect(lines[2]).toContain("02:30"); + expect(lines[2]).toContain("14:30"); }); test("shows 5-hour utilization as fraction", () => { @@ -246,6 +248,7 @@ describe("renderStatusLine", () => { resetTime: new Date(now + 3600000), utilization: 56, sevenDayUtilization: null, + sevenDayResetTime: null, }, }); const lines = renderStatusLine(ctx); @@ -264,6 +267,7 @@ describe("renderStatusLine", () => { resetTime: new Date(now + 3600000), utilization: 56, sevenDayUtilization: 37, + sevenDayResetTime: null, }, }); const lines = renderStatusLine(ctx); @@ -282,6 +286,7 @@ describe("renderStatusLine", () => { resetTime: new Date(now + 3600000), utilization: 56, sevenDayUtilization: null, + sevenDayResetTime: null, }, }); const lines = renderStatusLine(ctx); @@ -290,6 +295,67 @@ describe("renderStatusLine", () => { expect(allOutput).not.toContain("📅"); }); + test("shows weekly reset time when sevenDayResetTime is set", () => { + const now = Date.now(); + setSystemTime(now); + + const ctx = createRenderContext({ + showUsage: true, + blockUsage: { + resetTime: new Date(now + 3600000), + utilization: 56, + sevenDayUtilization: 37, + sevenDayResetTime: new Date("2024-02-15T09:00:00+09:00"), + }, + }); + const lines = renderStatusLine(ctx); + + const allOutput = lines.join("\n"); + expect(allOutput).toContain("⏰"); + }); + + test("omits weekly reset time when sevenDayResetTime is null", () => { + const now = Date.now(); + setSystemTime(now); + + const ctx = createRenderContext({ + showUsage: true, + blockUsage: { + resetTime: new Date(now + 3600000), + utilization: 56, + sevenDayUtilization: 37, + sevenDayResetTime: null, + }, + }); + const lines = renderStatusLine(ctx); + + const allOutput = lines.join("\n"); + expect(allOutput).not.toContain("⏰"); + }); + + test("weekly reset time appears between 📊 and 📅", () => { + const now = Date.now(); + setSystemTime(now); + + const ctx = createRenderContext({ + showUsage: true, + blockUsage: { + resetTime: new Date(now + 3600000), + utilization: 56, + sevenDayUtilization: 37, + sevenDayResetTime: new Date("2024-02-15T09:00:00+09:00"), + }, + }); + const lines = renderStatusLine(ctx); + + const usageLine = lines[2]; + const clockIdx = usageLine.indexOf("⏰"); + const chartIdx = usageLine.indexOf("📊"); + const calendarIdx = usageLine.indexOf("📅"); + expect(clockIdx).toBeGreaterThan(chartIdx); + expect(clockIdx).toBeLessThan(calendarIdx); + }); + test("uses correct color for high utilization", () => { const now = Date.now(); setSystemTime(now); @@ -300,6 +366,7 @@ describe("renderStatusLine", () => { resetTime: new Date(now + 3600000), utilization: 85, sevenDayUtilization: null, + sevenDayResetTime: null, }, }); const lines = renderStatusLine(ctx); diff --git a/src/__tests__/pure.test.ts b/src/__tests__/pure.test.ts index 59749ac..b574d6e 100644 --- a/src/__tests__/pure.test.ts +++ b/src/__tests__/pure.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect, setSystemTime, test } from "bun:test"; import { C, getUsageColor } from "../colors.ts"; import { formatNumber, + formatResetDate, formatTime, getTimeUntilReset, } from "../format/index.ts"; @@ -64,6 +65,28 @@ describe("formatTime", () => { }); }); +describe("formatResetDate", () => { + test("formats date as MM/DD HH:MM", () => { + const date = new Date(2024, 1, 15, 17, 0); + expect(formatResetDate(date)).toBe("02/15 17:00"); + }); + + test("zero-pads month, day, hours, minutes", () => { + const date = new Date(2024, 0, 5, 3, 7); + expect(formatResetDate(date)).toBe("01/05 03:07"); + }); + + test("handles midnight", () => { + const date = new Date(2024, 2, 1, 0, 0); + expect(formatResetDate(date)).toBe("03/01 00:00"); + }); + + test("handles end of day", () => { + const date = new Date(2024, 11, 31, 23, 59); + expect(formatResetDate(date)).toBe("12/31 23:59"); + }); +}); + describe("getTimeUntilReset", () => { afterEach(() => { setSystemTime(); // Reset to real time diff --git a/src/__tests__/usage-api.test.ts b/src/__tests__/usage-api.test.ts index 35783b6..d646521 100644 --- a/src/__tests__/usage-api.test.ts +++ b/src/__tests__/usage-api.test.ts @@ -17,6 +17,7 @@ describe("usageResponseToBlockUsage", () => { expect(result.utilization).toBe(56); expect(result.resetTime).toEqual(new Date("2024-01-01T15:00:00Z")); expect(result.sevenDayUtilization).toBeNull(); + expect(result.sevenDayResetTime).toBeNull(); }); test("handles five_hour.resets_at being null", () => { @@ -34,7 +35,7 @@ describe("usageResponseToBlockUsage", () => { expect(result.resetTime).toBeNull(); }); - test("extracts seven_day utilization", () => { + test("extracts seven_day utilization and resets_at", () => { const data: UsageAPIResponse = { five_hour: { utilization: 56, resets_at: "2024-01-01T15:00:00Z" }, seven_day: { utilization: 37, resets_at: "2024-01-07T00:00:00Z" }, @@ -47,6 +48,22 @@ describe("usageResponseToBlockUsage", () => { expect(result.utilization).toBe(56); expect(result.sevenDayUtilization).toBe(37); + expect(result.sevenDayResetTime).toEqual(new Date("2024-01-07T00:00:00Z")); + }); + + test("handles seven_day.resets_at being null", () => { + const data: UsageAPIResponse = { + five_hour: { utilization: 56, resets_at: "2024-01-01T15:00:00Z" }, + seven_day: { utilization: 37, resets_at: null }, + seven_day_oauth_apps: null, + seven_day_opus: null, + iguana_necktie: null, + }; + + const result = usageResponseToBlockUsage(data); + + expect(result.sevenDayUtilization).toBe(37); + expect(result.sevenDayResetTime).toBeNull(); }); test("returns defaults when five_hour is null", () => { @@ -63,6 +80,7 @@ describe("usageResponseToBlockUsage", () => { expect(result.utilization).toBe(0); expect(result.resetTime).toBeNull(); expect(result.sevenDayUtilization).toBe(20); + expect(result.sevenDayResetTime).toBeNull(); }); test("returns all defaults when both are null", () => { @@ -79,5 +97,6 @@ describe("usageResponseToBlockUsage", () => { expect(result.utilization).toBe(0); expect(result.resetTime).toBeNull(); expect(result.sevenDayUtilization).toBeNull(); + expect(result.sevenDayResetTime).toBeNull(); }); }); diff --git a/src/format/formatResetDate.ts b/src/format/formatResetDate.ts new file mode 100644 index 0000000..746acc7 --- /dev/null +++ b/src/format/formatResetDate.ts @@ -0,0 +1,9 @@ +// 리셋 시각을 "MM/DD HH:MM" 포맷으로 변환 (로컬 시간 기준) +export function formatResetDate(date: Date): string { + const month = (date.getMonth() + 1).toString().padStart(2, "0"); + const day = date.getDate().toString().padStart(2, "0"); + const hours = date.getHours().toString().padStart(2, "0"); + const minutes = date.getMinutes().toString().padStart(2, "0"); + + return `${month}/${day} ${hours}:${minutes}`; +} diff --git a/src/format/index.ts b/src/format/index.ts index 529b8a6..76493b8 100644 --- a/src/format/index.ts +++ b/src/format/index.ts @@ -1,3 +1,4 @@ export { formatNumber } from "./formatNumber.ts"; +export { formatResetDate } from "./formatResetDate.ts"; export { formatTime } from "./formatTime.ts"; export { getTimeUntilReset } from "./getTimeUntilReset.ts"; diff --git a/src/render.ts b/src/render.ts index f3aed42..f4919f4 100644 --- a/src/render.ts +++ b/src/render.ts @@ -1,5 +1,5 @@ import { C, getUsageColor } from "./colors.ts"; -import { formatNumber, formatTime, getTimeUntilReset } from "./format/index.ts"; +import { formatNumber, formatResetDate, formatTime } from "./format/index.ts"; import type { RenderContext } from "./types.ts"; // 상태 라인 렌더링 (순수 함수 - 테스트 가능) @@ -51,12 +51,11 @@ export function renderStatusLine(ctx: RenderContext): string[] { if (ctx.showUsage && ctx.blockUsage) { const parts: string[] = []; - // 리셋 타이머 + // 리셋 시각 if (ctx.blockUsage.resetTime) { - const resetTime = getTimeUntilReset(ctx.blockUsage.resetTime); - parts.push( - `${C.WHITE}⏳ ${formatTime(resetTime.hours, resetTime.minutes)}${C.RESET}`, - ); + const h = ctx.blockUsage.resetTime.getHours(); + const m = ctx.blockUsage.resetTime.getMinutes(); + parts.push(`${C.WHITE}⏳ ${formatTime(h, m)}${C.RESET}`); } // 5시간 사용량 (서버 utilization 그대로) @@ -65,6 +64,13 @@ export function renderStatusLine(ctx: RenderContext): string[] { `${usageColor}📊 ${Math.round(ctx.blockUsage.utilization)}/100${C.RESET}`, ); + // 7일 리셋 시간 + if (ctx.blockUsage.sevenDayResetTime) { + parts.push( + `${C.WHITE}⏰ ${formatResetDate(ctx.blockUsage.sevenDayResetTime)}${C.RESET}`, + ); + } + // 7일 사용량 if (ctx.blockUsage.sevenDayUtilization !== null) { const weekColor = getUsageColor(ctx.blockUsage.sevenDayUtilization); diff --git a/src/types.ts b/src/types.ts index 668c3ff..2af1db6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -39,6 +39,7 @@ export interface BlockUsageInfo { resetTime: Date | null; utilization: number; // 0-100+ (서버 계산 %) sevenDayUtilization: number | null; + sevenDayResetTime: Date | null; } // CLI 파싱 결과 타입 diff --git a/src/usage/api.ts b/src/usage/api.ts index 1eb6a34..dfdf4d3 100644 --- a/src/usage/api.ts +++ b/src/usage/api.ts @@ -36,6 +36,7 @@ export function usageResponseToBlockUsage( resetTime: null, utilization: 0, sevenDayUtilization: null, + sevenDayResetTime: null, }; if (data.five_hour) { @@ -47,6 +48,9 @@ export function usageResponseToBlockUsage( if (data.seven_day) { result.sevenDayUtilization = data.seven_day.utilization; + if (data.seven_day.resets_at) { + result.sevenDayResetTime = new Date(data.seven_day.resets_at); + } } return result;