Jelajahi Sumber

Merge branch 'dev' into brendan/lazy-init-plugins

Brendan Allan 2 hari lalu
induk
melakukan
b265742fd0
100 mengubah file dengan 6504 tambahan dan 453 penghapusan
  1. 1 5
      .opencode/opencode.jsonc
  2. 43 45
      bun.lock
  3. 0 1
      infra/console.ts
  4. 4 4
      nix/hashes.json
  5. 19 18
      nix/opencode.nix
  6. 4 2
      package.json
  7. 1 1
      packages/app/package.json
  8. TEMPAT SAMPAH
      packages/app/public/assets/JetBrainsMonoNerdFontMono-Regular.woff2
  9. 27 0
      packages/app/src/components/settings-general.tsx
  10. 3 3
      packages/app/src/components/terminal.tsx
  11. 18 0
      packages/app/src/context/settings.tsx
  12. 3 1
      packages/app/src/i18n/ar.ts
  13. 3 1
      packages/app/src/i18n/br.ts
  14. 3 1
      packages/app/src/i18n/bs.ts
  15. 3 1
      packages/app/src/i18n/da.ts
  16. 3 1
      packages/app/src/i18n/de.ts
  17. 3 1
      packages/app/src/i18n/en.ts
  18. 3 1
      packages/app/src/i18n/es.ts
  19. 3 1
      packages/app/src/i18n/fr.ts
  20. 3 1
      packages/app/src/i18n/ja.ts
  21. 3 1
      packages/app/src/i18n/ko.ts
  22. 3 1
      packages/app/src/i18n/no.ts
  23. 3 1
      packages/app/src/i18n/pl.ts
  24. 3 1
      packages/app/src/i18n/ru.ts
  25. 3 1
      packages/app/src/i18n/th.ts
  26. 3 1
      packages/app/src/i18n/tr.ts
  27. 3 1
      packages/app/src/i18n/zh.ts
  28. 3 1
      packages/app/src/i18n/zht.ts
  29. 7 0
      packages/app/src/index.css
  30. 6 1
      packages/app/src/pages/session/session-side-panel.tsx
  31. 4 1
      packages/app/src/pages/session/use-session-commands.tsx
  32. 1 1
      packages/console/app/package.json
  33. 8 1
      packages/console/app/src/i18n/ar.ts
  34. 8 1
      packages/console/app/src/i18n/br.ts
  35. 8 1
      packages/console/app/src/i18n/da.ts
  36. 8 1
      packages/console/app/src/i18n/de.ts
  37. 8 1
      packages/console/app/src/i18n/en.ts
  38. 8 1
      packages/console/app/src/i18n/es.ts
  39. 8 1
      packages/console/app/src/i18n/fr.ts
  40. 8 1
      packages/console/app/src/i18n/it.ts
  41. 8 1
      packages/console/app/src/i18n/ja.ts
  42. 8 1
      packages/console/app/src/i18n/ko.ts
  43. 8 1
      packages/console/app/src/i18n/no.ts
  44. 8 1
      packages/console/app/src/i18n/pl.ts
  45. 8 1
      packages/console/app/src/i18n/ru.ts
  46. 8 1
      packages/console/app/src/i18n/th.ts
  47. 8 1
      packages/console/app/src/i18n/tr.ts
  48. 8 1
      packages/console/app/src/i18n/zh.ts
  49. 8 1
      packages/console/app/src/i18n/zht.ts
  50. 69 2
      packages/console/app/src/routes/api/enterprise.ts
  51. 7 0
      packages/console/app/src/routes/stripe/webhook.ts
  52. 2 0
      packages/console/app/src/routes/workspace/[id]/billing/index.tsx
  53. 61 0
      packages/console/app/src/routes/workspace/[id]/billing/redeem-section.module.css
  54. 71 0
      packages/console/app/src/routes/workspace/[id]/billing/redeem-section.tsx
  55. 24 3
      packages/console/app/src/routes/zen/util/handler.ts
  56. 51 0
      packages/console/app/src/routes/zen/util/modelTpmLimiter.ts
  57. 6 0
      packages/console/core/migrations/20260417071612_tidy_diamondback/migration.sql
  58. 2567 0
      packages/console/core/migrations/20260417071612_tidy_diamondback/snapshot.json
  59. 6 0
      packages/console/core/migrations/20260418195905_shocking_marvel_zombies/migration.sql
  60. 2619 0
      packages/console/core/migrations/20260418195905_shocking_marvel_zombies/snapshot.json
  61. 1 1
      packages/console/core/package.json
  62. 24 0
      packages/console/core/script/create-coupon.ts
  63. 49 5
      packages/console/core/src/billing.ts
  64. 2 6
      packages/console/core/src/lite.ts
  65. 11 4
      packages/console/core/src/model.ts
  66. 23 1
      packages/console/core/src/schema/billing.sql.ts
  67. 10 0
      packages/console/core/src/schema/ip.sql.ts
  68. 0 4
      packages/console/core/sst-env.d.ts
  69. 1 1
      packages/console/function/package.json
  70. 0 4
      packages/console/function/sst-env.d.ts
  71. 1 1
      packages/console/mail/package.json
  72. 0 4
      packages/console/resource/sst-env.d.ts
  73. 3 2
      packages/desktop-electron/package.json
  74. 26 10
      packages/desktop-electron/src/main/index.ts
  75. 4 4
      packages/desktop-electron/src/main/migrate.ts
  76. 6 6
      packages/desktop-electron/src/main/server.ts
  77. 4 2
      packages/desktop-electron/src/main/store.ts
  78. 1 1
      packages/desktop/package.json
  79. 1 1
      packages/enterprise/package.json
  80. 0 4
      packages/enterprise/sst-env.d.ts
  81. 6 6
      packages/extensions/zed/extension.toml
  82. 1 1
      packages/function/package.json
  83. 0 4
      packages/function/sst-env.d.ts
  84. 8 9
      packages/opencode/package.json
  85. 1 9
      packages/opencode/script/build.ts
  86. 23 8
      packages/opencode/script/publish.ts
  87. 32 49
      packages/opencode/specs/effect/facades.md
  88. 85 46
      packages/opencode/specs/effect/http-api.md
  89. 10 10
      packages/opencode/specs/effect/instance-context.md
  90. 6 8
      packages/opencode/specs/effect/loose-ends.md
  91. 30 40
      packages/opencode/specs/effect/migration.md
  92. 16 18
      packages/opencode/specs/effect/routes.md
  93. 254 28
      packages/opencode/specs/effect/schema.md
  94. 19 17
      packages/opencode/specs/effect/server-package.md
  95. 3 5
      packages/opencode/specs/effect/tools.md
  96. 13 1
      packages/opencode/src/cli/cmd/generate.ts
  97. 8 4
      packages/opencode/src/cli/cmd/tui/app.tsx
  98. 1 0
      packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx
  99. 2 7
      packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx
  100. 11 1
      packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx

+ 1 - 5
.opencode/opencode.jsonc

@@ -1,10 +1,6 @@
 {
   "$schema": "https://opencode.ai/config.json",
-  "provider": {
-    "opencode": {
-      "options": {},
-    },
-  },
+  "provider": {},
   "permission": {
     "edit": {
       "packages/opencode/migration/*": "deny",

+ 43 - 45
bun.lock

@@ -29,7 +29,7 @@
     },
     "packages/app": {
       "name": "@opencode-ai/app",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
@@ -83,7 +83,7 @@
     },
     "packages/console/app": {
       "name": "@opencode-ai/console-app",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "@cloudflare/vite-plugin": "1.15.2",
         "@ibm/plex": "6.4.1",
@@ -117,7 +117,7 @@
     },
     "packages/console/core": {
       "name": "@opencode-ai/console-core",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "@aws-sdk/client-sts": "3.782.0",
         "@jsx-email/render": "1.1.1",
@@ -144,7 +144,7 @@
     },
     "packages/console/function": {
       "name": "@opencode-ai/console-function",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "@ai-sdk/anthropic": "3.0.64",
         "@ai-sdk/openai": "3.0.48",
@@ -168,7 +168,7 @@
     },
     "packages/console/mail": {
       "name": "@opencode-ai/console-mail",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "@jsx-email/all": "2.2.3",
         "@jsx-email/cli": "1.4.3",
@@ -192,7 +192,7 @@
     },
     "packages/desktop": {
       "name": "@opencode-ai/desktop",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "@opencode-ai/app": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
@@ -225,8 +225,9 @@
     },
     "packages/desktop-electron": {
       "name": "@opencode-ai/desktop-electron",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
+        "drizzle-orm": "catalog:",
         "effect": "catalog:",
         "electron-context-menu": "4.1.2",
         "electron-log": "^5",
@@ -248,7 +249,7 @@
         "@types/node": "catalog:",
         "@typescript/native-preview": "catalog:",
         "@valibot/to-json-schema": "1.6.0",
-        "electron": "40.4.1",
+        "electron": "41.2.1",
         "electron-builder": "^26",
         "electron-vite": "^5",
         "solid-js": "catalog:",
@@ -268,7 +269,7 @@
     },
     "packages/enterprise": {
       "name": "@opencode-ai/enterprise",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "@opencode-ai/shared": "workspace:*",
         "@opencode-ai/ui": "workspace:*",
@@ -297,7 +298,7 @@
     },
     "packages/function": {
       "name": "@opencode-ai/function",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "@octokit/auth-app": "8.0.1",
         "@octokit/rest": "catalog:",
@@ -313,7 +314,7 @@
     },
     "packages/opencode": {
       "name": "opencode",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "bin": {
         "opencode": "./bin/opencode",
       },
@@ -322,15 +323,15 @@
         "@actions/github": "6.0.1",
         "@agentclientprotocol/sdk": "0.16.1",
         "@ai-sdk/alibaba": "1.0.17",
-        "@ai-sdk/amazon-bedrock": "4.0.94",
-        "@ai-sdk/anthropic": "3.0.70",
+        "@ai-sdk/amazon-bedrock": "4.0.96",
+        "@ai-sdk/anthropic": "3.0.71",
         "@ai-sdk/azure": "3.0.49",
         "@ai-sdk/cerebras": "2.0.41",
         "@ai-sdk/cohere": "3.0.27",
         "@ai-sdk/deepinfra": "2.0.41",
-        "@ai-sdk/gateway": "3.0.102",
+        "@ai-sdk/gateway": "3.0.104",
         "@ai-sdk/google": "3.0.63",
-        "@ai-sdk/google-vertex": "4.0.111",
+        "@ai-sdk/google-vertex": "4.0.112",
         "@ai-sdk/groq": "3.0.31",
         "@ai-sdk/mistral": "3.0.27",
         "@ai-sdk/openai": "3.0.53",
@@ -365,8 +366,8 @@
         "@opentelemetry/exporter-trace-otlp-http": "0.214.0",
         "@opentelemetry/sdk-trace-base": "2.6.1",
         "@opentelemetry/sdk-trace-node": "2.6.1",
-        "@opentui/core": "0.1.99",
-        "@opentui/solid": "0.1.99",
+        "@opentui/core": "catalog:",
+        "@opentui/solid": "catalog:",
         "@parcel/watcher": "2.5.1",
         "@pierre/diffs": "catalog:",
         "@solid-primitives/event-bus": "1.1.2",
@@ -386,7 +387,7 @@
         "drizzle-orm": "catalog:",
         "effect": "catalog:",
         "fuzzysort": "3.1.0",
-        "gitlab-ai-provider": "6.4.2",
+        "gitlab-ai-provider": "6.6.0",
         "glob": "13.0.5",
         "google-auth-library": "10.5.0",
         "gray-matter": "4.0.3",
@@ -404,7 +405,6 @@
         "opentui-spinner": "0.0.6",
         "partial-json": "0.1.7",
         "remeda": "catalog:",
-        "ripgrep": "0.3.1",
         "semver": "^7.6.3",
         "solid-js": "catalog:",
         "strip-ansi": "7.1.2",
@@ -458,23 +458,23 @@
     },
     "packages/plugin": {
       "name": "@opencode-ai/plugin",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "effect": "catalog:",
         "zod": "catalog:",
       },
       "devDependencies": {
-        "@opentui/core": "0.1.99",
-        "@opentui/solid": "0.1.99",
+        "@opentui/core": "catalog:",
+        "@opentui/solid": "catalog:",
         "@tsconfig/node22": "catalog:",
         "@types/node": "catalog:",
         "@typescript/native-preview": "catalog:",
         "typescript": "catalog:",
       },
       "peerDependencies": {
-        "@opentui/core": ">=0.1.99",
-        "@opentui/solid": ">=0.1.99",
+        "@opentui/core": ">=0.1.100",
+        "@opentui/solid": ">=0.1.100",
       },
       "optionalPeers": [
         "@opentui/core",
@@ -493,7 +493,7 @@
     },
     "packages/sdk/js": {
       "name": "@opencode-ai/sdk",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "cross-spawn": "catalog:",
       },
@@ -508,7 +508,7 @@
     },
     "packages/shared": {
       "name": "@opencode-ai/shared",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "bin": {
         "opencode": "./bin/opencode",
       },
@@ -532,7 +532,7 @@
     },
     "packages/slack": {
       "name": "@opencode-ai/slack",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@slack/bolt": "^3.17.1",
@@ -567,7 +567,7 @@
     },
     "packages/ui": {
       "name": "@opencode-ai/ui",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
@@ -616,7 +616,7 @@
     },
     "packages/web": {
       "name": "@opencode-ai/web",
-      "version": "1.4.7",
+      "version": "1.14.18",
       "dependencies": {
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/markdown-remark": "6.3.1",
@@ -675,6 +675,8 @@
     "@npmcli/arborist": "9.4.0",
     "@octokit/rest": "22.0.0",
     "@openauthjs/openauth": "0.0.0-20250322224806",
+    "@opentui/core": "0.1.99",
+    "@opentui/solid": "0.1.99",
     "@pierre/diffs": "1.1.0-beta.18",
     "@playwright/test": "1.59.1",
     "@solid-primitives/storage": "4.3.3",
@@ -690,7 +692,7 @@
     "@types/node": "22.13.9",
     "@types/semver": "7.7.1",
     "@typescript/native-preview": "7.0.0-dev.20251207.1",
-    "ai": "6.0.158",
+    "ai": "6.0.168",
     "cross-spawn": "7.0.6",
     "diff": "8.0.2",
     "dompurify": "3.3.1",
@@ -738,7 +740,7 @@
 
     "@ai-sdk/alibaba": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
 
-    "@ai-sdk/amazon-bedrock": ["@ai-sdk/[email protected]4", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.70", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XKE7wAjXejsIfNQvn3onvGUByhGHVM6W+xlL+1DAQLmjEb+ue4sOJIRehJ96rEvTXVVHRVyA6bSXx7ayxXfn5A=="],
+    "@ai-sdk/amazon-bedrock": ["@ai-sdk/[email protected]6", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Mc4Ias2jRMD1jOB6xWtKNPdhECeuCZyIlbr9EAGfBnyBt++sS13ziZh9qv9TdyMCAZJ7xoQcpbchoRJcKwPdpA=="],
 
     "@ai-sdk/anthropic": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rwLi/Rsuj2pYniQXIrvClHvXDzgM4UQHHnvHTWEF14efnlKclG/1ghpNC+adsRujAbCTr6gRsSbDE2vEqriV7g=="],
 
@@ -758,11 +760,11 @@
 
     "@ai-sdk/fireworks": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XRKR0zgRyegdmtK5CDUEjlyRp0Fo+XVCdoG+301U1SGtgRIAYG3ObVtgzVJBVpJdHFSLHuYeLTnNiQoUxD7+FQ=="],
 
-    "@ai-sdk/gateway": ["@ai-sdk/[email protected]2", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-GrwDpaYJiVafrsA1MTbZtXPcQUI67g5AXiJo7Y1F8b+w+SiYHLk3ZIn1YmpQVoVAh2bjvxjj+Vo0AvfskuGH4g=="],
+    "@ai-sdk/gateway": ["@ai-sdk/[email protected]4", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA=="],
 
     "@ai-sdk/google": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-RfOZWVMYSPu2sPRfGajrauWAZ9BSaRopSn+AszkKWQ1MFj8nhaXvCqRHB5pBQUaHTfZKagvOmMpNfa/s3gPLgQ=="],
 
-    "@ai-sdk/google-vertex": ["@ai-sdk/[email protected]1", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.70", "@ai-sdk/google": "3.0.64", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5gILpAWWI5idfal/MfoH3tlQeSnOJ9jfL8JB8m2fdc3ue/9xoXkYDpXpDL/nyJImFjMCi6eR0Fpvlo/IKEWDIg=="],
+    "@ai-sdk/google-vertex": ["@ai-sdk/[email protected]2", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/google": "3.0.64", "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-cSfHCkM+9ZrFtQWIN1WlV93JPD+isGSdFxKj7u1L9m2aLVZajlXdcE41GL9hMt7ld7bZYE4NnZ+4VLxBAHE+Eg=="],
 
     "@ai-sdk/groq": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-XbbugpnFmXGu2TlXiq8KUJskP6/VVbuFcnFIGDzDIB/Chg6XHsNnqrTF80Zxkh0Pd3+NvbM+2Uqrtsndk6bDAg=="],
 
@@ -2454,7 +2456,7 @@
 
     "@valibot/to-json-schema": ["@valibot/[email protected]", "", { "peerDependencies": { "valibot": "^1.3.0" } }, "sha512-d6rYyK5KVa2XdqamWgZ4/Nr+cXhxjy7lmpe6Iajw15J/jmU+gyxl2IEd1Otg1d7Rl3gOQL5reulnSypzBtYy1A=="],
 
-    "@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
+    "@vercel/oidc": ["@vercel/oidc@3.2.0", "", {}, "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug=="],
 
     "@vitejs/plugin-react": ["@vitejs/[email protected]", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
 
@@ -2514,7 +2516,7 @@
 
     "agentkeepalive": ["[email protected]", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
 
-    "ai": ["[email protected]58", "", { "dependencies": { "@ai-sdk/gateway": "3.0.95", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-gLTp1UXFtMqKUi3XHs33K7UFglbvojkxF/aq337TxnLGOhHIW9+GyP2jwW4hYX87f1es+wId3VQoPRRu9zEStQ=="],
+    "ai": ["[email protected]68", "", { "dependencies": { "@ai-sdk/gateway": "3.0.104", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ=="],
 
     "ai-gateway-provider": ["[email protected]", "", { "optionalDependencies": { "@ai-sdk/amazon-bedrock": "^4.0.62", "@ai-sdk/anthropic": "^3.0.46", "@ai-sdk/azure": "^3.0.31", "@ai-sdk/cerebras": "^2.0.34", "@ai-sdk/cohere": "^3.0.21", "@ai-sdk/deepgram": "^2.0.20", "@ai-sdk/deepseek": "^2.0.20", "@ai-sdk/elevenlabs": "^2.0.20", "@ai-sdk/fireworks": "^2.0.34", "@ai-sdk/google": "^3.0.30", "@ai-sdk/google-vertex": "^4.0.61", "@ai-sdk/groq": "^3.0.24", "@ai-sdk/mistral": "^3.0.20", "@ai-sdk/openai": "^3.0.30", "@ai-sdk/perplexity": "^3.0.19", "@ai-sdk/xai": "^3.0.57", "@openrouter/ai-sdk-provider": "^2.2.3" }, "peerDependencies": { "@ai-sdk/openai-compatible": "^2.0.0", "@ai-sdk/provider": "^3.0.0", "@ai-sdk/provider-utils": "^4.0.0", "ai": "^6.0.0" } }, "sha512-krGNnJSoO/gJ7Hbe5nQDlsBpDUGIBGtMQTRUaW7s1MylsfvLduba0TLWzQaGtOmNRkP0pGhtGlwsnS6FNQMlyw=="],
 
@@ -3024,7 +3026,7 @@
 
     "ejs": ["[email protected]", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
 
-    "electron": ["electron@40.4.1", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-N1ZXybQZL8kYemO8vAeh9nrk4mSvqlAO8xs0QCHkXIvRnuB/7VGwEehjvQbsU5/f4bmTKpG+2GQERe/zmKpudQ=="],
+    "electron": ["electron@41.2.1", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-teeRThiYGTPKf/2yOW7zZA1bhb91KEQ4yLBPOg7GxpmnkLFLugKgQaAKOrCgdzwsXh/5mFIfmkm+4+wACJKwaA=="],
 
     "electron-builder": ["[email protected]", "", { "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.8.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "cli.js", "install-app-deps": "install-app-deps.js" } }, "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw=="],
 
@@ -3304,7 +3306,7 @@
 
     "get-tsconfig": ["[email protected]", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-J87BxkLXykmisLQ+KA4x2+O6rVf+PJrtFUO8lGyiRg4lyxJLJ8/v0sRAKdVZQOy6tR6lMRAF1NqzCf9BQijm0w=="],
 
-    "ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#4af877d", {}, "anomalyco-ghostty-web-4af877d", "sha512-fbEK8mtr7ar4ySsF+JUGjhaZrane7dKphanN+SxHt5XXI6yLMAh/Hpf6sNCOyyVa2UlGCd7YpXG/T2v2RUAX+A=="],
+    "ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#20bd361", {}, "anomalyco-ghostty-web-20bd361", "sha512-dW0nwaiBBcun9y5WJSvm3HxDLe5o9V0xLCndQvWonRVubU8CS1PHxZpLffyPt1YujPWC13ez03aWxcuKBPYYGQ=="],
 
     "gifwrap": ["[email protected]", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
 
@@ -3312,7 +3314,7 @@
 
     "github-slugger": ["[email protected]", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
 
-    "gitlab-ai-provider": ["gitlab-ai-provider@6.4.2", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-Wyw6uslCuipBOr/NYwAtpgXEUJj68iJY5aekad2DjePN99JetKVQBqkLgAy9PZp2EA4OuscfRQu9qKIBN/evNw=="],
+    "gitlab-ai-provider": ["gitlab-ai-provider@6.6.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=3.0.0", "@ai-sdk/provider-utils": ">=4.0.0" } }, "sha512-jUxYnKA4XQaPc3wxACDZ8bPDXO0Mzx7cZaBDxbT2uGgLqtGZmSi+9tVNIg7louSS+s/ioVra3SoUz3iOFVhKPA=="],
 
     "glob": ["[email protected]", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="],
 
@@ -4480,8 +4482,6 @@
 
     "rimraf": ["[email protected]", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="],
 
-    "ripgrep": ["[email protected]", "", { "bin": { "rg": "lib/rg.mjs", "ripgrep": "lib/rg.mjs" } }, "sha512-6bDtNIBh1qPviVIU685/4uv0Ap5t8eS4wiJhy/tR2LdIeIey9CVasENlGS+ul3HnTmGANIp7AjnfsztsRmALfQ=="],
-
     "roarr": ["[email protected]", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
 
     "rollup": ["[email protected]", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="],
@@ -5152,7 +5152,7 @@
 
     "@ai-sdk/alibaba/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
 
-    "@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/[email protected]0", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hubTFcfnG3NbrlcDW0tU2fsZhRy/7dF5GCymu4DzBQUYliy2lb7tCeeMhDtFBaYa01qSBHRjkwGnsAdUtDPCwA=="],
+    "@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/[email protected]1", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="],
 
     "@ai-sdk/amazon-bedrock/@smithy/eventstream-codec": ["@smithy/[email protected]", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.0", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ=="],
 
@@ -5170,7 +5170,7 @@
 
     "@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
 
-    "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/[email protected]0", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hubTFcfnG3NbrlcDW0tU2fsZhRy/7dF5GCymu4DzBQUYliy2lb7tCeeMhDtFBaYa01qSBHRjkwGnsAdUtDPCwA=="],
+    "@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/[email protected]1", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="],
 
     "@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CbR82EgGPNrj/6q0HtclwuCqe0/pDShyv3nWDP/A9DroujzWXnLMlUJVrgPOsg4b40zQCwwVs2XSKCxvt/4QaA=="],
 
@@ -5700,8 +5700,6 @@
 
     "accepts/negotiator": ["[email protected]", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
 
-    "ai/@ai-sdk/gateway": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ=="],
-
     "ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.69", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hcXDU8QDwpAzLVTuY932TQVlIij9+iaVTxc5mPGY6yb//JMAAC5hMVhg93IrxlrxWLvMgjezNgoZGwquR+SGnw=="],
 
     "ai-gateway-provider/@ai-sdk/anthropic": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="],
@@ -5920,7 +5918,7 @@
 
     "nypm/tinyexec": ["[email protected]", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="],
 
-    "opencode/@ai-sdk/anthropic": ["@ai-sdk/[email protected]0", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hubTFcfnG3NbrlcDW0tU2fsZhRy/7dF5GCymu4DzBQUYliy2lb7tCeeMhDtFBaYa01qSBHRjkwGnsAdUtDPCwA=="],
+    "opencode/@ai-sdk/anthropic": ["@ai-sdk/[email protected]1", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bUWOzrzR0gJKJO/PLGMR4uH2dqEgqGhrsCV+sSpk4KtOEnUQlfjZI/F7BFlqSvVpFbjdgYRRLysAeEZpJ6S1lg=="],
 
     "opencode/@ai-sdk/openai": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="],
 

+ 0 - 1
infra/console.ts

@@ -236,7 +236,6 @@ new sst.cloudflare.x.SolidStart("Console", {
     SALESFORCE_INSTANCE_URL,
     ZEN_BLACK_PRICE,
     ZEN_LITE_PRICE,
-    new sst.Secret("ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES"),
     new sst.Secret("ZEN_LIMITS"),
     new sst.Secret("ZEN_SESSION_SECRET"),
     ...ZEN_MODELS,

+ 4 - 4
nix/hashes.json

@@ -1,8 +1,8 @@
 {
   "nodeModules": {
-    "x86_64-linux": "sha256-OPbZUo/fQv2Xsf+NEZV08GLBMN/DXovhRvn2JkesFtY=",
-    "aarch64-linux": "sha256-WK7xlVLuirKDN5LaqjBn7qpv5bYVtYHZw0qRNKX4xXg=",
-    "aarch64-darwin": "sha256-BAoAdeLQ+lXDD7Klxoxij683OVVug8KXEMRUqIQAjc8=",
-    "x86_64-darwin": "sha256-ZOBwNR2gZgc5f+y3VIBBT4qZpeZfg7Of6AaGDOfqsG8="
+    "x86_64-linux": "sha256-i9TxYwWkJAR+kW6pbvhgQbRW9UYPtdrPQAGic4zPoa4=",
+    "aarch64-linux": "sha256-RYc/OYlETXUwkWBRDas+/P4cBW6zde4FqxxnMARu5vs=",
+    "aarch64-darwin": "sha256-jIhUOIRIQEa2WT62TVIedmRIhl/edhK8sbiAFvU3yCM=",
+    "x86_64-darwin": "sha256-xLGzaX7OofFlZzVgpORJR5QXD2u+54hp+t3cCfUtO84="
   }
 }

+ 19 - 18
nix/opencode.nix

@@ -7,6 +7,7 @@
   sysctl,
   makeBinaryWrapper,
   models-dev,
+  ripgrep,
   installShellFiles,
   versionCheckHook,
   writableTmpDirAsHomeHook,
@@ -51,25 +52,25 @@ stdenvNoCC.mkDerivation (finalAttrs: {
     runHook postBuild
   '';
 
-  installPhase =
-    ''
-      runHook preInstall
-
-      install -Dm755 dist/opencode-*/bin/opencode $out/bin/opencode
-      install -Dm644 schema.json $out/share/opencode/schema.json
-    ''
-    # bun runs sysctl to detect if dunning on rosetta2
-    + lib.optionalString stdenvNoCC.hostPlatform.isDarwin ''
-      wrapProgram $out/bin/opencode \
-        --prefix PATH : ${
-          lib.makeBinPath [
-            sysctl
+  installPhase = ''
+    runHook preInstall
+
+    install -Dm755 dist/opencode-*/bin/opencode $out/bin/opencode
+    install -Dm644 schema.json $out/share/opencode/schema.json
+
+    wrapProgram $out/bin/opencode \
+      --prefix PATH : ${
+        lib.makeBinPath (
+          [
+            ripgrep
           ]
-        }
-    ''
-    + ''
-      runHook postInstall
-    '';
+          # bun runs sysctl to detect if dunning on rosetta2
+          ++ lib.optional stdenvNoCC.hostPlatform.isDarwin sysctl
+        )
+      }
+
+    runHook postInstall
+  '';
 
   postInstall = lib.optionalString (stdenvNoCC.buildPlatform.canExecute stdenvNoCC.hostPlatform) ''
     # trick yargs into also generating zsh completions

+ 4 - 2
package.json

@@ -7,7 +7,7 @@
   "packageManager": "[email protected]",
   "scripts": {
     "dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
-    "dev:desktop": "bun --cwd packages/desktop tauri dev",
+    "dev:desktop": "bun --cwd packages/desktop-electron dev",
     "dev:web": "bun --cwd packages/app dev",
     "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
     "dev:storybook": "bun --cwd packages/storybook storybook",
@@ -34,6 +34,8 @@
       "@types/cross-spawn": "6.0.6",
       "@octokit/rest": "22.0.0",
       "@hono/zod-validator": "0.4.2",
+      "@opentui/core": "0.1.99",
+      "@opentui/solid": "0.1.99",
       "ulid": "3.0.1",
       "@kobalte/core": "0.13.11",
       "@types/luxon": "3.7.1",
@@ -51,7 +53,7 @@
       "drizzle-kit": "1.0.0-beta.19-d95b7a4",
       "drizzle-orm": "1.0.0-beta.19-d95b7a4",
       "effect": "4.0.0-beta.48",
-      "ai": "6.0.158",
+      "ai": "6.0.168",
       "cross-spawn": "7.0.6",
       "hono": "4.10.7",
       "hono-openapi": "1.1.2",

+ 1 - 1
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/app",
-  "version": "1.4.7",
+  "version": "1.14.18",
   "description": "",
   "type": "module",
   "exports": {

TEMPAT SAMPAH
packages/app/public/assets/JetBrainsMonoNerdFontMono-Regular.woff2


+ 27 - 0
packages/app/src/components/settings-general.tsx

@@ -19,6 +19,9 @@ import {
   sansDefault,
   sansFontFamily,
   sansInput,
+  terminalDefault,
+  terminalFontFamily,
+  terminalInput,
   useSettings,
 } from "@/context/settings"
 import { decode64 } from "@/utils/base64"
@@ -181,6 +184,7 @@ export const SettingsGeneral: Component = () => {
   const soundOptions = [noneSound, ...SOUND_OPTIONS]
   const mono = () => monoInput(settings.appearance.font())
   const sans = () => sansInput(settings.appearance.uiFont())
+  const terminal = () => terminalInput(settings.appearance.terminalFont())
 
   const soundSelectProps = (
     enabled: () => boolean,
@@ -451,6 +455,29 @@ export const SettingsGeneral: Component = () => {
             />
           </div>
         </SettingsRow>
+
+        <SettingsRow
+          title={language.t("settings.general.row.terminalFont.title")}
+          description={language.t("settings.general.row.terminalFont.description")}
+        >
+          <div class="w-full sm:w-[220px]">
+            <TextField
+              data-action="settings-terminal-font"
+              label={language.t("settings.general.row.terminalFont.title")}
+              hideLabel
+              type="text"
+              value={terminal()}
+              onChange={(value) => settings.appearance.setTerminalFont(value)}
+              placeholder={terminalDefault}
+              spellcheck={false}
+              autocorrect="off"
+              autocomplete="off"
+              autocapitalize="off"
+              class="text-12-regular"
+              style={{ "font-family": terminalFontFamily(settings.appearance.terminalFont()) }}
+            />
+          </div>
+        </SettingsRow>
       </SettingsList>
     </div>
   )

+ 3 - 3
packages/app/src/components/terminal.tsx

@@ -11,7 +11,7 @@ import { useLanguage } from "@/context/language"
 import { usePlatform } from "@/context/platform"
 import { useSDK } from "@/context/sdk"
 import { useServer } from "@/context/server"
-import { monoFontFamily, useSettings } from "@/context/settings"
+import { terminalFontFamily, useSettings } from "@/context/settings"
 import type { LocalPTY } from "@/context/terminal"
 import { disposeIfDisposable, getHoveredLinkText, setOptionIfSupported } from "@/utils/runtime-adapters"
 import { terminalWriter } from "@/utils/terminal-writer"
@@ -300,7 +300,7 @@ export const Terminal = (props: TerminalProps) => {
   })
 
   createEffect(() => {
-    const font = monoFontFamily(settings.appearance.font())
+    const font = terminalFontFamily(settings.appearance.terminalFont())
     if (!term) return
     setOptionIfSupported(term, "fontFamily", font)
     scheduleFit()
@@ -360,7 +360,7 @@ export const Terminal = (props: TerminalProps) => {
         cols: restoreSize?.cols,
         rows: restoreSize?.rows,
         fontSize: 14,
-        fontFamily: monoFontFamily(settings.appearance.font()),
+        fontFamily: terminalFontFamily(settings.appearance.terminalFont()),
         allowTransparency: false,
         convertEol: false,
         theme: terminalColors(),

+ 18 - 0
packages/app/src/context/settings.tsx

@@ -39,6 +39,7 @@ export interface Settings {
     fontSize: number
     mono: string
     sans: string
+    terminal: string
   }
   keybinds: Record<string, string>
   permissions: {
@@ -50,13 +51,17 @@ export interface Settings {
 
 export const monoDefault = "System Mono"
 export const sansDefault = "System Sans"
+export const terminalDefault = "JetBrainsMono Nerd Font Mono"
 
 const monoFallback =
   'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
 const sansFallback = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
+const terminalFallback =
+  '"JetBrainsMono Nerd Font Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
 
 const monoBase = monoFallback
 const sansBase = sansFallback
+const terminalBase = terminalFallback
 
 function input(font: string | undefined) {
   return font ?? ""
@@ -89,6 +94,14 @@ export function sansFontFamily(font: string | undefined) {
   return stack(font, sansBase)
 }
 
+export function terminalInput(font: string | undefined) {
+  return input(font)
+}
+
+export function terminalFontFamily(font: string | undefined) {
+  return stack(font, terminalBase)
+}
+
 const defaultSettings: Settings = {
   general: {
     autoSave: true,
@@ -110,6 +123,7 @@ const defaultSettings: Settings = {
     fontSize: 14,
     mono: "",
     sans: "",
+    terminal: "",
   },
   keybinds: {},
   permissions: {
@@ -233,6 +247,10 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
         setUIFont(value: string) {
           setStore("appearance", "sans", value.trim() ? value : "")
         },
+        terminalFont: withFallback(() => store.appearance?.terminal, defaultSettings.appearance.terminal),
+        setTerminalFont(value: string) {
+          setStore("appearance", "terminal", value.trim() ? value : "")
+        },
       },
       keybinds: {
         get: (action: string) => store.keybinds?.[action],

+ 3 - 1
packages/app/src/i18n/ar.ts

@@ -565,7 +565,9 @@ export const dict = {
   "settings.general.row.theme.title": "السمة",
   "settings.general.row.theme.description": "تخصيص سمة OpenCode.",
   "settings.general.row.font.title": "خط الكود",
-  "settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية والطرفيات",
+  "settings.general.row.font.description": "خصّص الخط المستخدم في كتل التعليمات البرمجية",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "خط الواجهة",
   "settings.general.row.uiFont.description": "خصّص الخط المستخدم في الواجهة بأكملها",
   "settings.general.row.followup.title": "سلوك المتابعة",

+ 3 - 1
packages/app/src/i18n/br.ts

@@ -572,7 +572,9 @@ export const dict = {
   "settings.general.row.theme.title": "Tema",
   "settings.general.row.theme.description": "Personalize como o OpenCode é tematizado.",
   "settings.general.row.font.title": "Fonte de código",
-  "settings.general.row.font.description": "Personalize a fonte usada em blocos de código e terminais",
+  "settings.general.row.font.description": "Personalize a fonte usada em blocos de código",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "Fonte da interface",
   "settings.general.row.uiFont.description": "Personalize a fonte usada em toda a interface",
   "settings.general.row.followup.title": "Comportamento de acompanhamento",

+ 3 - 1
packages/app/src/i18n/bs.ts

@@ -637,7 +637,9 @@ export const dict = {
   "settings.general.row.theme.title": "Tema",
   "settings.general.row.theme.description": "Prilagodi temu OpenCode-a.",
   "settings.general.row.font.title": "Font za kod",
-  "settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda i terminalima",
+  "settings.general.row.font.description": "Prilagodi font koji se koristi u blokovima koda",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "UI font",
   "settings.general.row.uiFont.description": "Prilagodi font koji se koristi u cijelom interfejsu",
   "settings.general.row.followup.title": "Ponašanje nadovezivanja",

+ 3 - 1
packages/app/src/i18n/da.ts

@@ -632,7 +632,9 @@ export const dict = {
   "settings.general.row.theme.title": "Tema",
   "settings.general.row.theme.description": "Tilpas hvordan OpenCode er temabestemt.",
   "settings.general.row.font.title": "Kode-skrifttype",
-  "settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke og terminaler",
+  "settings.general.row.font.description": "Tilpas skrifttypen, der bruges i kodeblokke",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "UI-skrifttype",
   "settings.general.row.uiFont.description": "Tilpas skrifttypen, der bruges i hele brugerfladen",
   "settings.general.row.followup.title": "Opfølgningsadfærd",

+ 3 - 1
packages/app/src/i18n/de.ts

@@ -582,7 +582,9 @@ export const dict = {
   "settings.general.row.theme.title": "Thema",
   "settings.general.row.theme.description": "Das Thema von OpenCode anpassen.",
   "settings.general.row.font.title": "Code-Schriftart",
-  "settings.general.row.font.description": "Die in Codeblöcken und Terminals verwendete Schriftart anpassen",
+  "settings.general.row.font.description": "Die in Codeblöcken verwendete Schriftart anpassen",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "UI-Schriftart",
   "settings.general.row.uiFont.description": "Die im gesamten Interface verwendete Schriftart anpassen",
   "settings.general.row.followup.title": "Verhalten bei Folgefragen",

+ 3 - 1
packages/app/src/i18n/en.ts

@@ -735,7 +735,9 @@ export const dict = {
   "settings.general.row.theme.title": "Theme",
   "settings.general.row.theme.description": "Customise how OpenCode is themed.",
   "settings.general.row.font.title": "Code Font",
-  "settings.general.row.font.description": "Customise the font used in code blocks and terminals",
+  "settings.general.row.font.description": "Customise the font used in code blocks",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "UI Font",
   "settings.general.row.uiFont.description": "Customise the font used throughout the interface",
   "settings.general.row.followup.title": "Follow-up behavior",

+ 3 - 1
packages/app/src/i18n/es.ts

@@ -640,7 +640,9 @@ export const dict = {
   "settings.general.row.theme.title": "Tema",
   "settings.general.row.theme.description": "Personaliza el tema de OpenCode.",
   "settings.general.row.font.title": "Fuente de código",
-  "settings.general.row.font.description": "Personaliza la fuente usada en bloques de código y terminales",
+  "settings.general.row.font.description": "Personaliza la fuente usada en bloques de código",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "Fuente de la interfaz",
   "settings.general.row.uiFont.description": "Personaliza la fuente usada en toda la interfaz",
   "settings.general.row.followup.title": "Comportamiento de seguimiento",

+ 3 - 1
packages/app/src/i18n/fr.ts

@@ -579,7 +579,9 @@ export const dict = {
   "settings.general.row.theme.title": "Thème",
   "settings.general.row.theme.description": "Personnaliser le thème d'OpenCode.",
   "settings.general.row.font.title": "Police de code",
-  "settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code et les terminaux",
+  "settings.general.row.font.description": "Personnaliser la police utilisée dans les blocs de code",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "Police de l'interface",
   "settings.general.row.uiFont.description": "Personnaliser la police utilisée dans toute l'interface",
   "settings.general.row.followup.title": "Comportement de suivi",

+ 3 - 1
packages/app/src/i18n/ja.ts

@@ -569,7 +569,9 @@ export const dict = {
   "settings.general.row.theme.title": "テーマ",
   "settings.general.row.theme.description": "OpenCodeのテーマをカスタマイズします。",
   "settings.general.row.font.title": "コードフォント",
-  "settings.general.row.font.description": "コードブロックとターミナルで使用するフォントをカスタマイズします",
+  "settings.general.row.font.description": "コードブロックで使用するフォントをカスタマイズします",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "UIフォント",
   "settings.general.row.uiFont.description": "インターフェース全体で使用するフォントをカスタマイズします",
   "settings.general.row.followup.title": "フォローアップの動作",

+ 3 - 1
packages/app/src/i18n/ko.ts

@@ -566,7 +566,9 @@ export const dict = {
   "settings.general.row.theme.title": "테마",
   "settings.general.row.theme.description": "OpenCode 테마 사용자 지정",
   "settings.general.row.font.title": "코드 글꼴",
-  "settings.general.row.font.description": "코드 블록과 터미널에 사용되는 글꼴을 사용자 지정",
+  "settings.general.row.font.description": "코드 블록에 사용되는 글꼴을 사용자 지정",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "UI 글꼴",
   "settings.general.row.uiFont.description": "인터페이스 전반에 사용되는 글꼴을 사용자 지정",
   "settings.general.row.followup.title": "후속 조치 동작",

+ 3 - 1
packages/app/src/i18n/no.ts

@@ -640,7 +640,9 @@ export const dict = {
   "settings.general.row.theme.title": "Tema",
   "settings.general.row.theme.description": "Tilpass hvordan OpenCode er tematisert.",
   "settings.general.row.font.title": "Kodefont",
-  "settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker og terminaler",
+  "settings.general.row.font.description": "Tilpass skrifttypen som brukes i kodeblokker",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "UI-skrift",
   "settings.general.row.uiFont.description": "Tilpass skrifttypen som brukes i hele grensesnittet",
   "settings.general.row.followup.title": "Oppfølgingsadferd",

+ 3 - 1
packages/app/src/i18n/pl.ts

@@ -571,7 +571,9 @@ export const dict = {
   "settings.general.row.theme.title": "Motyw",
   "settings.general.row.theme.description": "Dostosuj motyw OpenCode.",
   "settings.general.row.font.title": "Czcionka kodu",
-  "settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu i terminalach",
+  "settings.general.row.font.description": "Dostosuj czcionkę używaną w blokach kodu",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "Czcionka interfejsu",
   "settings.general.row.uiFont.description": "Dostosuj czcionkę używaną w całym interfejsie",
   "settings.general.row.followup.title": "Zachowanie kontynuacji",

+ 3 - 1
packages/app/src/i18n/ru.ts

@@ -637,7 +637,9 @@ export const dict = {
   "settings.general.row.theme.title": "Тема",
   "settings.general.row.theme.description": "Настройте оформление OpenCode.",
   "settings.general.row.font.title": "Шрифт кода",
-  "settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода и терминалах",
+  "settings.general.row.font.description": "Настройте шрифт, используемый в блоках кода",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "Шрифт интерфейса",
   "settings.general.row.uiFont.description": "Настройте шрифт, используемый во всем интерфейсе",
   "settings.general.row.followup.title": "Поведение уточняющих вопросов",

+ 3 - 1
packages/app/src/i18n/th.ts

@@ -631,7 +631,9 @@ export const dict = {
   "settings.general.row.theme.title": "ธีม",
   "settings.general.row.theme.description": "ปรับแต่งวิธีการที่ OpenCode มีธีม",
   "settings.general.row.font.title": "ฟอนต์โค้ด",
-  "settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ดและเทอร์มินัล",
+  "settings.general.row.font.description": "ปรับแต่งฟอนต์ที่ใช้ในบล็อกโค้ด",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "ฟอนต์ UI",
   "settings.general.row.uiFont.description": "ปรับแต่งฟอนต์ที่ใช้ทั่วทั้งอินเทอร์เฟซ",
   "settings.general.row.followup.title": "พฤติกรรมการติดตามผล",

+ 3 - 1
packages/app/src/i18n/tr.ts

@@ -644,7 +644,9 @@ export const dict = {
   "settings.general.row.theme.title": "Tema",
   "settings.general.row.theme.description": "OpenCode'un temasını özelleştirin.",
   "settings.general.row.font.title": "Kod Yazı Tipi",
-  "settings.general.row.font.description": "Kod bloklarında ve terminallerde kullanılan yazı tipini özelleştirin",
+  "settings.general.row.font.description": "Kod bloklarında kullanılan yazı tipini özelleştirin",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "Arayüz Yazı Tipi",
   "settings.general.row.uiFont.description": "Arayüz genelinde kullanılan yazı tipini özelleştirin",
   "settings.general.row.followup.title": "Takip davranışı",

+ 3 - 1
packages/app/src/i18n/zh.ts

@@ -631,7 +631,9 @@ export const dict = {
   "settings.general.row.theme.title": "主题",
   "settings.general.row.theme.description": "自定义 OpenCode 的主题。",
   "settings.general.row.font.title": "代码字体",
-  "settings.general.row.font.description": "自定义代码块和终端使用的字体",
+  "settings.general.row.font.description": "自定义代码块使用的字体",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "界面字体",
   "settings.general.row.uiFont.description": "自定义整个界面使用的字体",
   "settings.general.row.followup.title": "跟进消息行为",

+ 3 - 1
packages/app/src/i18n/zht.ts

@@ -626,7 +626,9 @@ export const dict = {
   "settings.general.row.theme.title": "主題",
   "settings.general.row.theme.description": "自訂 OpenCode 的主題。",
   "settings.general.row.font.title": "程式碼字型",
-  "settings.general.row.font.description": "自訂程式碼區塊和終端機使用的字型",
+  "settings.general.row.font.description": "自訂程式碼區塊使用的字型",
+  "settings.general.row.terminalFont.title": "Terminal Font",
+  "settings.general.row.terminalFont.description": "Customise the font used in the terminal",
   "settings.general.row.uiFont.title": "介面字型",
   "settings.general.row.uiFont.description": "自訂整個介面使用的字型",
   "settings.general.row.followup.title": "後續追問行為",

+ 7 - 0
packages/app/src/index.css

@@ -1,5 +1,12 @@
 @import "@opencode-ai/ui/styles/tailwind";
 
+@font-face {
+  font-family: "JetBrainsMono Nerd Font Mono";
+  src: url("/assets/JetBrainsMonoNerdFontMono-Regular.woff2") format("woff2");
+  font-weight: normal;
+  font-style: normal;
+}
+
 @layer components {
   @keyframes session-progress-whip {
     0% {

+ 6 - 1
packages/app/src/pages/session/session-side-panel.tsx

@@ -52,7 +52,12 @@ export function SessionSidePanel(props: {
   const { sessionKey, tabs, view } = useSessionLayout()
 
   const isDesktop = createMediaQuery("(min-width: 768px)")
-  const shown = createMemo(() => platform.platform !== "desktop" || settings.general.showFileTree())
+  const shown = createMemo(
+    () =>
+      platform.platform !== "desktop" ||
+      import.meta.env.VITE_OPENCODE_CHANNEL !== "beta" ||
+      settings.general.showFileTree(),
+  )
 
   const reviewOpen = createMemo(() => isDesktop() && view().reviewPanel.opened())
   const fileOpen = createMemo(() => isDesktop() && shown() && layout.fileTree.opened())

+ 4 - 1
packages/app/src/pages/session/use-session-commands.tsx

@@ -70,7 +70,10 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
   })
   const activeFileTab = tabState.activeFileTab
   const closableTab = tabState.closableTab
-  const shown = () => platform.platform !== "desktop" || settings.general.showFileTree()
+  const shown = () =>
+    platform.platform !== "desktop" ||
+    import.meta.env.VITE_OPENCODE_CHANNEL !== "beta" ||
+    settings.general.showFileTree()
 
   const idle = { type: "idle" as const }
   const status = () => sync.data.session_status[params.id ?? ""] ?? idle

+ 1 - 1
packages/console/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-app",
-  "version": "1.4.7",
+  "version": "1.14.18",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 8 - 1
packages/console/app/src/i18n/ar.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "المؤسسات",
   "nav.zen": "Zen",
   "nav.login": "تسجيل الدخول",
-  "nav.free": "مجانا",
+  "nav.free": "تحميل",
   "nav.home": "الرئيسية",
   "nav.openMenu": "فتح القائمة",
   "nav.getStartedFree": "ابدأ مجانا",
@@ -558,6 +558,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "الاستخدام الحالي لـ",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "هو $",
 
+  "workspace.redeem.title": "استرداد قسيمة",
+  "workspace.redeem.subtitle": "استرد رمز القسيمة للحصول على رصيد أو مزايا.",
+  "workspace.redeem.placeholder": "أدخل رمز القسيمة",
+  "workspace.redeem.redeem": "استرداد",
+  "workspace.redeem.redeeming": "جارٍ الاسترداد...",
+  "workspace.redeem.success": "تم استرداد القسيمة بنجاح.",
+
   "workspace.reload.title": "إعادة الشحن التلقائي",
   "workspace.reload.disabled.before": "إعادة الشحن التلقائي",
   "workspace.reload.disabled.state": "معطّل",

+ 8 - 1
packages/console/app/src/i18n/br.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "Enterprise",
   "nav.zen": "Zen",
   "nav.login": "Entrar",
-  "nav.free": "Grátis",
+  "nav.free": "Download",
   "nav.home": "Início",
   "nav.openMenu": "Abrir menu",
   "nav.getStartedFree": "Começar grátis",
@@ -567,6 +567,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "Uso atual para",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "é $",
 
+  "workspace.redeem.title": "Resgatar Cupom",
+  "workspace.redeem.subtitle": "Resgate um código de cupom para receber créditos ou vantagens.",
+  "workspace.redeem.placeholder": "Digite o código do cupom",
+  "workspace.redeem.redeem": "Resgatar",
+  "workspace.redeem.redeeming": "Resgatando...",
+  "workspace.redeem.success": "Cupom resgatado com sucesso.",
+
   "workspace.reload.title": "Recarga Automática",
   "workspace.reload.disabled.before": "A recarga automática está",
   "workspace.reload.disabled.state": "desativada",

+ 8 - 1
packages/console/app/src/i18n/da.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "Enterprise",
   "nav.zen": "Zen",
   "nav.login": "Log ind",
-  "nav.free": "Gratis",
+  "nav.free": "Download",
   "nav.home": "Hjem",
   "nav.openMenu": "Åbn menu",
   "nav.getStartedFree": "Kom i gang gratis",
@@ -563,6 +563,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "Nuværende brug for",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "er $",
 
+  "workspace.redeem.title": "Indløs kupon",
+  "workspace.redeem.subtitle": "Indløs en kuponkode for at få kreditter eller fordele.",
+  "workspace.redeem.placeholder": "Indtast kuponkode",
+  "workspace.redeem.redeem": "Indløs",
+  "workspace.redeem.redeeming": "Indløser...",
+  "workspace.redeem.success": "Kuponen blev indløst.",
+
   "workspace.reload.title": "Automatisk genopfyldning",
   "workspace.reload.disabled.before": "Automatisk genopfyldning er",
   "workspace.reload.disabled.state": "deaktiveret",

+ 8 - 1
packages/console/app/src/i18n/de.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "Enterprise",
   "nav.zen": "Zen",
   "nav.login": "Anmelden",
-  "nav.free": "Kostenlos",
+  "nav.free": "Download",
   "nav.home": "Startseite",
   "nav.openMenu": "Menü öffnen",
   "nav.getStartedFree": "Kostenlos starten",
@@ -566,6 +566,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "Aktuelle Nutzung für",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "ist $",
 
+  "workspace.redeem.title": "Gutschein einlösen",
+  "workspace.redeem.subtitle": "Löse einen Gutscheincode ein, um Guthaben oder Vorteile zu erhalten.",
+  "workspace.redeem.placeholder": "Gutscheincode eingeben",
+  "workspace.redeem.redeem": "Einlösen",
+  "workspace.redeem.redeeming": "Wird eingelöst...",
+  "workspace.redeem.success": "Gutschein erfolgreich eingelöst.",
+
   "workspace.reload.title": "Auto-Reload",
   "workspace.reload.disabled.before": "Auto-Reload ist",
   "workspace.reload.disabled.state": "deaktiviert",

+ 8 - 1
packages/console/app/src/i18n/en.ts

@@ -8,7 +8,7 @@ export const dict = {
   "nav.zen": "Zen",
   "nav.go": "Go",
   "nav.login": "Login",
-  "nav.free": "Free",
+  "nav.free": "Download",
   "nav.home": "Home",
   "nav.openMenu": "Open menu",
   "nav.getStartedFree": "Get started for free",
@@ -559,6 +559,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "Current usage for",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "is $",
 
+  "workspace.redeem.title": "Redeem Coupon",
+  "workspace.redeem.subtitle": "Redeem a coupon code to claim credits or perks.",
+  "workspace.redeem.placeholder": "Enter coupon code",
+  "workspace.redeem.redeem": "Redeem",
+  "workspace.redeem.redeeming": "Redeeming...",
+  "workspace.redeem.success": "Coupon redeemed successfully.",
+
   "workspace.reload.title": "Auto Reload",
   "workspace.reload.disabled.before": "Auto reload is",
   "workspace.reload.disabled.state": "disabled",

+ 8 - 1
packages/console/app/src/i18n/es.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "Enterprise",
   "nav.zen": "Zen",
   "nav.login": "Iniciar sesión",
-  "nav.free": "Gratis",
+  "nav.free": "Descargar",
   "nav.home": "Inicio",
   "nav.openMenu": "Abrir menú",
   "nav.getStartedFree": "Empezar gratis",
@@ -567,6 +567,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "Uso actual para",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "es $",
 
+  "workspace.redeem.title": "Canjear cupón",
+  "workspace.redeem.subtitle": "Canjea un código de cupón para obtener crédito o beneficios.",
+  "workspace.redeem.placeholder": "Introduce el código del cupón",
+  "workspace.redeem.redeem": "Canjear",
+  "workspace.redeem.redeeming": "Canjeando...",
+  "workspace.redeem.success": "Cupón canjeado correctamente.",
+
   "workspace.reload.title": "Auto Recarga",
   "workspace.reload.disabled.before": "La auto recarga está",
   "workspace.reload.disabled.state": "deshabilitada",

+ 8 - 1
packages/console/app/src/i18n/fr.ts

@@ -12,7 +12,7 @@ export const dict = {
   "nav.enterprise": "Entreprise",
   "nav.zen": "Zen",
   "nav.login": "Se connecter",
-  "nav.free": "Gratuit",
+  "nav.free": "Télécharger",
   "nav.home": "Accueil",
   "nav.openMenu": "Ouvrir le menu",
   "nav.getStartedFree": "Commencer gratuitement",
@@ -569,6 +569,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "L'utilisation actuelle pour",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "est de",
 
+  "workspace.redeem.title": "Utiliser un coupon",
+  "workspace.redeem.subtitle": "Utilisez un code promo pour obtenir du crédit ou des avantages.",
+  "workspace.redeem.placeholder": "Saisissez le code promo",
+  "workspace.redeem.redeem": "Utiliser",
+  "workspace.redeem.redeeming": "Utilisation...",
+  "workspace.redeem.success": "Coupon utilisé avec succès.",
+
   "workspace.reload.title": "Rechargement automatique",
   "workspace.reload.disabled.before": "Le rechargement automatique est",
   "workspace.reload.disabled.state": "désactivé",

+ 8 - 1
packages/console/app/src/i18n/it.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "Enterprise",
   "nav.zen": "Zen",
   "nav.login": "Accedi",
-  "nav.free": "Gratis",
+  "nav.free": "Scarica",
   "nav.home": "Home",
   "nav.openMenu": "Apri menu",
   "nav.getStartedFree": "Inizia gratis",
@@ -565,6 +565,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "Utilizzo attuale per",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "è $",
 
+  "workspace.redeem.title": "Riscatta Coupon",
+  "workspace.redeem.subtitle": "Riscatta un codice coupon per ottenere credito o vantaggi.",
+  "workspace.redeem.placeholder": "Inserisci il codice coupon",
+  "workspace.redeem.redeem": "Riscatta",
+  "workspace.redeem.redeeming": "Riscatto in corso...",
+  "workspace.redeem.success": "Coupon riscattato con successo.",
+
   "workspace.reload.title": "Ricarica Auto",
   "workspace.reload.disabled.before": "La ricarica auto è",
   "workspace.reload.disabled.state": "disabilitata",

+ 8 - 1
packages/console/app/src/i18n/ja.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "エンタープライズ",
   "nav.zen": "Zen",
   "nav.login": "ログイン",
-  "nav.free": "無料",
+  "nav.free": "ダウンロード",
   "nav.home": "ホーム",
   "nav.openMenu": "メニューを開く",
   "nav.getStartedFree": "無料ではじめる",
@@ -564,6 +564,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "現在の使用状況(",
   "workspace.monthlyLimit.currentUsage.beforeAmount": ")は $",
 
+  "workspace.redeem.title": "クーポンを利用",
+  "workspace.redeem.subtitle": "クーポンコードを利用して、クレジットや特典を受け取ります。",
+  "workspace.redeem.placeholder": "クーポンコードを入力",
+  "workspace.redeem.redeem": "利用する",
+  "workspace.redeem.redeeming": "利用中...",
+  "workspace.redeem.success": "クーポンを利用しました。",
+
   "workspace.reload.title": "自動チャージ",
   "workspace.reload.disabled.before": "自動チャージは",
   "workspace.reload.disabled.state": "無効",

+ 8 - 1
packages/console/app/src/i18n/ko.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "엔터프라이즈",
   "nav.zen": "Zen",
   "nav.login": "로그인",
-  "nav.free": "무료",
+  "nav.free": "다운로드",
   "nav.home": "홈",
   "nav.openMenu": "메뉴 열기",
   "nav.getStartedFree": "무료로 시작하기",
@@ -558,6 +558,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "현재",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "사용량: $",
 
+  "workspace.redeem.title": "쿠폰 사용",
+  "workspace.redeem.subtitle": "쿠폰 코드를 사용해 크레딧이나 혜택을 받으세요.",
+  "workspace.redeem.placeholder": "쿠폰 코드를 입력하세요",
+  "workspace.redeem.redeem": "사용",
+  "workspace.redeem.redeeming": "사용 중...",
+  "workspace.redeem.success": "쿠폰을 성공적으로 사용했습니다.",
+
   "workspace.reload.title": "자동 충전",
   "workspace.reload.disabled.before": "자동 충전이",
   "workspace.reload.disabled.state": "비활성화",

+ 8 - 1
packages/console/app/src/i18n/no.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "Enterprise",
   "nav.zen": "Zen",
   "nav.login": "Logg inn",
-  "nav.free": "Gratis",
+  "nav.free": "Last ned",
   "nav.home": "Hjem",
   "nav.openMenu": "Åpne meny",
   "nav.getStartedFree": "Kom i gang gratis",
@@ -564,6 +564,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "Gjeldende forbruk for",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "er $",
 
+  "workspace.redeem.title": "Løs inn kupong",
+  "workspace.redeem.subtitle": "Løs inn en kupongkode for å få kreditt eller fordeler.",
+  "workspace.redeem.placeholder": "Skriv inn kupongkode",
+  "workspace.redeem.redeem": "Løs inn",
+  "workspace.redeem.redeeming": "Løser inn...",
+  "workspace.redeem.success": "Kupongen ble løst inn.",
+
   "workspace.reload.title": "Auto-påfyll",
   "workspace.reload.disabled.before": "Auto-påfyll er",
   "workspace.reload.disabled.state": "deaktivert",

+ 8 - 1
packages/console/app/src/i18n/pl.ts

@@ -10,7 +10,7 @@ export const dict = {
   "nav.enterprise": "Enterprise",
   "nav.zen": "Zen",
   "nav.login": "Zaloguj się",
-  "nav.free": "Darmowe",
+  "nav.free": "Pobierz",
   "nav.home": "Strona główna",
   "nav.openMenu": "Otwórz menu",
   "nav.getStartedFree": "Zacznij za darmo",
@@ -565,6 +565,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "Aktualne użycie za",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "wynosi $",
 
+  "workspace.redeem.title": "Zrealizuj kupon",
+  "workspace.redeem.subtitle": "Zrealizuj kod kuponu, aby otrzymać środki lub korzyści.",
+  "workspace.redeem.placeholder": "Wpisz kod kuponu",
+  "workspace.redeem.redeem": "Zrealizuj",
+  "workspace.redeem.redeeming": "Realizowanie...",
+  "workspace.redeem.success": "Kupon został zrealizowany.",
+
   "workspace.reload.title": "Automatyczne doładowanie",
   "workspace.reload.disabled.before": "Automatyczne doładowanie jest",
   "workspace.reload.disabled.state": "wyłączone",

+ 8 - 1
packages/console/app/src/i18n/ru.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "Enterprise",
   "nav.zen": "Zen",
   "nav.login": "Войти",
-  "nav.free": "Бесплатно",
+  "nav.free": "Скачать",
   "nav.home": "Главная",
   "nav.openMenu": "Открыть меню",
   "nav.getStartedFree": "Начать бесплатно",
@@ -571,6 +571,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "Текущее использование за",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "составляет $",
 
+  "workspace.redeem.title": "Активировать купон",
+  "workspace.redeem.subtitle": "Активируйте код купона, чтобы получить кредит или бонусы.",
+  "workspace.redeem.placeholder": "Введите код купона",
+  "workspace.redeem.redeem": "Активировать",
+  "workspace.redeem.redeeming": "Активация...",
+  "workspace.redeem.success": "Купон успешно активирован.",
+
   "workspace.reload.title": "Автопополнение",
   "workspace.reload.disabled.before": "Автопополнение",
   "workspace.reload.disabled.state": "отключено",

+ 8 - 1
packages/console/app/src/i18n/th.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "องค์กร",
   "nav.zen": "Zen",
   "nav.login": "เข้าสู่ระบบ",
-  "nav.free": "ฟรี",
+  "nav.free": "ดาวน์โหลด",
   "nav.home": "หน้าหลัก",
   "nav.openMenu": "เปิดเมนู",
   "nav.getStartedFree": "เริ่มต้นฟรี",
@@ -560,6 +560,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "การใช้งานปัจจุบันสำหรับ",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "คือ $",
 
+  "workspace.redeem.title": "แลกคูปอง",
+  "workspace.redeem.subtitle": "แลกรหัสคูปองเพื่อรับเครดิตหรือสิทธิพิเศษ",
+  "workspace.redeem.placeholder": "กรอกรหัสคูปอง",
+  "workspace.redeem.redeem": "แลก",
+  "workspace.redeem.redeeming": "กำลังแลก...",
+  "workspace.redeem.success": "แลกคูปองสำเร็จ",
+
   "workspace.reload.title": "โหลดซ้ำอัตโนมัติ",
   "workspace.reload.disabled.before": "การโหลดซ้ำอัตโนมัติ",
   "workspace.reload.disabled.state": "ปิดใช้งานอยู่",

+ 8 - 1
packages/console/app/src/i18n/tr.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "Kurumsal",
   "nav.zen": "Zen",
   "nav.login": "Giriş",
-  "nav.free": "Ücretsiz",
+  "nav.free": "İndir",
   "nav.home": "Ana sayfa",
   "nav.openMenu": "Menüyü aç",
   "nav.getStartedFree": "Ücretsiz başla",
@@ -567,6 +567,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "Şu anki kullanım",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "$",
 
+  "workspace.redeem.title": "Kupon Kullan",
+  "workspace.redeem.subtitle": "Kredi veya avantajlardan yararlanmak için bir kupon kodu kullanın.",
+  "workspace.redeem.placeholder": "Kupon kodunu girin",
+  "workspace.redeem.redeem": "Kullan",
+  "workspace.redeem.redeeming": "Kullanılıyor...",
+  "workspace.redeem.success": "Kupon başarıyla kullanıldı.",
+
   "workspace.reload.title": "Otomatik Yeniden Yükleme",
   "workspace.reload.disabled.before": "Otomatik yeniden yükleme:",
   "workspace.reload.disabled.state": "devre dışı",

+ 8 - 1
packages/console/app/src/i18n/zh.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "企业版",
   "nav.zen": "Zen",
   "nav.login": "登录",
-  "nav.free": "免费",
+  "nav.free": "下载",
   "nav.home": "首页",
   "nav.openMenu": "打开菜单",
   "nav.getStartedFree": "免费开始",
@@ -542,6 +542,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "当前",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "的使用量为 $",
 
+  "workspace.redeem.title": "兑换优惠券",
+  "workspace.redeem.subtitle": "兑换优惠码以领取充值额度或权益。",
+  "workspace.redeem.placeholder": "输入优惠码",
+  "workspace.redeem.redeem": "兑换",
+  "workspace.redeem.redeeming": "兑换中...",
+  "workspace.redeem.success": "优惠券兑换成功。",
+
   "workspace.reload.title": "自动充值",
   "workspace.reload.disabled.before": "自动充值已",
   "workspace.reload.disabled.state": "禁用",

+ 8 - 1
packages/console/app/src/i18n/zht.ts

@@ -11,7 +11,7 @@ export const dict = {
   "nav.enterprise": "企業",
   "nav.zen": "Zen",
   "nav.login": "登入",
-  "nav.free": "免費",
+  "nav.free": "下載",
   "nav.home": "首頁",
   "nav.openMenu": "開啟選單",
   "nav.getStartedFree": "免費開始使用",
@@ -542,6 +542,13 @@ export const dict = {
   "workspace.monthlyLimit.currentUsage.beforeMonth": "目前",
   "workspace.monthlyLimit.currentUsage.beforeAmount": "的使用量為 $",
 
+  "workspace.redeem.title": "兌換優惠券",
+  "workspace.redeem.subtitle": "兌換優惠碼以領取儲值額度或權益。",
+  "workspace.redeem.placeholder": "輸入優惠碼",
+  "workspace.redeem.redeem": "兌換",
+  "workspace.redeem.redeeming": "兌換中...",
+  "workspace.redeem.success": "優惠券兌換成功。",
+
   "workspace.reload.title": "自動儲值",
   "workspace.reload.disabled.before": "自動儲值已",
   "workspace.reload.disabled.state": "停用",

+ 69 - 2
packages/console/app/src/routes/api/enterprise.ts

@@ -1,5 +1,6 @@
 import type { APIEvent } from "@solidjs/start/server"
 import { AWS } from "@opencode-ai/console-core/aws.js"
+import { Resource } from "@opencode-ai/console-resource"
 import { i18n } from "~/i18n"
 import { localeFromRequest } from "~/lib/language"
 import { createLead } from "~/lib/salesforce"
@@ -14,6 +15,64 @@ interface EnterpriseFormData {
   message: string
 }
 
+const EMAIL_OCTOPUS_LIST_ID = "1b381e5e-39bd-11f1-ba4a-cdd4791f0c43"
+
+function splitFullName(fullName: string) {
+  const parts = fullName
+    .trim()
+    .split(/\s+/)
+    .filter((p) => p.length > 0)
+  if (parts.length === 0) return { firstName: "", lastName: "" }
+  if (parts.length === 1) return { firstName: parts[0], lastName: "" }
+  return { firstName: parts[0], lastName: parts.slice(1).join(" ") }
+}
+
+function getEmailOctopusApiKey() {
+  if (process.env.EMAILOCTOPUS_API_KEY) return process.env.EMAILOCTOPUS_API_KEY
+  try {
+    return Resource.EMAILOCTOPUS_API_KEY.value
+  } catch {
+    return
+  }
+}
+
+function subscribe(email: string, fullName: string) {
+  const apiKey = getEmailOctopusApiKey()
+  if (!apiKey) {
+    console.warn("Skipping EmailOctopus subscribe: missing API key")
+    return Promise.resolve(false)
+  }
+
+  const name = splitFullName(fullName)
+  const fields: Record<string, string> = {}
+  if (name.firstName) fields.FirstName = name.firstName
+  if (name.lastName) fields.LastName = name.lastName
+
+  const payload: { email_address: string; fields?: Record<string, string> } = { email_address: email }
+  if (Object.keys(fields).length) payload.fields = fields
+
+  return fetch(`https://api.emailoctopus.com/lists/${EMAIL_OCTOPUS_LIST_ID}/contacts`, {
+    method: "PUT",
+    headers: {
+      Authorization: `Bearer ${apiKey}`,
+      "Content-Type": "application/json",
+    },
+    body: JSON.stringify(payload),
+  }).then(
+    (res) => {
+      if (!res.ok) {
+        console.error("EmailOctopus subscribe failed:", res.status, res.statusText)
+        return false
+      }
+      return true
+    },
+    (err) => {
+      console.error("Failed to subscribe enterprise email:", err)
+      return false
+    },
+  )
+}
+
 export async function POST(event: APIEvent) {
   const dict = i18n(localeFromRequest(event.request))
   try {
@@ -41,7 +100,7 @@ ${body.role}<br>
 ${body.company ? `${body.company}<br>` : ""}${body.email}<br>
 ${body.phone ? `${body.phone}<br>` : ""}`.trim()
 
-    const [lead, mail] = await Promise.all([
+    const [lead, mail, octopus] = await Promise.all([
       createLead({
         name: body.name,
         role: body.role,
@@ -49,6 +108,9 @@ ${body.phone ? `${body.phone}<br>` : ""}`.trim()
         email: body.email,
         phone: body.phone,
         message: body.message,
+      }).catch((err) => {
+        console.error("Failed to create Salesforce lead:", err)
+        return false
       }),
       AWS.sendEmail({
         to: "[email protected]",
@@ -62,9 +124,14 @@ ${body.phone ? `${body.phone}<br>` : ""}`.trim()
           return false
         },
       ),
+      subscribe(body.email, body.name),
     ])
 
-    if (!lead && !mail) {
+    if (!lead && !mail && !octopus) {
+      if (import.meta.env.DEV) {
+        console.warn("Enterprise inquiry accepted in dev mode without integrations", { email: body.email })
+        return Response.json({ success: true, message: dict["enterprise.form.success.submitted"] }, { status: 200 })
+      }
       console.error("Enterprise inquiry delivery failed", { email: body.email })
       return Response.json({ error: dict["enterprise.form.error.internalServer"] }, { status: 500 })
     }

+ 7 - 0
packages/console/app/src/routes/stripe/webhook.ts

@@ -9,6 +9,7 @@ import { Actor } from "@opencode-ai/console-core/actor.js"
 import { Resource } from "@opencode-ai/console-resource"
 import { LiteData } from "@opencode-ai/console-core/lite.js"
 import { BlackData } from "@opencode-ai/console-core/black.js"
+import { User } from "@opencode-ai/console-core/user.js"
 
 export async function POST(input: APIEvent) {
   const body = await Billing.stripe().webhooks.constructEventAsync(
@@ -109,6 +110,8 @@ export async function POST(input: APIEvent) {
       if (type === "lite") {
         const workspaceID = body.data.object.metadata?.workspaceID
         const userID = body.data.object.metadata?.userID
+        const userEmail = body.data.object.metadata?.userEmail
+        const coupon = body.data.object.metadata?.coupon
         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
@@ -156,6 +159,10 @@ export async function POST(input: APIEvent) {
               id: Identifier.create("lite"),
               userID: userID,
             })
+
+            if (userEmail && coupon === LiteData.firstMonth100Coupon) {
+              await Billing.redeemCoupon(userEmail, "GOFREEMONTH")
+            }
           })
         })
       }

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

@@ -3,6 +3,7 @@ import { BillingSection } from "./billing-section"
 import { ReloadSection } from "./reload-section"
 import { PaymentSection } from "./payment-section"
 import { BlackSection } from "./black-section"
+import { RedeemSection } from "./redeem-section"
 import { createMemo, Show } from "solid-js"
 import { createAsync, useParams } from "@solidjs/router"
 import { queryBillingInfo, querySessionInfo } from "../../common"
@@ -21,6 +22,7 @@ export default function () {
             <BlackSection />
           </Show>
           <BillingSection />
+          <RedeemSection />
           <Show when={billingInfo()?.customerID}>
             <ReloadSection />
             <MonthlyLimitSection />

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

@@ -0,0 +1,61 @@
+.root {
+  [data-slot="redeem-container"] {
+    display: flex;
+    flex-direction: column;
+    gap: var(--space-3);
+    min-width: 20rem;
+    width: fit-content;
+
+    @media (max-width: 30rem) {
+      width: 100%;
+    }
+  }
+
+  [data-slot="redeem-form"] {
+    display: flex;
+    flex-direction: column;
+    gap: var(--space-2);
+
+    [data-slot="input-row"] {
+      display: flex;
+      gap: var(--space-2);
+      align-items: stretch;
+
+      @media (max-width: 30rem) {
+        flex-direction: column;
+      }
+    }
+
+    input {
+      flex: 1;
+      padding: var(--space-2) var(--space-3);
+      border: 1px solid var(--color-border);
+      border-radius: var(--border-radius-sm);
+      background-color: var(--color-bg);
+      color: var(--color-text);
+      font-size: var(--font-size-sm);
+      font-family: var(--font-mono);
+
+      &:focus {
+        outline: none;
+        border-color: var(--color-accent);
+      }
+
+      &::placeholder {
+        color: var(--color-text-disabled);
+      }
+    }
+
+    [data-slot="form-error"] {
+      color: var(--color-danger);
+      font-size: var(--font-size-sm);
+      line-height: 1.4;
+    }
+
+    [data-slot="form-success"] {
+      color: var(--color-success, var(--color-accent));
+      font-size: var(--font-size-sm);
+      line-height: 1.4;
+    }
+  }
+}

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

@@ -0,0 +1,71 @@
+import { json, action, useParams, useSubmission } from "@solidjs/router"
+import { Show } from "solid-js"
+import { withActor } from "~/context/auth.withActor"
+import { Billing } from "@opencode-ai/console-core/billing.js"
+import { User } from "@opencode-ai/console-core/user.js"
+import { Actor } from "@opencode-ai/console-core/actor.js"
+import { CouponType } from "@opencode-ai/console-core/schema/billing.sql.js"
+import styles from "./redeem-section.module.css"
+import { queryBillingInfo } from "../../common"
+import { useI18n } from "~/context/i18n"
+import { formError, localizeError } from "~/lib/form-error"
+
+const redeem = action(async (form: FormData) => {
+  "use server"
+  const workspaceID = form.get("workspaceID") as string | null
+  if (!workspaceID) return { error: formError.workspaceRequired }
+  const code = (form.get("code") as string | null)?.trim().toUpperCase()
+  if (!code) return { error: "Coupon code is required." }
+  if (!(CouponType as readonly string[]).includes(code)) return { error: "Invalid coupon code." }
+
+  return json(
+    await withActor(async () => {
+      const actor = Actor.assert("user")
+      const email = await User.getAuthEmail(actor.properties.userID)
+      if (!email) return { error: "No email on account." }
+      return Billing.redeemCoupon(email, code as (typeof CouponType)[number])
+        .then(() => ({ error: undefined, data: true }))
+        .catch((e) => ({ error: e.message as string }))
+    }, workspaceID),
+    { revalidate: queryBillingInfo.key },
+  )
+}, "billing.redeemCoupon")
+
+export function RedeemSection() {
+  const params = useParams()
+  const i18n = useI18n()
+  const submission = useSubmission(redeem)
+
+  return (
+    <section class={styles.root}>
+      <div data-slot="section-title">
+        <h2>{i18n.t("workspace.redeem.title")}</h2>
+        <p>{i18n.t("workspace.redeem.subtitle")}</p>
+      </div>
+      <div data-slot="redeem-container">
+        <form action={redeem} method="post" data-slot="redeem-form">
+          <div data-slot="input-row">
+            <input
+              required
+              data-component="input"
+              name="code"
+              type="text"
+              autocomplete="off"
+              placeholder={i18n.t("workspace.redeem.placeholder")}
+            />
+            <button type="submit" data-color="primary" disabled={submission.pending}>
+              {submission.pending ? i18n.t("workspace.redeem.redeeming") : i18n.t("workspace.redeem.redeem")}
+            </button>
+          </div>
+          <Show when={submission.result && (submission.result as any).error}>
+            {(err: any) => <div data-slot="form-error">{localizeError(i18n.t, err())}</div>}
+          </Show>
+          <Show when={submission.result && !(submission.result as any).error && (submission.result as any).data}>
+            <div data-slot="form-success">{i18n.t("workspace.redeem.success")}</div>
+          </Show>
+          <input type="hidden" name="workspaceID" value={params.id} />
+        </form>
+      </div>
+    </section>
+  )
+}

+ 24 - 3
packages/console/app/src/routes/zen/util/handler.ts

@@ -45,6 +45,7 @@ import { LiteData } from "@opencode-ai/console-core/lite.js"
 import { Resource } from "@opencode-ai/console-resource"
 import { i18n, type Key } from "~/i18n"
 import { localeFromRequest } from "~/lib/language"
+import { createModelTpmLimiter } from "./modelTpmLimiter"
 
 type ZenData = Awaited<ReturnType<typeof ZenData.list>>
 type RetryOptions = {
@@ -121,6 +122,8 @@ export async function handler(
     const authInfo = await authenticate(modelInfo, zenApiKey)
     const billingSource = validateBilling(authInfo, modelInfo)
     logger.metric({ source: billingSource })
+    const modelTpmLimiter = createModelTpmLimiter(modelInfo.providers)
+    const modelTpmLimits = await modelTpmLimiter?.check()
 
     const retriableRequest = async (retry: RetryOptions = { excludeProviders: [], retryCount: 0 }) => {
       const providerInfo = selectProvider(
@@ -133,6 +136,7 @@ export async function handler(
         trialProviders,
         retry,
         stickyProvider,
+        modelTpmLimits,
       )
       validateModelSettings(billingSource, authInfo)
       updateProviderKey(authInfo, providerInfo)
@@ -229,6 +233,7 @@ export async function handler(
         const usageInfo = providerInfo.normalizeUsage(json.usage)
         const costInfo = calculateCost(modelInfo, usageInfo)
         await trialLimiter?.track(usageInfo)
+        await modelTpmLimiter?.track(providerInfo.id, providerInfo.model, usageInfo)
         await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo)
         await reload(billingSource, authInfo, costInfo)
         json.cost = calculateOccurredCost(billingSource, costInfo)
@@ -278,6 +283,7 @@ export async function handler(
                   const usageInfo = providerInfo.normalizeUsage(usage)
                   const costInfo = calculateCost(modelInfo, usageInfo)
                   await trialLimiter?.track(usageInfo)
+                  await modelTpmLimiter?.track(providerInfo.id, providerInfo.model, usageInfo)
                   await trackUsage(sessionId, billingSource, authInfo, modelInfo, providerInfo, usageInfo, costInfo)
                   await reload(billingSource, authInfo, costInfo)
                   const cost = calculateOccurredCost(billingSource, costInfo)
@@ -433,12 +439,16 @@ export async function handler(
     trialProviders: string[] | undefined,
     retry: RetryOptions,
     stickyProvider: string | undefined,
+    modelTpmLimits: Record<string, number> | undefined,
   ) {
     const modelProvider = (() => {
+      // Byok is top priority b/c if user set their own API key, we should use it
+      // instead of using the sticky provider for the same session
       if (authInfo?.provider?.credentials) {
         return modelInfo.providers.find((provider) => provider.id === modelInfo.byokProvider)
       }
 
+      // Always use the same provider for the same session
       if (stickyProvider) {
         const provider = modelInfo.providers.find((provider) => provider.id === stickyProvider)
         if (provider) return provider
@@ -451,10 +461,20 @@ export async function handler(
       }
 
       if (retry.retryCount !== MAX_FAILOVER_RETRIES) {
-        const providers = modelInfo.providers
+        const allProviders = modelInfo.providers
           .filter((provider) => !provider.disabled)
+          .filter((provider) => provider.weight !== 0)
           .filter((provider) => !retry.excludeProviders.includes(provider.id))
-          .flatMap((provider) => Array<typeof provider>(provider.weight ?? 1).fill(provider))
+          .filter((provider) => {
+            if (!provider.tpmLimit) return true
+            const usage = modelTpmLimits?.[`${provider.id}/${provider.model}`] ?? 0
+            return usage < provider.tpmLimit * 1_000_000
+          })
+
+        const topPriority = Math.min(...allProviders.map((p) => p.priority))
+        const providers = allProviders
+          .filter((p) => p.priority <= topPriority)
+          .flatMap((provider) => Array<typeof provider>(provider.weight).fill(provider))
 
         // Use the last 4 characters of session ID to select a provider
         const identifier = sessionId.length ? sessionId : ip
@@ -742,7 +762,8 @@ export async function handler(
     const billing = authInfo.billing
     const billingUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/billing`
     const membersUrl = `https://opencode.ai/workspace/${authInfo.workspaceID}/members`
-    if (!billing.paymentMethodID) throw new CreditsError(t("zen.api.error.noPaymentMethod", { billingUrl }))
+    if (!billing.paymentMethodID && billing.balance <= 0)
+      throw new CreditsError(t("zen.api.error.noPaymentMethod", { billingUrl }))
     if (billing.balance <= 0) throw new CreditsError(t("zen.api.error.insufficientBalance", { billingUrl }))
 
     const now = new Date()

+ 51 - 0
packages/console/app/src/routes/zen/util/modelTpmLimiter.ts

@@ -0,0 +1,51 @@
+import { and, Database, eq, inArray, sql } from "@opencode-ai/console-core/drizzle/index.js"
+import { ModelRateLimitTable } from "@opencode-ai/console-core/schema/ip.sql.js"
+import { UsageInfo } from "./provider/provider"
+
+export function createModelTpmLimiter(providers: { id: string; model: string; tpmLimit?: number }[]) {
+  const keys = providers.filter((p) => p.tpmLimit).map((p) => `${p.id}/${p.model}`)
+  if (keys.length === 0) return
+
+  const yyyyMMddHHmm = new Date(Date.now())
+    .toISOString()
+    .replace(/[^0-9]/g, "")
+    .substring(0, 12)
+
+  return {
+    check: async () => {
+      const data = await Database.use((tx) =>
+        tx
+          .select()
+          .from(ModelRateLimitTable)
+          .where(and(inArray(ModelRateLimitTable.key, keys), eq(ModelRateLimitTable.interval, yyyyMMddHHmm))),
+      )
+
+      // convert to map of model to count
+      return data.reduce(
+        (acc, curr) => {
+          acc[curr.key] = curr.count
+          return acc
+        },
+        {} as Record<string, number>,
+      )
+    },
+    track: async (id: string, model: string, usageInfo: UsageInfo) => {
+      const key = `${id}/${model}`
+      if (!keys.includes(key)) return
+      const usage =
+        usageInfo.inputTokens +
+        usageInfo.outputTokens +
+        (usageInfo.reasoningTokens ?? 0) +
+        (usageInfo.cacheReadTokens ?? 0) +
+        (usageInfo.cacheWrite5mTokens ?? 0) +
+        (usageInfo.cacheWrite1hTokens ?? 0)
+      if (usage <= 0) return
+      await Database.use((tx) =>
+        tx
+          .insert(ModelRateLimitTable)
+          .values({ key, interval: yyyyMMddHHmm, count: usage })
+          .onDuplicateKeyUpdate({ set: { count: sql`${ModelRateLimitTable.count} + ${usage}` } }),
+      )
+    },
+  }
+}

+ 6 - 0
packages/console/core/migrations/20260417071612_tidy_diamondback/migration.sql

@@ -0,0 +1,6 @@
+CREATE TABLE `model_rate_limit` (
+	`key` varchar(255) NOT NULL,
+	`interval` varchar(40) NOT NULL,
+	`count` int NOT NULL,
+	CONSTRAINT PRIMARY KEY(`key`,`interval`)
+);

+ 2567 - 0
packages/console/core/migrations/20260417071612_tidy_diamondback/snapshot.json

@@ -0,0 +1,2567 @@
+{
+  "version": "6",
+  "dialect": "mysql",
+  "id": "93c492af-c95b-4213-9fc2-38c3dd10374d",
+  "prevIds": ["a09a925d-6cdd-4e7c-b8b1-11c259928b4c"],
+  "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_rate_limit",
+      "entityType": "tables"
+    },
+    {
+      "name": "model_rate_limit",
+      "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(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "key",
+      "entityType": "columns",
+      "table": "key_rate_limit"
+    },
+    {
+      "type": "varchar(40)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "interval",
+      "entityType": "columns",
+      "table": "key_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": "key_rate_limit"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "key",
+      "entityType": "columns",
+      "table": "model_rate_limit"
+    },
+    {
+      "type": "varchar(40)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "interval",
+      "entityType": "columns",
+      "table": "model_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": "model_rate_limit"
+    },
+    {
+      "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": ["key", "interval"],
+      "name": "PRIMARY",
+      "table": "key_rate_limit",
+      "entityType": "pks"
+    },
+    {
+      "columns": ["key", "interval"],
+      "name": "PRIMARY",
+      "table": "model_rate_limit",
+      "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": []
+}

+ 6 - 0
packages/console/core/migrations/20260418195905_shocking_marvel_zombies/migration.sql

@@ -0,0 +1,6 @@
+CREATE TABLE `coupon` (
+	`email` varchar(255),
+	`type` enum('BUILDATHON','GOFREEMONTH') NOT NULL,
+	`time_redeemed` timestamp(3),
+	CONSTRAINT PRIMARY KEY(`email`,`type`)
+);

+ 2619 - 0
packages/console/core/migrations/20260418195905_shocking_marvel_zombies/snapshot.json

@@ -0,0 +1,2619 @@
+{
+  "version": "6",
+  "dialect": "mysql",
+  "id": "18b4281c-1609-47d8-9d51-0b08e3925f2b",
+  "prevIds": ["93c492af-c95b-4213-9fc2-38c3dd10374d"],
+  "ddl": [
+    {
+      "name": "account",
+      "entityType": "tables"
+    },
+    {
+      "name": "auth",
+      "entityType": "tables"
+    },
+    {
+      "name": "benchmark",
+      "entityType": "tables"
+    },
+    {
+      "name": "billing",
+      "entityType": "tables"
+    },
+    {
+      "name": "coupon",
+      "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_rate_limit",
+      "entityType": "tables"
+    },
+    {
+      "name": "model_rate_limit",
+      "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(255)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "email",
+      "entityType": "columns",
+      "table": "coupon"
+    },
+    {
+      "type": "enum('BUILDATHON','GOFREEMONTH')",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "type",
+      "entityType": "columns",
+      "table": "coupon"
+    },
+    {
+      "type": "timestamp(3)",
+      "notNull": false,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "time_redeemed",
+      "entityType": "columns",
+      "table": "coupon"
+    },
+    {
+      "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(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "key",
+      "entityType": "columns",
+      "table": "key_rate_limit"
+    },
+    {
+      "type": "varchar(40)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "interval",
+      "entityType": "columns",
+      "table": "key_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": "key_rate_limit"
+    },
+    {
+      "type": "varchar(255)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "key",
+      "entityType": "columns",
+      "table": "model_rate_limit"
+    },
+    {
+      "type": "varchar(40)",
+      "notNull": true,
+      "autoIncrement": false,
+      "default": null,
+      "onUpdateNow": false,
+      "onUpdateNowFsp": null,
+      "charSet": null,
+      "collation": null,
+      "generated": null,
+      "name": "interval",
+      "entityType": "columns",
+      "table": "model_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": "model_rate_limit"
+    },
+    {
+      "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": ["email", "type"],
+      "name": "PRIMARY",
+      "table": "coupon",
+      "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": ["key", "interval"],
+      "name": "PRIMARY",
+      "table": "key_rate_limit",
+      "entityType": "pks"
+    },
+    {
+      "columns": ["key", "interval"],
+      "name": "PRIMARY",
+      "table": "model_rate_limit",
+      "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": []
+}

+ 1 - 1
packages/console/core/package.json

@@ -1,7 +1,7 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
   "name": "@opencode-ai/console-core",
-  "version": "1.4.7",
+  "version": "1.14.18",
   "private": true,
   "type": "module",
   "license": "MIT",

+ 24 - 0
packages/console/core/script/create-coupon.ts

@@ -0,0 +1,24 @@
+import { Database } from "../src/drizzle/index.js"
+import { CouponTable, CouponType } from "../src/schema/billing.sql.js"
+
+const email = process.argv[2]
+const type = process.argv[3] as (typeof CouponType)[number]
+
+if (!email || !type) {
+  console.error(`Usage: bun create-coupon.ts <email> <${CouponType.join("|")}>`)
+  process.exit(1)
+}
+
+if (!(CouponType as readonly string[]).includes(type)) {
+  console.error(`Error: type must be one of ${CouponType.join(", ")}`)
+  process.exit(1)
+}
+
+await Database.use((tx) =>
+  tx.insert(CouponTable).values({
+    email,
+    type,
+  }),
+)
+
+console.log(`Created ${type} coupon for ${email}`)

+ 49 - 5
packages/console/core/src/billing.ts

@@ -1,6 +1,14 @@
 import { Stripe } from "stripe"
-import { Database, eq, sql } from "./drizzle"
-import { BillingTable, LiteTable, PaymentTable, SubscriptionTable, UsageTable } from "./schema/billing.sql"
+import { and, Database, eq, isNull, sql } from "./drizzle"
+import {
+  BillingTable,
+  CouponTable,
+  CouponType,
+  LiteTable,
+  PaymentTable,
+  SubscriptionTable,
+  UsageTable,
+} from "./schema/billing.sql"
 import { Actor } from "./actor"
 import { fn } from "./util/fn"
 import { z } from "zod"
@@ -147,6 +155,37 @@ export namespace Billing {
     return amountInMicroCents
   }
 
+  export const redeemCoupon = async (email: string, type: (typeof CouponType)[number]) => {
+    const coupon = await Database.use((tx) =>
+      tx
+        .select()
+        .from(CouponTable)
+        .where(and(eq(CouponTable.email, email), eq(CouponTable.type, type)))
+        .then((rows) => rows[0]),
+    )
+    if (!coupon) throw new Error("Invalid coupon code")
+    if (coupon.timeRedeemed) throw new Error("Coupon already redeemed")
+
+    if (type === "BUILDATHON") await grantCredit(Actor.workspace(), 500)
+
+    await Database.use((tx) =>
+      tx
+        .update(CouponTable)
+        .set({ timeRedeemed: sql`now()` })
+        .where(and(eq(CouponTable.email, email), eq(CouponTable.type, type))),
+    )
+  }
+
+  export const hasCoupon = async (email: string, type: (typeof CouponType)[number]) => {
+    return await Database.use((tx) =>
+      tx
+        .select()
+        .from(CouponTable)
+        .where(and(eq(CouponTable.email, email), eq(CouponTable.type, type), isNull(CouponTable.timeRedeemed)))
+        .then((rows) => rows.length > 0),
+    )
+  }
+
   export const setMonthlyLimit = fn(z.number(), async (input) => {
     return await Database.use((tx) =>
       tx
@@ -245,16 +284,19 @@ export namespace Billing {
       const user = Actor.assert("user")
       const { successUrl, cancelUrl, method } = input
 
-      const email = await User.getAuthEmail(user.properties.userID)
+      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 coupon = (await Billing.hasCoupon(email, "GOFREEMONTH"))
+        ? LiteData.firstMonth100Coupon
+        : LiteData.firstMonth50Coupon
       const createSession = () =>
         Billing.stripe().checkout.sessions.create({
           mode: "subscription",
-          discounts: [{ coupon: LiteData.firstMonthCoupon(email!) }],
+          discounts: [{ coupon }],
           ...(billing.customerID
             ? {
                 customer: billing.customerID,
@@ -264,7 +306,7 @@ export namespace Billing {
                 },
               }
             : {
-                customer_email: email!,
+                customer_email: email,
               }),
           ...(() => {
             if (method === "alipay") {
@@ -312,6 +354,8 @@ export namespace Billing {
             metadata: {
               workspaceID: Actor.workspace(),
               userID: user.properties.userID,
+              userEmail: email,
+              coupon,
               type: "lite",
             },
           },

+ 2 - 6
packages/console/core/src/lite.ts

@@ -11,11 +11,7 @@ export namespace LiteData {
   export const productID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.product)
   export const priceID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.price)
   export const priceInr = fn(z.void(), () => Resource.ZEN_LITE_PRICE.priceInr)
-  export const firstMonthCoupon = fn(z.string(), (email) => {
-    const invitees = Resource.ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES.value.split(",")
-    return invitees.includes(email)
-      ? Resource.ZEN_LITE_PRICE.firstMonth100Coupon
-      : Resource.ZEN_LITE_PRICE.firstMonth50Coupon
-  })
+  export const firstMonth100Coupon = Resource.ZEN_LITE_PRICE.firstMonth100Coupon
+  export const firstMonth50Coupon = Resource.ZEN_LITE_PRICE.firstMonth50Coupon
   export const planName = fn(z.void(), () => "lite")
 }

+ 11 - 4
packages/console/core/src/model.ts

@@ -34,6 +34,8 @@ export namespace ZenData {
       z.object({
         id: z.string(),
         model: z.string(),
+        priority: z.number().optional(),
+        tpmLimit: z.number().optional(),
         weight: z.number().optional(),
         disabled: z.boolean().optional(),
         storeModel: z.string().optional(),
@@ -123,10 +125,16 @@ export namespace ZenData {
       ),
       models: (() => {
         const normalize = (model: z.infer<typeof ModelSchema>) => {
-          const composite = model.providers.find((p) => compositeProviders[p.id].length > 1)
+          const providers = model.providers.map((p) => ({
+            ...p,
+            priority: p.priority ?? Infinity,
+            weight: p.weight ?? 1,
+          }))
+          const composite = providers.find((p) => compositeProviders[p.id].length > 1)
           if (!composite)
             return {
               trialProvider: model.trialProvider ? [model.trialProvider] : undefined,
+              providers,
             }
 
           const weightMulti = compositeProviders[composite.id].length
@@ -137,17 +145,16 @@ export namespace ZenData {
               if (model.trialProvider === composite.id) return compositeProviders[composite.id].map((p) => p.id)
               return [model.trialProvider]
             })(),
-            providers: model.providers.flatMap((p) =>
+            providers: providers.flatMap((p) =>
               p.id === composite.id
                 ? compositeProviders[p.id].map((sub) => ({
                     ...p,
                     id: sub.id,
-                    weight: p.weight ?? 1,
                   }))
                 : [
                     {
                       ...p,
-                      weight: (p.weight ?? 1) * weightMulti,
+                      weight: p.weight * weightMulti,
                     },
                   ],
             ),

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

@@ -1,4 +1,15 @@
-import { bigint, boolean, index, int, json, mysqlEnum, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
+import {
+  bigint,
+  boolean,
+  index,
+  int,
+  json,
+  mysqlEnum,
+  mysqlTable,
+  primaryKey,
+  uniqueIndex,
+  varchar,
+} from "drizzle-orm/mysql-core"
 import { timestamps, ulid, utc, workspaceColumns } from "../drizzle/types"
 import { workspaceIndexes } from "./workspace.sql"
 
@@ -121,3 +132,14 @@ export const UsageTable = mysqlTable(
   },
   (table) => [...workspaceIndexes(table), index("usage_time_created").on(table.workspaceID, table.timeCreated)],
 )
+
+export const CouponType = ["BUILDATHON", "GOFREEMONTH"] as const
+export const CouponTable = mysqlTable(
+  "coupon",
+  {
+    email: varchar("email", { length: 255 }),
+    type: mysqlEnum("type", CouponType).notNull(),
+    timeRedeemed: utc("time_redeemed"),
+  },
+  (table) => [primaryKey({ columns: [table.email, table.type] })],
+)

+ 10 - 0
packages/console/core/src/schema/ip.sql.ts

@@ -30,3 +30,13 @@ export const KeyRateLimitTable = mysqlTable(
   },
   (table) => [primaryKey({ columns: [table.key, table.interval] })],
 )
+
+export const ModelRateLimitTable = mysqlTable(
+  "model_rate_limit",
+  {
+    key: varchar("key", { length: 255 }).notNull(),
+    interval: varchar("interval", { length: 40 }).notNull(),
+    count: int("count").notNull(),
+  },
+  (table) => [primaryKey({ columns: [table.key, table.interval] })],
+)

+ 0 - 4
packages/console/core/sst-env.d.ts

@@ -142,10 +142,6 @@ declare module "sst" {
       "type": "sst.sst.Secret"
       "value": string
     }
-    "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
     "ZEN_LITE_PRICE": {
       "firstMonth100Coupon": string
       "firstMonth50Coupon": string

+ 1 - 1
packages/console/function/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-function",
-  "version": "1.4.7",
+  "version": "1.14.18",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "type": "module",

+ 0 - 4
packages/console/function/sst-env.d.ts

@@ -142,10 +142,6 @@ declare module "sst" {
       "type": "sst.sst.Secret"
       "value": string
     }
-    "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
     "ZEN_LITE_PRICE": {
       "firstMonth100Coupon": string
       "firstMonth50Coupon": string

+ 1 - 1
packages/console/mail/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/console-mail",
-  "version": "1.4.7",
+  "version": "1.14.18",
   "dependencies": {
     "@jsx-email/all": "2.2.3",
     "@jsx-email/cli": "1.4.3",

+ 0 - 4
packages/console/resource/sst-env.d.ts

@@ -142,10 +142,6 @@ declare module "sst" {
       "type": "sst.sst.Secret"
       "value": string
     }
-    "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
     "ZEN_LITE_PRICE": {
       "firstMonth100Coupon": string
       "firstMonth50Coupon": string

+ 3 - 2
packages/desktop-electron/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@opencode-ai/desktop-electron",
   "private": true,
-  "version": "1.4.7",
+  "version": "1.14.18",
   "type": "module",
   "license": "MIT",
   "homepage": "https://opencode.ai",
@@ -30,6 +30,7 @@
     "electron-store": "^10",
     "electron-updater": "^6",
     "electron-window-state": "^5.0.3",
+    "drizzle-orm": "catalog:",
     "marked": "^15"
   },
   "devDependencies": {
@@ -45,7 +46,7 @@
     "@types/node": "catalog:",
     "@typescript/native-preview": "catalog:",
     "@valibot/to-json-schema": "1.6.0",
-    "electron": "40.4.1",
+    "electron": "41.2.1",
     "electron-builder": "^26",
     "electron-vite": "^5",
     "solid-js": "catalog:",

+ 26 - 10
packages/desktop-electron/src/main/index.ts

@@ -28,8 +28,10 @@ const APP_IDS: Record<string, string> = {
   beta: "ai.opencode.desktop.beta",
   prod: "ai.opencode.desktop",
 }
+const appId = app.isPackaged ? APP_IDS[CHANNEL] : "ai.opencode.desktop.dev"
 app.setName(app.isPackaged ? APP_NAMES[CHANNEL] : "OpenCode Dev")
-app.setPath("userData", join(app.getPath("appData"), app.isPackaged ? APP_IDS[CHANNEL] : "ai.opencode.desktop.dev"))
+app.setAppUserModelId(appId)
+app.setPath("userData", join(app.getPath("appData"), appId))
 const { autoUpdater } = pkg
 
 import type { InitStep, ServerReadyData, SqliteMigrationProgress, WslConfig } from "../preload/types"
@@ -41,6 +43,7 @@ import { parseMarkdown } from "./markdown"
 import { createMenu } from "./menu"
 import { getDefaultServerUrl, getWslConfig, setDefaultServerUrl, setWslConfig, spawnLocalServer } from "./server"
 import { createLoadingWindow, createMainWindow, setBackgroundColor, setDockIcon } from "./windows"
+import { drizzle } from "drizzle-orm/node-sqlite/driver"
 import type { Server } from "virtual:opencode-server"
 
 const initEmitter = new EventEmitter()
@@ -137,15 +140,6 @@ async function initialize() {
   const url = `http://${hostname}:${port}`
   const password = randomUUID()
 
-  logger.log("spawning sidecar", { url })
-  const { listener, health } = await spawnLocalServer(hostname, port, password)
-  server = listener
-  serverReady.resolve({
-    url,
-    username: "opencode",
-    password,
-  })
-
   const loadingTask = (async () => {
     logger.log("sidecar connection started", { url })
 
@@ -156,10 +150,32 @@ async function initialize() {
       if (progress.type === "Done") sqliteDone?.resolve()
     })
 
+    if (needsMigration) {
+      const { Database, JsonMigration } = await import("virtual:opencode-server")
+      await JsonMigration.run(drizzle({ client: Database.Client().$client }), {
+        progress: (event: { current: number; total: number }) => {
+          const percent = Math.round(event.current / event.total) * 100
+          initEmitter.emit("sqlite", { type: "InProgress", value: percent })
+        },
+      })
+      initEmitter.emit("sqlite", { type: "Done" })
+
+      sqliteDone?.resolve()
+    }
+
     if (needsMigration) {
       await sqliteDone?.promise
     }
 
+    logger.log("spawning sidecar", { url })
+    const { listener, health } = await spawnLocalServer(hostname, port, password)
+    server = listener
+    serverReady.resolve({
+      url,
+      username: "opencode",
+      password,
+    })
+
     await Promise.race([
       health.wait,
       delay(30_000).then(() => {

+ 4 - 4
packages/desktop-electron/src/main/migrate.ts

@@ -4,7 +4,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs"
 import { homedir } from "node:os"
 import { join } from "node:path"
 import { CHANNEL } from "./constants"
-import { getStore, store } from "./store"
+import { getStore } from "./store"
 
 const TAURI_MIGRATED_KEY = "tauriMigrated"
 
@@ -67,7 +67,7 @@ function migrateFile(datPath: string, filename: string) {
 }
 
 export function migrate() {
-  if (store.get(TAURI_MIGRATED_KEY)) {
+  if (getStore().get(TAURI_MIGRATED_KEY)) {
     log.log("tauri migration: already done, skipping")
     return
   }
@@ -77,7 +77,7 @@ export function migrate() {
 
   if (!existsSync(dir)) {
     log.log("tauri migration: no tauri data directory found, nothing to migrate")
-    store.set(TAURI_MIGRATED_KEY, true)
+    getStore().set(TAURI_MIGRATED_KEY, true)
     return
   }
 
@@ -87,5 +87,5 @@ export function migrate() {
   }
 
   log.log("tauri migration: complete")
-  store.set(TAURI_MIGRATED_KEY, true)
+  getStore().set(TAURI_MIGRATED_KEY, true)
 }

+ 6 - 6
packages/desktop-electron/src/main/server.ts

@@ -1,33 +1,33 @@
 import { app } from "electron"
 import { DEFAULT_SERVER_URL_KEY, WSL_ENABLED_KEY } from "./constants"
 import { getUserShell, loadShellEnv } from "./shell-env"
-import { store } from "./store"
+import { getStore } from "./store"
 
 export type WslConfig = { enabled: boolean }
 
 export type HealthCheck = { wait: Promise<void> }
 
 export function getDefaultServerUrl(): string | null {
-  const value = store.get(DEFAULT_SERVER_URL_KEY)
+  const value = getStore().get(DEFAULT_SERVER_URL_KEY)
   return typeof value === "string" ? value : null
 }
 
 export function setDefaultServerUrl(url: string | null) {
   if (url) {
-    store.set(DEFAULT_SERVER_URL_KEY, url)
+    getStore().set(DEFAULT_SERVER_URL_KEY, url)
     return
   }
 
-  store.delete(DEFAULT_SERVER_URL_KEY)
+  getStore().delete(DEFAULT_SERVER_URL_KEY)
 }
 
 export function getWslConfig(): WslConfig {
-  const value = store.get(WSL_ENABLED_KEY)
+  const value = getStore().get(WSL_ENABLED_KEY)
   return { enabled: typeof value === "boolean" ? value : false }
 }
 
 export function setWslConfig(config: WslConfig) {
-  store.set(WSL_ENABLED_KEY, config.enabled)
+  getStore().set(WSL_ENABLED_KEY, config.enabled)
 }
 
 export async function spawnLocalServer(hostname: string, port: number, password: string) {

+ 4 - 2
packages/desktop-electron/src/main/store.ts

@@ -4,6 +4,10 @@ import { SETTINGS_STORE } from "./constants"
 
 const cache = new Map<string, Store>()
 
+// We cannot instantiate the electron-store at module load time because
+// module import hoisting causes this to run before app.setPath("userData", ...)
+// in index.ts has executed, which would result in files being written to the default directory
+// (e.g. bad: %APPDATA%\@opencode-ai\desktop-electron\opencode.settings vs good: %APPDATA%\ai.opencode.desktop.dev\opencode.settings).
 export function getStore(name = SETTINGS_STORE) {
   const cached = cache.get(name)
   if (cached) return cached
@@ -11,5 +15,3 @@ export function getStore(name = SETTINGS_STORE) {
   cache.set(name, next)
   return next
 }
-
-export const store = getStore(SETTINGS_STORE)

+ 1 - 1
packages/desktop/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@opencode-ai/desktop",
   "private": true,
-  "version": "1.4.7",
+  "version": "1.14.18",
   "type": "module",
   "license": "MIT",
   "scripts": {

+ 1 - 1
packages/enterprise/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/enterprise",
-  "version": "1.4.7",
+  "version": "1.14.18",
   "private": true,
   "type": "module",
   "license": "MIT",

+ 0 - 4
packages/enterprise/sst-env.d.ts

@@ -142,10 +142,6 @@ declare module "sst" {
       "type": "sst.sst.Secret"
       "value": string
     }
-    "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
     "ZEN_LITE_PRICE": {
       "firstMonth100Coupon": string
       "firstMonth50Coupon": string

+ 6 - 6
packages/extensions/zed/extension.toml

@@ -1,7 +1,7 @@
 id = "opencode"
 name = "OpenCode"
 description = "The open source coding agent."
-version = "1.4.7"
+version = "1.14.18"
 schema_version = 1
 authors = ["Anomaly"]
 repository = "https://github.com/anomalyco/opencode"
@@ -11,26 +11,26 @@ name = "OpenCode"
 icon = "./icons/opencode.svg"
 
 [agent_servers.opencode.targets.darwin-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.7/opencode-darwin-arm64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.18/opencode-darwin-arm64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.darwin-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.7/opencode-darwin-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.18/opencode-darwin-x64.zip"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.linux-aarch64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.7/opencode-linux-arm64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.18/opencode-linux-arm64.tar.gz"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.linux-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.7/opencode-linux-x64.tar.gz"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.18/opencode-linux-x64.tar.gz"
 cmd = "./opencode"
 args = ["acp"]
 
 [agent_servers.opencode.targets.windows-x86_64]
-archive = "https://github.com/anomalyco/opencode/releases/download/v1.4.7/opencode-windows-x64.zip"
+archive = "https://github.com/anomalyco/opencode/releases/download/v1.14.18/opencode-windows-x64.zip"
 cmd = "./opencode.exe"
 args = ["acp"]

+ 1 - 1
packages/function/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/function",
-  "version": "1.4.7",
+  "version": "1.14.18",
   "$schema": "https://json.schemastore.org/package.json",
   "private": true,
   "type": "module",

+ 0 - 4
packages/function/sst-env.d.ts

@@ -142,10 +142,6 @@ declare module "sst" {
       "type": "sst.sst.Secret"
       "value": string
     }
-    "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
     "ZEN_LITE_PRICE": {
       "firstMonth100Coupon": string
       "firstMonth50Coupon": string

+ 8 - 9
packages/opencode/package.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/package.json",
-  "version": "1.4.7",
+  "version": "1.14.18",
   "name": "opencode",
   "type": "module",
   "license": "MIT",
@@ -79,15 +79,15 @@
     "@actions/github": "6.0.1",
     "@agentclientprotocol/sdk": "0.16.1",
     "@ai-sdk/alibaba": "1.0.17",
-    "@ai-sdk/amazon-bedrock": "4.0.94",
-    "@ai-sdk/anthropic": "3.0.70",
+    "@ai-sdk/amazon-bedrock": "4.0.96",
+    "@ai-sdk/anthropic": "3.0.71",
     "@ai-sdk/azure": "3.0.49",
     "@ai-sdk/cerebras": "2.0.41",
     "@ai-sdk/cohere": "3.0.27",
     "@ai-sdk/deepinfra": "2.0.41",
-    "@ai-sdk/gateway": "3.0.102",
+    "@ai-sdk/gateway": "3.0.104",
     "@ai-sdk/google": "3.0.63",
-    "@ai-sdk/google-vertex": "4.0.111",
+    "@ai-sdk/google-vertex": "4.0.112",
     "@ai-sdk/groq": "3.0.31",
     "@ai-sdk/mistral": "3.0.27",
     "@ai-sdk/openai": "3.0.53",
@@ -122,8 +122,8 @@
     "@opentelemetry/exporter-trace-otlp-http": "0.214.0",
     "@opentelemetry/sdk-trace-base": "2.6.1",
     "@opentelemetry/sdk-trace-node": "2.6.1",
-    "@opentui/core": "0.1.99",
-    "@opentui/solid": "0.1.99",
+    "@opentui/core": "catalog:",
+    "@opentui/solid": "catalog:",
     "@parcel/watcher": "2.5.1",
     "@pierre/diffs": "catalog:",
     "@solid-primitives/event-bus": "1.1.2",
@@ -143,7 +143,7 @@
     "drizzle-orm": "catalog:",
     "effect": "catalog:",
     "fuzzysort": "3.1.0",
-    "gitlab-ai-provider": "6.4.2",
+    "gitlab-ai-provider": "6.6.0",
     "glob": "13.0.5",
     "google-auth-library": "10.5.0",
     "gray-matter": "4.0.3",
@@ -161,7 +161,6 @@
     "opentui-spinner": "0.0.6",
     "partial-json": "0.1.7",
     "remeda": "catalog:",
-    "ripgrep": "0.3.1",
     "semver": "^7.6.3",
     "solid-js": "catalog:",
     "strip-ansi": "7.1.2",

+ 1 - 9
packages/opencode/script/build.ts

@@ -187,7 +187,6 @@ for (const item of targets) {
   const rootPath = path.resolve(dir, "../../node_modules/@opentui/core/parser.worker.js")
   const parserWorker = fs.realpathSync(fs.existsSync(localPath) ? localPath : rootPath)
   const workerPath = "./src/cli/cmd/tui/worker.ts"
-  const rgPath = "./src/file/ripgrep.worker.ts"
 
   // Use platform-specific bunfs root path based on target OS
   const bunfsRoot = item.os === "win32" ? "B:/~BUN/root/" : "/$bunfs/root/"
@@ -212,19 +211,12 @@ for (const item of targets) {
       windows: {},
     },
     files: embeddedFileMap ? { "opencode-web-ui.gen.ts": embeddedFileMap } : {},
-    entrypoints: [
-      "./src/index.ts",
-      parserWorker,
-      workerPath,
-      rgPath,
-      ...(embeddedFileMap ? ["opencode-web-ui.gen.ts"] : []),
-    ],
+    entrypoints: ["./src/index.ts", parserWorker, workerPath, ...(embeddedFileMap ? ["opencode-web-ui.gen.ts"] : [])],
     define: {
       OPENCODE_VERSION: `'${Script.version}'`,
       OPENCODE_MIGRATIONS: JSON.stringify(migrations),
       OTUI_TREE_SITTER_WORKER_PATH: bunfsRoot + workerRelativePath,
       OPENCODE_WORKER_PATH: workerPath,
-      OPENCODE_RIPGREP_WORKER_PATH: rgPath,
       OPENCODE_CHANNEL: `'${Script.channel}'`,
       OPENCODE_LIBC: item.os === "linux" ? `'${item.abi ?? "glibc"}'` : "",
     },

+ 23 - 8
packages/opencode/script/publish.ts

@@ -7,6 +7,22 @@ import { fileURLToPath } from "url"
 const dir = fileURLToPath(new URL("..", import.meta.url))
 process.chdir(dir)
 
+async function published(name: string, version: string) {
+  return (await $`npm view ${name}@${version} version`.nothrow()).exitCode === 0
+}
+
+async function publish(dir: string, name: string, version: string) {
+  // GitHub artifact downloads can drop the executable bit, and Docker uses the
+  // unpacked dist binaries directly rather than the published tarball.
+  if (process.platform !== "win32") await $`chmod -R 755 .`.cwd(dir)
+  if (await published(name, version)) {
+    console.log(`already published ${name}@${version}`)
+    return
+  }
+  await $`bun pm pack`.cwd(dir)
+  await $`npm publish *.tgz --access public --tag ${Script.channel}`.cwd(dir)
+}
+
 const binaries: Record<string, string> = {}
 for (const filepath of new Bun.Glob("*/package.json").scanSync({ cwd: "./dist" })) {
   const pkg = await Bun.file(`./dist/${filepath}`).json()
@@ -40,14 +56,10 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
 )
 
 const tasks = Object.entries(binaries).map(async ([name]) => {
-  if (process.platform !== "win32") {
-    await $`chmod -R 755 .`.cwd(`./dist/${name}`)
-  }
-  await $`bun pm pack`.cwd(`./dist/${name}`)
-  await $`npm publish *.tgz --access public --tag ${Script.channel}`.cwd(`./dist/${name}`)
+  await publish(`./dist/${name}`, name, binaries[name])
 })
 await Promise.all(tasks)
-await $`cd ./dist/${pkg.name} && bun pm pack && npm publish *.tgz --access public --tag ${Script.channel}`
+await publish(`./dist/${pkg.name}`, `${pkg.name}-ai`, version)
 
 const image = "ghcr.io/anomalyco/opencode"
 const platforms = "linux/amd64,linux/arm64"
@@ -104,6 +116,7 @@ if (!Script.preview) {
         await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild)
         await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
         await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
+        if ((await $`cd ./dist/aur-${pkg} && git diff --cached --quiet`.nothrow()).exitCode === 0) break
         await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${Script.version}"`
         await $`cd ./dist/aur-${pkg} && git push`
         break
@@ -176,6 +189,8 @@ if (!Script.preview) {
   await $`git clone ${tap} ./dist/homebrew-tap`
   await Bun.file("./dist/homebrew-tap/opencode.rb").write(homebrewFormula)
   await $`cd ./dist/homebrew-tap && git add opencode.rb`
-  await $`cd ./dist/homebrew-tap && git commit -m "Update to v${Script.version}"`
-  await $`cd ./dist/homebrew-tap && git push`
+  if ((await $`cd ./dist/homebrew-tap && git diff --cached --quiet`.nothrow()).exitCode !== 0) {
+    await $`cd ./dist/homebrew-tap && git commit -m "Update to v${Script.version}"`
+    await $`cd ./dist/homebrew-tap && git push`
+  }
 }

+ 32 - 49
packages/opencode/specs/effect/facades.md

@@ -1,12 +1,13 @@
 # Facade removal checklist
 
-Concrete inventory of the remaining `makeRuntime(...)`-backed service facades in `packages/opencode`.
+Concrete inventory of the remaining `makeRuntime(...)`-backed facades in `packages/opencode`.
 
-As of 2026-04-13, latest `origin/dev`:
+Current status on this branch:
 
-- `src/` still has 15 `makeRuntime(...)` call sites.
-- 13 of those are still in scope for facade removal.
-- 2 are excluded from this checklist: `bus/index.ts` and `effect/cross-spawn-spawner.ts`.
+- `src/` has 5 `makeRuntime(...)` call sites total.
+- 2 are intentionally excluded from this checklist: `src/bus/index.ts` and `src/effect/cross-spawn-spawner.ts`.
+- 1 is tracked primarily by the instance-context migration rather than facade removal: `src/project/instance.ts`.
+- That leaves 2 live runtime-backed service facades still worth tracking here: `src/npm/index.ts` and `src/cli/cmd/tui/config/tui.ts`.
 
 Recent progress:
 
@@ -15,8 +16,9 @@ Recent progress:
 
 ## Priority hotspots
 
-- `server/instance/session.ts` still depends on `Session`, `SessionPrompt`, `SessionRevert`, `SessionCompaction`, `SessionSummary`, `ShareSession`, `Agent`, and `Permission` facades.
-- `src/effect/app-runtime.ts` still references many facade namespaces directly, so it should stay in view during each deletion.
+- `src/cli/cmd/tui/config/tui.ts` still exports `makeRuntime(...)` plus async facade helpers for `get()` and `waitForDependencies()`.
+- `src/npm/index.ts` still exports `makeRuntime(...)` plus async facade helpers for `install()`, `add()`, `outdated()`, and `which()`.
+- `src/project/instance.ts` still uses a dedicated runtime for project boot, but that file is really part of the broader legacy instance-context transition tracked in `instance-context.md`.
 
 ## Completed Batches
 
@@ -184,53 +186,34 @@ These were the recurring mistakes and useful corrections from the first two batc
 5. For CLI readability, extract file-local preload helpers when the handler starts doing config load + service load + batched effect fanout inline.
 6. When rebasing a facade branch after nearby merges, prefer the already-cleaned service/test version over older inline facade-era code.
 
-## Next batch
+## Remaining work
 
-Recommended next five, in order:
+Most of the original facade-removal backlog is already done. The practical remaining work is narrower now:
 
-1. `src/permission/index.ts`
-2. `src/agent/agent.ts`
-3. `src/session/summary.ts`
-4. `src/session/revert.ts`
-5. `src/mcp/auth.ts`
-
-Why this batch:
-
-- It keeps pushing the session-adjacent cleanup without jumping straight into `session/index.ts` or `session/prompt.ts`.
-- `Permission`, `Agent`, `SessionSummary`, and `SessionRevert` all reduce fanout in `server/instance/session.ts`.
-- `McpAuth` is small and closely related to the just-landed `MCP` cleanup.
-
-After that batch, the expected follow-up is the main session cluster:
-
-1. `src/session/index.ts`
-2. `src/session/prompt.ts`
-3. `src/session/compaction.ts`
+1. remove the `Npm` runtime-backed facade from `src/npm/index.ts`
+2. remove the `TuiConfig` runtime-backed facade from `src/cli/cmd/tui/config/tui.ts`
+3. keep `src/project/instance.ts` in the separate instance-context migration, not this checklist
 
 ## Checklist
 
-- [ ] `src/session/index.ts` (`Session`) - facades: `create`, `fork`, `get`, `setTitle`, `setArchived`, `setPermission`, `setRevert`, `messages`, `children`, `remove`, `updateMessage`, `removeMessage`, `removePart`, `updatePart`; main callers: `server/instance/session.ts`, `cli/cmd/session.ts`, `cli/cmd/export.ts`, `cli/cmd/github.ts`; tests: `test/server/session-actions.test.ts`, `test/server/session-list.test.ts`, `test/server/global-session-list.test.ts`
-- [ ] `src/session/prompt.ts` (`SessionPrompt`) - facades: `prompt`, `resolvePromptParts`, `cancel`, `loop`, `shell`, `command`; main callers: `server/instance/session.ts`, `cli/cmd/github.ts`; tests: `test/session/prompt.test.ts`, `test/session/prompt-effect.test.ts`, `test/session/structured-output-integration.test.ts`
-- [ ] `src/session/revert.ts` (`SessionRevert`) - facades: `revert`, `unrevert`, `cleanup`; main callers: `server/instance/session.ts`; tests: `test/session/revert-compact.test.ts`
-- [ ] `src/session/compaction.ts` (`SessionCompaction`) - facades: `isOverflow`, `prune`, `create`; main callers: `server/instance/session.ts`; tests: `test/session/compaction.test.ts`
-- [ ] `src/session/summary.ts` (`SessionSummary`) - facades: `summarize`, `diff`; main callers: `session/prompt.ts`, `session/processor.ts`, `server/instance/session.ts`; tests: `test/session/snapshot-tool-race.test.ts`
-- [ ] `src/share/session.ts` (`ShareSession`) - facades: `create`, `share`, `unshare`; main callers: `server/instance/session.ts`, `cli/cmd/github.ts`
-- [ ] `src/agent/agent.ts` (`Agent`) - facades: `get`, `list`, `defaultAgent`, `generate`; main callers: `cli/cmd/agent.ts`, `server/instance/session.ts`, `server/instance/experimental.ts`; tests: `test/agent/agent.test.ts`
-- [ ] `src/permission/index.ts` (`Permission`) - facades: `ask`, `reply`, `list`; main callers: `server/instance/permission.ts`, `server/instance/session.ts`, `session/llm.ts`; tests: `test/permission/next.test.ts`
-- [x] `src/file/index.ts` (`File`) - facades removed and merged.
-- [x] `src/lsp/index.ts` (`LSP`) - facades removed and merged.
-- [x] `src/mcp/index.ts` (`MCP`) - facades removed and merged.
-- [x] `src/config/config.ts` (`Config`) - facades removed and merged.
-- [x] `src/provider/provider.ts` (`Provider`) - facades removed and merged.
-- [x] `src/pty/index.ts` (`Pty`) - facades removed and merged.
-- [x] `src/skill/index.ts` (`Skill`) - facades removed and merged.
-- [x] `src/project/vcs.ts` (`Vcs`) - facades removed and merged.
-- [x] `src/tool/registry.ts` (`ToolRegistry`) - facades removed and merged.
-- [ ] `src/worktree/index.ts` (`Worktree`) - facades: `makeWorktreeInfo`, `createFromInfo`, `create`, `remove`, `reset`; main callers: `control-plane/adaptors/worktree.ts`, `server/instance/experimental.ts`; tests: `test/project/worktree.test.ts`, `test/project/worktree-remove.test.ts`
-- [x] `src/auth/index.ts` (`Auth`) - facades removed and merged.
-- [ ] `src/mcp/auth.ts` (`McpAuth`) - facades: `get`, `getForUrl`, `all`, `set`, `remove`, `updateTokens`, `updateClientInfo`, `updateCodeVerifier`, `updateOAuthState`; main callers: `mcp/oauth-provider.ts`, `cli/cmd/mcp.ts`; tests: `test/mcp/oauth-auto-connect.test.ts`
-- [ ] `src/plugin/index.ts` (`Plugin`) - facades: `trigger`, `list`, `init`; main callers: `agent/agent.ts`, `session/llm.ts`, `project/bootstrap.ts`; tests: `test/plugin/trigger.test.ts`, `test/provider/provider.test.ts`
-- [ ] `src/project/project.ts` (`Project`) - facades: `fromDirectory`, `discover`, `initGit`, `update`, `sandboxes`, `addSandbox`, `removeSandbox`; main callers: `project/instance.ts`, `server/instance/project.ts`, `server/instance/experimental.ts`; tests: `test/project/project.test.ts`, `test/project/migrate-global.test.ts`
-- [ ] `src/snapshot/index.ts` (`Snapshot`) - facades: `init`, `track`, `patch`, `restore`, `revert`, `diff`, `diffFull`; main callers: `project/bootstrap.ts`, `cli/cmd/debug/snapshot.ts`; tests: `test/snapshot/snapshot.test.ts`, `test/session/revert-compact.test.ts`
+- [ ] `src/npm/index.ts` (`Npm`) - still exports runtime-backed async facade helpers on top of `Npm.Service`
+- [ ] `src/cli/cmd/tui/config/tui.ts` (`TuiConfig`) - still exports runtime-backed async facade helpers on top of `TuiConfig.Service`
+- [x] `src/session/session.ts` / `src/session/prompt.ts` / `src/session/revert.ts` / `src/session/summary.ts` - service-local facades removed
+- [x] `src/agent/agent.ts` (`Agent`) - service-local facades removed
+- [x] `src/permission/index.ts` (`Permission`) - service-local facades removed
+- [x] `src/worktree/index.ts` (`Worktree`) - service-local facades removed
+- [x] `src/plugin/index.ts` (`Plugin`) - service-local facades removed
+- [x] `src/snapshot/index.ts` (`Snapshot`) - service-local facades removed
+- [x] `src/file/index.ts` (`File`) - facades removed and merged
+- [x] `src/lsp/index.ts` (`LSP`) - facades removed and merged
+- [x] `src/mcp/index.ts` (`MCP`) - facades removed and merged
+- [x] `src/config/config.ts` (`Config`) - facades removed and merged
+- [x] `src/provider/provider.ts` (`Provider`) - facades removed and merged
+- [x] `src/pty/index.ts` (`Pty`) - facades removed and merged
+- [x] `src/skill/index.ts` (`Skill`) - facades removed and merged
+- [x] `src/project/vcs.ts` (`Vcs`) - facades removed and merged
+- [x] `src/tool/registry.ts` (`ToolRegistry`) - facades removed and merged
+- [x] `src/auth/index.ts` (`Auth`) - facades removed and merged
 
 ## Excluded `makeRuntime(...)` sites
 

+ 85 - 46
packages/opencode/specs/effect/http-api.md

@@ -76,7 +76,7 @@ Many route boundaries still use Zod-first validators. That does not block all ex
 
 ### Mixed handler styles
 
-Many current `server/instance/*.ts` handlers still call async facades directly. Migrating those to composed `Effect.gen(...)` handlers is the low-risk step to do first.
+Many current `server/routes/instance/*.ts` handlers still mix composed Effect code with smaller Promise- or ALS-backed seams. Migrating those to consistent `Effect.gen(...)` handlers is the low-risk step to do first.
 
 ### Non-JSON routes
 
@@ -90,7 +90,7 @@ The current server composition, middleware, and docs flow are Hono-centered toda
 
 ### 1. Finish the prerequisites first
 
-- continue route-handler effectification in `server/instance/*.ts`
+- continue route-handler effectification in `server/routes/instance/*.ts`
 - continue schema migration toward Effect Schema-first DTOs and errors
 - keep removing service facades
 
@@ -98,9 +98,9 @@ The current server composition, middleware, and docs flow are Hono-centered toda
 
 Introduce one small `HttpApi` group for plain JSON endpoints only. Good initial candidates are the least stateful endpoints in:
 
-- `server/instance/question.ts`
-- `server/instance/provider.ts`
-- `server/instance/permission.ts`
+- `server/routes/instance/question.ts`
+- `server/routes/instance/provider.ts`
+- `server/routes/instance/permission.ts`
 
 Avoid `session.ts`, SSE, websocket, and TUI-facing routes first.
 
@@ -155,9 +155,9 @@ This gives:
 
 As each route group is ported to `HttpApi`:
 
-1. change its `root` path from `/experimental/httpapi/<group>` to `/<group>`
-2. add `.all("/<group>", handler)` / `.all("/<group>/*", handler)` to the flag block in `instance/index.ts`
-3. for partial ports (e.g. only `GET /provider/auth`), bridge only the specific path
+1. add `.get(...)` / `.post(...)` bridge entries to the flag block in `server/routes/instance/index.ts`
+2. for partial ports (e.g. only `GET /provider/auth`), bridge only the specific path
+3. keep the legacy Hono route registered behind it for OpenAPI / SDK generation until the spec pipeline changes
 4. verify SDK output is unchanged
 
 Leave streaming-style endpoints on Hono until there is a clear reason to move them.
@@ -189,10 +189,46 @@ Ordering for a route-group migration:
 
 SDK shape rule:
 
-- every schema migration must preserve the generated SDK output byte-for-byte
-- `Schema.Class` emits a named `$ref` in OpenAPI via its identifier — use it only for types that already had `.meta({ ref })` in the old Zod schema
-- inner / nested types that were anonymous in the old Zod schema should stay as `Schema.Struct` (not `Schema.Class`) to avoid introducing new named components in the OpenAPI spec
-- if a diff appears in `packages/sdk/js/src/v2/gen/types.gen.ts`, the migration introduced an unintended API surface change — fix it before merging
+- every schema migration must preserve the generated SDK output byte-for-byte **unless the new ref is intentional** (see Schema.Class vs Schema.Struct below)
+- if an unintended diff appears in `packages/sdk/js/src/v2/gen/types.gen.ts`, the migration introduced an unintended API surface change — fix it before merging
+
+### Schema.Class vs Schema.Struct
+
+The pattern choice determines whether a schema becomes a **named** export in the SDK or stays **anonymous inline**.
+
+**Schema.Class** emits a named `$ref` in OpenAPI via its identifier → produces a named `export type Foo = ...` in `types.gen.ts`:
+
+```ts
+export class Info extends Schema.Class<Info>("FooConfig")({ ... }) {
+  static readonly zod = zod(this)
+}
+```
+
+**Schema.Struct** stays anonymous and is inlined everywhere it is referenced:
+
+```ts
+export const Info = Schema.Struct({ ... }).pipe(
+  withStatics((s) => ({ zod: zod(s) })),
+)
+export type Info = Schema.Schema.Type<typeof Info>
+```
+
+When to use each:
+
+- Use **Schema.Class** when:
+  - the original Zod had `.meta({ ref: ... })` (preserve the existing named SDK type byte-for-byte)
+  - the schema is a top-level endpoint request or response (SDK consumers benefit from a stable importable name)
+- Use **Schema.Struct** when:
+  - the type is only used as a nested field inside another named schema
+  - the original Zod was anonymous and promoting it would bloat SDK types with no import value
+
+Promoting a previously-anonymous schema to Schema.Class is acceptable when it is top-level or endpoint-facing, but call it out in the PR — it is an additive SDK change (`export type Foo = ...` newly appears) even if it preserves the JSON shape.
+
+Schemas that are **not** pure objects (enums, unions, records, tuples) cannot use Schema.Class. For those, add `.annotate({ identifier: "FooName" })` to get the same named-ref behavior:
+
+```ts
+export const Action = Schema.Literals(["ask", "allow", "deny"]).annotate({ identifier: "PermissionActionConfig" })
+```
 
 Temporary exception:
 
@@ -231,7 +267,7 @@ Use the same sequence for each route group.
 3. Apply the schema migration ordering above so those types are Effect Schema-first.
 4. Define the `HttpApi` contract separately from the handlers.
 5. Implement handlers by yielding the existing service from context.
-6. Mount the new surface in parallel under an experimental prefix.
+6. Mount the new surface in parallel behind the `OPENCODE_EXPERIMENTAL_HTTPAPI` bridge.
 7. Regenerate the SDK and verify zero diff against `dev` (see SDK shape rule above).
 8. Add one end-to-end test and one OpenAPI-focused test.
 9. Compare ergonomics before migrating the next endpoint.
@@ -250,20 +286,20 @@ Placement rule:
 - keep `HttpApi` code under `src/server`, not `src/effect`
 - `src/effect` should stay focused on runtimes, layers, instance state, and shared Effect plumbing
 - place each `HttpApi` slice next to the HTTP boundary it serves
-- for instance-scoped routes, prefer `src/server/instance/httpapi/*`
-- if control-plane routes ever migrate, prefer `src/server/control/httpapi/*`
+- for instance-scoped routes, prefer `src/server/routes/instance/httpapi/*`
+- if control-plane routes ever migrate, prefer `src/server/routes/control/httpapi/*`
 
 Suggested file layout for a repeatable spike:
 
-- `src/server/instance/httpapi/question.ts` — contract and handler layer for one route group
-- `src/server/instance/httpapi/server.ts` — standalone Effect HTTP server that composes all groups
-- `test/server/question-httpapi.test.ts` — end-to-end test against the real service
+- `src/server/routes/instance/httpapi/question.ts` — contract and handler layer for one route group
+- `src/server/routes/instance/httpapi/server.ts` — bridged Effect HTTP layer that composes all groups
+- route or OpenAPI verification should live alongside the existing server tests; there is no dedicated `question-httpapi` test file on this branch
 
 Suggested responsibilities:
 
 - `question.ts` defines the `HttpApi` contract and `HttpApiBuilder.group(...)` handlers
-- `server.ts` composes all route groups into one `HttpRouter.serve` layer with shared middleware (auth, instance lookup)
-- tests use `ExperimentalHttpApiServer.layerTest` to run against a real in-process HTTP server
+- `server.ts` composes all route groups into one `HttpRouter.toWebHandler(...)` bridge with shared middleware (auth, instance lookup)
+- tests should verify the bridged routes through the normal server surface
 
 ## Example migration shape
 
@@ -283,33 +319,33 @@ Each route-group spike should follow the same shape.
 - keep handler bodies thin
 - keep transport mapping at the HTTP boundary only
 
-### 3. Standalone server
+### 3. Bridged server
 
-- the Effect HTTP server is self-contained in `httpapi/server.ts`
-- it is **not** mounted into the Hono app — no bridge, no `toWebHandler`
-- route paths use the `/experimental/httpapi` prefix so they match the eventual cutover
-- each route group exposes its own OpenAPI doc endpoint
+- the Effect HTTP layer is composed in `httpapi/server.ts`
+- it is mounted into the Hono app via `HttpRouter.toWebHandler(...)`
+- routes keep their normal instance paths and are gated by the `OPENCODE_EXPERIMENTAL_HTTPAPI` flag
+- the legacy Hono handlers stay registered after the bridge so current OpenAPI / SDK generation still works
 
 ### 4. Verification
 
 - seed real state through the existing service
-- call the experimental endpoints
+- call the bridged endpoints with the flag enabled
 - assert that the service behavior is unchanged
 - assert that the generated OpenAPI contains the migrated paths and schemas
 
 ## Boundary composition
 
-The standalone Effect server owns its own middleware stack. It does not share middleware with the Hono server.
+The Effect `HttpApi` layer owns its own auth and instance middleware, but it is currently mounted inside the existing Hono server.
 
 ### Auth
 
-- the standalone server implements auth as an `HttpApiMiddleware.Service` using `HttpApiSecurity.basic`
+- the bridged `HttpApi` layer implements auth as an `HttpApiMiddleware.Service` using `HttpApiSecurity.basic`
 - each route group's `HttpApi` is wrapped with `.middleware(Authorization)` before being served
-- this is independent of the Hono `AuthMiddleware` — when the Effect server eventually replaces Hono, this becomes the only auth layer
+- this is independent of the Hono auth layer; the current bridge keeps the responsibility local to the `HttpApi` slice
 
 ### Instance and workspace lookup
 
-- the standalone server resolves instance context via an `HttpRouter.middleware` that reads `x-opencode-directory` headers and `directory` query params
+- the bridged `HttpApi` layer resolves instance context via an `HttpRouter.middleware` that reads `x-opencode-directory` headers and `directory` query params
 - this is the Effect equivalent of the Hono `WorkspaceRouterMiddleware`
 - `HttpApi` handlers yield services from context and assume the correct instance has already been provided
 
@@ -324,7 +360,7 @@ The standalone Effect server owns its own middleware stack. It does not share mi
 
 The first slice is successful if:
 
-- the standalone Effect server starts and serves the endpoints independently of the Hono server
+- the bridged endpoints serve correctly through the existing Hono host when the flag is enabled
 - the handlers reuse the existing Effect service
 - request decoding and response shapes are schema-defined from canonical Effect schemas
 - any remaining Zod boundary usage is derived from `.zod` or clearly temporary
@@ -365,17 +401,16 @@ Current instance route inventory:
   endpoints: `GET /question`, `POST /question/:requestID/reply`, `POST /question/:requestID/reject`
 - `permission` - `bridged`
   endpoints: `GET /permission`, `POST /permission/:requestID/reply`
-- `provider` - `bridged` (partial)
-  bridged endpoint: `GET /provider/auth`
-  not yet ported: `GET /provider`, OAuth mutations
-- `config` - `next`
-  best next endpoint: `GET /config/providers`
+- `provider` - `bridged`
+  endpoints: `GET /provider`, `GET /provider/auth`, `POST /provider/:providerID/oauth/authorize`, `POST /provider/:providerID/oauth/callback`
+- `config` - `bridged` (partial)
+  bridged endpoint: `GET /config/providers`
   later endpoint: `GET /config`
   defer `PATCH /config` for now
-- `project` - `later`
-  best small reads: `GET /project`, `GET /project/current`
+- `project` - `bridged` (partial)
+  bridged endpoints: `GET /project`, `GET /project/current`
   defer git-init mutation first
-- `workspace` - `later`
+- `workspace` - `next`
   best small reads: `GET /experimental/workspace/adaptor`, `GET /experimental/workspace`, `GET /experimental/workspace/status`
   defer create/remove mutations first
 - `file` - `later`
@@ -393,12 +428,12 @@ Current instance route inventory:
 - `tui` - `defer`
   queue-style UI bridge, weak early `HttpApi` fit
 
-Recommended near-term sequence after the first spike:
+Recommended near-term sequence:
 
-1. `provider` auth read endpoint
-2. `config` providers read endpoint
-3. `project` read endpoints
-4. `workspace` read endpoints
+1. `workspace` read endpoints (`GET /experimental/workspace/adaptor`, `GET /experimental/workspace`, `GET /experimental/workspace/status`)
+2. `config` full read endpoint (`GET /config`)
+3. `file` JSON read endpoints
+4. `mcp` JSON read endpoints
 
 ## Checklist
 
@@ -411,8 +446,12 @@ Recommended near-term sequence after the first spike:
 - [x] gate behind `OPENCODE_EXPERIMENTAL_HTTPAPI` flag
 - [x] verify OTEL spans and HTTP logs flow to motel
 - [x] bridge question, permission, and provider auth routes
-- [ ] port remaining provider endpoints (`GET /provider`, OAuth mutations)
-- [ ] port `config` read endpoints
+- [x] port remaining provider endpoints (`GET /provider`, OAuth mutations)
+- [x] port `config` providers read endpoint
+- [x] port `project` read endpoints (`GET /project`, `GET /project/current`)
+- [ ] port `workspace` read endpoints
+- [ ] port `GET /config` full read endpoint
+- [ ] port `file` JSON read endpoints
 - [ ] decide when to remove the flag and make Effect routes the default
 
 ## Rule of thumb

+ 10 - 10
packages/opencode/specs/effect/instance-context.md

@@ -157,7 +157,7 @@ Direct legacy usage means any source file that still calls one of:
 - `Instance.reload(...)`
 - `Instance.dispose()` / `Instance.disposeAll()`
 
-Current total: `54` files in `packages/opencode/src`.
+Current total: `56` files in `packages/opencode/src`.
 
 ### Core bridge and plumbing
 
@@ -177,13 +177,13 @@ Migration rule:
 
 These are the current request-entry seams that still create or consume instance context through the legacy helper.
 
-- `src/server/instance/middleware.ts`
-- `src/server/instance/index.ts`
-- `src/server/instance/project.ts`
-- `src/server/instance/workspace.ts`
-- `src/server/instance/file.ts`
-- `src/server/instance/experimental.ts`
-- `src/server/instance/global.ts`
+- `src/server/routes/instance/middleware.ts`
+- `src/server/routes/instance/index.ts`
+- `src/server/routes/instance/project.ts`
+- `src/server/routes/control/workspace.ts`
+- `src/server/routes/instance/file.ts`
+- `src/server/routes/instance/experimental.ts`
+- `src/server/routes/global.ts`
 
 Migration rule:
 
@@ -239,7 +239,7 @@ Migration rule:
 These modules are already the best near-term migration targets because they are in Effect code but still read sync getters from the legacy helper.
 
 - `src/agent/agent.ts`
-- `src/config/tui-migrate.ts`
+- `src/cli/cmd/tui/config/tui-migrate.ts`
 - `src/file/index.ts`
 - `src/file/watcher.ts`
 - `src/format/formatter.ts`
@@ -250,7 +250,7 @@ These modules are already the best near-term migration targets because they are
 - `src/project/vcs.ts`
 - `src/provider/provider.ts`
 - `src/pty/index.ts`
-- `src/session/index.ts`
+- `src/session/session.ts`
 - `src/session/instruction.ts`
 - `src/session/llm.ts`
 - `src/session/system.ts`

+ 6 - 8
packages/opencode/specs/effect/loose-ends.md

@@ -4,11 +4,11 @@ Small follow-ups that do not fit neatly into the main facade, route, tool, or sc
 
 ## Config / TUI
 
-- [ ] `config/tui.ts` - finish the internal Effect migration after the `Instance.state(...)` removal.
+- [ ] `cli/cmd/tui/config/tui.ts` - finish the internal Effect migration.
       Keep the current precedence and migration semantics intact while converting the remaining internal async helpers (`loadState`, `mergeFile`, `loadFile`, `load`) to `Effect.gen(...)` / `Effect.fn(...)`.
-- [ ] `config/tui.ts` callers - once the internal service is stable, migrate plain async callers to use `TuiConfig.Service` directly where that actually simplifies the code.
+- [ ] `cli/cmd/tui/config/tui.ts` callers - once the internal service is stable, migrate plain async callers to use `TuiConfig.Service` directly where that actually simplifies the code.
       Likely first callers: `cli/cmd/tui/attach.ts`, `cli/cmd/tui/thread.ts`, `cli/cmd/tui/plugin/runtime.ts`.
-- [ ] `env/index.ts` - move the last production `Instance.state(...)` usage onto `InstanceState` (or its replacement) so `Instance.state` can be deleted.
+- [x] `env/index.ts` - already uses `InstanceState.make(...)`.
 
 ## ConfigPaths
 
@@ -21,14 +21,12 @@ Small follow-ups that do not fit neatly into the main facade, route, tool, or sc
   - `readFile(...)`
   - `parseText(...)`
 - [ ] `config/config.ts` - switch internal config loading from `Effect.promise(() => ConfigPaths.*(...))` to `yield* paths.*(...)` once the service exists.
-- [ ] `config/tui.ts` - switch TUI config loading from async `ConfigPaths.*` wrappers to the `ConfigPaths.Service` once that service exists.
-- [ ] `config/tui-migrate.ts` - decide whether to leave this as a plain async module using wrapper functions or effectify it fully after `ConfigPaths.Service` lands.
+- [ ] `cli/cmd/tui/config/tui.ts` - switch TUI config loading from async `ConfigPaths.*` wrappers to the `ConfigPaths.Service` once that service exists.
+- [ ] `cli/cmd/tui/config/tui-migrate.ts` - decide whether to leave this as a plain async module using wrapper functions or effectify it fully after `ConfigPaths.Service` lands.
 
 ## Instance cleanup
 
-- [ ] `project/instance.ts` - remove `Instance.state(...)` once `env/index.ts` is migrated.
-- [ ] `project/state.ts` - delete the bespoke per-instance state helper after the last production caller is gone.
-- [ ] `test/project/state.test.ts` - replace or delete the old `Instance.state(...)` tests after the removal.
+- [ ] `project/instance.ts` - keep shrinking the legacy ALS / Promise cache after the remaining `Instance.*` callers move over.
 
 ## Notes
 

+ 30 - 40
packages/opencode/specs/effect/migration.md

@@ -19,53 +19,43 @@ See `instance-context.md` for the phased plan to remove the legacy ALS / promise
 
 ## Service shape
 
-Every service follows the same pattern — a single namespace with the service definition, layer, `runPromise`, and async facade functions:
+Every service follows the same pattern: one module, flat top-level exports, traced Effect methods, and a self-reexport at the bottom when the file is the public module.
 
 ```ts
-export namespace Foo {
-  export interface Interface {
-    readonly get: (id: FooID) => Effect.Effect<FooInfo, FooError>
-  }
-
-  export class Service extends Context.Service<Service, Interface>()("@opencode/Foo") {}
-
-  export const layer = Layer.effect(
-    Service,
-    Effect.gen(function* () {
-      // For instance-scoped services:
-      const state = yield* InstanceState.make<State>(
-        Effect.fn("Foo.state")(() => Effect.succeed({ ... })),
-      )
+export interface Interface {
+  readonly get: (id: FooID) => Effect.Effect<FooInfo, FooError>
+}
 
-      const get = Effect.fn("Foo.get")(function* (id: FooID) {
-        const s = yield* InstanceState.get(state)
-        // ...
-      })
+export class Service extends Context.Service<Service, Interface>()("@opencode/Foo") {}
 
-      return Service.of({ get })
-    }),
-  )
+export const layer = Layer.effect(
+  Service,
+  Effect.gen(function* () {
+    const state = yield* InstanceState.make<State>(
+      Effect.fn("Foo.state")(() => Effect.succeed({ ... })),
+    )
 
-  // Optional: wire dependencies
-  export const defaultLayer = layer.pipe(Layer.provide(FooDep.layer))
+    const get = Effect.fn("Foo.get")(function* (id: FooID) {
+      const s = yield* InstanceState.get(state)
+      // ...
+    })
 
-  // Per-service runtime (inside the namespace)
-  const { runPromise } = makeRuntime(Service, defaultLayer)
+    return Service.of({ get })
+  }),
+)
 
-  // Async facade functions
-  export async function get(id: FooID) {
-    return runPromise((svc) => svc.get(id))
-  }
-}
+export const defaultLayer = layer.pipe(Layer.provide(FooDep.layer))
+
+export * as Foo from "."
 ```
 
 Rules:
 
-- Keep everything in one namespace, one file — no separate `service.ts` / `index.ts` split
-- `runPromise` goes inside the namespace (not exported unless tests need it)
-- Facade functions are plain `async function` — no `fn()` wrappers
-- Use `Effect.fn("Namespace.method")` for all Effect functions (for tracing)
-- No `Layer.fresh` — InstanceState handles per-directory isolation
+- Keep the service surface in one module; prefer flat top-level exports over `export namespace Foo { ... }`
+- Use `Effect.fn("Foo.method")` for Effect methods
+- Use a self-reexport (`export * as Foo from "."` or `"./foo"`) for the public namespace projection
+- Avoid service-local `makeRuntime(...)` facades unless a file is still intentionally in the older migration phase
+- No `Layer.fresh` for normal per-directory isolation; use `InstanceState`
 
 ## Schema → Zod interop
 
@@ -266,7 +256,7 @@ Tool-specific filesystem cleanup notes live in `tools.md`.
 
 ## Destroying the facades
 
-This phase is still broadly open. As of 2026-04-13 there are still 15 `makeRuntime(...)` call sites under `src/`, with 13 still in scope for facade removal. The live checklist now lives in `facades.md`.
+This phase is no longer broadly open. There are 5 `makeRuntime(...)` call sites under `src/`, and only a small subset are still ordinary facade-removal targets. The live checklist now lives in `facades.md`.
 
 These facades exist because cyclic imports used to force each service to build its own independent runtime. Now that the layer DAG is acyclic and `AppRuntime` (`src/effect/app-runtime.ts`) composes everything into one `ManagedRuntime`, we're removing them.
 
@@ -297,11 +287,11 @@ For each service, the migration is roughly:
 - `ShareNext` — migrated 2026-04-11. Swapped remaining async callers to `AppRuntime.runPromise(ShareNext.Service.use(...))`, removed the `makeRuntime(...)` facade, and kept instance bootstrap on the shared app runtime.
 - `SessionTodo` — migrated 2026-04-10. Already matched the target service shape in `session/todo.ts`: single namespace, traced Effect methods, and no `makeRuntime(...)` facade remained; checklist updated to reflect the completed migration.
 - `Storage` — migrated 2026-04-10. One production caller (`Session.diff`) and all storage.test.ts tests converted to effectful style. Facades and `makeRuntime` removed.
-- `SessionRunState` — migrated 2026-04-11. Single caller in `server/instance/session.ts` converted; facade removed.
-- `Account` — migrated 2026-04-11. Callers in `server/instance/experimental.ts` and `cli/cmd/account.ts` converted; facade removed.
+- `SessionRunState` — migrated 2026-04-11. Single caller in `server/routes/instance/session.ts` converted; facade removed.
+- `Account` — migrated 2026-04-11. Callers in `server/routes/instance/experimental.ts` and `cli/cmd/account.ts` converted; facade removed.
 - `Instruction` — migrated 2026-04-11. Test-only callers converted; facade removed.
 - `FileWatcher` — migrated 2026-04-11. Callers in `project/bootstrap.ts` and test converted; facade removed.
-- `Question` — migrated 2026-04-11. Callers in `server/instance/question.ts` and test converted; facade removed.
+- `Question` — migrated 2026-04-11. Callers in `server/routes/instance/question.ts` and test converted; facade removed.
 - `Truncate` — migrated 2026-04-11. Caller in `tool/tool.ts` and test converted; facade removed.
 
 ## Route handler effectification

+ 16 - 18
packages/opencode/specs/effect/routes.md

@@ -39,28 +39,26 @@ This eliminates multiple `runPromise` round-trips and lets handlers compose natu
 
 ## Current route files
 
-Current instance route files live under `src/server/instance`, not `server/routes`.
+Current instance route files live under `src/server/routes/instance`.
 
-The main migration targets are:
+Files that are already mostly on the intended service-yielding shape:
 
-- [ ] `server/instance/session.ts` — heaviest; still has many direct facade calls for Session, SessionPrompt, SessionRevert, SessionCompaction, SessionShare, SessionSummary, Agent, Bus
-- [ ] `server/instance/global.ts` — still has direct facade calls for Config and instance lifecycle actions
-- [ ] `server/instance/provider.ts` — still has direct facade calls for Config and Provider
-- [ ] `server/instance/question.ts` — partially converted; still worth tracking here until it consistently uses the composed style
-- [ ] `server/instance/pty.ts` — still calls Pty facades directly
-- [ ] `server/instance/experimental.ts` — mixed state; some handlers are already composed, others still use facades
+- [x] `server/routes/instance/question.ts` — handlers yield `Question.Service`
+- [x] `server/routes/instance/provider.ts` — handlers yield `Provider.Service`, `ProviderAuth.Service`, and `Config.Service`
+- [x] `server/routes/instance/permission.ts` — handlers yield `Permission.Service`
+- [x] `server/routes/instance/mcp.ts` — handlers mostly yield `MCP.Service`
+- [x] `server/routes/instance/pty.ts` — handlers yield `Pty.Service`
 
-Additional route files that still participate in the migration:
+Files still worth tracking here:
 
-- [ ] `server/instance/index.ts` — Vcs, Agent, Skill, LSP, Format
-- [ ] `server/instance/file.ts` — Ripgrep, File, LSP
-- [ ] `server/instance/mcp.ts` — MCP facade-heavy
-- [ ] `server/instance/permission.ts` — Permission
-- [ ] `server/instance/workspace.ts` — Workspace
-- [ ] `server/instance/tui.ts` — Bus and Session
-- [ ] `server/instance/middleware.ts` — Session and Workspace lookups
+- [ ] `server/routes/instance/session.ts` — still the heaviest mixed file; many handlers are composed, but the file still mixes patterns and has direct `Bus.publish(...)` / `Session.list(...)` usage
+- [ ] `server/routes/instance/index.ts` — mostly converted, but still has direct `Instance.dispose()` / `Instance.*` reads for `/instance/dispose` and `/path`
+- [ ] `server/routes/instance/file.ts` — most handlers yield services, but `/find` still passes `Instance.directory` directly into ripgrep and `/find/symbol` is still stubbed
+- [ ] `server/routes/instance/experimental.ts` — mixed state; many handlers are composed, but some still rely on `runRequest(...)` or direct `Instance.project` reads
+- [ ] `server/routes/instance/middleware.ts` — still enters the instance via `Instance.provide(...)`
+- [ ] `server/routes/global.ts` — still uses `Instance.disposeAll()` and remains partly outside the fully-composed style
 
 ## Notes
 
-- Some handlers already use `AppRuntime.runPromise(Effect.gen(...))` in isolated places. Keep pushing those files toward one consistent style.
-- Route conversion is closely tied to facade removal. As services lose `makeRuntime`-backed async exports, route handlers should switch to yielding the service directly.
+- Route conversion is now less about facade removal and more about removing the remaining direct `Instance.*` reads, `Instance.provide(...)` boundaries, and small Promise-style bridges inside route files.
+- `jsonRequest(...)` / `runRequest(...)` already provide a good intermediate shape for many handlers. The remaining cleanup is mostly consistency work in the heavier files.

+ 254 - 28
packages/opencode/specs/effect/schema.md

@@ -1,12 +1,19 @@
 # Schema migration
 
-Practical reference for migrating data types in `packages/opencode` from Zod-first definitions to Effect Schema with Zod compatibility shims.
+Practical reference for migrating data types in `packages/opencode` from
+Zod-first definitions to Effect Schema with Zod compatibility shims.
 
 ## Goal
 
-Use Effect Schema as the source of truth for domain models, IDs, inputs, outputs, and typed errors.
+Use Effect Schema as the source of truth for domain models, IDs, inputs,
+outputs, and typed errors. Keep Zod available at existing HTTP, tool, and
+compatibility boundaries by exposing a `.zod` static derived from the Effect
+schema via `@/util/effect-zod`.
 
-Keep Zod available at existing HTTP, tool, and compatibility boundaries by exposing a `.zod` field derived from the Effect schema.
+The long-term driver is `specs/effect/http-api.md` — once the HTTP server
+moves to `@effect/platform`, every Schema-first DTO can flow through
+`HttpApi` / `HttpRouter` without a zod translation layer, and the entire
+`effect-zod` walker plus every `.zod` static can be deleted.
 
 ## Preferred shapes
 
@@ -24,17 +31,14 @@ export class Info extends Schema.Class<Info>("Foo.Info")({
 }
 ```
 
-If the class cannot reference itself cleanly during initialization, use the existing two-step pattern:
+If the class cannot reference itself cleanly during initialization, use the
+two-step `withStatics` pattern:
 
 ```ts
-const _Info = Schema.Struct({
+export const Info = Schema.Struct({
   id: FooID,
   name: Schema.String,
-})
-
-export const Info = Object.assign(_Info, {
-  zod: zod(_Info),
-})
+}).pipe(withStatics((s) => ({ zod: zod(s) })))
 ```
 
 ### Errors
@@ -49,27 +53,89 @@ export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("Foo
 
 ### IDs and branded leaf types
 
-Keep branded/schema-backed IDs as Effect schemas and expose `static readonly zod` for compatibility when callers still expect Zod.
+Keep branded/schema-backed IDs as Effect schemas and expose
+`static readonly zod` for compatibility when callers still expect Zod.
+
+### Refinements
+
+Reuse named refinements instead of re-spelling `z.number().int().positive()`
+in every schema. The `effect-zod` walker translates the Effect versions into
+the corresponding zod methods, so JSON Schema output (`type: integer`,
+`exclusiveMinimum`, `pattern`, `format: uuid`, …) is preserved.
+
+```ts
+const PositiveInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThan(0))
+const NonNegativeInt = Schema.Number.check(Schema.isInt()).check(Schema.isGreaterThanOrEqualTo(0))
+const HexColor = Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/))
+```
+
+See `test/util/effect-zod.test.ts` for the full set of translated checks.
 
 ## Compatibility rule
 
-During migration, route validators, tool parameters, and any existing Zod-based boundary should consume the derived `.zod` schema instead of maintaining a second hand-written Zod schema.
+During migration, route validators, tool parameters, and any existing
+Zod-based boundary should consume the derived `.zod` schema instead of
+maintaining a second hand-written Zod schema.
 
 The default should be:
 
 - Effect Schema owns the type
 - `.zod` exists only as a compatibility surface
-- new domain models should not start Zod-first unless there is a concrete boundary-specific need
+- new domain models should not start Zod-first unless there is a concrete
+  boundary-specific need
 
 ## When Zod can stay
 
 It is fine to keep a Zod-native schema temporarily when:
 
-- the type is only used at an HTTP or tool boundary
+- the type is only used at an HTTP or tool boundary and is not reused elsewhere
 - the validator depends on Zod-only transforms or behavior not yet covered by `zod()`
 - the migration would force unrelated churn across a large call graph
 
-When this happens, prefer leaving a short note or TODO rather than silently creating a parallel schema source of truth.
+When this happens, prefer leaving a short note or TODO rather than silently
+creating a parallel schema source of truth.
+
+## Escape hatches
+
+The walker in `@/util/effect-zod` exposes three explicit escape hatches for
+cases the pure-Schema path cannot express. Each one stays in the codebase
+only as long as its upstream or local dependency requires it — inline
+comments document when each can be deleted.
+
+### `ZodOverride` annotation
+
+Replaces the entire derivation with a hand-crafted zod schema. Used when:
+
+- the target carries external `$ref` metadata (e.g.
+  `config/model-id.ts` points at `https://models.dev/...`)
+- the target is a zod-only schema that cannot yet be expressed as Schema
+  (e.g. `ConfigAgent.Info`, `ConfigPermission.Info`, `Log.Level`)
+
+### `ZodPreprocess` annotation
+
+Wraps the derived zod schema with `z.preprocess(fn, inner)`. Used by
+`config/permission.ts` to inject `__originalKeys` before parsing, because
+`Schema.StructWithRest` canonicalises output (known fields first, catchall
+after) and destroys the user's original property order — which permission
+rule precedence depends on.
+
+Tracked upstream as `effect:core/wlh553`: "Schema: add preserveInputOrder
+(or pre-parse hook) for open structs." Once that lands, `ZodPreprocess` and
+the `__originalKeys` hack can both be deleted.
+
+### Local `DeepMutable<T>` in `config/config.ts`
+
+`Schema.Struct` produces `readonly` types. Some consumer code (notably the
+`Config` service) mutates `Info` objects directly, so a readonly-stripping
+utility is needed when casting the derived zod schema's output type.
+
+`Types.DeepMutable` from effect-smol would be a drop-in, but it widens
+`unknown` to `{}` in the fallback branch — a bug that affects any schema
+using `Schema.Record(String, Schema.Unknown)`.
+
+Tracked upstream as `effect:core/x228my`: "Types.DeepMutable widens unknown
+to `{}`." Once that lands, the local `DeepMutable` copy can be deleted and
+`Types.DeepMutable` used directly.
 
 ## Ordering
 
@@ -81,19 +147,179 @@ Migrate in this order:
 4. Service-local internal models
 5. Route and tool boundary validators that can switch to `.zod`
 
-This keeps shared types canonical first and makes boundary updates mostly mechanical.
-
-## Checklist
-
-- [ ] Shared `schema.ts` leaf models are Effect Schema-first
-- [ ] Exported `Info` / `Input` / `Output` types use `Schema.Class` where appropriate
-- [ ] Domain errors use `Schema.TaggedErrorClass`
-- [ ] Migrated types expose `.zod` for back compatibility
-- [ ] Route and tool validators consume derived `.zod` instead of duplicate Zod definitions
-- [ ] New domain models default to Effect Schema first
+This keeps shared types canonical first and makes boundary updates mostly
+mechanical.
+
+## Progress tracker
+
+### `src/config/` ✅ complete
+
+All of `packages/opencode/src/config/` has been migrated. Files that still
+import `z` do so only for local `ZodOverride` bridges or for `z.ZodType`
+type annotations — the `export const <Info|Spec>` values are all Effect
+Schema at source.
+
+- [x] skills, formatter, console-state, mcp, lsp, permission (leaves), model-id, command, plugin, provider
+- [x] server, layout
+- [x] keybinds
+- [x] permission#Info
+- [x] agent
+- [x] config.ts root
+
+### `src/*/schema.ts` leaf modules
+
+These are the highest-priority next targets. Each is a small, self-contained
+schema module with a clear domain.
+
+- [ ] `src/control-plane/schema.ts`
+- [ ] `src/permission/schema.ts`
+- [ ] `src/project/schema.ts`
+- [ ] `src/provider/schema.ts`
+- [ ] `src/pty/schema.ts`
+- [ ] `src/question/schema.ts`
+- [ ] `src/session/schema.ts`
+- [ ] `src/sync/schema.ts`
+- [ ] `src/tool/schema.ts`
+
+### Session domain
+
+Major cluster. Message + event types flow through the SSE API and every SDK
+output, so byte-identical SDK surface is critical.
+
+- [ ] `src/session/compaction.ts`
+- [ ] `src/session/message-v2.ts`
+- [ ] `src/session/message.ts`
+- [ ] `src/session/prompt.ts`
+- [ ] `src/session/revert.ts`
+- [ ] `src/session/session.ts`
+- [ ] `src/session/status.ts`
+- [ ] `src/session/summary.ts`
+- [ ] `src/session/todo.ts`
+
+### Provider domain
+
+- [ ] `src/provider/auth.ts`
+- [ ] `src/provider/models.ts`
+- [ ] `src/provider/provider.ts`
+
+### Tool schemas
+
+Each tool declares its parameters via a zod schema. Tools are consumed by
+both the in-process runtime and the AI SDK's tool-calling layer, so the
+emitted JSON Schema must stay byte-identical.
+
+- [ ] `src/tool/apply_patch.ts`
+- [ ] `src/tool/bash.ts`
+- [ ] `src/tool/codesearch.ts`
+- [ ] `src/tool/edit.ts`
+- [ ] `src/tool/glob.ts`
+- [ ] `src/tool/grep.ts`
+- [ ] `src/tool/invalid.ts`
+- [ ] `src/tool/lsp.ts`
+- [ ] `src/tool/multiedit.ts`
+- [ ] `src/tool/plan.ts`
+- [ ] `src/tool/question.ts`
+- [ ] `src/tool/read.ts`
+- [ ] `src/tool/registry.ts`
+- [ ] `src/tool/skill.ts`
+- [ ] `src/tool/task.ts`
+- [ ] `src/tool/todo.ts`
+- [ ] `src/tool/tool.ts`
+- [ ] `src/tool/webfetch.ts`
+- [ ] `src/tool/websearch.ts`
+- [ ] `src/tool/write.ts`
+
+### HTTP route boundaries
+
+Every file in `src/server/routes/` uses hono-openapi with zod validators for
+route inputs/outputs. Migrating these individually is the last step; most
+will switch to `.zod` derived from the Schema-migrated domain types above,
+which means touching them is largely mechanical once the domain side is
+done.
+
+- [ ] `src/server/error.ts`
+- [ ] `src/server/event.ts`
+- [ ] `src/server/projectors.ts`
+- [ ] `src/server/routes/control/index.ts`
+- [ ] `src/server/routes/control/workspace.ts`
+- [ ] `src/server/routes/global.ts`
+- [ ] `src/server/routes/instance/index.ts`
+- [ ] `src/server/routes/instance/config.ts`
+- [ ] `src/server/routes/instance/event.ts`
+- [ ] `src/server/routes/instance/experimental.ts`
+- [ ] `src/server/routes/instance/file.ts`
+- [ ] `src/server/routes/instance/mcp.ts`
+- [ ] `src/server/routes/instance/permission.ts`
+- [ ] `src/server/routes/instance/project.ts`
+- [ ] `src/server/routes/instance/provider.ts`
+- [ ] `src/server/routes/instance/pty.ts`
+- [ ] `src/server/routes/instance/question.ts`
+- [ ] `src/server/routes/instance/session.ts`
+- [ ] `src/server/routes/instance/sync.ts`
+- [ ] `src/server/routes/instance/tui.ts`
+
+The bigger prize for this group is the `@effect/platform` HTTP migration
+described in `specs/effect/http-api.md`. Once that lands, every one of
+these files changes shape entirely (`HttpApi.endpoint(...)` and friends),
+so the Schema-first domain types become a prerequisite rather than a
+sibling task.
+
+### Everything else
+
+Small / shared / control-plane / CLI. Mostly independent; can be done
+piecewise.
+
+- [ ] `src/acp/agent.ts`
+- [ ] `src/agent/agent.ts`
+- [ ] `src/bus/bus-event.ts`
+- [ ] `src/bus/index.ts`
+- [ ] `src/cli/cmd/tui/config/tui-migrate.ts`
+- [ ] `src/cli/cmd/tui/config/tui-schema.ts`
+- [ ] `src/cli/cmd/tui/config/tui.ts`
+- [ ] `src/cli/cmd/tui/event.ts`
+- [ ] `src/cli/ui.ts`
+- [ ] `src/command/index.ts`
+- [ ] `src/control-plane/adaptors/worktree.ts`
+- [ ] `src/control-plane/types.ts`
+- [ ] `src/control-plane/workspace.ts`
+- [ ] `src/file/index.ts`
+- [ ] `src/file/ripgrep.ts`
+- [ ] `src/file/watcher.ts`
+- [ ] `src/format/index.ts`
+- [ ] `src/id/id.ts`
+- [ ] `src/ide/index.ts`
+- [ ] `src/installation/index.ts`
+- [ ] `src/lsp/client.ts`
+- [ ] `src/lsp/lsp.ts`
+- [ ] `src/mcp/auth.ts`
+- [ ] `src/patch/index.ts`
+- [ ] `src/plugin/github-copilot/models.ts`
+- [ ] `src/project/project.ts`
+- [ ] `src/project/vcs.ts`
+- [ ] `src/pty/index.ts`
+- [ ] `src/skill/index.ts`
+- [ ] `src/snapshot/index.ts`
+- [ ] `src/storage/db.ts`
+- [ ] `src/storage/storage.ts`
+- [ ] `src/sync/index.ts`
+- [ ] `src/util/fn.ts`
+- [ ] `src/util/log.ts`
+- [ ] `src/util/update-schema.ts`
+- [ ] `src/worktree/index.ts`
+
+### Do-not-migrate
+
+- `src/util/effect-zod.ts` — the walker itself. Stays zod-importing forever
+  (it's what emits zod from Schema). Goes away only when the `.zod`
+  compatibility layer is no longer needed anywhere.
 
 ## Notes
 
-- Use `@/util/effect-zod` for all Schema -> Zod conversion.
-- Prefer one canonical schema definition. Avoid maintaining parallel Zod and Effect definitions for the same domain type.
-- Keep the migration incremental. Converting the domain model first is more valuable than converting every boundary in the same change.
+- Use `@/util/effect-zod` for all Schema → Zod conversion.
+- Prefer one canonical schema definition. Avoid maintaining parallel Zod and
+  Effect definitions for the same domain type.
+- Keep the migration incremental. Converting the domain model first is more
+  valuable than converting every boundary in the same change.
+- Every migrated file should leave the generated SDK output (`packages/sdk/
+openapi.json` and `packages/sdk/js/src/v2/gen/types.gen.ts`) byte-identical
+  unless the change is deliberately user-visible.

+ 19 - 17
packages/opencode/specs/effect/server-package.md

@@ -40,13 +40,13 @@ Everything still lives in `packages/opencode`.
 Important current facts:
 
 - there is no `packages/core` or `packages/cli` workspace yet
-- `packages/server` now exists as a minimal scaffold package, but it does not own any real route contracts, handlers, or runtime composition yet
+- there is no `packages/server` workspace yet on this branch
 - the main host server is still Hono-based in `src/server/server.ts`
 - current OpenAPI generation is Hono-based through `Server.openapi()` and `cli/cmd/generate.ts`
 - the Effect runtime and app layer are centralized in `src/effect/app-runtime.ts` and `src/effect/run-service.ts`
-- there is already one experimental Effect `HttpApi` slice at `src/server/instance/httpapi/question.ts`
-- that experimental slice is mounted under `/experimental/httpapi/question`
-- that experimental slice already has an end-to-end test at `test/server/question-httpapi.test.ts`
+- there are already bridged Effect `HttpApi` slices under `src/server/routes/instance/httpapi/*`
+- those slices are mounted into the Hono server behind `OPENCODE_EXPERIMENTAL_HTTPAPI`
+- the bridge currently covers `question`, `permission`, `provider`, partial `config`, and partial `project` routes
 
 This means the package split should start from an extraction path, not from greenfield package ownership.
 
@@ -209,17 +209,19 @@ Current host and route composition:
 
 - `src/server/server.ts`
 - `src/server/control/index.ts`
-- `src/server/instance/index.ts`
+- `src/server/routes/instance/index.ts`
 - `src/server/middleware.ts`
 - `src/server/adapter.bun.ts`
 - `src/server/adapter.node.ts`
 
-Current experimental `HttpApi` slice:
+Current bridged `HttpApi` slices:
 
-- `src/server/instance/httpapi/question.ts`
-- `src/server/instance/httpapi/index.ts`
-- `src/server/instance/experimental.ts`
-- `test/server/question-httpapi.test.ts`
+- `src/server/routes/instance/httpapi/question.ts`
+- `src/server/routes/instance/httpapi/permission.ts`
+- `src/server/routes/instance/httpapi/provider.ts`
+- `src/server/routes/instance/httpapi/config.ts`
+- `src/server/routes/instance/httpapi/project.ts`
+- `src/server/routes/instance/httpapi/server.ts`
 
 Current OpenAPI flow:
 
@@ -245,7 +247,7 @@ Keep in `packages/opencode` for now:
 
 - `src/server/server.ts`
 - `src/server/control/index.ts`
-- `src/server/instance/*.ts`
+- `src/server/routes/**/*.ts`
 - `src/server/middleware.ts`
 - `src/server/adapter.*.ts`
 - `src/effect/app-runtime.ts`
@@ -305,14 +307,13 @@ Bad early migration targets:
 
 ## First vertical slice
 
-The first slice for the package split is the existing experimental `question` group.
+The first slice for the package split is still the existing `question` `HttpApi` group.
 
 Why `question` first:
 
 - it already exists as an experimental `HttpApi` slice
 - it already follows the desired contract and implementation split in one file
 - it is already mounted through the current Hono host
-- it already has an end-to-end test
 - it is JSON-only
 - it has low blast radius
 
@@ -357,7 +358,7 @@ Done means:
 
 Scope:
 
-- extract the pure `HttpApi` contract from `src/server/instance/httpapi/question.ts`
+- extract the pure `HttpApi` contract from `src/server/routes/instance/httpapi/question.ts`
 - place it in `packages/server/src/definition/question.ts`
 - aggregate it in `packages/server/src/definition/api.ts`
 - generate OpenAPI in `packages/server/src/openapi.ts`
@@ -399,8 +400,9 @@ Scope:
 
 - replace local experimental question route wiring in `packages/opencode`
 - keep the same mount path:
-- `/experimental/httpapi/question`
-- `/experimental/httpapi/question/doc`
+- `/question`
+- `/question/:requestID/reply`
+- `/question/:requestID/reject`
 
 Rules:
 
@@ -569,7 +571,7 @@ For package-split PRs, validate the smallest useful thing.
 Typical validation for the first waves:
 
 - `bun typecheck` in the touched package directory or directories
-- the relevant route test, especially `test/server/question-httpapi.test.ts`
+- the relevant server / route coverage for the migrated slice
 - merged OpenAPI coverage if the PR touches spec generation
 
 Do not run tests from repo root.

+ 3 - 5
packages/opencode/specs/effect/tools.md

@@ -36,7 +36,7 @@ This keeps tool tests aligned with the production service graph and makes follow
 
 ## Exported tools
 
-These exported tool definitions already exist in `src/tool` and are on the current Effect-native `Tool.define(...)` path:
+These exported tool definitions currently use `Tool.define(...)` in `src/tool`:
 
 - [x] `apply_patch.ts`
 - [x] `bash.ts`
@@ -45,7 +45,6 @@ These exported tool definitions already exist in `src/tool` and are on the curre
 - [x] `glob.ts`
 - [x] `grep.ts`
 - [x] `invalid.ts`
-- [x] `ls.ts`
 - [x] `lsp.ts`
 - [x] `multiedit.ts`
 - [x] `plan.ts`
@@ -60,7 +59,7 @@ These exported tool definitions already exist in `src/tool` and are on the curre
 
 Notes:
 
-- `batch.ts` is no longer a current tool file and should not be tracked here.
+- There is no current `ls.ts` tool file on this branch.
 - `truncate.ts` is an Effect service used by tools, not a tool definition itself.
 - `mcp-exa.ts`, `external-directory.ts`, and `schema.ts` are support modules, not standalone tool definitions.
 
@@ -73,7 +72,7 @@ Current spot cleanups worth tracking:
 - [ ] `read.ts` — still bridges to Node stream / `readline` helpers and Promise-based binary detection
 - [ ] `bash.ts` — already uses Effect child-process primitives; only keep tracking shell-specific platform bridges and parser/loading details as they come up
 - [ ] `webfetch.ts` — already uses `HttpClient`; remaining work is limited to smaller boundary helpers like HTML text extraction
-- [ ] `file/ripgrep.ts` — adjacent to tool migration; still has raw fs/process usage that affects `grep.ts` and `ls.ts`
+- [ ] `file/ripgrep.ts` — adjacent to tool migration; still has raw fs/process usage that affects `grep.ts` and file-search routes
 - [ ] `patch/index.ts` — adjacent to tool migration; still has raw fs usage behind patch application
 
 Notable items that are already effectively on the target path and do not need separate migration bullets right now:
@@ -83,7 +82,6 @@ Notable items that are already effectively on the target path and do not need se
 - `write.ts`
 - `codesearch.ts`
 - `websearch.ts`
-- `ls.ts`
 - `multiedit.ts`
 - `edit.ts`
 

+ 13 - 1
packages/opencode/src/cli/cmd/generate.ts

@@ -25,7 +25,19 @@ export const GenerateCommand = {
         ]
       }
     }
-    const json = JSON.stringify(specs, null, 2)
+    const raw = JSON.stringify(specs, null, 2)
+
+    // Format through prettier so output is byte-identical to committed file
+    // regardless of whether ./script/format.ts runs afterward.
+    const prettier = await import("prettier")
+    const babel = await import("prettier/plugins/babel")
+    const estree = await import("prettier/plugins/estree")
+    const format = prettier.format ?? prettier.default?.format
+    const json = await format(raw, {
+      parser: "json",
+      plugins: [babel.default ?? babel, estree.default ?? estree],
+      printWidth: 120,
+    })
 
     // Wait for stdout to finish writing before process.exit() is called
     await new Promise<void>((resolve, reject) => {

+ 8 - 4
packages/opencode/src/cli/cmd/tui/app.tsx

@@ -135,9 +135,7 @@ export function tui(input: {
       await TuiPluginRuntime.dispose()
     }
 
-    console.log("starting renderer")
     const renderer = await createCliRenderer(rendererConfig(input.config))
-    console.log("renderer started")
 
     await render(() => {
       return (
@@ -152,7 +150,7 @@ export function tui(input: {
                 <ToastProvider>
                   <RouteProvider
                     initialRoute={
-                      (input.args.sessionID || input.args.continue) && !input.args.fork
+                      input.args.continue
                         ? {
                             type: "session",
                             sessionID: "dummy",
@@ -344,6 +342,12 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
           })
         local.model.set({ providerID, modelID }, { recent: true })
       }
+      if (args.sessionID && !args.fork) {
+        route.navigate({
+          type: "session",
+          sessionID: args.sessionID,
+        })
+      }
     })
   })
 
@@ -602,7 +606,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
       category: "System",
     },
     {
-      title: "Toggle theme mode",
+      title: mode() === "dark" ? "Switch to light mode" : "Switch to dark mode",
       value: "theme.switch_mode",
       onSelect: (dialog) => {
         setMode(mode() === "dark" ? "light" : "dark")

+ 1 - 0
packages/opencode/src/cli/cmd/tui/component/dialog-command.tsx

@@ -63,6 +63,7 @@ function init() {
   useKeyboard((evt) => {
     if (suspended()) return
     if (dialog.stack.length > 0) return
+    if (evt.defaultPrevented) return
     for (const option of entries()) {
       if (!isEnabled(option)) continue
       if (option.keybind && keybind.match(option.keybind, evt)) {

+ 2 - 7
packages/opencode/src/cli/cmd/tui/component/dialog-session-list.tsx

@@ -139,15 +139,10 @@ export function DialogSessionList() {
                 {desc}{" "}
                 <span
                   style={{
-                    fg:
-                      workspaceStatus === "error"
-                        ? theme.error
-                        : workspaceStatus === "disconnected"
-                          ? theme.textMuted
-                          : theme.success,
+                    fg: workspaceStatus === "connected" ? theme.success : theme.error,
                   }}
                 >
-                  
+                  ●
                 </span>
               </>
             )

+ 11 - 1
packages/opencode/src/cli/cmd/tui/component/dialog-workspace-create.tsx

@@ -139,7 +139,13 @@ export async function restoreWorkspaceSession(input: {
     total: result.data.total,
   })
 
-  await Promise.all([input.project.workspace.sync(), input.sync.session.refresh()]).catch((err) => {
+  input.project.workspace.set(input.workspaceID)
+
+  try {
+    await input.sync.bootstrap({ fatal: false })
+  } catch (e) {}
+
+  await Promise.all([input.project.workspace.sync(), input.sync.session.sync(input.sessionID)]).catch((err) => {
     log.error("session restore refresh failed", {
       workspaceID: input.workspaceID,
       sessionID: input.sessionID,
@@ -229,6 +235,10 @@ export function DialogWorkspaceCreate(props: { onSelect: (workspaceID: string) =
     })
 
     const result = await sdk.client.experimental.workspace.create({ type, branch: null }).catch((err) => {
+      toast.show({
+        message: "Creating workspace failed",
+        variant: "error",
+      })
       log.error("workspace create request failed", {
         type,
         error: errorData(err),

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini