2
0
Frank 1 сар өмнө
parent
commit
fb6d201ee0
41 өөрчлөгдсөн 4127 нэмэгдсэн , 432 устгасан
  1. 27 0
      packages/console/app/src/i18n/ar.ts
  2. 27 0
      packages/console/app/src/i18n/br.ts
  3. 27 0
      packages/console/app/src/i18n/da.ts
  4. 27 0
      packages/console/app/src/i18n/de.ts
  5. 27 0
      packages/console/app/src/i18n/en.ts
  6. 27 0
      packages/console/app/src/i18n/es.ts
  7. 28 0
      packages/console/app/src/i18n/fr.ts
  8. 27 0
      packages/console/app/src/i18n/it.ts
  9. 27 0
      packages/console/app/src/i18n/ja.ts
  10. 27 0
      packages/console/app/src/i18n/ko.ts
  11. 27 0
      packages/console/app/src/i18n/no.ts
  12. 27 0
      packages/console/app/src/i18n/pl.ts
  13. 27 0
      packages/console/app/src/i18n/ru.ts
  14. 27 0
      packages/console/app/src/i18n/th.ts
  15. 27 0
      packages/console/app/src/i18n/tr.ts
  16. 26 0
      packages/console/app/src/i18n/zh.ts
  17. 26 0
      packages/console/app/src/i18n/zht.ts
  18. 73 289
      packages/console/app/src/routes/stripe/webhook.ts
  19. 1 1
      packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx
  20. 7 2
      packages/console/app/src/routes/workspace/[id]/billing/index.tsx
  21. 160 0
      packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css
  22. 269 0
      packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx
  23. 49 13
      packages/console/app/src/routes/workspace/[id]/graph-section.tsx
  24. 18 18
      packages/console/app/src/routes/workspace/[id]/usage-section.tsx
  25. 2 0
      packages/console/app/src/routes/workspace/common.tsx
  26. 12 0
      packages/console/app/src/routes/zen/lite/v1/messages.ts
  27. 188 58
      packages/console/app/src/routes/zen/util/handler.ts
  28. 19 0
      packages/console/core/migrations/20260224043338_nifty_starjammers/migration.sql
  29. 2505 0
      packages/console/core/migrations/20260224043338_nifty_starjammers/snapshot.json
  30. 3 3
      packages/console/core/script/black-select-workspaces.ts
  31. 2 8
      packages/console/core/script/lookup-user.ts
  32. 79 3
      packages/console/core/src/billing.ts
  33. 5 3
      packages/console/core/src/black.ts
  34. 1 0
      packages/console/core/src/identifier.ts
  35. 5 8
      packages/console/core/src/lite.ts
  36. 23 3
      packages/console/core/src/schema/billing.sql.ts
  37. 37 3
      packages/console/core/src/subscription.ts
  38. 0 20
      packages/console/core/src/util/date.test.ts
  39. 29 0
      packages/console/core/src/util/date.ts
  40. 76 0
      packages/console/core/test/date.test.ts
  41. 106 0
      packages/console/core/test/subscription.test.ts

+ 27 - 0
packages/console/app/src/i18n/ar.ts

@@ -345,6 +345,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "الخرج",
   "workspace.usage.breakdown.reasoning": "المنطق",
   "workspace.usage.subscription": "الاشتراك (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "التكلفة",
@@ -354,6 +355,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(محذوف)",
   "workspace.cost.empty": "لا توجد بيانات استخدام متاحة للفترة المحددة.",
   "workspace.cost.subscriptionShort": "اشتراك",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "مفاتيح API",
   "workspace.keys.subtitle": "إدارة مفاتيح API الخاصة بك للوصول إلى خدمات opencode.",
@@ -481,6 +483,31 @@ export const dict = {
   "workspace.black.waitlist.enrolled": "مسجل",
   "workspace.black.waitlist.enrollNote": 'عند النقر فوق "تسجيل"، يبدأ اشتراكك على الفور وسيتم خصم الرسوم من بطاقتك.',
 
+  "workspace.lite.loading": "جارٍ التحميل...",
+  "workspace.lite.time.day": "يوم",
+  "workspace.lite.time.days": "أيام",
+  "workspace.lite.time.hour": "ساعة",
+  "workspace.lite.time.hours": "ساعات",
+  "workspace.lite.time.minute": "دقيقة",
+  "workspace.lite.time.minutes": "دقائق",
+  "workspace.lite.time.fewSeconds": "بضع ثوان",
+  "workspace.lite.subscription.title": "اشتراك Lite",
+  "workspace.lite.subscription.message": "أنت مشترك في OpenCode Lite.",
+  "workspace.lite.subscription.manage": "إدارة الاشتراك",
+  "workspace.lite.subscription.rollingUsage": "الاستخدام المتجدد",
+  "workspace.lite.subscription.weeklyUsage": "الاستخدام الأسبوعي",
+  "workspace.lite.subscription.monthlyUsage": "الاستخدام الشهري",
+  "workspace.lite.subscription.resetsIn": "إعادة تعيين في",
+  "workspace.lite.subscription.useBalance": "استخدم رصيدك المتوفر بعد الوصول إلى حدود الاستخدام",
+  "workspace.lite.other.title": "اشتراك Lite",
+  "workspace.lite.other.message":
+    "عضو آخر في مساحة العمل هذه مشترك بالفعل في OpenCode Lite. يمكن لعضو واحد فقط لكل مساحة عمل الاشتراك.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "احصل على وصول إلى أفضل النماذج المفتوحة — Kimi K2.5، و GLM-5، و MiniMax M2.5 — مع حدود استخدام سخية مقابل $10 شهريًا.",
+  "workspace.lite.promo.subscribe": "الاشتراك في Lite",
+  "workspace.lite.promo.subscribing": "جارٍ إعادة التوجيه...",
+
   "download.title": "OpenCode | تنزيل",
   "download.meta.description": "نزّل OpenCode لـ macOS، Windows، وLinux",
   "download.hero.title": "تنزيل OpenCode",

+ 27 - 0
packages/console/app/src/i18n/br.ts

@@ -350,6 +350,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "Saída",
   "workspace.usage.breakdown.reasoning": "Raciocínio",
   "workspace.usage.subscription": "assinatura (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "Custo",
@@ -359,6 +360,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(excluído)",
   "workspace.cost.empty": "Nenhum dado de uso disponível para o período selecionado.",
   "workspace.cost.subscriptionShort": "ass",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "Chaves de API",
   "workspace.keys.subtitle": "Gerencie suas chaves de API para acessar os serviços opencode.",
@@ -487,6 +489,31 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "Ao clicar em Inscrever-se, sua assinatura começará imediatamente e seu cartão será cobrado.",
 
+  "workspace.lite.loading": "Carregando...",
+  "workspace.lite.time.day": "dia",
+  "workspace.lite.time.days": "dias",
+  "workspace.lite.time.hour": "hora",
+  "workspace.lite.time.hours": "horas",
+  "workspace.lite.time.minute": "minuto",
+  "workspace.lite.time.minutes": "minutos",
+  "workspace.lite.time.fewSeconds": "alguns segundos",
+  "workspace.lite.subscription.title": "Assinatura Lite",
+  "workspace.lite.subscription.message": "Você assina o OpenCode Lite.",
+  "workspace.lite.subscription.manage": "Gerenciar Assinatura",
+  "workspace.lite.subscription.rollingUsage": "Uso Contínuo",
+  "workspace.lite.subscription.weeklyUsage": "Uso Semanal",
+  "workspace.lite.subscription.monthlyUsage": "Uso Mensal",
+  "workspace.lite.subscription.resetsIn": "Reinicia em",
+  "workspace.lite.subscription.useBalance": "Use seu saldo disponível após atingir os limites de uso",
+  "workspace.lite.other.title": "Assinatura Lite",
+  "workspace.lite.other.message":
+    "Outro membro neste workspace já assina o OpenCode Lite. Apenas um membro por workspace pode assinar.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "Tenha acesso aos melhores modelos abertos — Kimi K2.5, GLM-5 e MiniMax M2.5 — com limites de uso generosos por $10 por mês.",
+  "workspace.lite.promo.subscribe": "Assinar Lite",
+  "workspace.lite.promo.subscribing": "Redirecionando...",
+
   "download.title": "OpenCode | Baixar",
   "download.meta.description": "Baixe o OpenCode para macOS, Windows e Linux",
   "download.hero.title": "Baixar OpenCode",

+ 27 - 0
packages/console/app/src/i18n/da.ts

@@ -348,6 +348,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "Output",
   "workspace.usage.breakdown.reasoning": "Ræsonnement",
   "workspace.usage.subscription": "abonnement (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "Omkostninger",
@@ -357,6 +358,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(slettet)",
   "workspace.cost.empty": "Ingen brugsdata tilgængelige for den valgte periode.",
   "workspace.cost.subscriptionShort": "sub",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "API-nøgler",
   "workspace.keys.subtitle": "Administrer dine API-nøgler for at få adgang til opencode-tjenester.",
@@ -485,6 +487,31 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "Når du klikker på Tilmeld, starter dit abonnement med det samme, og dit kort vil blive debiteret.",
 
+  "workspace.lite.loading": "Indlæser...",
+  "workspace.lite.time.day": "dag",
+  "workspace.lite.time.days": "dage",
+  "workspace.lite.time.hour": "time",
+  "workspace.lite.time.hours": "timer",
+  "workspace.lite.time.minute": "minut",
+  "workspace.lite.time.minutes": "minutter",
+  "workspace.lite.time.fewSeconds": "et par sekunder",
+  "workspace.lite.subscription.title": "Lite-abonnement",
+  "workspace.lite.subscription.message": "Du abonnerer på OpenCode Lite.",
+  "workspace.lite.subscription.manage": "Administrer abonnement",
+  "workspace.lite.subscription.rollingUsage": "Løbende forbrug",
+  "workspace.lite.subscription.weeklyUsage": "Ugentligt forbrug",
+  "workspace.lite.subscription.monthlyUsage": "Månedligt forbrug",
+  "workspace.lite.subscription.resetsIn": "Nulstiller i",
+  "workspace.lite.subscription.useBalance": "Brug din tilgængelige saldo, når du har nået forbrugsgrænserne",
+  "workspace.lite.other.title": "Lite-abonnement",
+  "workspace.lite.other.message":
+    "Et andet medlem i dette workspace abonnerer allerede på OpenCode Lite. Kun ét medlem pr. workspace kan abonnere.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "Få adgang til de bedste åbne modeller — Kimi K2.5, GLM-5 og MiniMax M2.5 — med generøse forbrugsgrænser for $10 om måneden.",
+  "workspace.lite.promo.subscribe": "Abonner på Lite",
+  "workspace.lite.promo.subscribing": "Omdirigerer...",
+
   "download.title": "OpenCode | Download",
   "download.meta.description": "Download OpenCode til macOS, Windows og Linux",
   "download.hero.title": "Download OpenCode",

+ 27 - 0
packages/console/app/src/i18n/de.ts

@@ -350,6 +350,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "Output",
   "workspace.usage.breakdown.reasoning": "Reasoning",
   "workspace.usage.subscription": "Abonnement (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "Kosten",
@@ -359,6 +360,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(gelöscht)",
   "workspace.cost.empty": "Keine Nutzungsdaten für den gewählten Zeitraum verfügbar.",
   "workspace.cost.subscriptionShort": "Abo",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "API Keys",
   "workspace.keys.subtitle": "Verwalte deine API Keys für den Zugriff auf OpenCode-Dienste.",
@@ -487,6 +489,31 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "Wenn du auf Einschreiben klickst, startet dein Abo sofort und deine Karte wird belastet.",
 
+  "workspace.lite.loading": "Lade...",
+  "workspace.lite.time.day": "Tag",
+  "workspace.lite.time.days": "Tage",
+  "workspace.lite.time.hour": "Stunde",
+  "workspace.lite.time.hours": "Stunden",
+  "workspace.lite.time.minute": "Minute",
+  "workspace.lite.time.minutes": "Minuten",
+  "workspace.lite.time.fewSeconds": "einige Sekunden",
+  "workspace.lite.subscription.title": "Lite-Abonnement",
+  "workspace.lite.subscription.message": "Du hast OpenCode Lite abonniert.",
+  "workspace.lite.subscription.manage": "Abo verwalten",
+  "workspace.lite.subscription.rollingUsage": "Fortlaufende Nutzung",
+  "workspace.lite.subscription.weeklyUsage": "Wöchentliche Nutzung",
+  "workspace.lite.subscription.monthlyUsage": "Monatliche Nutzung",
+  "workspace.lite.subscription.resetsIn": "Setzt zurück in",
+  "workspace.lite.subscription.useBalance": "Nutze dein verfügbares Guthaben, nachdem die Nutzungslimits erreicht sind",
+  "workspace.lite.other.title": "Lite-Abonnement",
+  "workspace.lite.other.message":
+    "Ein anderes Mitglied in diesem Workspace hat OpenCode Lite bereits abonniert. Nur ein Mitglied pro Workspace kann abonnieren.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "Erhalte Zugriff auf die besten offenen Modelle — Kimi K2.5, GLM-5 und MiniMax M2.5 — mit großzügigen Nutzungslimits für $10 pro Monat.",
+  "workspace.lite.promo.subscribe": "Lite abonnieren",
+  "workspace.lite.promo.subscribing": "Leite weiter...",
+
   "download.title": "OpenCode | Download",
   "download.meta.description": "Lade OpenCode für macOS, Windows und Linux herunter",
   "download.hero.title": "OpenCode herunterladen",

+ 27 - 0
packages/console/app/src/i18n/en.ts

@@ -342,6 +342,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "Output",
   "workspace.usage.breakdown.reasoning": "Reasoning",
   "workspace.usage.subscription": "subscription (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "Cost",
@@ -351,6 +352,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(deleted)",
   "workspace.cost.empty": "No usage data available for the selected period.",
   "workspace.cost.subscriptionShort": "sub",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "API Keys",
   "workspace.keys.subtitle": "Manage your API keys for accessing opencode services.",
@@ -479,6 +481,31 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "When you click Enroll, your subscription starts immediately and your card will be charged.",
 
+  "workspace.lite.loading": "Loading...",
+  "workspace.lite.time.day": "day",
+  "workspace.lite.time.days": "days",
+  "workspace.lite.time.hour": "hour",
+  "workspace.lite.time.hours": "hours",
+  "workspace.lite.time.minute": "minute",
+  "workspace.lite.time.minutes": "minutes",
+  "workspace.lite.time.fewSeconds": "a few seconds",
+  "workspace.lite.subscription.title": "Lite Subscription",
+  "workspace.lite.subscription.message": "You are subscribed to OpenCode Lite.",
+  "workspace.lite.subscription.manage": "Manage Subscription",
+  "workspace.lite.subscription.rollingUsage": "Rolling Usage",
+  "workspace.lite.subscription.weeklyUsage": "Weekly Usage",
+  "workspace.lite.subscription.monthlyUsage": "Monthly Usage",
+  "workspace.lite.subscription.resetsIn": "Resets in",
+  "workspace.lite.subscription.useBalance": "Use your available balance after reaching the usage limits",
+  "workspace.lite.other.title": "Lite Subscription",
+  "workspace.lite.other.message":
+    "Another member in this workspace is already subscribed to OpenCode Lite. Only one member per workspace can subscribe.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "Get access to the best open models — Kimi K2.5, GLM-5, and MiniMax M2.5 — with generous usage limits for $10 per month.",
+  "workspace.lite.promo.subscribe": "Subscribe to Lite",
+  "workspace.lite.promo.subscribing": "Redirecting...",
+
   "download.title": "OpenCode | Download",
   "download.meta.description": "Download OpenCode for macOS, Windows, and Linux",
   "download.hero.title": "Download OpenCode",

+ 27 - 0
packages/console/app/src/i18n/es.ts

@@ -351,6 +351,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "Salida",
   "workspace.usage.breakdown.reasoning": "Razonamiento",
   "workspace.usage.subscription": "suscripción (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "Costo",
@@ -360,6 +361,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(eliminado)",
   "workspace.cost.empty": "No hay datos de uso disponibles para el periodo seleccionado.",
   "workspace.cost.subscriptionShort": "sub",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "Claves API",
   "workspace.keys.subtitle": "Gestiona tus claves API para acceder a los servicios de opencode.",
@@ -488,6 +490,31 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "Cuando haces clic en Inscribirse, tu suscripción comienza inmediatamente y se cargará a tu tarjeta.",
 
+  "workspace.lite.loading": "Cargando...",
+  "workspace.lite.time.day": "día",
+  "workspace.lite.time.days": "días",
+  "workspace.lite.time.hour": "hora",
+  "workspace.lite.time.hours": "horas",
+  "workspace.lite.time.minute": "minuto",
+  "workspace.lite.time.minutes": "minutos",
+  "workspace.lite.time.fewSeconds": "unos pocos segundos",
+  "workspace.lite.subscription.title": "Suscripción Lite",
+  "workspace.lite.subscription.message": "Estás suscrito a OpenCode Lite.",
+  "workspace.lite.subscription.manage": "Gestionar Suscripción",
+  "workspace.lite.subscription.rollingUsage": "Uso Continuo",
+  "workspace.lite.subscription.weeklyUsage": "Uso Semanal",
+  "workspace.lite.subscription.monthlyUsage": "Uso Mensual",
+  "workspace.lite.subscription.resetsIn": "Se reinicia en",
+  "workspace.lite.subscription.useBalance": "Usa tu saldo disponible después de alcanzar los límites de uso",
+  "workspace.lite.other.title": "Suscripción Lite",
+  "workspace.lite.other.message":
+    "Otro miembro de este espacio de trabajo ya está suscrito a OpenCode Lite. Solo un miembro por espacio de trabajo puede suscribirse.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "Obtén acceso a los mejores modelos abiertos — Kimi K2.5, GLM-5 y MiniMax M2.5 — con generosos límites de uso por $10 al mes.",
+  "workspace.lite.promo.subscribe": "Suscribirse a Lite",
+  "workspace.lite.promo.subscribing": "Redirigiendo...",
+
   "download.title": "OpenCode | Descargar",
   "download.meta.description": "Descarga OpenCode para macOS, Windows y Linux",
   "download.hero.title": "Descargar OpenCode",

+ 28 - 0
packages/console/app/src/i18n/fr.ts

@@ -356,6 +356,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "Sortie",
   "workspace.usage.breakdown.reasoning": "Raisonnement",
   "workspace.usage.subscription": "abonnement ({{amount}} $)",
+  "workspace.usage.lite": "lite ({{amount}} $)",
   "workspace.usage.byok": "BYOK ({{amount}} $)",
 
   "workspace.cost.title": "Coût",
@@ -365,6 +366,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(supprimé)",
   "workspace.cost.empty": "Aucune donnée d'utilisation disponible pour la période sélectionnée.",
   "workspace.cost.subscriptionShort": "abo",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "Clés API",
   "workspace.keys.subtitle": "Gérez vos clés API pour accéder aux services OpenCode.",
@@ -496,6 +498,32 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "Lorsque vous cliquez sur S'inscrire, votre abonnement démarre immédiatement et votre carte sera débitée.",
 
+  "workspace.lite.loading": "Chargement...",
+  "workspace.lite.time.day": "jour",
+  "workspace.lite.time.days": "jours",
+  "workspace.lite.time.hour": "heure",
+  "workspace.lite.time.hours": "heures",
+  "workspace.lite.time.minute": "minute",
+  "workspace.lite.time.minutes": "minutes",
+  "workspace.lite.time.fewSeconds": "quelques secondes",
+  "workspace.lite.subscription.title": "Abonnement Lite",
+  "workspace.lite.subscription.message": "Vous êtes abonné à OpenCode Lite.",
+  "workspace.lite.subscription.manage": "Gérer l'abonnement",
+  "workspace.lite.subscription.rollingUsage": "Utilisation glissante",
+  "workspace.lite.subscription.weeklyUsage": "Utilisation hebdomadaire",
+  "workspace.lite.subscription.monthlyUsage": "Utilisation mensuelle",
+  "workspace.lite.subscription.resetsIn": "Réinitialisation dans",
+  "workspace.lite.subscription.useBalance":
+    "Utilisez votre solde disponible après avoir atteint les limites d'utilisation",
+  "workspace.lite.other.title": "Abonnement Lite",
+  "workspace.lite.other.message":
+    "Un autre membre de cet espace de travail est déjà abonné à OpenCode Lite. Un seul membre par espace de travail peut s'abonner.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "Accédez aux meilleurs modèles ouverts — Kimi K2.5, GLM-5 et MiniMax M2.5 — avec des limites d'utilisation généreuses pour 10 $ par mois.",
+  "workspace.lite.promo.subscribe": "S'abonner à Lite",
+  "workspace.lite.promo.subscribing": "Redirection...",
+
   "download.title": "OpenCode | Téléchargement",
   "download.meta.description": "Téléchargez OpenCode pour macOS, Windows et Linux",
   "download.hero.title": "Télécharger OpenCode",

+ 27 - 0
packages/console/app/src/i18n/it.ts

@@ -350,6 +350,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "Output",
   "workspace.usage.breakdown.reasoning": "Reasoning",
   "workspace.usage.subscription": "abbonamento (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "Costo",
@@ -359,6 +360,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(eliminato)",
   "workspace.cost.empty": "Nessun dato di utilizzo disponibile per il periodo selezionato.",
   "workspace.cost.subscriptionShort": "sub",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "Chiavi API",
   "workspace.keys.subtitle": "Gestisci le tue chiavi API per accedere ai servizi opencode.",
@@ -487,6 +489,31 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "Quando clicchi su Iscriviti, il tuo abbonamento inizia immediatamente e la tua carta verrà addebitata.",
 
+  "workspace.lite.loading": "Caricamento...",
+  "workspace.lite.time.day": "giorno",
+  "workspace.lite.time.days": "giorni",
+  "workspace.lite.time.hour": "ora",
+  "workspace.lite.time.hours": "ore",
+  "workspace.lite.time.minute": "minuto",
+  "workspace.lite.time.minutes": "minuti",
+  "workspace.lite.time.fewSeconds": "pochi secondi",
+  "workspace.lite.subscription.title": "Abbonamento Lite",
+  "workspace.lite.subscription.message": "Sei abbonato a OpenCode Lite.",
+  "workspace.lite.subscription.manage": "Gestisci Abbonamento",
+  "workspace.lite.subscription.rollingUsage": "Utilizzo Continuativo",
+  "workspace.lite.subscription.weeklyUsage": "Utilizzo Settimanale",
+  "workspace.lite.subscription.monthlyUsage": "Utilizzo Mensile",
+  "workspace.lite.subscription.resetsIn": "Si resetta tra",
+  "workspace.lite.subscription.useBalance": "Usa il tuo saldo disponibile dopo aver raggiunto i limiti di utilizzo",
+  "workspace.lite.other.title": "Abbonamento Lite",
+  "workspace.lite.other.message":
+    "Un altro membro in questo workspace è già abbonato a OpenCode Lite. Solo un membro per workspace può abbonarsi.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "Ottieni l'accesso ai migliori modelli aperti — Kimi K2.5, GLM-5 e MiniMax M2.5 — con limiti di utilizzo generosi per $10 al mese.",
+  "workspace.lite.promo.subscribe": "Abbonati a Lite",
+  "workspace.lite.promo.subscribing": "Reindirizzamento...",
+
   "download.title": "OpenCode | Download",
   "download.meta.description": "Scarica OpenCode per macOS, Windows e Linux",
   "download.hero.title": "Scarica OpenCode",

+ 27 - 0
packages/console/app/src/i18n/ja.ts

@@ -347,6 +347,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "出力",
   "workspace.usage.breakdown.reasoning": "推論",
   "workspace.usage.subscription": "サブスクリプション (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "コスト",
@@ -356,6 +357,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(削除済み)",
   "workspace.cost.empty": "選択した期間の使用状況データはありません。",
   "workspace.cost.subscriptionShort": "サブ",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "APIキー",
   "workspace.keys.subtitle": "OpenCodeサービスにアクセスするためのAPIキーを管理します。",
@@ -485,6 +487,31 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "「登録する」をクリックすると、サブスクリプションがすぐに開始され、カードに請求されます。",
 
+  "workspace.lite.loading": "読み込み中...",
+  "workspace.lite.time.day": "日",
+  "workspace.lite.time.days": "日",
+  "workspace.lite.time.hour": "時間",
+  "workspace.lite.time.hours": "時間",
+  "workspace.lite.time.minute": "分",
+  "workspace.lite.time.minutes": "分",
+  "workspace.lite.time.fewSeconds": "数秒",
+  "workspace.lite.subscription.title": "Liteサブスクリプション",
+  "workspace.lite.subscription.message": "あなたは OpenCode Lite を購読しています。",
+  "workspace.lite.subscription.manage": "サブスクリプションの管理",
+  "workspace.lite.subscription.rollingUsage": "ローリング利用量",
+  "workspace.lite.subscription.weeklyUsage": "週間利用量",
+  "workspace.lite.subscription.monthlyUsage": "月間利用量",
+  "workspace.lite.subscription.resetsIn": "リセットまで",
+  "workspace.lite.subscription.useBalance": "利用限度額に達したら利用可能な残高を使用する",
+  "workspace.lite.other.title": "Liteサブスクリプション",
+  "workspace.lite.other.message":
+    "このワークスペースの別のメンバーが既に OpenCode Lite を購読しています。ワークスペースにつき1人のメンバーのみが購読できます。",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "月額$10で、十分な利用枠が設けられた最高のオープンモデル — Kimi K2.5、GLM-5、および MiniMax M2.5 — にアクセスできます。",
+  "workspace.lite.promo.subscribe": "Liteを購読する",
+  "workspace.lite.promo.subscribing": "リダイレクト中...",
+
   "download.title": "OpenCode | ダウンロード",
   "download.meta.description": "OpenCode を macOS、Windows、Linux 向けにダウンロード",
   "download.hero.title": "OpenCode をダウンロード",

+ 27 - 0
packages/console/app/src/i18n/ko.ts

@@ -344,6 +344,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "출력",
   "workspace.usage.breakdown.reasoning": "추론",
   "workspace.usage.subscription": "구독 (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "비용",
@@ -353,6 +354,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(삭제됨)",
   "workspace.cost.empty": "선택한 기간에 사용 데이터가 없습니다.",
   "workspace.cost.subscriptionShort": "구독",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "API 키",
   "workspace.keys.subtitle": "OpenCode 서비스 액세스를 위한 API 키를 관리하세요.",
@@ -480,6 +482,31 @@ export const dict = {
   "workspace.black.waitlist.enrolled": "등록됨",
   "workspace.black.waitlist.enrollNote": "등록을 클릭하면 구독이 즉시 시작되며 카드에 요금이 청구됩니다.",
 
+  "workspace.lite.loading": "로드 중...",
+  "workspace.lite.time.day": "일",
+  "workspace.lite.time.days": "일",
+  "workspace.lite.time.hour": "시간",
+  "workspace.lite.time.hours": "시간",
+  "workspace.lite.time.minute": "분",
+  "workspace.lite.time.minutes": "분",
+  "workspace.lite.time.fewSeconds": "몇 초",
+  "workspace.lite.subscription.title": "Lite 구독",
+  "workspace.lite.subscription.message": "현재 OpenCode Lite를 구독 중입니다.",
+  "workspace.lite.subscription.manage": "구독 관리",
+  "workspace.lite.subscription.rollingUsage": "롤링 사용량",
+  "workspace.lite.subscription.weeklyUsage": "주간 사용량",
+  "workspace.lite.subscription.monthlyUsage": "월간 사용량",
+  "workspace.lite.subscription.resetsIn": "초기화까지 남은 시간:",
+  "workspace.lite.subscription.useBalance": "사용 한도 도달 후에는 보유 잔액 사용",
+  "workspace.lite.other.title": "Lite 구독",
+  "workspace.lite.other.message":
+    "이 워크스페이스의 다른 멤버가 이미 OpenCode Lite를 구독 중입니다. 워크스페이스당 한 명의 멤버만 구독할 수 있습니다.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "월 $10의 넉넉한 사용 한도로 최고의 오픈 모델인 Kimi K2.5, GLM-5, MiniMax M2.5에 액세스하세요.",
+  "workspace.lite.promo.subscribe": "Lite 구독하기",
+  "workspace.lite.promo.subscribing": "리디렉션 중...",
+
   "download.title": "OpenCode | 다운로드",
   "download.meta.description": "macOS, Windows, Linux용 OpenCode 다운로드",
   "download.hero.title": "OpenCode 다운로드",

+ 27 - 0
packages/console/app/src/i18n/no.ts

@@ -348,6 +348,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "Output",
   "workspace.usage.breakdown.reasoning": "Resonnering",
   "workspace.usage.subscription": "abonnement (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "Kostnad",
@@ -357,6 +358,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(slettet)",
   "workspace.cost.empty": "Ingen bruksdata tilgjengelig for den valgte perioden.",
   "workspace.cost.subscriptionShort": "sub",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "API-nøkler",
   "workspace.keys.subtitle": "Administrer API-nøklene dine for å få tilgang til opencode-tjenester.",
@@ -485,6 +487,31 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "Når du klikker på Meld på, starter abonnementet umiddelbart og kortet ditt belastes.",
 
+  "workspace.lite.loading": "Laster...",
+  "workspace.lite.time.day": "dag",
+  "workspace.lite.time.days": "dager",
+  "workspace.lite.time.hour": "time",
+  "workspace.lite.time.hours": "timer",
+  "workspace.lite.time.minute": "minutt",
+  "workspace.lite.time.minutes": "minutter",
+  "workspace.lite.time.fewSeconds": "noen få sekunder",
+  "workspace.lite.subscription.title": "Lite-abonnement",
+  "workspace.lite.subscription.message": "Du abonnerer på OpenCode Lite.",
+  "workspace.lite.subscription.manage": "Administrer abonnement",
+  "workspace.lite.subscription.rollingUsage": "Løpende bruk",
+  "workspace.lite.subscription.weeklyUsage": "Ukentlig bruk",
+  "workspace.lite.subscription.monthlyUsage": "Månedlig bruk",
+  "workspace.lite.subscription.resetsIn": "Nullstilles om",
+  "workspace.lite.subscription.useBalance": "Bruk din tilgjengelige saldo etter å ha nådd bruksgrensene",
+  "workspace.lite.other.title": "Lite-abonnement",
+  "workspace.lite.other.message":
+    "Et annet medlem i dette arbeidsområdet abonnerer allerede på OpenCode Lite. Kun ett medlem per arbeidsområde kan abonnere.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "Få tilgang til de beste åpne modellene — Kimi K2.5, GLM-5 og MiniMax M2.5 — med generøse bruksgrenser for $10 per måned.",
+  "workspace.lite.promo.subscribe": "Abonner på Lite",
+  "workspace.lite.promo.subscribing": "Omdirigerer...",
+
   "download.title": "OpenCode | Last ned",
   "download.meta.description": "Last ned OpenCode for macOS, Windows og Linux",
   "download.hero.title": "Last ned OpenCode",

+ 27 - 0
packages/console/app/src/i18n/pl.ts

@@ -349,6 +349,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "Wyjście",
   "workspace.usage.breakdown.reasoning": "Rozumowanie",
   "workspace.usage.subscription": "subskrypcja (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "Koszt",
@@ -358,6 +359,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(usunięte)",
   "workspace.cost.empty": "Brak danych o użyciu dla wybranego okresu.",
   "workspace.cost.subscriptionShort": "sub",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "Klucze API",
   "workspace.keys.subtitle": "Zarządzaj kluczami API do usług opencode.",
@@ -486,6 +488,31 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "Po kliknięciu Zapisz się, Twoja subskrypcja rozpocznie się natychmiast, a karta zostanie obciążona.",
 
+  "workspace.lite.loading": "Ładowanie...",
+  "workspace.lite.time.day": "dzień",
+  "workspace.lite.time.days": "dni",
+  "workspace.lite.time.hour": "godzina",
+  "workspace.lite.time.hours": "godzin(y)",
+  "workspace.lite.time.minute": "minuta",
+  "workspace.lite.time.minutes": "minut(y)",
+  "workspace.lite.time.fewSeconds": "kilka sekund",
+  "workspace.lite.subscription.title": "Subskrypcja Lite",
+  "workspace.lite.subscription.message": "Subskrybujesz OpenCode Lite.",
+  "workspace.lite.subscription.manage": "Zarządzaj subskrypcją",
+  "workspace.lite.subscription.rollingUsage": "Użycie kroczące",
+  "workspace.lite.subscription.weeklyUsage": "Użycie tygodniowe",
+  "workspace.lite.subscription.monthlyUsage": "Użycie miesięczne",
+  "workspace.lite.subscription.resetsIn": "Resetuje się za",
+  "workspace.lite.subscription.useBalance": "Użyj dostępnego salda po osiągnięciu limitów użycia",
+  "workspace.lite.other.title": "Subskrypcja Lite",
+  "workspace.lite.other.message":
+    "Inny członek tego obszaru roboczego już subskrybuje OpenCode Lite. Tylko jeden członek na obszar roboczy może subskrybować.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "Uzyskaj dostęp do najlepszych otwartych modeli — Kimi K2.5, GLM-5 i MiniMax M2.5 — z hojnymi limitami użycia za $10 miesięcznie.",
+  "workspace.lite.promo.subscribe": "Subskrybuj Lite",
+  "workspace.lite.promo.subscribing": "Przekierowywanie...",
+
   "download.title": "OpenCode | Pobierz",
   "download.meta.description": "Pobierz OpenCode na macOS, Windows i Linux",
   "download.hero.title": "Pobierz OpenCode",

+ 27 - 0
packages/console/app/src/i18n/ru.ts

@@ -354,6 +354,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "Выход",
   "workspace.usage.breakdown.reasoning": "Reasoning (рассуждения)",
   "workspace.usage.subscription": "подписка (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "Расходы",
@@ -363,6 +364,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(удалено)",
   "workspace.cost.empty": "Нет данных об использовании за выбранный период.",
   "workspace.cost.subscriptionShort": "подписка",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "API Ключи",
   "workspace.keys.subtitle": "Управляйте вашими API ключами для доступа к сервисам opencode.",
@@ -491,6 +493,31 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "Когда вы нажмете Подключиться, ваша подписка начнется немедленно, и с карты будет списана оплата.",
 
+  "workspace.lite.loading": "Загрузка...",
+  "workspace.lite.time.day": "день",
+  "workspace.lite.time.days": "дней",
+  "workspace.lite.time.hour": "час",
+  "workspace.lite.time.hours": "часов",
+  "workspace.lite.time.minute": "минута",
+  "workspace.lite.time.minutes": "минут",
+  "workspace.lite.time.fewSeconds": "несколько секунд",
+  "workspace.lite.subscription.title": "Подписка Lite",
+  "workspace.lite.subscription.message": "Вы подписаны на OpenCode Lite.",
+  "workspace.lite.subscription.manage": "Управление подпиской",
+  "workspace.lite.subscription.rollingUsage": "Скользящее использование",
+  "workspace.lite.subscription.weeklyUsage": "Недельное использование",
+  "workspace.lite.subscription.monthlyUsage": "Ежемесячное использование",
+  "workspace.lite.subscription.resetsIn": "Сброс через",
+  "workspace.lite.subscription.useBalance": "Использовать доступный баланс после достижения лимитов",
+  "workspace.lite.other.title": "Подписка Lite",
+  "workspace.lite.other.message":
+    "Другой участник в этом рабочем пространстве уже подписан на OpenCode Lite. Только один участник в рабочем пространстве может оформить подписку.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "Получите доступ к лучшим открытым моделям — Kimi K2.5, GLM-5 и MiniMax M2.5 — с щедрыми лимитами использования за $10 в месяц.",
+  "workspace.lite.promo.subscribe": "Подписаться на Lite",
+  "workspace.lite.promo.subscribing": "Перенаправление...",
+
   "download.title": "OpenCode | Скачать",
   "download.meta.description": "Скачать OpenCode для macOS, Windows и Linux",
   "download.hero.title": "Скачать OpenCode",

+ 27 - 0
packages/console/app/src/i18n/th.ts

@@ -347,6 +347,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "Output",
   "workspace.usage.breakdown.reasoning": "Reasoning",
   "workspace.usage.subscription": "สมัครสมาชิก (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "ค่าใช้จ่าย",
@@ -356,6 +357,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(ลบแล้ว)",
   "workspace.cost.empty": "ไม่มีข้อมูลการใช้งานในช่วงเวลาที่เลือก",
   "workspace.cost.subscriptionShort": "sub",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "API Keys",
   "workspace.keys.subtitle": "จัดการ API keys ของคุณสำหรับการเข้าถึงบริการ OpenCode",
@@ -484,6 +486,31 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "เมื่อคุณคลิกลงทะเบียน การสมัครสมาชิกของคุณจะเริ่มต้นทันทีและบัตรของคุณจะถูกเรียกเก็บเงิน",
 
+  "workspace.lite.loading": "กำลังโหลด...",
+  "workspace.lite.time.day": "วัน",
+  "workspace.lite.time.days": "วัน",
+  "workspace.lite.time.hour": "ชั่วโมง",
+  "workspace.lite.time.hours": "ชั่วโมง",
+  "workspace.lite.time.minute": "นาที",
+  "workspace.lite.time.minutes": "นาที",
+  "workspace.lite.time.fewSeconds": "ไม่กี่วินาที",
+  "workspace.lite.subscription.title": "การสมัครสมาชิก Lite",
+  "workspace.lite.subscription.message": "คุณได้สมัครสมาชิก OpenCode Lite แล้ว",
+  "workspace.lite.subscription.manage": "จัดการการสมัครสมาชิก",
+  "workspace.lite.subscription.rollingUsage": "การใช้งานแบบหมุนเวียน",
+  "workspace.lite.subscription.weeklyUsage": "การใช้งานรายสัปดาห์",
+  "workspace.lite.subscription.monthlyUsage": "การใช้งานรายเดือน",
+  "workspace.lite.subscription.resetsIn": "รีเซ็ตใน",
+  "workspace.lite.subscription.useBalance": "ใช้ยอดคงเหลือของคุณหลังจากถึงขีดจำกัดการใช้งาน",
+  "workspace.lite.other.title": "การสมัครสมาชิก Lite",
+  "workspace.lite.other.message":
+    "สมาชิกคนอื่นใน Workspace นี้ได้สมัคร OpenCode Lite แล้ว สามารถสมัครได้เพียงหนึ่งคนต่อหนึ่ง Workspace เท่านั้น",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "เข้าถึงโมเดลเปิดที่ดีที่สุด — Kimi K2.5, GLM-5 และ MiniMax M2.5 — พร้อมขีดจำกัดการใช้งานมากมายในราคา $10 ต่อเดือน",
+  "workspace.lite.promo.subscribe": "สมัครสมาชิก Lite",
+  "workspace.lite.promo.subscribing": "กำลังเปลี่ยนเส้นทาง...",
+
   "download.title": "OpenCode | ดาวน์โหลด",
   "download.meta.description": "ดาวน์โหลด OpenCode สำหรับ macOS, Windows และ Linux",
   "download.hero.title": "ดาวน์โหลด OpenCode",

+ 27 - 0
packages/console/app/src/i18n/tr.ts

@@ -350,6 +350,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "Çıkış",
   "workspace.usage.breakdown.reasoning": "Muhakeme",
   "workspace.usage.subscription": "abonelik (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "Maliyet",
@@ -359,6 +360,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(silindi)",
   "workspace.cost.empty": "Seçilen döneme ait kullanım verisi yok.",
   "workspace.cost.subscriptionShort": "abonelik",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "API Anahtarları",
   "workspace.keys.subtitle": "opencode hizmetlerine erişim için API anahtarlarınızı yönetin.",
@@ -487,6 +489,31 @@ export const dict = {
   "workspace.black.waitlist.enrollNote":
     "Kayıt Ol'a tıkladığınızda aboneliğiniz hemen başlar ve kartınızdan çekim yapılır.",
 
+  "workspace.lite.loading": "Yükleniyor...",
+  "workspace.lite.time.day": "gün",
+  "workspace.lite.time.days": "gün",
+  "workspace.lite.time.hour": "saat",
+  "workspace.lite.time.hours": "saat",
+  "workspace.lite.time.minute": "dakika",
+  "workspace.lite.time.minutes": "dakika",
+  "workspace.lite.time.fewSeconds": "birkaç saniye",
+  "workspace.lite.subscription.title": "Lite Aboneliği",
+  "workspace.lite.subscription.message": "OpenCode Lite abonesisiniz.",
+  "workspace.lite.subscription.manage": "Aboneliği Yönet",
+  "workspace.lite.subscription.rollingUsage": "Devam Eden Kullanım",
+  "workspace.lite.subscription.weeklyUsage": "Haftalık Kullanım",
+  "workspace.lite.subscription.monthlyUsage": "Aylık Kullanım",
+  "workspace.lite.subscription.resetsIn": "Sıfırlama süresi",
+  "workspace.lite.subscription.useBalance": "Kullanım limitlerine ulaştıktan sonra mevcut bakiyenizi kullanın",
+  "workspace.lite.other.title": "Lite Aboneliği",
+  "workspace.lite.other.message":
+    "Bu çalışma alanındaki başka bir üye zaten OpenCode Lite abonesi. Çalışma alanı başına yalnızca bir üye abone olabilir.",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "Ayda $10 karşılığında cömert kullanım limitleriyle en iyi açık modellere — Kimi K2.5, GLM-5 ve MiniMax M2.5 — erişin.",
+  "workspace.lite.promo.subscribe": "Lite'a Abone Ol",
+  "workspace.lite.promo.subscribing": "Yönlendiriliyor...",
+
   "download.title": "OpenCode | İndir",
   "download.meta.description": "OpenCode'u macOS, Windows ve Linux için indirin",
   "download.hero.title": "OpenCode'u İndir",

+ 26 - 0
packages/console/app/src/i18n/zh.ts

@@ -335,6 +335,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "输出",
   "workspace.usage.breakdown.reasoning": "推理",
   "workspace.usage.subscription": "订阅 (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "成本",
@@ -344,6 +345,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(已删除)",
   "workspace.cost.empty": "所选期间无可用使用数据。",
   "workspace.cost.subscriptionShort": "订阅",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "API 密钥",
   "workspace.keys.subtitle": "管理访问 OpenCode 服务的 API 密钥。",
@@ -471,6 +473,30 @@ export const dict = {
   "workspace.black.waitlist.enrolled": "已加入",
   "workspace.black.waitlist.enrollNote": "点击加入后,您的订阅将立即开始,并将从您的卡中扣费。",
 
+  "workspace.lite.loading": "加载中...",
+  "workspace.lite.time.day": "天",
+  "workspace.lite.time.days": "天",
+  "workspace.lite.time.hour": "小时",
+  "workspace.lite.time.hours": "小时",
+  "workspace.lite.time.minute": "分钟",
+  "workspace.lite.time.minutes": "分钟",
+  "workspace.lite.time.fewSeconds": "几秒钟",
+  "workspace.lite.subscription.title": "Lite 订阅",
+  "workspace.lite.subscription.message": "您已订阅 OpenCode Lite。",
+  "workspace.lite.subscription.manage": "管理订阅",
+  "workspace.lite.subscription.rollingUsage": "滚动用量",
+  "workspace.lite.subscription.weeklyUsage": "每周用量",
+  "workspace.lite.subscription.monthlyUsage": "每月用量",
+  "workspace.lite.subscription.resetsIn": "重置于",
+  "workspace.lite.subscription.useBalance": "达到使用限额后使用您的可用余额",
+  "workspace.lite.other.title": "Lite 订阅",
+  "workspace.lite.other.message": "此工作区中的另一位成员已经订阅了 OpenCode Lite。每个工作区只有一名成员可以订阅。",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "每月仅需 $10 即可访问最优秀的开源模型 — Kimi K2.5, GLM-5, 和 MiniMax M2.5 — 并享受充裕的使用限额。",
+  "workspace.lite.promo.subscribe": "订阅 Lite",
+  "workspace.lite.promo.subscribing": "正在重定向...",
+
   "download.title": "OpenCode | 下载",
   "download.meta.description": "下载适用于 macOS, Windows, 和 Linux 的 OpenCode",
   "download.hero.title": "下载 OpenCode",

+ 26 - 0
packages/console/app/src/i18n/zht.ts

@@ -335,6 +335,7 @@ export const dict = {
   "workspace.usage.breakdown.output": "輸出",
   "workspace.usage.breakdown.reasoning": "推理",
   "workspace.usage.subscription": "訂閱 (${{amount}})",
+  "workspace.usage.lite": "lite (${{amount}})",
   "workspace.usage.byok": "BYOK (${{amount}})",
 
   "workspace.cost.title": "成本",
@@ -344,6 +345,7 @@ export const dict = {
   "workspace.cost.deletedSuffix": "(已刪除)",
   "workspace.cost.empty": "所選期間沒有可用的使用資料。",
   "workspace.cost.subscriptionShort": "訂",
+  "workspace.cost.liteShort": "lite",
 
   "workspace.keys.title": "API 金鑰",
   "workspace.keys.subtitle": "管理你的 API 金鑰以存取 OpenCode 服務。",
@@ -471,6 +473,30 @@ export const dict = {
   "workspace.black.waitlist.enrolled": "已加入",
   "workspace.black.waitlist.enrollNote": "當你點選「加入」後,你的訂閱將立即開始,並且將從你的卡片中扣款。",
 
+  "workspace.lite.loading": "載入中...",
+  "workspace.lite.time.day": "天",
+  "workspace.lite.time.days": "天",
+  "workspace.lite.time.hour": "小時",
+  "workspace.lite.time.hours": "小時",
+  "workspace.lite.time.minute": "分鐘",
+  "workspace.lite.time.minutes": "分鐘",
+  "workspace.lite.time.fewSeconds": "幾秒",
+  "workspace.lite.subscription.title": "Lite 訂閱",
+  "workspace.lite.subscription.message": "您已訂閱 OpenCode Lite。",
+  "workspace.lite.subscription.manage": "管理訂閱",
+  "workspace.lite.subscription.rollingUsage": "滾動使用量",
+  "workspace.lite.subscription.weeklyUsage": "每週使用量",
+  "workspace.lite.subscription.monthlyUsage": "每月使用量",
+  "workspace.lite.subscription.resetsIn": "重置時間:",
+  "workspace.lite.subscription.useBalance": "達到使用限制後使用您的可用餘額",
+  "workspace.lite.other.title": "Lite 訂閱",
+  "workspace.lite.other.message": "此工作區中的另一位成員已訂閱 OpenCode Lite。每個工作區只能有一位成員訂閱。",
+  "workspace.lite.promo.title": "OpenCode Lite",
+  "workspace.lite.promo.description":
+    "每月只需 $10 即可使用最佳的開放模型 — Kimi K2.5、GLM-5 和 MiniMax M2.5 — 並享有慷慨的使用限制。",
+  "workspace.lite.promo.subscribe": "訂閱 Lite",
+  "workspace.lite.promo.subscribing": "重新導向中...",
+
   "download.title": "OpenCode | 下載",
   "download.meta.description": "下載適用於 macOS、Windows 與 Linux 的 OpenCode",
   "download.hero.title": "下載 OpenCode",

+ 73 - 289
packages/console/app/src/routes/stripe/webhook.ts

@@ -1,13 +1,13 @@
 import { Billing } from "@opencode-ai/console-core/billing.js"
 import type { APIEvent } from "@solidjs/start/server"
-import { and, Database, eq, isNull, sql } from "@opencode-ai/console-core/drizzle/index.js"
-import { BillingTable, PaymentTable, SubscriptionTable } from "@opencode-ai/console-core/schema/billing.sql.js"
+import { and, Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js"
+import { BillingTable, LiteTable, PaymentTable } from "@opencode-ai/console-core/schema/billing.sql.js"
 import { Identifier } from "@opencode-ai/console-core/identifier.js"
 import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
 import { Actor } from "@opencode-ai/console-core/actor.js"
 import { Resource } from "@opencode-ai/console-resource"
-import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
-import { AuthTable } from "@opencode-ai/console-core/schema/auth.sql.js"
+import { LiteData } from "@opencode-ai/console-core/lite.js"
+import { BlackData } from "@opencode-ai/console-core/black.js"
 
 export async function POST(input: APIEvent) {
   const body = await Billing.stripe().webhooks.constructEventAsync(
@@ -103,310 +103,93 @@ export async function POST(input: APIEvent) {
         })
       })
     }
-    if (body.type === "checkout.session.completed" && body.data.object.mode === "subscription") {
-      const workspaceID = body.data.object.custom_fields.find((f) => f.key === "workspaceid")?.text?.value
-      const amountInCents = body.data.object.amount_total as number
-      const customerID = body.data.object.customer as string
-      const customerEmail = body.data.object.customer_details?.email as string
-      const invoiceID = body.data.object.invoice as string
-      const subscriptionID = body.data.object.subscription as string
-      const promoCode = body.data.object.discounts?.[0]?.promotion_code as string
-
-      if (!workspaceID) throw new Error("Workspace ID not found")
-      if (!customerID) throw new Error("Customer ID not found")
-      if (!amountInCents) throw new Error("Amount not found")
-      if (!invoiceID) throw new Error("Invoice ID not found")
-      if (!subscriptionID) throw new Error("Subscription ID not found")
-
-      // get payment id from invoice
-      const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
-        expand: ["payments"],
-      })
-      const paymentID = invoice.payments?.data[0].payment.payment_intent as string
-      if (!paymentID) throw new Error("Payment ID not found")
-
-      // get payment method for the payment intent
-      const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
-        expand: ["payment_method"],
-      })
-      const paymentMethod = paymentIntent.payment_method
-      if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
-
-      // get coupon id from promotion code
-      const couponID = await (async () => {
-        if (!promoCode) return
-        const coupon = await Billing.stripe().promotionCodes.retrieve(promoCode)
-        const couponID = coupon.coupon.id
-        if (!couponID) throw new Error("Coupon not found for promotion code")
-        return couponID
-      })()
-
-      await Actor.provide("system", { workspaceID }, async () => {
-        // look up current billing
-        const billing = await Billing.get()
-        if (!billing) throw new Error(`Workspace with ID ${workspaceID} not found`)
+    if (body.type === "customer.subscription.created") {
+      const type = body.data.object.metadata?.type
+      if (type === "lite") {
+        const workspaceID = body.data.object.metadata?.workspaceID
+        const userID = body.data.object.metadata?.userID
+        const customerID = body.data.object.customer as string
+        const invoiceID = body.data.object.latest_invoice as string
+        const subscriptionID = body.data.object.id as string
 
-        // Temporarily skip this check because during Black drop, user can checkout
-        // as a new customer
-        //if (billing.customerID !== customerID) throw new Error("Customer ID mismatch")
+        if (!workspaceID) throw new Error("Workspace ID not found")
+        if (!userID) throw new Error("User ID not found")
+        if (!customerID) throw new Error("Customer ID not found")
+        if (!invoiceID) throw new Error("Invoice ID not found")
+        if (!subscriptionID) throw new Error("Subscription ID not found")
 
-        // Temporarily check the user to apply to. After Black drop, we will allow
-        // look up the user to apply to
-        const users = await Database.use((tx) =>
-          tx
-            .select({ id: UserTable.id, email: AuthTable.subject })
-            .from(UserTable)
-            .innerJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email")))
-            .where(and(eq(UserTable.workspaceID, workspaceID), isNull(UserTable.timeDeleted))),
-        )
-        const user = users.find((u) => u.email === customerEmail) ?? users[0]
-        if (!user) {
-          console.error(`Error: User with email ${customerEmail} not found in workspace ${workspaceID}`)
-          process.exit(1)
-        }
+        // get payment id from invoice
+        const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
+          expand: ["payments"],
+        })
+        const paymentID = invoice.payments?.data[0].payment.payment_intent as string
+        if (!paymentID) throw new Error("Payment ID not found")
 
-        // set customer metadata
-        if (!billing?.customerID) {
-          await Billing.stripe().customers.update(customerID, {
-            metadata: {
-              workspaceID,
-            },
-          })
-        }
+        // get payment method for the payment intent
+        const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
+          expand: ["payment_method"],
+        })
+        const paymentMethod = paymentIntent.payment_method
+        if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
 
-        await Database.transaction(async (tx) => {
-          await tx
-            .update(BillingTable)
-            .set({
-              customerID,
-              subscriptionID,
-              subscription: {
-                status: "subscribed",
-                coupon: couponID,
-                seats: 1,
-                plan: "200",
+        await Actor.provide("system", { workspaceID }, async () => {
+          // look up current billing
+          const billing = await Billing.get()
+          if (!billing) throw new Error(`Workspace with ID ${workspaceID} not found`)
+          if (billing.customerID && billing.customerID !== customerID) throw new Error("Customer ID mismatch")
+
+          // set customer metadata
+          if (!billing?.customerID) {
+            await Billing.stripe().customers.update(customerID, {
+              metadata: {
+                workspaceID,
               },
-              paymentMethodID: paymentMethod.id,
-              paymentMethodLast4: paymentMethod.card?.last4 ?? null,
-              paymentMethodType: paymentMethod.type,
             })
-            .where(eq(BillingTable.workspaceID, workspaceID))
+          }
 
-          await tx.insert(SubscriptionTable).values({
-            workspaceID,
-            id: Identifier.create("subscription"),
-            userID: user.id,
-          })
+          await Database.transaction(async (tx) => {
+            await tx
+              .update(BillingTable)
+              .set({
+                customerID,
+                liteSubscriptionID: subscriptionID,
+                lite: {},
+                paymentMethodID: paymentMethod.id,
+                paymentMethodLast4: paymentMethod.card?.last4 ?? null,
+                paymentMethodType: paymentMethod.type,
+              })
+              .where(eq(BillingTable.workspaceID, workspaceID))
 
-          await tx.insert(PaymentTable).values({
-            workspaceID,
-            id: Identifier.create("payment"),
-            amount: centsToMicroCents(amountInCents),
-            paymentID,
-            invoiceID,
-            customerID,
-            enrichment: {
-              type: "subscription",
-              couponID,
-            },
+            await tx.insert(LiteTable).values({
+              workspaceID,
+              id: Identifier.create("lite"),
+              userID: userID,
+            })
           })
         })
-      })
-    }
-    if (body.type === "customer.subscription.created") {
-      /*
-{
-  id: "evt_1Smq802SrMQ2Fneksse5FMNV",
-  object: "event",
-  api_version: "2025-07-30.basil",
-  created: 1767766916,
-  data: {
-    object: {
-      id: "sub_1Smq7x2SrMQ2Fnek8F1yf3ZD",
-      object: "subscription",
-      application: null,
-      application_fee_percent: null,
-      automatic_tax: {
-        disabled_reason: null,
-        enabled: false,
-        liability: null,
-      },
-      billing_cycle_anchor: 1770445200,
-      billing_cycle_anchor_config: null,
-      billing_mode: {
-        flexible: {
-          proration_discounts: "included",
-        },
-        type: "flexible",
-        updated_at: 1770445200,
-      },
-      billing_thresholds: null,
-      cancel_at: null,
-      cancel_at_period_end: false,
-      canceled_at: null,
-      cancellation_details: {
-        comment: null,
-        feedback: null,
-        reason: null,
-      },
-      collection_method: "charge_automatically",
-      created: 1770445200,
-      currency: "usd",
-      customer: "cus_TkKmZZvysJ2wej",
-      customer_account: null,
-      days_until_due: null,
-      default_payment_method: null,
-      default_source: "card_1Smq7u2SrMQ2FneknjyOa7sq",
-      default_tax_rates: [],
-      description: null,
-      discounts: [],
-      ended_at: null,
-      invoice_settings: {
-        account_tax_ids: null,
-        issuer: {
-          type: "self",
-        },
-      },
-      items: {
-        object: "list",
-        data: [
-          {
-            id: "si_TkKnBKXFX76t0O",
-            object: "subscription_item",
-            billing_thresholds: null,
-            created: 1770445200,
-            current_period_end: 1772864400,
-            current_period_start: 1770445200,
-            discounts: [],
-            metadata: {},
-            plan: {
-              id: "price_1SmfFG2SrMQ2FnekJuzwHMea",
-              object: "plan",
-              active: true,
-              amount: 20000,
-              amount_decimal: "20000",
-              billing_scheme: "per_unit",
-              created: 1767725082,
-              currency: "usd",
-              interval: "month",
-              interval_count: 1,
-              livemode: false,
-              metadata: {},
-              meter: null,
-              nickname: null,
-              product: "prod_Tk9LjWT1n0DgYm",
-              tiers_mode: null,
-              transform_usage: null,
-              trial_period_days: null,
-              usage_type: "licensed",
-            },
-            price: {
-              id: "price_1SmfFG2SrMQ2FnekJuzwHMea",
-              object: "price",
-              active: true,
-              billing_scheme: "per_unit",
-              created: 1767725082,
-              currency: "usd",
-              custom_unit_amount: null,
-              livemode: false,
-              lookup_key: null,
-              metadata: {},
-              nickname: null,
-              product: "prod_Tk9LjWT1n0DgYm",
-              recurring: {
-                interval: "month",
-                interval_count: 1,
-                meter: null,
-                trial_period_days: null,
-                usage_type: "licensed",
-              },
-              tax_behavior: "unspecified",
-              tiers_mode: null,
-              transform_quantity: null,
-              type: "recurring",
-              unit_amount: 20000,
-              unit_amount_decimal: "20000",
-            },
-            quantity: 1,
-            subscription: "sub_1Smq7x2SrMQ2Fnek8F1yf3ZD",
-            tax_rates: [],
-          },
-        ],
-        has_more: false,
-        total_count: 1,
-        url: "/v1/subscription_items?subscription=sub_1Smq7x2SrMQ2Fnek8F1yf3ZD",
-      },
-      latest_invoice: "in_1Smq7x2SrMQ2FnekSJesfPwE",
-      livemode: false,
-      metadata: {},
-      next_pending_invoice_item_invoice: null,
-      on_behalf_of: null,
-      pause_collection: null,
-      payment_settings: {
-        payment_method_options: null,
-        payment_method_types: null,
-        save_default_payment_method: "off",
-      },
-      pending_invoice_item_interval: null,
-      pending_setup_intent: null,
-      pending_update: null,
-      plan: {
-        id: "price_1SmfFG2SrMQ2FnekJuzwHMea",
-        object: "plan",
-        active: true,
-        amount: 20000,
-        amount_decimal: "20000",
-        billing_scheme: "per_unit",
-        created: 1767725082,
-        currency: "usd",
-        interval: "month",
-        interval_count: 1,
-        livemode: false,
-        metadata: {},
-        meter: null,
-        nickname: null,
-        product: "prod_Tk9LjWT1n0DgYm",
-        tiers_mode: null,
-        transform_usage: null,
-        trial_period_days: null,
-        usage_type: "licensed",
-      },
-      quantity: 1,
-      schedule: null,
-      start_date: 1770445200,
-      status: "active",
-      test_clock: "clock_1Smq6n2SrMQ2FnekQw4yt2PZ",
-      transfer_data: null,
-      trial_end: null,
-      trial_settings: {
-        end_behavior: {
-          missing_payment_method: "create_invoice",
-        },
-      },
-      trial_start: null,
-    },
-  },
-  livemode: false,
-  pending_webhooks: 0,
-  request: {
-    id: "req_6YO9stvB155WJD",
-    idempotency_key: "581ba059-6f86-49b2-9c49-0d8450255322",
-  },
-  type: "customer.subscription.created",
-}
-  */
+      }
     }
     if (body.type === "customer.subscription.updated" && body.data.object.status === "incomplete_expired") {
       const subscriptionID = body.data.object.id
       if (!subscriptionID) throw new Error("Subscription ID not found")
 
-      await Billing.unsubscribe({ subscriptionID })
+      const productID = body.data.object.items.data[0].price.product as string
+      if (productID === LiteData.productID()) {
+        await Billing.unsubscribeLite({ subscriptionID })
+      } else if (productID === BlackData.productID()) {
+        await Billing.unsubscribeBlack({ subscriptionID })
+      }
     }
     if (body.type === "customer.subscription.deleted") {
       const subscriptionID = body.data.object.id
       if (!subscriptionID) throw new Error("Subscription ID not found")
 
-      await Billing.unsubscribe({ subscriptionID })
+      const productID = body.data.object.items.data[0].price.product as string
+      if (productID === LiteData.productID()) {
+        await Billing.unsubscribeLite({ subscriptionID })
+      } else if (productID === BlackData.productID()) {
+        await Billing.unsubscribeBlack({ subscriptionID })
+      }
     }
     if (body.type === "invoice.payment_succeeded") {
       if (
@@ -430,6 +213,7 @@ export async function POST(input: APIEvent) {
           typeof subscriptionData.discounts[0] === "string"
             ? subscriptionData.discounts[0]
             : subscriptionData.discounts[0]?.coupon?.id
+        const productID = subscriptionData.items.data[0].price.product as string
 
         // get payment id from invoice
         const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
@@ -459,7 +243,7 @@ export async function POST(input: APIEvent) {
             invoiceID,
             customerID,
             enrichment: {
-              type: "subscription",
+              type: productID === LiteData.productID() ? "lite" : "subscription",
               couponID,
             },
           }),

+ 1 - 1
packages/console/app/src/routes/workspace/[id]/billing/black-section.tsx

@@ -90,7 +90,7 @@ const enroll = action(async (workspaceID: string) => {
   "use server"
   return json(
     await withActor(async () => {
-      await Billing.subscribe({ seats: 1 })
+      await Billing.subscribeBlack({ seats: 1 })
       return { error: undefined }
     }, workspaceID).catch((e) => ({ error: e.message as string })),
     { revalidate: [queryBillingInfo.key, querySubscription.key] },

+ 7 - 2
packages/console/app/src/routes/workspace/[id]/billing/index.tsx

@@ -3,7 +3,8 @@ import { BillingSection } from "./billing-section"
 import { ReloadSection } from "./reload-section"
 import { PaymentSection } from "./payment-section"
 import { BlackSection } from "./black-section"
-import { Show } from "solid-js"
+import { LiteSection } from "./lite-section"
+import { createMemo, Show } from "solid-js"
 import { createAsync, useParams } from "@solidjs/router"
 import { queryBillingInfo, querySessionInfo } from "../../common"
 
@@ -11,14 +12,18 @@ export default function () {
   const params = useParams()
   const sessionInfo = createAsync(() => querySessionInfo(params.id!))
   const billingInfo = createAsync(() => queryBillingInfo(params.id!))
+  const isBlack = createMemo(() => billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked)
 
   return (
     <div data-page="workspace-[id]">
       <div data-slot="sections">
         <Show when={sessionInfo()?.isAdmin}>
-          <Show when={billingInfo()?.subscriptionID || billingInfo()?.timeSubscriptionBooked}>
+          <Show when={isBlack()}>
             <BlackSection />
           </Show>
+          <Show when={!isBlack() && sessionInfo()?.isBeta}>
+            <LiteSection />
+          </Show>
           <BillingSection />
           <Show when={billingInfo()?.customerID}>
             <ReloadSection />

+ 160 - 0
packages/console/app/src/routes/workspace/[id]/billing/lite-section.module.css

@@ -0,0 +1,160 @@
+.root {
+  [data-slot="title-row"] {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    gap: var(--space-4);
+  }
+
+  [data-slot="usage"] {
+    display: flex;
+    gap: var(--space-6);
+    margin-top: var(--space-4);
+
+    @media (max-width: 40rem) {
+      flex-direction: column;
+      gap: var(--space-4);
+    }
+  }
+
+  [data-slot="usage-item"] {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    gap: var(--space-2);
+  }
+
+  [data-slot="usage-header"] {
+    display: flex;
+    justify-content: space-between;
+    align-items: baseline;
+  }
+
+  [data-slot="usage-label"] {
+    font-size: var(--font-size-md);
+    font-weight: 500;
+    color: var(--color-text);
+  }
+
+  [data-slot="usage-value"] {
+    font-size: var(--font-size-sm);
+    color: var(--color-text-muted);
+  }
+
+  [data-slot="progress"] {
+    height: 8px;
+    background-color: var(--color-bg-surface);
+    border-radius: var(--border-radius-sm);
+    overflow: hidden;
+  }
+
+  [data-slot="progress-bar"] {
+    height: 100%;
+    background-color: var(--color-accent);
+    border-radius: var(--border-radius-sm);
+    transition: width 0.3s ease;
+  }
+
+  [data-slot="reset-time"] {
+    font-size: var(--font-size-sm);
+    color: var(--color-text-muted);
+  }
+
+  [data-slot="setting-row"] {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: var(--space-3);
+    margin-top: var(--space-4);
+
+    p {
+      font-size: var(--font-size-sm);
+      line-height: 1.5;
+      color: var(--color-text-secondary);
+      margin: 0;
+    }
+  }
+
+  [data-slot="toggle-label"] {
+    position: relative;
+    display: inline-block;
+    width: 2.5rem;
+    height: 1.5rem;
+    cursor: pointer;
+    flex-shrink: 0;
+
+    input {
+      opacity: 0;
+      width: 0;
+      height: 0;
+    }
+
+    span {
+      position: absolute;
+      inset: 0;
+      background-color: #ccc;
+      border: 1px solid #bbb;
+      border-radius: 1.5rem;
+      transition: all 0.3s ease;
+      cursor: pointer;
+
+      &::before {
+        content: "";
+        position: absolute;
+        top: 50%;
+        left: 0.125rem;
+        width: 1.25rem;
+        height: 1.25rem;
+        background-color: white;
+        border: 1px solid #ddd;
+        border-radius: 50%;
+        transform: translateY(-50%);
+        transition: all 0.3s ease;
+      }
+    }
+
+    input:checked + span {
+      background-color: #21ad0e;
+      border-color: #148605;
+
+      &::before {
+        transform: translateX(1rem) translateY(-50%);
+      }
+    }
+
+    &:hover span {
+      box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
+    }
+
+    input:checked:hover + span {
+      box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.3);
+    }
+
+    &:has(input:disabled) {
+      cursor: not-allowed;
+    }
+
+    input:disabled + span {
+      opacity: 0.5;
+      cursor: not-allowed;
+    }
+  }
+
+  [data-slot="other-message"] {
+    font-size: var(--font-size-sm);
+    color: var(--color-text-muted);
+    line-height: 1.5;
+  }
+
+  [data-slot="promo-description"] {
+    font-size: var(--font-size-sm);
+    color: var(--color-text-secondary);
+    line-height: 1.5;
+    margin-top: var(--space-2);
+  }
+
+  [data-slot="subscribe-button"] {
+    align-self: flex-start;
+    margin-top: var(--space-4);
+  }
+}

+ 269 - 0
packages/console/app/src/routes/workspace/[id]/billing/lite-section.tsx

@@ -0,0 +1,269 @@
+import { action, useParams, useAction, useSubmission, json, query, createAsync } from "@solidjs/router"
+import { createStore } from "solid-js/store"
+import { Show } from "solid-js"
+import { Billing } from "@opencode-ai/console-core/billing.js"
+import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js"
+import { BillingTable, LiteTable } from "@opencode-ai/console-core/schema/billing.sql.js"
+import { Actor } from "@opencode-ai/console-core/actor.js"
+import { Subscription } from "@opencode-ai/console-core/subscription.js"
+import { LiteData } from "@opencode-ai/console-core/lite.js"
+import { withActor } from "~/context/auth.withActor"
+import { queryBillingInfo } from "../../common"
+import styles from "./lite-section.module.css"
+import { useI18n } from "~/context/i18n"
+
+const queryLiteSubscription = query(async (workspaceID: string) => {
+  "use server"
+  return withActor(async () => {
+    const row = await Database.use((tx) =>
+      tx
+        .select({
+          userID: LiteTable.userID,
+          rollingUsage: LiteTable.rollingUsage,
+          weeklyUsage: LiteTable.weeklyUsage,
+          monthlyUsage: LiteTable.monthlyUsage,
+          timeRollingUpdated: LiteTable.timeRollingUpdated,
+          timeWeeklyUpdated: LiteTable.timeWeeklyUpdated,
+          timeMonthlyUpdated: LiteTable.timeMonthlyUpdated,
+          timeCreated: LiteTable.timeCreated,
+          lite: BillingTable.lite,
+        })
+        .from(BillingTable)
+        .innerJoin(LiteTable, eq(LiteTable.workspaceID, BillingTable.workspaceID))
+        .where(and(eq(LiteTable.workspaceID, Actor.workspace()), isNull(LiteTable.timeDeleted)))
+        .then((r) => r[0]),
+    )
+    if (!row) return null
+
+    const limits = LiteData.getLimits()
+    const mine = row.userID === Actor.userID()
+
+    return {
+      mine,
+      useBalance: row.lite?.useBalance ?? false,
+      rollingUsage: Subscription.analyzeRollingUsage({
+        limit: limits.rollingLimit,
+        window: limits.rollingWindow,
+        usage: row.rollingUsage ?? 0,
+        timeUpdated: row.timeRollingUpdated ?? new Date(),
+      }),
+      weeklyUsage: Subscription.analyzeWeeklyUsage({
+        limit: limits.weeklyLimit,
+        usage: row.weeklyUsage ?? 0,
+        timeUpdated: row.timeWeeklyUpdated ?? new Date(),
+      }),
+      monthlyUsage: Subscription.analyzeMonthlyUsage({
+        limit: limits.monthlyLimit,
+        usage: row.monthlyUsage ?? 0,
+        timeUpdated: row.timeMonthlyUpdated ?? new Date(),
+        timeSubscribed: row.timeCreated,
+      }),
+    }
+  }, workspaceID)
+}, "lite.subscription.get")
+
+function formatResetTime(seconds: number, i18n: ReturnType<typeof useI18n>) {
+  const days = Math.floor(seconds / 86400)
+  if (days >= 1) {
+    const hours = Math.floor((seconds % 86400) / 3600)
+    return `${days} ${days === 1 ? i18n.t("workspace.lite.time.day") : i18n.t("workspace.lite.time.days")} ${hours} ${hours === 1 ? i18n.t("workspace.lite.time.hour") : i18n.t("workspace.lite.time.hours")}`
+  }
+  const hours = Math.floor(seconds / 3600)
+  const minutes = Math.floor((seconds % 3600) / 60)
+  if (hours >= 1)
+    return `${hours} ${hours === 1 ? i18n.t("workspace.lite.time.hour") : i18n.t("workspace.lite.time.hours")} ${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}`
+  if (minutes === 0) return i18n.t("workspace.lite.time.fewSeconds")
+  return `${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}`
+}
+
+const createLiteCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => {
+  "use server"
+  return json(
+    await withActor(
+      () =>
+        Billing.generateLiteCheckoutUrl({ successUrl, cancelUrl })
+          .then((data) => ({ error: undefined, data }))
+          .catch((e) => ({
+            error: e.message as string,
+            data: undefined,
+          })),
+      workspaceID,
+    ),
+    { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
+  )
+}, "liteCheckoutUrl")
+
+const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
+  "use server"
+  return json(
+    await withActor(
+      () =>
+        Billing.generateSessionUrl({ returnUrl })
+          .then((data) => ({ error: undefined, data }))
+          .catch((e) => ({
+            error: e.message as string,
+            data: undefined,
+          })),
+      workspaceID,
+    ),
+    { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
+  )
+}, "liteSessionUrl")
+
+const setLiteUseBalance = action(async (form: FormData) => {
+  "use server"
+  const workspaceID = form.get("workspaceID")?.toString()
+  if (!workspaceID) return { error: "Workspace ID is required" }
+  const useBalance = form.get("useBalance")?.toString() === "true"
+
+  return json(
+    await withActor(async () => {
+      await Database.use((tx) =>
+        tx
+          .update(BillingTable)
+          .set({
+            lite: useBalance ? { useBalance: true } : {},
+          })
+          .where(eq(BillingTable.workspaceID, workspaceID)),
+      )
+      return { error: undefined }
+    }, workspaceID).catch((e) => ({ error: e.message as string })),
+    { revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
+  )
+}, "setLiteUseBalance")
+
+export function LiteSection() {
+  const params = useParams()
+  const i18n = useI18n()
+  const lite = createAsync(() => queryLiteSubscription(params.id!))
+  const sessionAction = useAction(createSessionUrl)
+  const sessionSubmission = useSubmission(createSessionUrl)
+  const checkoutAction = useAction(createLiteCheckoutUrl)
+  const checkoutSubmission = useSubmission(createLiteCheckoutUrl)
+  const useBalanceSubmission = useSubmission(setLiteUseBalance)
+  const [store, setStore] = createStore({
+    redirecting: false,
+  })
+
+  async function onClickSession() {
+    const result = await sessionAction(params.id!, window.location.href)
+    if (result.data) {
+      setStore("redirecting", true)
+      window.location.href = result.data
+    }
+  }
+
+  async function onClickSubscribe() {
+    const result = await checkoutAction(params.id!, window.location.href, window.location.href)
+    if (result.data) {
+      setStore("redirecting", true)
+      window.location.href = result.data
+    }
+  }
+
+  return (
+    <>
+      <Show when={lite() && lite()!.mine && lite()!}>
+        {(sub) => (
+          <section class={styles.root}>
+            <div data-slot="section-title">
+              <h2>{i18n.t("workspace.lite.subscription.title")}</h2>
+              <div data-slot="title-row">
+                <p>{i18n.t("workspace.lite.subscription.message")}</p>
+                <button
+                  data-color="primary"
+                  disabled={sessionSubmission.pending || store.redirecting}
+                  onClick={onClickSession}
+                >
+                  {sessionSubmission.pending || store.redirecting
+                    ? i18n.t("workspace.lite.loading")
+                    : i18n.t("workspace.lite.subscription.manage")}
+                </button>
+              </div>
+            </div>
+            <div data-slot="usage">
+              <div data-slot="usage-item">
+                <div data-slot="usage-header">
+                  <span data-slot="usage-label">{i18n.t("workspace.lite.subscription.rollingUsage")}</span>
+                  <span data-slot="usage-value">{sub().rollingUsage.usagePercent}%</span>
+                </div>
+                <div data-slot="progress">
+                  <div data-slot="progress-bar" style={{ width: `${sub().rollingUsage.usagePercent}%` }} />
+                </div>
+                <span data-slot="reset-time">
+                  {i18n.t("workspace.lite.subscription.resetsIn")}{" "}
+                  {formatResetTime(sub().rollingUsage.resetInSec, i18n)}
+                </span>
+              </div>
+              <div data-slot="usage-item">
+                <div data-slot="usage-header">
+                  <span data-slot="usage-label">{i18n.t("workspace.lite.subscription.weeklyUsage")}</span>
+                  <span data-slot="usage-value">{sub().weeklyUsage.usagePercent}%</span>
+                </div>
+                <div data-slot="progress">
+                  <div data-slot="progress-bar" style={{ width: `${sub().weeklyUsage.usagePercent}%` }} />
+                </div>
+                <span data-slot="reset-time">
+                  {i18n.t("workspace.lite.subscription.resetsIn")} {formatResetTime(sub().weeklyUsage.resetInSec, i18n)}
+                </span>
+              </div>
+              <div data-slot="usage-item">
+                <div data-slot="usage-header">
+                  <span data-slot="usage-label">{i18n.t("workspace.lite.subscription.monthlyUsage")}</span>
+                  <span data-slot="usage-value">{sub().monthlyUsage.usagePercent}%</span>
+                </div>
+                <div data-slot="progress">
+                  <div data-slot="progress-bar" style={{ width: `${sub().monthlyUsage.usagePercent}%` }} />
+                </div>
+                <span data-slot="reset-time">
+                  {i18n.t("workspace.lite.subscription.resetsIn")}{" "}
+                  {formatResetTime(sub().monthlyUsage.resetInSec, i18n)}
+                </span>
+              </div>
+            </div>
+            <form action={setLiteUseBalance} method="post" data-slot="setting-row">
+              <p>{i18n.t("workspace.lite.subscription.useBalance")}</p>
+              <input type="hidden" name="workspaceID" value={params.id} />
+              <input type="hidden" name="useBalance" value={sub().useBalance ? "false" : "true"} />
+              <label data-slot="toggle-label">
+                <input
+                  type="checkbox"
+                  checked={sub().useBalance}
+                  disabled={useBalanceSubmission.pending}
+                  onChange={(e) => e.currentTarget.form?.requestSubmit()}
+                />
+                <span></span>
+              </label>
+            </form>
+          </section>
+        )}
+      </Show>
+      <Show when={lite() && !lite()!.mine}>
+        <section class={styles.root}>
+          <div data-slot="section-title">
+            <h2>{i18n.t("workspace.lite.other.title")}</h2>
+          </div>
+          <p data-slot="other-message">{i18n.t("workspace.lite.other.message")}</p>
+        </section>
+      </Show>
+      <Show when={lite() === null}>
+        <section class={styles.root}>
+          <div data-slot="section-title">
+            <h2>{i18n.t("workspace.lite.promo.title")}</h2>
+          </div>
+          <p data-slot="promo-description">{i18n.t("workspace.lite.promo.description")}</p>
+          <button
+            data-slot="subscribe-button"
+            data-color="primary"
+            disabled={checkoutSubmission.pending || store.redirecting}
+            onClick={onClickSubscribe}
+          >
+            {checkoutSubmission.pending || store.redirecting
+              ? i18n.t("workspace.lite.promo.subscribing")
+              : i18n.t("workspace.lite.promo.subscribe")}
+          </button>
+        </section>
+      </Show>
+    </>
+  )
+}

+ 49 - 13
packages/console/app/src/routes/workspace/[id]/graph-section.tsx

@@ -36,7 +36,7 @@ async function getCosts(workspaceID: string, year: number, month: number) {
           model: UsageTable.model,
           totalCost: sum(UsageTable.cost),
           keyId: UsageTable.keyID,
-          subscription: sql<boolean>`COALESCE(JSON_EXTRACT(${UsageTable.enrichment}, '$.plan') = 'sub', false)`,
+          plan: sql<string | null>`JSON_EXTRACT(${UsageTable.enrichment}, '$.plan')`,
         })
         .from(UsageTable)
         .where(
@@ -50,13 +50,13 @@ async function getCosts(workspaceID: string, year: number, month: number) {
           sql`DATE(${UsageTable.timeCreated})`,
           UsageTable.model,
           UsageTable.keyID,
-          sql`COALESCE(JSON_EXTRACT(${UsageTable.enrichment}, '$.plan') = 'sub', false)`,
+          sql`JSON_EXTRACT(${UsageTable.enrichment}, '$.plan')`,
         )
         .then((x) =>
           x.map((r) => ({
             ...r,
             totalCost: r.totalCost ? parseInt(r.totalCost) : 0,
-            subscription: Boolean(r.subscription),
+            plan: r.plan as "sub" | "lite" | "byok" | null,
           })),
         ),
     )
@@ -218,18 +218,21 @@ export function GraphSection() {
     const colorTextSecondary = styles.getPropertyValue("--color-text-secondary").trim()
     const colorBorder = styles.getPropertyValue("--color-border").trim()
     const subSuffix = ` (${i18n.t("workspace.cost.subscriptionShort")})`
+    const liteSuffix = ` (${i18n.t("workspace.cost.liteShort")})`
 
+    const dailyDataRegular = new Map<string, Map<string, number>>()
     const dailyDataSub = new Map<string, Map<string, number>>()
-    const dailyDataNonSub = new Map<string, Map<string, number>>()
+    const dailyDataLite = new Map<string, Map<string, number>>()
     for (const dateKey of dates) {
+      dailyDataRegular.set(dateKey, new Map())
       dailyDataSub.set(dateKey, new Map())
-      dailyDataNonSub.set(dateKey, new Map())
+      dailyDataLite.set(dateKey, new Map())
     }
 
     data.usage
       .filter((row) => (store.key ? row.keyId === store.key : true))
       .forEach((row) => {
-        const targetMap = row.subscription ? dailyDataSub : dailyDataNonSub
+        const targetMap = row.plan === "sub" ? dailyDataSub : row.plan === "lite" ? dailyDataLite : dailyDataRegular
         const dayMap = targetMap.get(row.date)
         if (!dayMap) return
         dayMap.set(row.model, (dayMap.get(row.model) ?? 0) + row.totalCost)
@@ -237,15 +240,15 @@ export function GraphSection() {
 
     const filteredModels = store.model === null ? getModels() : [store.model]
 
-    // Create datasets: non-subscription first, then subscription (with hatched pattern effect via opacity)
+    // Create datasets: regular first, then subscription, then lite (with visual distinction via opacity)
     const datasets = [
       ...filteredModels
-        .filter((model) => dates.some((date) => (dailyDataNonSub.get(date)?.get(model) || 0) > 0))
+        .filter((model) => dates.some((date) => (dailyDataRegular.get(date)?.get(model) || 0) > 0))
         .map((model) => {
           const color = getModelColor(model)
           return {
             label: model,
-            data: dates.map((date) => (dailyDataNonSub.get(date)?.get(model) || 0) / 100_000_000),
+            data: dates.map((date) => (dailyDataRegular.get(date)?.get(model) || 0) / 100_000_000),
             backgroundColor: color,
             hoverBackgroundColor: color,
             borderWidth: 0,
@@ -266,6 +269,21 @@ export function GraphSection() {
             stack: "subscription",
           }
         }),
+      ...filteredModels
+        .filter((model) => dates.some((date) => (dailyDataLite.get(date)?.get(model) || 0) > 0))
+        .map((model) => {
+          const color = getModelColor(model)
+          return {
+            label: `${model}${liteSuffix}`,
+            data: dates.map((date) => (dailyDataLite.get(date)?.get(model) || 0) / 100_000_000),
+            backgroundColor: addOpacityToColor(color, 0.35),
+            hoverBackgroundColor: addOpacityToColor(color, 0.55),
+            borderWidth: 1,
+            borderColor: addOpacityToColor(color, 0.7),
+            borderDash: [4, 2],
+            stack: "lite",
+          }
+        }),
     ]
 
     return {
@@ -347,9 +365,18 @@ export function GraphSection() {
                 const meta = chart.getDatasetMeta(i)
                 const label = dataset.label || ""
                 const isSub = label.endsWith(subSuffix)
-                const model = isSub ? label.slice(0, -subSuffix.length) : label
+                const isLite = label.endsWith(liteSuffix)
+                const model = isSub
+                  ? label.slice(0, -subSuffix.length)
+                  : isLite
+                    ? label.slice(0, -liteSuffix.length)
+                    : label
                 const baseColor = getModelColor(model)
-                const originalColor = isSub ? addOpacityToColor(baseColor, 0.5) : baseColor
+                const originalColor = isSub
+                  ? addOpacityToColor(baseColor, 0.5)
+                  : isLite
+                    ? addOpacityToColor(baseColor, 0.35)
+                    : baseColor
                 const color = i === legendItem.datasetIndex ? originalColor : addOpacityToColor(baseColor, 0.15)
                 meta.data.forEach((bar: any) => {
                   bar.options.backgroundColor = color
@@ -363,9 +390,18 @@ export function GraphSection() {
                 const meta = chart.getDatasetMeta(i)
                 const label = dataset.label || ""
                 const isSub = label.endsWith(subSuffix)
-                const model = isSub ? label.slice(0, -subSuffix.length) : label
+                const isLite = label.endsWith(liteSuffix)
+                const model = isSub
+                  ? label.slice(0, -subSuffix.length)
+                  : isLite
+                    ? label.slice(0, -liteSuffix.length)
+                    : label
                 const baseColor = getModelColor(model)
-                const color = isSub ? addOpacityToColor(baseColor, 0.5) : baseColor
+                const color = isSub
+                  ? addOpacityToColor(baseColor, 0.5)
+                  : isLite
+                    ? addOpacityToColor(baseColor, 0.35)
+                    : baseColor
                 meta.data.forEach((bar: any) => {
                   bar.options.backgroundColor = color
                 })

+ 18 - 18
packages/console/app/src/routes/workspace/[id]/usage-section.tsx

@@ -1,6 +1,6 @@
 import { Billing } from "@opencode-ai/console-core/billing.js"
 import { createAsync, query, useParams } from "@solidjs/router"
-import { createMemo, For, Show, createEffect, createSignal } from "solid-js"
+import { createMemo, For, Show, Switch, Match, createEffect, createSignal } from "solid-js"
 import { formatDateUTC, formatDateForTable } from "../common"
 import { withActor } from "~/context/auth.withActor"
 import { IconChevronLeft, IconChevronRight, IconBreakdown } from "~/component/icon"
@@ -175,23 +175,23 @@ export function UsageSection() {
                         </div>
                       </td>
                       <td data-slot="usage-cost">
-                        <Show
-                          when={usage.enrichment?.plan === "sub"}
-                          fallback={
-                            <Show
-                              when={usage.enrichment?.plan === "byok"}
-                              fallback={<>${((usage.cost ?? 0) / 100000000).toFixed(4)}</>}
-                            >
-                              {i18n.t("workspace.usage.byok", {
-                                amount: ((usage.cost ?? 0) / 100000000).toFixed(4),
-                              })}
-                            </Show>
-                          }
-                        >
-                          {i18n.t("workspace.usage.subscription", {
-                            amount: ((usage.cost ?? 0) / 100000000).toFixed(4),
-                          })}
-                        </Show>
+                        <Switch fallback={<>${((usage.cost ?? 0) / 100000000).toFixed(4)}</>}>
+                          <Match when={usage.enrichment?.plan === "sub"}>
+                            {i18n.t("workspace.usage.subscription", {
+                              amount: ((usage.cost ?? 0) / 100000000).toFixed(4),
+                            })}
+                          </Match>
+                          <Match when={usage.enrichment?.plan === "lite"}>
+                            {i18n.t("workspace.usage.lite", {
+                              amount: ((usage.cost ?? 0) / 100000000).toFixed(4),
+                            })}
+                          </Match>
+                          <Match when={usage.enrichment?.plan === "byok"}>
+                            {i18n.t("workspace.usage.byok", {
+                              amount: ((usage.cost ?? 0) / 100000000).toFixed(4),
+                            })}
+                          </Match>
+                        </Switch>
                       </td>
                       <td data-slot="usage-session">{usage.sessionID?.slice(-8) ?? "-"}</td>
                     </tr>

+ 2 - 0
packages/console/app/src/routes/workspace/common.tsx

@@ -115,6 +115,8 @@ export const queryBillingInfo = query(async (workspaceID: string) => {
       subscriptionPlan: billing.subscriptionPlan,
       timeSubscriptionBooked: billing.timeSubscriptionBooked,
       timeSubscriptionSelected: billing.timeSubscriptionSelected,
+      lite: billing.lite,
+      liteSubscriptionID: billing.liteSubscriptionID,
     }
   }, workspaceID)
 }, "billing.get")

+ 12 - 0
packages/console/app/src/routes/zen/lite/v1/messages.ts

@@ -0,0 +1,12 @@
+import type { APIEvent } from "@solidjs/start/server"
+import { handler } from "~/routes/zen/util/handler"
+
+export function POST(input: APIEvent) {
+  return handler(input, {
+    format: "anthropic",
+    modelList: "lite",
+    parseApiKey: (headers: Headers) => headers.get("x-api-key") ?? undefined,
+    parseModel: (url: string, body: any) => body.model,
+    parseIsStream: (url: string, body: any) => !!body.stream,
+  })
+}

+ 188 - 58
packages/console/app/src/routes/zen/util/handler.ts

@@ -1,9 +1,9 @@
 import type { APIEvent } from "@solidjs/start/server"
 import { and, Database, eq, isNull, lt, or, sql } from "@opencode-ai/console-core/drizzle/index.js"
 import { KeyTable } from "@opencode-ai/console-core/schema/key.sql.js"
-import { BillingTable, SubscriptionTable, UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js"
+import { BillingTable, LiteTable, SubscriptionTable, UsageTable } from "@opencode-ai/console-core/schema/billing.sql.js"
 import { centsToMicroCents } from "@opencode-ai/console-core/util/price.js"
-import { getWeekBounds } from "@opencode-ai/console-core/util/date.js"
+import { getMonthlyBounds, getWeekBounds } from "@opencode-ai/console-core/util/date.js"
 import { Identifier } from "@opencode-ai/console-core/identifier.js"
 import { Billing } from "@opencode-ai/console-core/billing.js"
 import { Actor } from "@opencode-ai/console-core/actor.js"
@@ -33,13 +33,14 @@ import { createRateLimiter } from "./rateLimiter"
 import { createDataDumper } from "./dataDumper"
 import { createTrialLimiter } from "./trialLimiter"
 import { createStickyTracker } from "./stickyProviderTracker"
+import { LiteData } from "@opencode-ai/console-core/lite.js"
 
 type ZenData = Awaited<ReturnType<typeof ZenData.list>>
 type RetryOptions = {
   excludeProviders: string[]
   retryCount: number
 }
-type BillingSource = "anonymous" | "free" | "byok" | "subscription" | "balance"
+type BillingSource = "anonymous" | "free" | "byok" | "subscription" | "lite" | "balance"
 
 export async function handler(
   input: APIEvent,
@@ -454,6 +455,7 @@ export async function handler(
             reloadTrigger: BillingTable.reloadTrigger,
             timeReloadLockedTill: BillingTable.timeReloadLockedTill,
             subscription: BillingTable.subscription,
+            lite: BillingTable.lite,
           },
           user: {
             id: UserTable.id,
@@ -461,13 +463,23 @@ export async function handler(
             monthlyUsage: UserTable.monthlyUsage,
             timeMonthlyUsageUpdated: UserTable.timeMonthlyUsageUpdated,
           },
-          subscription: {
+          black: {
             id: SubscriptionTable.id,
             rollingUsage: SubscriptionTable.rollingUsage,
             fixedUsage: SubscriptionTable.fixedUsage,
             timeRollingUpdated: SubscriptionTable.timeRollingUpdated,
             timeFixedUpdated: SubscriptionTable.timeFixedUpdated,
           },
+          lite: {
+            id: LiteTable.id,
+            timeCreated: LiteTable.timeCreated,
+            rollingUsage: LiteTable.rollingUsage,
+            weeklyUsage: LiteTable.weeklyUsage,
+            monthlyUsage: LiteTable.monthlyUsage,
+            timeRollingUpdated: LiteTable.timeRollingUpdated,
+            timeWeeklyUpdated: LiteTable.timeWeeklyUpdated,
+            timeMonthlyUpdated: LiteTable.timeMonthlyUpdated,
+          },
           provider: {
             credentials: ProviderTable.credentials,
           },
@@ -495,6 +507,14 @@ export async function handler(
             isNull(SubscriptionTable.timeDeleted),
           ),
         )
+        .leftJoin(
+          LiteTable,
+          and(
+            eq(LiteTable.workspaceID, KeyTable.workspaceID),
+            eq(LiteTable.userID, KeyTable.userID),
+            isNull(LiteTable.timeDeleted),
+          ),
+        )
         .where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
         .then((rows) => rows[0]),
     )
@@ -503,8 +523,19 @@ export async function handler(
     logger.metric({
       api_key: data.apiKey,
       workspace: data.workspaceID,
-      isSubscription: data.subscription ? true : false,
-      subscription: data.billing.subscription?.plan,
+      ...(() => {
+        if (data.billing.subscription)
+          return {
+            isSubscription: true,
+            subscription: data.billing.subscription.plan,
+          }
+        if (data.billing.lite)
+          return {
+            isSubscription: true,
+            subscription: "lite",
+          }
+        return {}
+      })(),
     })
 
     return {
@@ -512,7 +543,8 @@ export async function handler(
       workspaceID: data.workspaceID,
       billing: data.billing,
       user: data.user,
-      subscription: data.subscription,
+      black: data.black,
+      lite: data.lite,
       provider: data.provider,
       isFree: FREE_WORKSPACES.includes(data.workspaceID),
       isDisabled: !!data.timeDisabled,
@@ -525,21 +557,21 @@ export async function handler(
     if (authInfo.isFree) return "free"
     if (modelInfo.allowAnonymous) return "free"
 
-    // Validate subscription billing
-    if (authInfo.billing.subscription && authInfo.subscription) {
+    const formatRetryTime = (seconds: number) => {
+      const days = Math.floor(seconds / 86400)
+      if (days >= 1) return `${days} day${days > 1 ? "s" : ""}`
+      const hours = Math.floor(seconds / 3600)
+      const minutes = Math.ceil((seconds % 3600) / 60)
+      if (hours >= 1) return `${hours}hr ${minutes}min`
+      return `${minutes}min`
+    }
+
+    // Validate black subscription billing
+    if (authInfo.billing.subscription && authInfo.black) {
       try {
-        const sub = authInfo.subscription
+        const sub = authInfo.black
         const plan = authInfo.billing.subscription.plan
 
-        const formatRetryTime = (seconds: number) => {
-          const days = Math.floor(seconds / 86400)
-          if (days >= 1) return `${days} day${days > 1 ? "s" : ""}`
-          const hours = Math.floor(seconds / 3600)
-          const minutes = Math.ceil((seconds % 3600) / 60)
-          if (hours >= 1) return `${hours}hr ${minutes}min`
-          return `${minutes}min`
-        }
-
         // Check weekly limit
         if (sub.fixedUsage && sub.timeFixedUpdated) {
           const blackData = BlackData.getLimits({ plan })
@@ -577,6 +609,62 @@ export async function handler(
       }
     }
 
+    // Validate lite subscription billing
+    if (opts.modelList === "lite" && authInfo.billing.lite && authInfo.lite) {
+      try {
+        const sub = authInfo.lite
+        const liteData = LiteData.getLimits()
+
+        // Check weekly limit
+        if (sub.weeklyUsage && sub.timeWeeklyUpdated) {
+          const result = Subscription.analyzeWeeklyUsage({
+            limit: liteData.weeklyLimit,
+            usage: sub.weeklyUsage,
+            timeUpdated: sub.timeWeeklyUpdated,
+          })
+          if (result.status === "rate-limited")
+            throw new SubscriptionUsageLimitError(
+              `Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
+              result.resetInSec,
+            )
+        }
+
+        // Check monthly limit
+        if (sub.monthlyUsage && sub.timeMonthlyUpdated) {
+          const result = Subscription.analyzeMonthlyUsage({
+            limit: liteData.monthlyLimit,
+            usage: sub.monthlyUsage,
+            timeUpdated: sub.timeMonthlyUpdated,
+            timeSubscribed: sub.timeCreated,
+          })
+          if (result.status === "rate-limited")
+            throw new SubscriptionUsageLimitError(
+              `Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
+              result.resetInSec,
+            )
+        }
+
+        // Check rolling limit
+        if (sub.monthlyUsage && sub.timeMonthlyUpdated) {
+          const result = Subscription.analyzeRollingUsage({
+            limit: liteData.rollingLimit,
+            window: liteData.rollingWindow,
+            usage: sub.monthlyUsage,
+            timeUpdated: sub.timeMonthlyUpdated,
+          })
+          if (result.status === "rate-limited")
+            throw new SubscriptionUsageLimitError(
+              `Subscription quota exceeded. Retry in ${formatRetryTime(result.resetInSec)}.`,
+              result.resetInSec,
+            )
+        }
+
+        return "lite"
+      } catch (e) {
+        if (!authInfo.billing.lite.useBalance) throw e
+      }
+    }
+
     // Validate pay as you go billing
     const billing = authInfo.billing
     if (!billing.paymentMethodID)
@@ -743,6 +831,7 @@ export async function handler(
           enrichment: (() => {
             if (billingSource === "subscription") return { plan: "sub" }
             if (billingSource === "byok") return { plan: "byok" }
+            if (billingSource === "lite") return { plan: "lite" }
             return undefined
           })(),
         }),
@@ -750,74 +839,115 @@ export async function handler(
           .update(KeyTable)
           .set({ timeUsed: sql`now()` })
           .where(and(eq(KeyTable.workspaceID, authInfo.workspaceID), eq(KeyTable.id, authInfo.apiKeyId))),
-        ...(billingSource === "subscription"
-          ? (() => {
-              const plan = authInfo.billing.subscription!.plan
-              const black = BlackData.getLimits({ plan })
-              const week = getWeekBounds(new Date())
-              const rollingWindowSeconds = black.rollingWindow * 3600
-              return [
-                db
-                  .update(SubscriptionTable)
-                  .set({
-                    fixedUsage: sql`
+        ...(() => {
+          if (billingSource === "subscription") {
+            const plan = authInfo.billing.subscription!.plan
+            const black = BlackData.getLimits({ plan })
+            const week = getWeekBounds(new Date())
+            const rollingWindowSeconds = black.rollingWindow * 3600
+            return [
+              db
+                .update(SubscriptionTable)
+                .set({
+                  fixedUsage: sql`
               CASE
                 WHEN ${SubscriptionTable.timeFixedUpdated} >= ${week.start} THEN ${SubscriptionTable.fixedUsage} + ${cost}
                 ELSE ${cost}
               END
             `,
-                    timeFixedUpdated: sql`now()`,
-                    rollingUsage: sql`
+                  timeFixedUpdated: sql`now()`,
+                  rollingUsage: sql`
               CASE
                 WHEN UNIX_TIMESTAMP(${SubscriptionTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${SubscriptionTable.rollingUsage} + ${cost}
                 ELSE ${cost}
               END
             `,
-                    timeRollingUpdated: sql`
+                  timeRollingUpdated: sql`
               CASE
                 WHEN UNIX_TIMESTAMP(${SubscriptionTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${SubscriptionTable.timeRollingUpdated}
                 ELSE now()
               END
             `,
-                  })
-                  .where(
-                    and(
-                      eq(SubscriptionTable.workspaceID, authInfo.workspaceID),
-                      eq(SubscriptionTable.userID, authInfo.user.id),
-                    ),
+                })
+                .where(
+                  and(
+                    eq(SubscriptionTable.workspaceID, authInfo.workspaceID),
+                    eq(SubscriptionTable.userID, authInfo.user.id),
                   ),
-              ]
-            })()
-          : [
+                ),
+            ]
+          }
+          if (billingSource === "lite") {
+            const lite = LiteData.getLimits()
+            const week = getWeekBounds(new Date())
+            const month = getMonthlyBounds(new Date(), authInfo.lite!.timeCreated)
+            const rollingWindowSeconds = lite.rollingWindow * 3600
+            return [
               db
-                .update(BillingTable)
+                .update(LiteTable)
                 .set({
-                  balance:
-                    billingSource === "free" || billingSource === "byok"
-                      ? sql`${BillingTable.balance} - ${0}`
-                      : sql`${BillingTable.balance} - ${cost}`,
                   monthlyUsage: sql`
               CASE
-                WHEN MONTH(${BillingTable.timeMonthlyUsageUpdated}) = MONTH(now()) AND YEAR(${BillingTable.timeMonthlyUsageUpdated}) = YEAR(now()) THEN ${BillingTable.monthlyUsage} + ${cost}
+                WHEN ${LiteTable.timeMonthlyUpdated} >= ${month.start} THEN ${LiteTable.monthlyUsage} + ${cost}
                 ELSE ${cost}
               END
             `,
-                  timeMonthlyUsageUpdated: sql`now()`,
+                  timeMonthlyUpdated: sql`now()`,
+                  weeklyUsage: sql`
+              CASE
+                WHEN ${LiteTable.timeWeeklyUpdated} >= ${week.start} THEN ${LiteTable.weeklyUsage} + ${cost}
+                ELSE ${cost}
+              END
+            `,
+                  timeWeeklyUpdated: sql`now()`,
+                  rollingUsage: sql`
+              CASE
+                WHEN UNIX_TIMESTAMP(${LiteTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${LiteTable.rollingUsage} + ${cost}
+                ELSE ${cost}
+              END
+            `,
+                  timeRollingUpdated: sql`
+              CASE
+                WHEN UNIX_TIMESTAMP(${LiteTable.timeRollingUpdated}) >= UNIX_TIMESTAMP(now()) - ${rollingWindowSeconds} THEN ${LiteTable.timeRollingUpdated}
+                ELSE now()
+              END
+            `,
                 })
-                .where(eq(BillingTable.workspaceID, authInfo.workspaceID)),
-              db
-                .update(UserTable)
-                .set({
-                  monthlyUsage: sql`
+                .where(and(eq(LiteTable.workspaceID, authInfo.workspaceID), eq(LiteTable.userID, authInfo.user.id))),
+            ]
+          }
+
+          return [
+            db
+              .update(BillingTable)
+              .set({
+                balance:
+                  billingSource === "free" || billingSource === "byok"
+                    ? sql`${BillingTable.balance} - ${0}`
+                    : sql`${BillingTable.balance} - ${cost}`,
+                monthlyUsage: sql`
+              CASE
+                WHEN MONTH(${BillingTable.timeMonthlyUsageUpdated}) = MONTH(now()) AND YEAR(${BillingTable.timeMonthlyUsageUpdated}) = YEAR(now()) THEN ${BillingTable.monthlyUsage} + ${cost}
+                ELSE ${cost}
+              END
+            `,
+                timeMonthlyUsageUpdated: sql`now()`,
+              })
+              .where(eq(BillingTable.workspaceID, authInfo.workspaceID)),
+            db
+              .update(UserTable)
+              .set({
+                monthlyUsage: sql`
               CASE
                 WHEN MONTH(${UserTable.timeMonthlyUsageUpdated}) = MONTH(now()) AND YEAR(${UserTable.timeMonthlyUsageUpdated}) = YEAR(now()) THEN ${UserTable.monthlyUsage} + ${cost}
                 ELSE ${cost}
               END
             `,
-                  timeMonthlyUsageUpdated: sql`now()`,
-                })
-                .where(and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id))),
-            ]),
+                timeMonthlyUsageUpdated: sql`now()`,
+              })
+              .where(and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id))),
+          ]
+        })(),
       ]),
     )
 

+ 19 - 0
packages/console/core/migrations/20260224043338_nifty_starjammers/migration.sql

@@ -0,0 +1,19 @@
+CREATE TABLE `lite` (
+	`id` varchar(30) NOT NULL,
+	`workspace_id` varchar(30) NOT NULL,
+	`time_created` timestamp(3) NOT NULL DEFAULT (now()),
+	`time_updated` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+	`time_deleted` timestamp(3),
+	`user_id` varchar(30) NOT NULL,
+	`rolling_usage` bigint,
+	`weekly_usage` bigint,
+	`monthly_usage` bigint,
+	`time_rolling_updated` timestamp(3),
+	`time_weekly_updated` timestamp(3),
+	`time_monthly_updated` timestamp(3),
+	CONSTRAINT `PRIMARY` PRIMARY KEY(`workspace_id`,`id`),
+	CONSTRAINT `workspace_user_id` UNIQUE INDEX(`workspace_id`,`user_id`)
+);
+--> statement-breakpoint
+ALTER TABLE `billing` ADD `lite_subscription_id` varchar(28);--> statement-breakpoint
+ALTER TABLE `billing` ADD `lite` json;

+ 2505 - 0
packages/console/core/migrations/20260224043338_nifty_starjammers/snapshot.json

@@ -0,0 +1,2505 @@
+{
+  "version": "6",
+  "dialect": "mysql",
+  "id": "5e506dec-61e7-4726-81d1-afa4ffbc61ed",
+  "prevIds": [
+    "4bf45b3f-3edd-4db7-94d5-097aa55ca5f7"
+  ],
+  "ddl": [
+    {
+      "name": "account",
+      "entityType": "tables"
+    },
+    {
+      "name": "auth",
+      "entityType": "tables"
+    },
+    {
+      "name": "benchmark",
+      "entityType": "tables"
+    },
+    {
+      "name": "billing",
+      "entityType": "tables"
+    },
+    {
+      "name": "lite",
+      "entityType": "tables"
+    },
+    {
+      "name": "payment",
+      "entityType": "tables"
+    },
+    {
+      "name": "subscription",
+      "entityType": "tables"
+    },
+    {
+      "name": "usage",
+      "entityType": "tables"
+    },
+    {
+      "name": "ip_rate_limit",
+      "entityType": "tables"
+    },
+    {
+      "name": "ip",
+      "entityType": "tables"
+    },
+    {
+      "name": "key",
+      "entityType": "tables"
+    },
+    {
+      "name": "model",
+      "entityType": "tables"
+    },
+    {
+      "name": "provider",
+      "entityType": "tables"
+    },
+    {
+      "name": "user",
+      "entityType": "tables"
+    },
+    {
+      "name": "workspace",
+      "entityType": "tables"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "account"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "account"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "account"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "account"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "enum('email','github','google')",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "provider",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "subject",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "account_id",
+      "entityType": "columns",
+      "table": "auth"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "varchar(64)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "model",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "varchar(64)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "agent",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "mediumtext",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "result",
+      "entityType": "columns",
+      "table": "benchmark"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "customer_id",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "payment_method_id",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(32)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "payment_method_type",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(4)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "payment_method_last4",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "bigint",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "balance",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "monthly_limit",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "monthly_usage",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_monthly_usage_updated",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "boolean",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "reload",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "reload_trigger",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "reload_amount",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "reload_error",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_reload_error",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_reload_locked_till",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "json",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "subscription",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(28)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "subscription_id",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "enum('20','100','200')",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "subscription_plan",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_subscription_booked",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_subscription_selected",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(28)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "lite_subscription_id",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "json",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "lite",
+      "entityType": "columns",
+      "table": "billing"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "user_id",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "rolling_usage",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "weekly_usage",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "monthly_usage",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_rolling_updated",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_weekly_updated",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_monthly_updated",
+      "entityType": "columns",
+      "table": "lite"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "customer_id",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "invoice_id",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "payment_id",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "bigint",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "amount",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_refunded",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "json",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "enrichment",
+      "entityType": "columns",
+      "table": "payment"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "user_id",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "rolling_usage",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "fixed_usage",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_rolling_updated",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_fixed_updated",
+      "entityType": "columns",
+      "table": "subscription"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "model",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "provider",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "int",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "input_tokens",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "int",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "output_tokens",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "reasoning_tokens",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "cache_read_tokens",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "cache_write_5m_tokens",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "cache_write_1h_tokens",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "bigint",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "cost",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "key_id",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "session_id",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "json",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "enrichment",
+      "entityType": "columns",
+      "table": "usage"
+    },
+    {
+      "type": "varchar(45)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "ip",
+      "entityType": "columns",
+      "table": "ip_rate_limit"
+    },
+    {
+      "type": "varchar(10)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "interval",
+      "entityType": "columns",
+      "table": "ip_rate_limit"
+    },
+    {
+      "type": "int",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "count",
+      "entityType": "columns",
+      "table": "ip_rate_limit"
+    },
+    {
+      "type": "varchar(45)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "ip",
+      "entityType": "columns",
+      "table": "ip"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "ip"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "ip"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "ip"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "usage",
+      "entityType": "columns",
+      "table": "ip"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "name",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "key",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "user_id",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_used",
+      "entityType": "columns",
+      "table": "key"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "model"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "model"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "model"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "model"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "model"
+    },
+    {
+      "type": "varchar(64)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "model",
+      "entityType": "columns",
+      "table": "model"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "varchar(64)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "provider",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "text",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "credentials",
+      "entityType": "columns",
+      "table": "provider"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "workspace_id",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "account_id",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "email",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "name",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_seen",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "color",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "enum('admin','member')",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "role",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "int",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "monthly_limit",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "bigint",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "monthly_usage",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_monthly_usage_updated",
+      "entityType": "columns",
+      "table": "user"
+    },
+    {
+      "type": "varchar(30)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "id",
+      "entityType": "columns",
+      "table": "workspace"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "slug",
+      "entityType": "columns",
+      "table": "workspace"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "name",
+      "entityType": "columns",
+      "table": "workspace"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(now())",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_created",
+      "entityType": "columns",
+      "table": "workspace"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": "(CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3))",
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_updated",
+      "entityType": "columns",
+      "table": "workspace"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_deleted",
+      "entityType": "columns",
+      "table": "workspace"
+    },
+    {
+      "columns": [
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "account",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "auth",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "benchmark",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "billing",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "lite",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "payment",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "subscription",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "usage",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "ip",
+        "interval"
+      ],
+      "name": "PRIMARY",
+      "table": "ip_rate_limit",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "ip"
+      ],
+      "name": "PRIMARY",
+      "table": "ip",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "key",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "model",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "provider",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "workspace_id",
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "user",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        "id"
+      ],
+      "name": "PRIMARY",
+      "table": "workspace",
+      "entityType": "pks"
+    },
+    {
+      "columns": [
+        {
+          "value": "provider",
+          "isExpression": false
+        },
+        {
+          "value": "subject",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "provider",
+      "entityType": "indexes",
+      "table": "auth"
+    },
+    {
+      "columns": [
+        {
+          "value": "account_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": false,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "account_id",
+      "entityType": "indexes",
+      "table": "auth"
+    },
+    {
+      "columns": [
+        {
+          "value": "time_created",
+          "isExpression": false
+        }
+      ],
+      "isUnique": false,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "time_created",
+      "entityType": "indexes",
+      "table": "benchmark"
+    },
+    {
+      "columns": [
+        {
+          "value": "customer_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "global_customer_id",
+      "entityType": "indexes",
+      "table": "billing"
+    },
+    {
+      "columns": [
+        {
+          "value": "subscription_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "global_subscription_id",
+      "entityType": "indexes",
+      "table": "billing"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "user_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "workspace_user_id",
+      "entityType": "indexes",
+      "table": "lite"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "user_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "workspace_user_id",
+      "entityType": "indexes",
+      "table": "subscription"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "time_created",
+          "isExpression": false
+        }
+      ],
+      "isUnique": false,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "usage_time_created",
+      "entityType": "indexes",
+      "table": "usage"
+    },
+    {
+      "columns": [
+        {
+          "value": "key",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "global_key",
+      "entityType": "indexes",
+      "table": "key"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "model",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "model_workspace_model",
+      "entityType": "indexes",
+      "table": "model"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "provider",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "workspace_provider",
+      "entityType": "indexes",
+      "table": "provider"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "account_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "user_account_id",
+      "entityType": "indexes",
+      "table": "user"
+    },
+    {
+      "columns": [
+        {
+          "value": "workspace_id",
+          "isExpression": false
+        },
+        {
+          "value": "email",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "user_email",
+      "entityType": "indexes",
+      "table": "user"
+    },
+    {
+      "columns": [
+        {
+          "value": "account_id",
+          "isExpression": false
+        }
+      ],
+      "isUnique": false,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "global_account_id",
+      "entityType": "indexes",
+      "table": "user"
+    },
+    {
+      "columns": [
+        {
+          "value": "email",
+          "isExpression": false
+        }
+      ],
+      "isUnique": false,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "global_email",
+      "entityType": "indexes",
+      "table": "user"
+    },
+    {
+      "columns": [
+        {
+          "value": "slug",
+          "isExpression": false
+        }
+      ],
+      "isUnique": true,
+      "using": null,
+      "algorithm": null,
+      "lock": null,
+      "nameExplicit": true,
+      "name": "slug",
+      "entityType": "indexes",
+      "table": "workspace"
+    }
+  ],
+  "renames": []
+}

+ 3 - 3
packages/console/core/script/black-select-workspaces.ts

@@ -1,10 +1,10 @@
 import { Database, eq, and, sql, inArray, isNull, count } from "../src/drizzle/index.js"
-import { BillingTable, SubscriptionPlan } from "../src/schema/billing.sql.js"
+import { BillingTable, BlackPlans } from "../src/schema/billing.sql.js"
 import { UserTable } from "../src/schema/user.sql.js"
 import { AuthTable } from "../src/schema/auth.sql.js"
 
-const plan = process.argv[2] as (typeof SubscriptionPlan)[number]
-if (!SubscriptionPlan.includes(plan)) {
+const plan = process.argv[2] as (typeof BlackPlans)[number]
+if (!BlackPlans.includes(plan)) {
   console.error("Usage: bun foo.ts <count>")
   process.exit(1)
 }

+ 2 - 8
packages/console/core/script/lookup-user.ts

@@ -1,13 +1,7 @@
 import { Database, and, eq, sql } from "../src/drizzle/index.js"
 import { AuthTable } from "../src/schema/auth.sql.js"
 import { UserTable } from "../src/schema/user.sql.js"
-import {
-  BillingTable,
-  PaymentTable,
-  SubscriptionTable,
-  SubscriptionPlan,
-  UsageTable,
-} from "../src/schema/billing.sql.js"
+import { BillingTable, PaymentTable, SubscriptionTable, BlackPlans, UsageTable } from "../src/schema/billing.sql.js"
 import { WorkspaceTable } from "../src/schema/workspace.sql.js"
 import { BlackData } from "../src/black.js"
 import { centsToMicroCents } from "../src/util/price.js"
@@ -235,7 +229,7 @@ function formatRetryTime(seconds: number) {
 
 function getSubscriptionStatus(row: {
   subscription: {
-    plan: (typeof SubscriptionPlan)[number]
+    plan: (typeof BlackPlans)[number]
   } | null
   timeSubscriptionCreated: Date | null
   fixedUsage: number | null

+ 79 - 3
packages/console/core/src/billing.ts

@@ -1,6 +1,6 @@
 import { Stripe } from "stripe"
 import { Database, eq, sql } from "./drizzle"
-import { BillingTable, PaymentTable, SubscriptionTable, UsageTable } from "./schema/billing.sql"
+import { BillingTable, LiteTable, PaymentTable, SubscriptionTable, UsageTable } from "./schema/billing.sql"
 import { Actor } from "./actor"
 import { fn } from "./util/fn"
 import { z } from "zod"
@@ -9,6 +9,7 @@ import { Identifier } from "./identifier"
 import { centsToMicroCents } from "./util/price"
 import { User } from "./user"
 import { BlackData } from "./black"
+import { LiteData } from "./lite"
 
 export namespace Billing {
   export const ITEM_CREDIT_NAME = "opencode credits"
@@ -233,6 +234,56 @@ export namespace Billing {
     },
   )
 
+  export const generateLiteCheckoutUrl = fn(
+    z.object({
+      successUrl: z.string(),
+      cancelUrl: z.string(),
+    }),
+    async (input) => {
+      const user = Actor.assert("user")
+      const { successUrl, cancelUrl } = input
+
+      const email = await User.getAuthEmail(user.properties.userID)
+      const billing = await Billing.get()
+
+      if (billing.subscriptionID) throw new Error("Already subscribed to Black")
+      if (billing.liteSubscriptionID) throw new Error("Already subscribed to Lite")
+
+      const session = await Billing.stripe().checkout.sessions.create({
+        mode: "subscription",
+        billing_address_collection: "required",
+        line_items: [{ price: LiteData.priceID(), quantity: 1 }],
+        ...(billing.customerID
+          ? {
+              customer: billing.customerID,
+              customer_update: {
+                name: "auto",
+                address: "auto",
+              },
+            }
+          : {
+              customer_email: email!,
+            }),
+        currency: "usd",
+        payment_method_types: ["card"],
+        tax_id_collection: {
+          enabled: true,
+        },
+        success_url: successUrl,
+        cancel_url: cancelUrl,
+        subscription_data: {
+          metadata: {
+            workspaceID: Actor.workspace(),
+            userID: user.properties.userID,
+            type: "lite",
+          },
+        },
+      })
+
+      return session.url
+    },
+  )
+
   export const generateSessionUrl = fn(
     z.object({
       returnUrl: z.string(),
@@ -271,7 +322,7 @@ export namespace Billing {
     },
   )
 
-  export const subscribe = fn(
+  export const subscribeBlack = fn(
     z.object({
       seats: z.number(),
       coupon: z.string().optional(),
@@ -336,7 +387,7 @@ export namespace Billing {
     },
   )
 
-  export const unsubscribe = fn(
+  export const unsubscribeBlack = fn(
     z.object({
       subscriptionID: z.string(),
     }),
@@ -360,4 +411,29 @@ export namespace Billing {
       })
     },
   )
+
+  export const unsubscribeLite = fn(
+    z.object({
+      subscriptionID: z.string(),
+    }),
+    async ({ subscriptionID }) => {
+      const workspaceID = await Database.use((tx) =>
+        tx
+          .select({ workspaceID: BillingTable.workspaceID })
+          .from(BillingTable)
+          .where(eq(BillingTable.liteSubscriptionID, subscriptionID))
+          .then((rows) => rows[0]?.workspaceID),
+      )
+      if (!workspaceID) throw new Error("Workspace ID not found for subscription")
+
+      await Database.transaction(async (tx) => {
+        await tx
+          .update(BillingTable)
+          .set({ liteSubscriptionID: null, lite: null })
+          .where(eq(BillingTable.workspaceID, workspaceID))
+
+        await tx.delete(LiteTable).where(eq(LiteTable.workspaceID, workspaceID))
+      })
+    },
+  )
 }

+ 5 - 3
packages/console/core/src/black.ts

@@ -1,7 +1,7 @@
 import { z } from "zod"
 import { fn } from "./util/fn"
 import { Resource } from "@opencode-ai/console-resource"
-import { SubscriptionPlan } from "./schema/billing.sql"
+import { BlackPlans } from "./schema/billing.sql"
 
 export namespace BlackData {
   const Schema = z.object({
@@ -28,7 +28,7 @@ export namespace BlackData {
 
   export const getLimits = fn(
     z.object({
-      plan: z.enum(SubscriptionPlan),
+      plan: z.enum(BlackPlans),
     }),
     ({ plan }) => {
       const json = JSON.parse(Resource.ZEN_BLACK_LIMITS.value)
@@ -36,9 +36,11 @@ export namespace BlackData {
     },
   )
 
+  export const productID = fn(z.void(), () => Resource.ZEN_BLACK_PRICE.product)
+
   export const planToPriceID = fn(
     z.object({
-      plan: z.enum(SubscriptionPlan),
+      plan: z.enum(BlackPlans),
     }),
     ({ plan }) => {
       if (plan === "200") return Resource.ZEN_BLACK_PRICE.plan200

+ 1 - 0
packages/console/core/src/identifier.ts

@@ -8,6 +8,7 @@ export namespace Identifier {
     benchmark: "ben",
     billing: "bil",
     key: "key",
+    lite: "lit",
     model: "mod",
     payment: "pay",
     provider: "prv",

+ 5 - 8
packages/console/core/src/lite.ts

@@ -4,9 +4,10 @@ import { Resource } from "@opencode-ai/console-resource"
 
 export namespace LiteData {
   const Schema = z.object({
-    fixedLimit: z.number().int(),
     rollingLimit: z.number().int(),
     rollingWindow: z.number().int(),
+    weeklyLimit: z.number().int(),
+    monthlyLimit: z.number().int(),
   })
 
   export const validate = fn(Schema, (input) => {
@@ -18,11 +19,7 @@ export namespace LiteData {
     return Schema.parse(json)
   })
 
-  export const planToPriceID = fn(z.void(), () => {
-    return Resource.ZEN_LITE_PRICE.price
-  })
-
-  export const priceIDToPlan = fn(z.void(), () => {
-    return "lite"
-  })
+  export const productID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.product)
+  export const priceID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.price)
+  export const planName = fn(z.void(), () => "lite")
 }

+ 23 - 3
packages/console/core/src/schema/billing.sql.ts

@@ -2,7 +2,7 @@ import { bigint, boolean, index, int, json, mysqlEnum, mysqlTable, uniqueIndex,
 import { timestamps, ulid, utc, workspaceColumns } from "../drizzle/types"
 import { workspaceIndexes } from "./workspace.sql"
 
-export const SubscriptionPlan = ["20", "100", "200"] as const
+export const BlackPlans = ["20", "100", "200"] as const
 export const BillingTable = mysqlTable(
   "billing",
   {
@@ -25,14 +25,18 @@ export const BillingTable = mysqlTable(
     subscription: json("subscription").$type<{
       status: "subscribed"
       seats: number
-      plan: "20" | "100" | "200"
+      plan: (typeof BlackPlans)[number]
       useBalance?: boolean
       coupon?: string
     }>(),
     subscriptionID: varchar("subscription_id", { length: 28 }),
-    subscriptionPlan: mysqlEnum("subscription_plan", SubscriptionPlan),
+    subscriptionPlan: mysqlEnum("subscription_plan", BlackPlans),
     timeSubscriptionBooked: utc("time_subscription_booked"),
     timeSubscriptionSelected: utc("time_subscription_selected"),
+    liteSubscriptionID: varchar("lite_subscription_id", { length: 28 }),
+    lite: json("lite").$type<{
+      useBalance?: boolean
+    }>(),
   },
   (table) => [
     ...workspaceIndexes(table),
@@ -55,6 +59,22 @@ export const SubscriptionTable = mysqlTable(
   (table) => [...workspaceIndexes(table), uniqueIndex("workspace_user_id").on(table.workspaceID, table.userID)],
 )
 
+export const LiteTable = mysqlTable(
+  "lite",
+  {
+    ...workspaceColumns,
+    ...timestamps,
+    userID: ulid("user_id").notNull(),
+    rollingUsage: bigint("rolling_usage", { mode: "number" }),
+    weeklyUsage: bigint("weekly_usage", { mode: "number" }),
+    monthlyUsage: bigint("monthly_usage", { mode: "number" }),
+    timeRollingUpdated: utc("time_rolling_updated"),
+    timeWeeklyUpdated: utc("time_weekly_updated"),
+    timeMonthlyUpdated: utc("time_monthly_updated"),
+  },
+  (table) => [...workspaceIndexes(table), uniqueIndex("workspace_user_id").on(table.workspaceID, table.userID)],
+)
+
 export const PaymentTable = mysqlTable(
   "payment",
   {

+ 37 - 3
packages/console/core/src/subscription.ts

@@ -1,7 +1,7 @@
 import { z } from "zod"
 import { fn } from "./util/fn"
 import { centsToMicroCents } from "./util/price"
-import { getWeekBounds } from "./util/date"
+import { getWeekBounds, getMonthlyBounds } from "./util/date"
 
 export namespace Subscription {
   export const analyzeRollingUsage = fn(
@@ -29,7 +29,7 @@ export namespace Subscription {
         return {
           status: "ok" as const,
           resetInSec: Math.ceil((windowEnd.getTime() - now.getTime()) / 1000),
-          usagePercent: Math.ceil(Math.min(100, (usage / rollingLimitInMicroCents) * 100)),
+          usagePercent: Math.floor(Math.min(100, (usage / rollingLimitInMicroCents) * 100)),
         }
       }
       return {
@@ -61,7 +61,7 @@ export namespace Subscription {
         return {
           status: "ok" as const,
           resetInSec: Math.ceil((week.end.getTime() - now.getTime()) / 1000),
-          usagePercent: Math.ceil(Math.min(100, (usage / fixedLimitInMicroCents) * 100)),
+          usagePercent: Math.floor(Math.min(100, (usage / fixedLimitInMicroCents) * 100)),
         }
       }
 
@@ -72,4 +72,38 @@ export namespace Subscription {
       }
     },
   )
+
+  export const analyzeMonthlyUsage = fn(
+    z.object({
+      limit: z.number().int(),
+      usage: z.number().int(),
+      timeUpdated: z.date(),
+      timeSubscribed: z.date(),
+    }),
+    ({ limit, usage, timeUpdated, timeSubscribed }) => {
+      const now = new Date()
+      const month = getMonthlyBounds(now, timeSubscribed)
+      const fixedLimitInMicroCents = centsToMicroCents(limit * 100)
+      if (timeUpdated < month.start) {
+        return {
+          status: "ok" as const,
+          resetInSec: Math.ceil((month.end.getTime() - now.getTime()) / 1000),
+          usagePercent: 0,
+        }
+      }
+      if (usage < fixedLimitInMicroCents) {
+        return {
+          status: "ok" as const,
+          resetInSec: Math.ceil((month.end.getTime() - now.getTime()) / 1000),
+          usagePercent: Math.floor(Math.min(100, (usage / fixedLimitInMicroCents) * 100)),
+        }
+      }
+
+      return {
+        status: "rate-limited" as const,
+        resetInSec: Math.ceil((month.end.getTime() - now.getTime()) / 1000),
+        usagePercent: 100,
+      }
+    },
+  )
 }

+ 0 - 20
packages/console/core/src/util/date.test.ts

@@ -1,20 +0,0 @@
-import { describe, expect, test } from "bun:test"
-import { getWeekBounds } from "./date"
-
-describe("util.date.getWeekBounds", () => {
-  test("returns a Monday-based week for Sunday dates", () => {
-    const date = new Date("2026-01-18T12:00:00Z")
-    const bounds = getWeekBounds(date)
-
-    expect(bounds.start.toISOString()).toBe("2026-01-12T00:00:00.000Z")
-    expect(bounds.end.toISOString()).toBe("2026-01-19T00:00:00.000Z")
-  })
-
-  test("returns a seven day window", () => {
-    const date = new Date("2026-01-14T12:00:00Z")
-    const bounds = getWeekBounds(date)
-
-    const span = bounds.end.getTime() - bounds.start.getTime()
-    expect(span).toBe(7 * 24 * 60 * 60 * 1000)
-  })
-})

+ 29 - 0
packages/console/core/src/util/date.ts

@@ -7,3 +7,32 @@ export function getWeekBounds(date: Date) {
   end.setUTCDate(start.getUTCDate() + 7)
   return { start, end }
 }
+
+export function getMonthlyBounds(now: Date, subscribed: Date) {
+  const day = subscribed.getUTCDate()
+  const hh = subscribed.getUTCHours()
+  const mm = subscribed.getUTCMinutes()
+  const ss = subscribed.getUTCSeconds()
+  const ms = subscribed.getUTCMilliseconds()
+
+  function anchor(year: number, month: number) {
+    const max = new Date(Date.UTC(year, month + 1, 0)).getUTCDate()
+    return new Date(Date.UTC(year, month, Math.min(day, max), hh, mm, ss, ms))
+  }
+
+  function shift(year: number, month: number, delta: number) {
+    const total = year * 12 + month + delta
+    return [Math.floor(total / 12), ((total % 12) + 12) % 12] as const
+  }
+
+  let y = now.getUTCFullYear()
+  let m = now.getUTCMonth()
+  let start = anchor(y, m)
+  if (start > now) {
+    ;[y, m] = shift(y, m, -1)
+    start = anchor(y, m)
+  }
+  const [ny, nm] = shift(y, m, 1)
+  const end = anchor(ny, nm)
+  return { start, end }
+}

+ 76 - 0
packages/console/core/test/date.test.ts

@@ -0,0 +1,76 @@
+import { describe, expect, test } from "bun:test"
+import { getWeekBounds, getMonthlyBounds } from "../src/util/date"
+
+describe("util.date.getWeekBounds", () => {
+  test("returns a Monday-based week for Sunday dates", () => {
+    const date = new Date("2026-01-18T12:00:00Z")
+    const bounds = getWeekBounds(date)
+
+    expect(bounds.start.toISOString()).toBe("2026-01-12T00:00:00.000Z")
+    expect(bounds.end.toISOString()).toBe("2026-01-19T00:00:00.000Z")
+  })
+
+  test("returns a seven day window", () => {
+    const date = new Date("2026-01-14T12:00:00Z")
+    const bounds = getWeekBounds(date)
+
+    const span = bounds.end.getTime() - bounds.start.getTime()
+    expect(span).toBe(7 * 24 * 60 * 60 * 1000)
+  })
+})
+
+describe("util.date.getMonthlyBounds", () => {
+  test("resets on subscription day mid-month", () => {
+    const now = new Date("2026-03-20T10:00:00Z")
+    const subscribed = new Date("2026-01-15T08:00:00Z")
+    const bounds = getMonthlyBounds(now, subscribed)
+
+    expect(bounds.start.toISOString()).toBe("2026-03-15T08:00:00.000Z")
+    expect(bounds.end.toISOString()).toBe("2026-04-15T08:00:00.000Z")
+  })
+
+  test("before subscription day in current month uses previous month anchor", () => {
+    const now = new Date("2026-03-10T10:00:00Z")
+    const subscribed = new Date("2026-01-15T08:00:00Z")
+    const bounds = getMonthlyBounds(now, subscribed)
+
+    expect(bounds.start.toISOString()).toBe("2026-02-15T08:00:00.000Z")
+    expect(bounds.end.toISOString()).toBe("2026-03-15T08:00:00.000Z")
+  })
+
+  test("clamps day for short months", () => {
+    const now = new Date("2026-03-01T10:00:00Z")
+    const subscribed = new Date("2026-01-31T12:00:00Z")
+    const bounds = getMonthlyBounds(now, subscribed)
+
+    expect(bounds.start.toISOString()).toBe("2026-02-28T12:00:00.000Z")
+    expect(bounds.end.toISOString()).toBe("2026-03-31T12:00:00.000Z")
+  })
+
+  test("handles subscription on the 1st", () => {
+    const now = new Date("2026-04-15T00:00:00Z")
+    const subscribed = new Date("2026-01-01T00:00:00Z")
+    const bounds = getMonthlyBounds(now, subscribed)
+
+    expect(bounds.start.toISOString()).toBe("2026-04-01T00:00:00.000Z")
+    expect(bounds.end.toISOString()).toBe("2026-05-01T00:00:00.000Z")
+  })
+
+  test("exactly on the reset boundary uses current period", () => {
+    const now = new Date("2026-03-15T08:00:00Z")
+    const subscribed = new Date("2026-01-15T08:00:00Z")
+    const bounds = getMonthlyBounds(now, subscribed)
+
+    expect(bounds.start.toISOString()).toBe("2026-03-15T08:00:00.000Z")
+    expect(bounds.end.toISOString()).toBe("2026-04-15T08:00:00.000Z")
+  })
+
+  test("february to march with day 30 subscription", () => {
+    const now = new Date("2026-02-15T06:00:00Z")
+    const subscribed = new Date("2025-12-30T06:00:00Z")
+    const bounds = getMonthlyBounds(now, subscribed)
+
+    expect(bounds.start.toISOString()).toBe("2026-01-30T06:00:00.000Z")
+    expect(bounds.end.toISOString()).toBe("2026-02-28T06:00:00.000Z")
+  })
+})

+ 106 - 0
packages/console/core/test/subscription.test.ts

@@ -0,0 +1,106 @@
+import { describe, expect, test, setSystemTime, afterEach } from "bun:test"
+import { Subscription } from "../src/subscription"
+import { centsToMicroCents } from "../src/util/price"
+
+afterEach(() => {
+  setSystemTime()
+})
+
+describe("Subscription.analyzeMonthlyUsage", () => {
+  const subscribed = new Date("2026-01-15T08:00:00Z")
+
+  test("returns ok with 0% when usage was last updated before current period", () => {
+    setSystemTime(new Date("2026-03-20T10:00:00Z"))
+    const result = Subscription.analyzeMonthlyUsage({
+      limit: 10,
+      usage: centsToMicroCents(500),
+      timeUpdated: new Date("2026-02-10T00:00:00Z"),
+      timeSubscribed: subscribed,
+    })
+
+    expect(result.status).toBe("ok")
+    expect(result.usagePercent).toBe(0)
+    // reset should be seconds until 2026-04-15T08:00:00Z
+    const expected = Math.ceil(
+      (new Date("2026-04-15T08:00:00Z").getTime() - new Date("2026-03-20T10:00:00Z").getTime()) / 1000,
+    )
+    expect(result.resetInSec).toBe(expected)
+  })
+
+  test("returns ok with usage percent when under limit", () => {
+    setSystemTime(new Date("2026-03-20T10:00:00Z"))
+    const limit = 10 // $10
+    const half = centsToMicroCents(10 * 100) / 2
+    const result = Subscription.analyzeMonthlyUsage({
+      limit,
+      usage: half,
+      timeUpdated: new Date("2026-03-18T00:00:00Z"),
+      timeSubscribed: subscribed,
+    })
+
+    expect(result.status).toBe("ok")
+    expect(result.usagePercent).toBe(50)
+  })
+
+  test("returns rate-limited when at or over limit", () => {
+    setSystemTime(new Date("2026-03-20T10:00:00Z"))
+    const limit = 10
+    const result = Subscription.analyzeMonthlyUsage({
+      limit,
+      usage: centsToMicroCents(limit * 100),
+      timeUpdated: new Date("2026-03-18T00:00:00Z"),
+      timeSubscribed: subscribed,
+    })
+
+    expect(result.status).toBe("rate-limited")
+    expect(result.usagePercent).toBe(100)
+  })
+
+  test("resets usage when crossing monthly boundary", () => {
+    // subscribed on 15th, now is April 16th — period is Apr 15 to May 15
+    // timeUpdated is March 20 (previous period)
+    setSystemTime(new Date("2026-04-16T10:00:00Z"))
+    const result = Subscription.analyzeMonthlyUsage({
+      limit: 10,
+      usage: centsToMicroCents(10 * 100),
+      timeUpdated: new Date("2026-03-20T00:00:00Z"),
+      timeSubscribed: subscribed,
+    })
+
+    expect(result.status).toBe("ok")
+    expect(result.usagePercent).toBe(0)
+  })
+
+  test("caps usage percent at 100", () => {
+    setSystemTime(new Date("2026-03-20T10:00:00Z"))
+    const limit = 10
+    const result = Subscription.analyzeMonthlyUsage({
+      limit,
+      usage: centsToMicroCents(limit * 100) - 1,
+      timeUpdated: new Date("2026-03-18T00:00:00Z"),
+      timeSubscribed: subscribed,
+    })
+
+    expect(result.status).toBe("ok")
+    expect(result.usagePercent).toBeLessThanOrEqual(100)
+  })
+
+  test("handles subscription day 31 in short month", () => {
+    const sub31 = new Date("2026-01-31T12:00:00Z")
+    // now is March 1 — period should be Feb 28 to Mar 31
+    setSystemTime(new Date("2026-03-01T10:00:00Z"))
+    const result = Subscription.analyzeMonthlyUsage({
+      limit: 10,
+      usage: 0,
+      timeUpdated: new Date("2026-03-01T09:00:00Z"),
+      timeSubscribed: sub31,
+    })
+
+    expect(result.status).toBe("ok")
+    expect(result.usagePercent).toBe(0)
+    const expected = Math.ceil(
+      (new Date("2026-03-31T12:00:00Z").getTime() - new Date("2026-03-01T10:00:00Z").getTime()) / 1000,
+    )
+    expect(result.resetInSec).toBe(expected)
+  })
+})