diff --git a/.gitignore b/.gitignore index 0f0fb03c..f231c22c 100644 --- a/.gitignore +++ b/.gitignore @@ -88,4 +88,7 @@ graphify-out/* .next-analyze/* # Kiro local workspace state -.kiro/ \ No newline at end of file +.kiro/ + +# CommandCode CLI local state (auth/taste/projects) +.commandcode/ \ No newline at end of file diff --git a/open-sse/services/usage.js b/open-sse/services/usage.js index 68a3a71d..99085beb 100644 --- a/open-sse/services/usage.js +++ b/open-sse/services/usage.js @@ -15,6 +15,7 @@ import { getXaiUsage } from "./usage/xai.js"; import { getGrokCliUsage } from "./usage/grok-cli.js"; import { getKimiUsage } from "./usage/kimi.js"; import { getDeepseekUsage } from "./usage/deepseek.js"; +import { getCommandCodeUsage } from "./usage/commandcode.js"; import { getOpenCodeGoUsage } from "./usage/opencode-go.js"; import { getGroqUsage } from "./usage/groq.js"; import { getZedUsage } from "./usage/zed.js"; @@ -60,6 +61,7 @@ const USAGE_HANDLERS = { kimi: (c) => getKimiUsage(c.accessToken, c.apiKey, c.proxyOptions, c.providerSpecificData), "opencode-go": (c) => getOpenCodeGoUsage(c.apiKey, c.proxyOptions), deepseek: (c) => getDeepseekUsage(c.apiKey, c.proxyOptions), + commandcode: (c) => getCommandCodeUsage(c.apiKey, c.proxyOptions), groq: (c) => getGroqUsage(c.apiKey, c.proxyOptions), zed: (c) => getZedUsage(c.accessToken, c.providerSpecificData, c.proxyOptions), }; diff --git a/src/app/(dashboard)/dashboard/profile/page.js b/src/app/(dashboard)/dashboard/profile/page.js index ad70628b..cffddde2 100644 --- a/src/app/(dashboard)/dashboard/profile/page.js +++ b/src/app/(dashboard)/dashboard/profile/page.js @@ -295,7 +295,9 @@ export default function ProfilePage() { const handleGlobalTimeoutChange = async (e) => { const raw = e.target.value.replace(/[^0-9]/g, ""); const numTimeout = parseInt(raw, 10); - const patchValue = (raw !== "" && Number.isFinite(numTimeout) && numTimeout > 0) ? numTimeout : null; + // Enforce a sane minimum (1s) so a stray "60" never becomes a 60ms + // connect timeout — same guard as the per-provider timeout input. + const patchValue = (raw !== "" && Number.isFinite(numTimeout) && numTimeout > 0) ? Math.max(1000, numTimeout) : null; try { const res = await fetch("/api/settings", { diff --git a/src/app/(dashboard)/dashboard/providers/[id]/page.js b/src/app/(dashboard)/dashboard/providers/[id]/page.js index 9a87ced8..c821a1c8 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/page.js +++ b/src/app/(dashboard)/dashboard/providers/[id]/page.js @@ -326,7 +326,7 @@ export default function ProviderDetailPage() { setProviderStrategy(override.fallbackStrategy || null); setProviderStickyLimit(override.stickyRoundRobinLimit != null ? String(override.stickyRoundRobinLimit) : "1"); setProviderNoAuthEnabled(override.enabled !== false); - // Load per-provider connect timeout + // Load per-provider connect timeout (ms) const timeoutCfg = (settingsData.providerTimeouts || {})[providerId] || {}; setProviderTimeout(timeoutCfg.timeoutMs != null ? String(timeoutCfg.timeoutMs) : ""); // Load per-provider thinking config @@ -478,8 +478,10 @@ export default function ProviderDetailPage() { delete updated[providerId]; } else { const timeoutMs = parseInt(ms, 10); + // Stored in ms; enforce a sane minimum (1s) so a stray "60" means + // 60 SECONDS-worth of protection — never a 60ms connect timeout. if (Number.isFinite(timeoutMs) && timeoutMs > 0) { - updated[providerId] = { timeoutMs }; + updated[providerId] = { timeoutMs: Math.max(1000, timeoutMs) }; } else { delete updated[providerId]; } diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js index 86fd30d8..40d937c6 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js @@ -648,6 +648,24 @@ export function parseQuotaData(provider, data) { } break; + case "commandcode": + // CommandCode reports currency credits (5-hour/weekly windows + monthly + // credits) with used/total in dollars. Forward remainingPercentage (the + // UI would otherwise render "$0.05" balances as "0%") and unit "$". + if (data.quotas) { + Object.entries(data.quotas).forEach(([name, quota]) => { + normalizedQuotas.push({ + name, + used: quota.used || 0, + total: quota.total || 0, + unit: quota.unit || "$", + resetAt: quota.resetAt || null, + remainingPercentage: quota.remainingPercentage, + }); + }); + } + break; + default: // Generic fallback for unknown providers if (data.quotas) {