From 42b49e9a858d8be1c0402ddbd893579de43632c8 Mon Sep 17 00:00:00 2001 From: Mert Can Demir Date: Wed, 6 May 2026 20:23:33 +0300 Subject: [PATCH] fix(claude): remove peak hours indicator integration --- README.md | 2 +- docs/providers/claude.md | 13 --- plugins/claude/plugin.js | 67 ------------- plugins/claude/plugin.json | 1 - plugins/claude/plugin.test.js | 176 ---------------------------------- 5 files changed, 1 insertion(+), 258 deletions(-) diff --git a/README.md b/README.md index 1bcdda59..d992761a 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ OpenUsage lives in your menu bar and shows you how much of your AI coding subscr - [**Amp**](docs/providers/amp.md) / free tier, bonus, credits - [**Antigravity**](docs/providers/antigravity.md) / all models -- [**Claude**](docs/providers/claude.md) / session, weekly, peak/off-peak, extra usage, local token usage (ccusage) +- [**Claude**](docs/providers/claude.md) / session, weekly, extra usage, local token usage (ccusage) - [**Codex**](docs/providers/codex.md) / session, weekly, reviews, credits - [**Copilot**](docs/providers/copilot.md) / premium, chat, completions - [**Cursor**](docs/providers/cursor.md) / credits, total usage, auto usage, API usage, on-demand, CLI auth diff --git a/docs/providers/claude.md b/docs/providers/claude.md index e6cd82f7..c761ed40 100644 --- a/docs/providers/claude.md +++ b/docs/providers/claude.md @@ -12,7 +12,6 @@ - **Utilization:** integer percentage (0-100) - **Credits:** cents (divide by 100 for dollars) - **Timestamps:** ISO 8601 (response), unix milliseconds (credentials file) -- **Peak hours status:** supplemental best-effort data from PromoClock's public API; does not affect Claude usage math ## Endpoints @@ -60,18 +59,6 @@ Returns rate limit windows and optional extra credits. All windows are enforced simultaneously — hitting any limit throttles the user. -## Supplemental Peak Hours Status - -OpenUsage also augments the Claude card with PromoClock peak/off-peak status: - -- **Endpoint:** `GET https://promoclock.co/api/status` -- **Auth:** none -- **Fields used:** `isPeak`, `isOffPeak`, `isWeekend`, `status` (fallback) -- **UI mapping:** binary Peak / Off-Peak badge (weekend is treated as off-peak) -- **Failure mode:** ignored on network, HTTP, or payload errors; Claude usage lines still render normally - -This is informational only. PromoClock is an independent public service, not an official Anthropic API. - ## Authentication ### Token Location diff --git a/plugins/claude/plugin.js b/plugins/claude/plugin.js index 4ff79e80..a496926d 100644 --- a/plugins/claude/plugin.js +++ b/plugins/claude/plugin.js @@ -6,9 +6,6 @@ const PROD_REFRESH_URL = "https://platform.claude.com/v1/oauth/token" const PROD_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e" const NON_PROD_CLIENT_ID = "22422756-60c9-4084-8eb7-27705fd5cf9a" - const PROMOCLOCK_STATUS_URL = "https://promoclock.co/api/status" - const PROMOCLOCK_PEAK_COLOR = "#ef4444" - const PROMOCLOCK_OFF_PEAK_COLOR = "#22c55e" const SCOPES = "user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload" const REFRESH_BUFFER_MS = 5 * 60 * 1000 // refresh 5 minutes before expiration @@ -616,66 +613,6 @@ })) } - function getPromoClockBadgeText(data) { - if (!data || typeof data !== "object") return null - if (data.isPeak === true) return "Peak" - if (data.isOffPeak === true || data.isWeekend === true) return "Off-Peak" - - const status = typeof data.status === "string" ? data.status.trim().toLowerCase() : "" - if (status === "peak") return "Peak" - if (status === "off_peak" || status === "off-peak" || status === "weekend") return "Off-Peak" - return null - } - - function getPromoClockColor(badgeText) { - if (badgeText === "Peak") return PROMOCLOCK_PEAK_COLOR - if (badgeText === "Off-Peak") return PROMOCLOCK_OFF_PEAK_COLOR - return null - } - - function fetchPromoClockLine(ctx) { - let resp - let json - try { - const result = ctx.util.requestJson({ - method: "GET", - url: PROMOCLOCK_STATUS_URL, - headers: { - Accept: "application/json", - }, - timeoutMs: 2000, - }) - resp = result.resp - json = result.json - } catch (e) { - ctx.host.log.warn("promoclock request failed: " + String(e)) - return null - } - - if (!resp || resp.status < 200 || resp.status >= 300) { - ctx.host.log.warn("promoclock returned unexpected status: " + String(resp && resp.status)) - return null - } - - if (!json || typeof json !== "object") { - ctx.host.log.warn("promoclock response invalid") - return null - } - - const badgeText = getPromoClockBadgeText(json) - - if (!badgeText) { - ctx.host.log.warn("promoclock response missing expected fields") - return null - } - - return ctx.line.badge({ - label: "Peak Hours", - text: badgeText, - color: getPromoClockColor(badgeText), - }) - } - function probe(ctx) { const creds = loadCredentials(ctx) if (!creds || !creds.oauth || !creds.oauth.accessToken || !creds.oauth.accessToken.trim()) { @@ -910,8 +847,6 @@ } } - const promoClockLine = fetchPromoClockLine(ctx) - if (rateLimited) { const retryText = retryAfterSeconds !== null ? fmtRateLimitMinutes(retryAfterSeconds) @@ -928,8 +863,6 @@ lines.push(ctx.line.badge({ label: "Status", text: "No usage data", color: "#a3a3a3" })) } - if (promoClockLine) lines.push(promoClockLine) - return { plan: plan, lines: lines } } diff --git a/plugins/claude/plugin.json b/plugins/claude/plugin.json index a6afabca..6e7af09b 100644 --- a/plugins/claude/plugin.json +++ b/plugins/claude/plugin.json @@ -13,7 +13,6 @@ "lines": [ { "type": "progress", "label": "Session", "scope": "overview", "primaryOrder": 1 }, { "type": "progress", "label": "Weekly", "scope": "overview" }, - { "type": "badge", "label": "Peak Hours", "scope": "overview" }, { "type": "progress", "label": "Sonnet", "scope": "detail" }, { "type": "progress", "label": "Claude Design", "scope": "detail" }, { "type": "progress", "label": "Extra usage spent", "scope": "detail" }, diff --git a/plugins/claude/plugin.test.js b/plugins/claude/plugin.test.js index d2144e68..8bf0fcc9 100644 --- a/plugins/claude/plugin.test.js +++ b/plugins/claude/plugin.test.js @@ -22,60 +22,6 @@ beforeEach(() => { const loadPlugin = async () => plugin -const SAMPLE_PROMOCLOCK_RESPONSE = { - status: "off_peak", - isPeak: false, - isOffPeak: true, - isWeekend: false, - sessionLimitSpeed: "normal", - emoji: "🟢", - label: "Off-Peak — Normal Speed", - peakHours: "Weekdays 1pm–7pm UTC / 1:00 PM–7:00 PM GMT", - nextChange: "2026-04-09T13:00:00.000Z", - minutesUntilChange: 720, - timestamp: "2026-04-09T01:00:00.000Z", - utcHour: 1, - utcDay: 4, - note: "No known end date for peak hours adjustment. Weekly limits unchanged.", -} - -function mockClaudeUsageAndPromoClock( - ctx, - { - usageBody = { - five_hour: { utilization: 10, resets_at: "2099-01-01T00:00:00.000Z" }, - seven_day: { utilization: 20, resets_at: "2099-01-01T00:00:00.000Z" }, - }, - usageStatus = 200, - promoClockBody = SAMPLE_PROMOCLOCK_RESPONSE, - promoClockStatus = 200, - promoClockBodyText, - } = {} -) { - ctx.host.http.request.mockImplementation((opts) => { - const url = String(opts && opts.url ? opts.url : "") - if (url === "https://promoclock.co/api/status") { - return { - status: promoClockStatus, - headers: {}, - bodyText: - promoClockBodyText !== undefined - ? promoClockBodyText - : JSON.stringify(promoClockBody), - } - } - - return { - status: usageStatus, - headers: {}, - bodyText: - typeof usageBody === "string" - ? usageBody - : JSON.stringify(usageBody), - } - }) -} - describe("claude plugin", () => { it("throws when no credentials", async () => { const ctx = makeCtx() @@ -452,120 +398,6 @@ describe("claude plugin", () => { expect(result.lines.find((line) => line.label === "Weekly")).toBeTruthy() }) - describe("PromoClock integration", () => { - it("maps the real off-peak endpoint payload to the compact badge", async () => { - const ctx = makeCtx() - ctx.host.fs.readText = () => - JSON.stringify({ claudeAiOauth: { accessToken: "token", subscriptionType: "pro" } }) - ctx.host.fs.exists = () => true - mockClaudeUsageAndPromoClock(ctx) - - const plugin = await loadPlugin() - const result = plugin.probe(ctx) - - expect(result.lines.find((line) => line.label === "Session")).toBeTruthy() - expect(result.lines.find((line) => line.label === "Weekly")).toBeTruthy() - expect(result.lines.find((line) => line.label === "Peak Hours")).toEqual({ - type: "badge", - label: "Peak Hours", - text: "Off-Peak", - color: "#22c55e", - }) - expect(result.lines.find((line) => line.label === "Next change")).toBeUndefined() - }) - - it("maps peak PromoClock responses into the badge-only UI", async () => { - const ctx = makeCtx() - ctx.host.fs.readText = () => - JSON.stringify({ claudeAiOauth: { accessToken: "token", subscriptionType: "pro" } }) - ctx.host.fs.exists = () => true - mockClaudeUsageAndPromoClock(ctx, { - promoClockBody: { - ...SAMPLE_PROMOCLOCK_RESPONSE, - status: "peak", - isPeak: true, - isOffPeak: false, - emoji: "🔴", - label: "Peak Hours — Limits Drain Faster", - nextChange: "2026-04-08T19:00:00.000Z", - minutesUntilChange: 111, - timestamp: "2026-04-08T17:08:33.089Z", - utcHour: 17, - }, - }) - - const plugin = await loadPlugin() - const result = plugin.probe(ctx) - - expect(result.lines.find((line) => line.label === "Peak Hours")?.text).toBe("Peak") - expect(result.lines.find((line) => line.label === "Peak Hours")?.color).toBe("#ef4444") - }) - - it("treats weekend as off-peak", async () => { - const ctx = makeCtx() - ctx.host.fs.readText = () => - JSON.stringify({ claudeAiOauth: { accessToken: "token", subscriptionType: "pro" } }) - ctx.host.fs.exists = () => true - mockClaudeUsageAndPromoClock(ctx, { - promoClockBody: { - ...SAMPLE_PROMOCLOCK_RESPONSE, - status: "weekend", - isPeak: false, - isOffPeak: false, - isWeekend: true, - label: "Weekend — Normal Speed", - }, - }) - - const plugin = await loadPlugin() - const result = plugin.probe(ctx) - - expect(result.lines.find((line) => line.label === "Peak Hours")?.text).toBe("Off-Peak") - expect(result.lines.find((line) => line.label === "Peak Hours")?.color).toBe("#22c55e") - }) - - it("ignores PromoClock failures and still returns Claude usage lines", async () => { - const ctx = makeCtx() - ctx.host.fs.readText = () => - JSON.stringify({ claudeAiOauth: { accessToken: "token", subscriptionType: "pro" } }) - ctx.host.fs.exists = () => true - mockClaudeUsageAndPromoClock(ctx, { - promoClockStatus: 503, - promoClockBody: { error: "temporarily unavailable" }, - }) - - const plugin = await loadPlugin() - const result = plugin.probe(ctx) - - expect(result.lines.find((line) => line.label === "Session")).toBeTruthy() - expect(result.lines.find((line) => line.label === "Weekly")).toBeTruthy() - expect(result.lines.find((line) => line.label === "Peak Hours")).toBeUndefined() - expect(result.lines.find((line) => line.label === "Next change")).toBeUndefined() - }) - - it("falls back to status string when boolean flags are absent", async () => { - const ctx = makeCtx() - ctx.host.fs.readText = () => - JSON.stringify({ claudeAiOauth: { accessToken: "token", subscriptionType: "pro" } }) - ctx.host.fs.exists = () => true - mockClaudeUsageAndPromoClock(ctx, { - promoClockBody: { - ...SAMPLE_PROMOCLOCK_RESPONSE, - status: "off_peak", - isPeak: undefined, - isOffPeak: undefined, - isWeekend: undefined, - }, - }) - - const plugin = await loadPlugin() - const result = plugin.probe(ctx) - - expect(result.lines.find((line) => line.label === "Peak Hours")?.text).toBe("Off-Peak") - expect(result.lines.find((line) => line.label === "Peak Hours")?.color).toBe("#22c55e") - }) - }) - it("appends max rate limit tier to the plan label when present", async () => { const runCase = async (rateLimitTier, expectedPlan) => { const ctx = makeCtx() @@ -2039,8 +1871,6 @@ describe("claude plugin", () => { const ctx = makeCtx() ctx.host.fs.readText = () => JSON.stringify({ claudeAiOauth: { accessToken: "token" } }) ctx.host.fs.exists = () => true - // Isolate Promoclock so it doesn't add extra calls to ctx.host.http.request - ctx.util.requestJson = vi.fn(() => ({ resp: { status: 200, bodyText: "{}", headers: {} }, json: {} })) ctx.host.http.request.mockReturnValue({ status: 429, bodyText: "", @@ -2071,8 +1901,6 @@ describe("claude plugin", () => { const ctx = makeCtx() ctx.host.fs.readText = () => JSON.stringify({ claudeAiOauth: { accessToken: "token" } }) ctx.host.fs.exists = () => true - // Isolate Promoclock so it doesn't add extra calls to ctx.host.http.request - ctx.util.requestJson = vi.fn(() => ({ resp: { status: 200, bodyText: "{}", headers: {} }, json: {} })) const usageBody = JSON.stringify({ five_hour: { utilization: 50, resets_at: null } }) ctx.host.http.request .mockReturnValueOnce({ status: 429, bodyText: "", headers: { "Retry-After": "60" } }) @@ -2101,8 +1929,6 @@ describe("claude plugin", () => { const ctx = makeCtx() ctx.host.fs.readText = () => JSON.stringify({ claudeAiOauth: { accessToken: "token" } }) ctx.host.fs.exists = () => true - // Isolate Promoclock so it doesn't add extra calls to ctx.host.http.request - ctx.util.requestJson = vi.fn(() => ({ resp: { status: 200, bodyText: "{}", headers: {} }, json: {} })) ctx.host.http.request.mockReturnValue({ status: 200, bodyText: "{}", headers: {} }) const plugin = await loadPlugin() @@ -2160,8 +1986,6 @@ describe("claude plugin", () => { const ctx = makeCtx() ctx.host.fs.readText = () => JSON.stringify({ claudeAiOauth: { accessToken: "token" } }) ctx.host.fs.exists = () => true - // Isolate Promoclock so it doesn't add extra calls to ctx.host.http.request - ctx.util.requestJson = vi.fn(() => ({ resp: { status: 200, bodyText: "{}", headers: {} }, json: {} })) ctx.host.http.request .mockReturnValueOnce({ status: 429, bodyText: "", headers: {} }) // no Retry-After .mockReturnValue({ status: 200, bodyText: "{}", headers: {} })