From 1f4776e6891348d2dbdcbbf704c0a5901b008ecf Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Wed, 20 May 2026 23:37:01 +0900 Subject: feat: Claude Reset scheduler + fix key config and Claude grouping - Added claude-pro key pointing to default credentials, claude-max pointing to .credentials-2.json (docker path /root/.claude/) - POST /models/wake sends 'hi' to haiku for all Claude accounts - ClaudeReset.svelte: 2 AM rows + 2 PM rows of 6 hour blocks each (12-hour American format). Click blocks to schedule wake at :15 - Key Usage now groups all Claude accounts under one 'Claude' card instead of duplicating under claude-pro and claude-max cards --- .../frontend/src/lib/components/ClaudeReset.svelte | 238 +++++++++++++++++++++ .../frontend/src/lib/components/KeyUsage.svelte | 157 +++++++++----- .../src/lib/components/SidebarPanel.svelte | 5 +- 3 files changed, 344 insertions(+), 56 deletions(-) create mode 100644 packages/frontend/src/lib/components/ClaudeReset.svelte (limited to 'packages/frontend/src/lib/components') diff --git a/packages/frontend/src/lib/components/ClaudeReset.svelte b/packages/frontend/src/lib/components/ClaudeReset.svelte new file mode 100644 index 0000000..58150fe --- /dev/null +++ b/packages/frontend/src/lib/components/ClaudeReset.svelte @@ -0,0 +1,238 @@ + + +
+
Claude Wake Schedule
+ + +
+ AM +
+
+ {#each amRow1 as hour} + + {/each} +
+
+ {#each amRow2 as hour} + + {/each} +
+
+
+ + +
+ PM +
+
+ {#each pmRow1 as hour} + + {/each} +
+
+ {#each pmRow2 as hour} + + {/each} +
+
+
+ + + {#if scheduledHours.length > 0} +
+ {#each scheduledHours as hour} +
+ {formatHour(hour)}:15 + Wake scheduled for {formatWakeTime(schedule[hour] ?? 0)} +
+ {/each} +
+ {:else} +

No wake times scheduled. Click a block to schedule.

+ {/if} +
diff --git a/packages/frontend/src/lib/components/KeyUsage.svelte b/packages/frontend/src/lib/components/KeyUsage.svelte index 3587ff8..51e08c2 100644 --- a/packages/frontend/src/lib/components/KeyUsage.svelte +++ b/packages/frontend/src/lib/components/KeyUsage.svelte @@ -71,6 +71,39 @@ } }); + // Merge duplicate Claude entries — all anthropic keys return the same + // set of accounts, so collect and deduplicate under one "Claude" card. + const claudeAccounts = $derived.by(() => { + const seen = new Set(); + const accounts: Array<{ + label: string; + source: string; + subscriptionType?: string; + fiveHour?: UsageBucket; + sevenDay?: UsageBucket; + error?: string; + }> = []; + const claudeEntries = entries.filter((e) => e.provider === "anthropic"); + for (const e of claudeEntries) { + if (!e.data || e.data.provider !== "anthropic" || !e.data.accounts) continue; + for (const acct of e.data.accounts) { + if (!seen.has(acct.source)) { + seen.add(acct.source); + accounts.push(acct); + } + } + } + return accounts; + }); + + const claudeLoading = $derived( + entries.some((e) => e.provider === "anthropic" && e.loading), + ); + + const nonClaudeEntries = $derived( + entries.filter((e) => e.provider !== "anthropic"), + ); + function progressClass(utilization: number): string { if (utilization > 0.8) return "progress-error"; if (utilization >= 0.5) return "progress-warning"; @@ -91,7 +124,75 @@

No keys available.

{:else}
- {#each entries as entry (entry.keyId)} + + {#if claudeLoading} +
+
+ Claude + anthropic +
+
+ + Loading... +
+
+ {:else if claudeAccounts.length > 0} +
+
+ Claude + anthropic +
+ {#each claudeAccounts as acct, idx (acct.source)} + {#if idx > 0} +
+ {/if} +
+
+ {acct.label} + {#if acct.subscriptionType} + {acct.subscriptionType} + {/if} +
+ {#if acct.error} +

{acct.error}

+ {/if} + {#if hasBucketData(acct.fiveHour)} + {@const b = acct.fiveHour!} + {@const u = b.utilization ?? 0} + {@const p = Math.round(u * 100)} +
+
+ 5-Hour + {p}% +
+ + {#if b.resetsAt} + Resets: {formatDate(b.resetsAt)} + {/if} +
+ {/if} + {#if hasBucketData(acct.sevenDay)} + {@const b = acct.sevenDay!} + {@const u = b.utilization ?? 0} + {@const p = Math.round(u * 100)} +
+
+ Weekly + {p}% +
+ + {#if b.resetsAt} + Resets: {formatDate(b.resetsAt)} + {/if} +
+ {/if} +
+ {/each} +
+ {/if} + + + {#each nonClaudeEntries as entry (entry.keyId)}
{entry.keyId} @@ -110,60 +211,6 @@ {:else if !entry.data}

No data.

- {:else if entry.data.provider === "anthropic"} - - {#if entry.data.accounts} - {#each entry.data.accounts as acct, idx (acct.source)} - {#if idx > 0} -
- {/if} -
-
- {acct.label} - {#if acct.subscriptionType} - {acct.subscriptionType} - {/if} -
- - {#if acct.error} -

{acct.error}

- {/if} - - {#if hasBucketData(acct.fiveHour)} - {@const b = acct.fiveHour!} - {@const u = b.utilization ?? 0} - {@const p = Math.round(u * 100)} -
-
- 5-Hour - {p}% -
- - {#if b.resetsAt} - Resets: {formatDate(b.resetsAt)} - {/if} -
- {/if} - - {#if hasBucketData(acct.sevenDay)} - {@const b = acct.sevenDay!} - {@const u = b.utilization ?? 0} - {@const p = Math.round(u * 100)} -
-
- Weekly - {p}% -
- - {#if b.resetsAt} - Resets: {formatDate(b.resetsAt)} - {/if} -
- {/if} -
- {/each} - {/if} - {:else if entry.data.provider === "opencode-go"} {#if entry.data.unavailable}

Check console for usage.

diff --git a/packages/frontend/src/lib/components/SidebarPanel.svelte b/packages/frontend/src/lib/components/SidebarPanel.svelte index 2184818..ddd8ebd 100644 --- a/packages/frontend/src/lib/components/SidebarPanel.svelte +++ b/packages/frontend/src/lib/components/SidebarPanel.svelte @@ -5,6 +5,7 @@ import SkillsBrowser from "./SkillsBrowser.svelte"; import PermissionLog from "./PermissionLog.svelte"; import KeyUsage from "./KeyUsage.svelte"; + import ClaudeReset from "./ClaudeReset.svelte"; import type { TaskItem, LogEntry, KeyInfo, ModelInfo } from "../types.js"; const { @@ -25,7 +26,7 @@ let selected = $state("Tasks"); - const options = ["Key Usage", "Model Status", "Tasks", "Config", "Skills", "Permission Log"]; + const options = ["Key Usage", "Claude Reset", "Model Status", "Tasks", "Config", "Skills", "Permission Log"];
@@ -41,6 +42,8 @@
{#if selected === "Key Usage"} + {:else if selected === "Claude Reset"} + {:else if selected === "Model Status"} {:else if selected === "Tasks"} -- cgit v1.2.3