diff --git a/frontend/src/components/organisms/ProviderNavbarStatus.tsx b/frontend/src/components/organisms/ProviderNavbarStatus.tsx
new file mode 100644
index 0000000..9db7aac
--- /dev/null
+++ b/frontend/src/components/organisms/ProviderNavbarStatus.tsx
@@ -0,0 +1,388 @@
+"use client";
+
+import { useCallback, useEffect, useMemo, useState } from "react";
+import { ChevronDown } from "lucide-react";
+
+import { useAuth } from "@/auth/clerk";
+import type {
+ ProviderCredentialRead,
+ ProviderUsageLiveRead,
+ SubscriptionWindowRead,
+} from "@/api/generated/model";
+import {
+ getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet,
+ listProviderCredentialsApiV1ProviderCredentialsGet,
+} from "@/api/generated/provider-credentials/provider-credentials";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+
+type NavbarProviderId = "anthropic" | "openai";
+
+type ProviderNavbarItem = {
+ providerId: NavbarProviderId;
+ label: string;
+ status: "Active" | "Inactive" | "Not configured" | "Syncing";
+ sessionRemaining: number | null;
+ modelsRemaining: number | null;
+ reset: string;
+};
+
+const NAVBAR_PROVIDER_IDS: NavbarProviderId[] = ["anthropic", "openai"];
+
+function providerShortLabel(provider: string): string {
+ if (provider === "anthropic") return "Claude";
+ if (provider === "openai") return "GPT";
+ return provider;
+}
+
+function clampPct(pct: number): number {
+ return Math.max(0, Math.min(100, pct));
+}
+
+function fmtResetMs(ms: number | null | undefined): string {
+ if (ms == null || ms <= 0) return "< 1m";
+ const s = Math.floor(ms / 1000);
+ if (s < 60) return `${s}s`;
+ const m = Math.floor(s / 60);
+ if (m < 60) return `${m}m ${s % 60}s`;
+ const h = Math.floor(m / 60);
+ return `${h}h ${m % 60}m`;
+}
+
+function remainingPct(window: SubscriptionWindowRead | null): number | null {
+ if (!window) return null;
+ return clampPct(100 - window.pct_used);
+}
+
+function remainingColor(pct: number | null): string {
+ if (pct == null) return "bg-[color:var(--text-quiet)]";
+ if (pct <= 10) return "bg-[color:var(--danger)]";
+ if (pct <= 25) return "bg-[color:var(--warning)]";
+ return "bg-[color:var(--success)]";
+}
+
+function findSessionWindow(usage: ProviderUsageLiveRead | null | undefined) {
+ const windows = usage?.subscription_windows ?? [];
+ return (
+ windows.find((window) => {
+ const key = `${window.key} ${window.label}`.toLowerCase();
+ return (
+ key.includes("current") ||
+ key.includes("session") ||
+ key.includes("five") ||
+ key.includes("hour")
+ );
+ }) ??
+ windows[0] ??
+ null
+ );
+}
+
+function findModelsWindow(
+ usage: ProviderUsageLiveRead | null | undefined,
+ sessionWindow: SubscriptionWindowRead | null,
+) {
+ const windows = usage?.subscription_windows ?? [];
+ return (
+ windows.find((window) => {
+ if (window.key === sessionWindow?.key) return false;
+ const key = `${window.key} ${window.label}`.toLowerCase();
+ return key.includes("all") || key.includes("model") || key.includes("week");
+ }) ??
+ windows.find((window) => window.key !== sessionWindow?.key) ??
+ null
+ );
+}
+
+function resetLabel(
+ sessionWindow: SubscriptionWindowRead | null,
+ modelsWindow: SubscriptionWindowRead | null,
+): string {
+ const resetInMs = sessionWindow?.reset_in_ms ?? modelsWindow?.reset_in_ms;
+ return resetInMs == null ? "-" : fmtResetMs(resetInMs);
+}
+
+function formatRemaining(pct: number | null): string {
+ return pct == null ? "-" : `${Math.round(pct)}% left`;
+}
+
+function providerStatusColor(status: ProviderNavbarItem["status"]): string {
+ if (status === "Active") return "bg-[color:var(--success)]";
+ if (status === "Syncing") return "bg-[color:var(--accent)]";
+ if (status === "Inactive") return "bg-[color:var(--warning)]";
+ return "bg-[color:var(--text-quiet)]";
+}
+
+function MiniRemainingBar({
+ pct,
+ className = "w-10",
+}: {
+ pct: number | null;
+ className?: string;
+}) {
+ if (pct == null) return null;
+ const width = clampPct(pct);
+ return (
+
+
+
+ );
+}
+
+function buildProviderNavbarItems({
+ credentials,
+ usageByCredentialId,
+ isLoading,
+}: {
+ credentials: ProviderCredentialRead[];
+ usageByCredentialId: Record
;
+ isLoading: boolean;
+}): ProviderNavbarItem[] {
+ return NAVBAR_PROVIDER_IDS.map((providerId) => {
+ const providerCredentials = credentials
+ .filter((cred) => cred.provider === providerId)
+ .sort((a, b) => a.account_key.localeCompare(b.account_key));
+ const activeCredential =
+ providerCredentials.find(
+ (cred) =>
+ cred.active &&
+ (cred.has_api_key || cred.has_session_key || Boolean(cred.base_url)),
+ ) ?? null;
+ const credential = activeCredential ?? providerCredentials[0] ?? null;
+ const usage = activeCredential
+ ? usageByCredentialId[activeCredential.id]
+ : null;
+ const status: ProviderNavbarItem["status"] = activeCredential
+ ? isLoading && usage == null
+ ? "Syncing"
+ : "Active"
+ : credential
+ ? "Inactive"
+ : "Not configured";
+ const sessionWindow = findSessionWindow(usage);
+ const modelsWindow = findModelsWindow(usage, sessionWindow);
+ const sessionRemaining = remainingPct(sessionWindow);
+ const modelsRemaining = remainingPct(modelsWindow);
+
+ return {
+ providerId,
+ label: providerShortLabel(providerId),
+ status,
+ sessionRemaining,
+ modelsRemaining,
+ reset: resetLabel(sessionWindow, modelsWindow),
+ };
+ });
+}
+
+function ProviderInlineStatus({
+ item,
+ compact = false,
+}: {
+ item: ProviderNavbarItem;
+ compact?: boolean;
+}) {
+ return (
+
+
+ {item.label}
+ {item.status}
+ |
+
+ Session{" "}
+
+ {formatRemaining(item.sessionRemaining)}
+
+
+
+ |
+
+ Models{" "}
+
+ {formatRemaining(item.modelsRemaining)}
+
+
+
+ |
+
+ Reset {item.reset}
+
+
+ );
+}
+
+export function ProviderNavbarStatus() {
+ const { isSignedIn } = useAuth();
+ const [credentials, setCredentials] = useState([]);
+ const [usageByCredentialId, setUsageByCredentialId] = useState<
+ Record
+ >({});
+ const [isUsageLoading, setIsUsageLoading] = useState(false);
+
+ const usageCredentials = useMemo(() => {
+ return credentials
+ .filter(
+ (cred) =>
+ NAVBAR_PROVIDER_IDS.includes(cred.provider as NavbarProviderId) &&
+ cred.active &&
+ (cred.has_api_key || cred.has_session_key || Boolean(cred.base_url)),
+ )
+ .sort((a, b) => {
+ const providerDelta =
+ NAVBAR_PROVIDER_IDS.indexOf(a.provider as NavbarProviderId) -
+ NAVBAR_PROVIDER_IDS.indexOf(b.provider as NavbarProviderId);
+ if (providerDelta !== 0) return providerDelta;
+ return a.account_key.localeCompare(b.account_key);
+ });
+ }, [credentials]);
+
+ const loadCredentials = useCallback(async () => {
+ try {
+ const res = await listProviderCredentialsApiV1ProviderCredentialsGet();
+ if (res.status === 200) setCredentials(res.data);
+ } catch {
+ setCredentials([]);
+ }
+ }, []);
+
+ useEffect(() => {
+ if (!isSignedIn) return;
+
+ const initialLoad = window.setTimeout(() => {
+ void loadCredentials();
+ }, 0);
+ window.addEventListener("provider-credentials-updated", loadCredentials);
+ const interval = window.setInterval(() => {
+ void loadCredentials();
+ }, 60_000);
+
+ return () => {
+ window.clearTimeout(initialLoad);
+ window.removeEventListener("provider-credentials-updated", loadCredentials);
+ window.clearInterval(interval);
+ };
+ }, [isSignedIn, loadCredentials]);
+
+ useEffect(() => {
+ if (!isSignedIn || usageCredentials.length === 0) {
+ const resetUsage = window.setTimeout(() => {
+ setUsageByCredentialId({});
+ setIsUsageLoading(false);
+ }, 0);
+ return () => window.clearTimeout(resetUsage);
+ }
+
+ let cancelled = false;
+ const fetchUsage = async (refresh = false) => {
+ setIsUsageLoading(true);
+ const pairs = await Promise.all(
+ usageCredentials.map(async (cred) => {
+ try {
+ const res = await getProviderUsageLiveApiV1ProviderCredentialsCredentialIdUsageGet(
+ cred.id,
+ refresh ? { refresh: true } : undefined,
+ );
+ return [cred.id, res.status === 200 ? res.data : null] as const;
+ } catch {
+ return [cred.id, null] as const;
+ }
+ }),
+ );
+ if (!cancelled) {
+ setUsageByCredentialId(Object.fromEntries(pairs));
+ setIsUsageLoading(false);
+ }
+ };
+
+ const initialFetch = window.setTimeout(() => {
+ void fetchUsage();
+ }, 0);
+ const interval = window.setInterval(() => {
+ void fetchUsage(true);
+ }, 60_000);
+
+ return () => {
+ cancelled = true;
+ window.clearTimeout(initialFetch);
+ window.clearInterval(interval);
+ };
+ }, [isSignedIn, usageCredentials]);
+
+ const items = buildProviderNavbarItems({
+ credentials,
+ usageByCredentialId,
+ isLoading: isUsageLoading,
+ });
+ const activeCount = items.filter(
+ (item) => item.status === "Active" || item.status === "Syncing",
+ ).length;
+ const primaryRemaining =
+ items
+ .flatMap((item) => [item.sessionRemaining, item.modelsRemaining])
+ .filter((pct): pct is number => pct != null)
+ .sort((a, b) => a - b)[0] ?? null;
+
+ return (
+
+
+ {items.map((item, index) => (
+
+ {index > 0 ? (
+
+ ) : null}
+
+
+ ))}
+
+
+
+
+
+
+
+ {items.map((item) => (
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/templates/DashboardShell.tsx b/frontend/src/components/templates/DashboardShell.tsx
index 3b4ae7e..518dc83 100644
--- a/frontend/src/components/templates/DashboardShell.tsx
+++ b/frontend/src/components/templates/DashboardShell.tsx
@@ -14,6 +14,7 @@ import {
} from "@/api/generated/users/users";
import { BrandMark } from "@/components/atoms/BrandMark";
import { OrgSwitcher } from "@/components/organisms/OrgSwitcher";
+import { ProviderNavbarStatus } from "@/components/organisms/ProviderNavbarStatus";
import { ThemeToggle } from "@/components/organisms/ThemeToggle";
import { UserMenu } from "@/components/organisms/UserMenu";
import { isOnboardingComplete } from "@/lib/onboarding";
@@ -137,7 +138,9 @@ export function DashboardShell({
{topNavContent}
) : (
-