Explorar o código

Merge branch 'dev' of https://github.com/sst/opencode into dev

David Hill hai 3 meses
pai
achega
c6e830c954
Modificáronse 100 ficheiros con 2652 adicións e 4540 borrados
  1. 2 2
      .github/workflows/auto-label-tui.yml
  2. 5 0
      .github/workflows/publish.yml
  3. 34 0
      .github/workflows/sync-zed-extension.yml
  4. 0 0
      .opencode/opencode.json
  5. 23 23
      AGENTS.md
  6. 6 0
      STATS.md
  7. 178 136
      bun.lock
  8. 14 43
      github/index.ts
  9. 1 1
      github/sst-env.d.ts
  10. 9 7
      infra/console.ts
  11. 14 14
      logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json
  12. 3 3
      package.json
  13. 3 0
      packages/console/app/.gitignore
  14. 2 2
      packages/console/app/package.json
  15. 103 0
      packages/console/app/script/generate-sitemap.ts
  16. 1 1
      packages/console/app/src/app.tsx
  17. 1 1
      packages/console/app/src/component/dropdown.css
  18. 1 1
      packages/console/app/src/component/modal.css
  19. 3 0
      packages/console/app/src/config.ts
  20. 1 4
      packages/console/app/src/lib/github.ts
  21. 5 8
      packages/console/app/src/routes/brand/index.css
  22. 25 125
      packages/console/app/src/routes/brand/index.tsx
  23. 5 0
      packages/console/app/src/routes/desktop-feedback.ts
  24. 1 1
      packages/console/app/src/routes/enterprise/index.css
  25. 23 40
      packages/console/app/src/routes/enterprise/index.tsx
  26. 1 1
      packages/console/app/src/routes/index.css
  27. 110 647
      packages/console/app/src/routes/index.tsx
  28. 116 118
      packages/console/app/src/routes/stripe/webhook.ts
  29. 4 6
      packages/console/app/src/routes/temp.tsx
  30. 1 1
      packages/console/app/src/routes/user-menu.css
  31. 1 1
      packages/console/app/src/routes/workspace-picker.css
  32. 1 1
      packages/console/app/src/routes/workspace.css
  33. 52 1
      packages/console/app/src/routes/workspace/[id]/billing/billing-section.module.css
  34. 127 58
      packages/console/app/src/routes/workspace/[id]/billing/billing-section.tsx
  35. 1 1
      packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.module.css
  36. 11 17
      packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx
  37. 202 1
      packages/console/app/src/routes/workspace/[id]/billing/reload-section.module.css
  38. 131 35
      packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx
  39. 22 18
      packages/console/app/src/routes/workspace/[id]/index.tsx
  40. 3 6
      packages/console/app/src/routes/workspace/[id]/keys/key-section.module.css
  41. 1 1
      packages/console/app/src/routes/workspace/[id]/members/role-dropdown.css
  42. 3 11
      packages/console/app/src/routes/workspace/[id]/model-section.tsx
  43. 1 1
      packages/console/app/src/routes/workspace/[id]/new-user-section.module.css
  44. 1 2
      packages/console/app/src/routes/workspace/[id]/provider-section.module.css
  45. 3 1
      packages/console/app/src/routes/workspace/[id]/provider-section.tsx
  46. 3 3
      packages/console/app/src/routes/workspace/[id]/settings/settings-section.module.css
  47. 30 8
      packages/console/app/src/routes/workspace/common.tsx
  48. 1 1
      packages/console/app/src/routes/zen/index.css
  49. 3 1
      packages/console/app/src/routes/zen/index.tsx
  50. 1 0
      packages/console/app/src/routes/zen/util/error.ts
  51. 53 80
      packages/console/app/src/routes/zen/util/handler.ts
  52. 29 6
      packages/console/app/src/routes/zen/util/provider/anthropic.ts
  53. 3 1
      packages/console/app/src/routes/zen/util/provider/openai-compatible.ts
  54. 34 7
      packages/console/app/src/routes/zen/util/provider/openai.ts
  55. 35 0
      packages/console/app/src/routes/zen/util/rateLimiter.ts
  56. 1 4
      packages/console/app/src/routes/zen/v1/models.ts
  57. 1 1
      packages/console/app/sst-env.d.ts
  58. 13 44
      packages/console/core/migrations/meta/0018_snapshot.json
  59. 13 44
      packages/console/core/migrations/meta/0019_snapshot.json
  60. 13 44
      packages/console/core/migrations/meta/0020_snapshot.json
  61. 13 44
      packages/console/core/migrations/meta/0021_snapshot.json
  62. 14 48
      packages/console/core/migrations/meta/0022_snapshot.json
  63. 16 54
      packages/console/core/migrations/meta/0023_snapshot.json
  64. 16 54
      packages/console/core/migrations/meta/0024_snapshot.json
  65. 16 54
      packages/console/core/migrations/meta/0025_snapshot.json
  66. 15 50
      packages/console/core/migrations/meta/0026_snapshot.json
  67. 15 50
      packages/console/core/migrations/meta/0027_snapshot.json
  68. 15 50
      packages/console/core/migrations/meta/0028_snapshot.json
  69. 15 50
      packages/console/core/migrations/meta/0029_snapshot.json
  70. 17 58
      packages/console/core/migrations/meta/0030_snapshot.json
  71. 19 66
      packages/console/core/migrations/meta/0031_snapshot.json
  72. 19 66
      packages/console/core/migrations/meta/0032_snapshot.json
  73. 19 66
      packages/console/core/migrations/meta/0033_snapshot.json
  74. 20 70
      packages/console/core/migrations/meta/0034_snapshot.json
  75. 20 70
      packages/console/core/migrations/meta/0035_snapshot.json
  76. 22 76
      packages/console/core/migrations/meta/0036_snapshot.json
  77. 22 76
      packages/console/core/migrations/meta/0037_snapshot.json
  78. 1 1
      packages/console/core/migrations/meta/_journal.json
  79. 1 1
      packages/console/core/package.json
  80. 3 10
      packages/console/core/script/lookup-user.ts
  81. 1 9
      packages/console/core/script/reset-db.ts
  82. 0 1
      packages/console/core/script/update-models.ts
  83. 38 23
      packages/console/core/src/billing.ts
  84. 2 3
      packages/console/core/src/model.ts
  85. 96 87
      packages/console/core/sst-env.d.ts
  86. 1 1
      packages/console/function/package.json
  87. 96 87
      packages/console/function/sst-env.d.ts
  88. 1 1
      packages/console/mail/package.json
  89. 1 1
      packages/console/mail/sst-env.d.ts
  90. 4 1
      packages/console/resource/package.json
  91. 58 1
      packages/console/resource/resource.node.ts
  92. 96 87
      packages/console/resource/sst-env.d.ts
  93. 2 5
      packages/desktop/package.json
  94. 0 846
      packages/desktop/src/components/code.tsx
  95. 2 2
      packages/desktop/src/components/file-tree.tsx
  96. 5 6
      packages/desktop/src/components/message-progress.tsx
  97. 175 68
      packages/desktop/src/components/prompt-input.tsx
  98. 0 558
      packages/desktop/src/components/theme.json
  99. 71 155
      packages/desktop/src/context/local.tsx
  100. 217 0
      packages/desktop/src/context/session.tsx

+ 2 - 2
.github/workflows/auto-label-tui.yml

@@ -20,8 +20,8 @@ jobs:
             const title = issue.title;
             const description = issue.body || '';
 
-            // Check for web/desktop keywords
-            const webPattern = /\b(web|desktop)\b/i;
+            // Check for "opencode web" keyword
+            const webPattern = /(opencode web)/i;
             const isWebRelated = webPattern.test(title) || webPattern.test(description);
 
             // Check for version patterns like v1.0.x or 1.0.x

+ 5 - 0
.github/workflows/publish.yml

@@ -12,6 +12,10 @@ on:
           - major
           - minor
           - patch
+      version:
+        description: "Override version (optional)"
+        required: false
+        type: string
 
 concurrency: ${{ github.workflow }}-${{ github.ref }}
 
@@ -62,6 +66,7 @@ jobs:
           ./script/publish.ts
         env:
           OPENCODE_BUMP: ${{ inputs.bump }}
+          OPENCODE_VERSION: ${{ inputs.version }}
           OPENCODE_CHANNEL: latest
           NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
           GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}

+ 34 - 0
.github/workflows/sync-zed-extension.yml

@@ -0,0 +1,34 @@
+name: "sync-zed-extension"
+
+on:
+  workflow_dispatch:
+  release:
+    types: [published]
+
+jobs:
+  zed:
+    name: Release Zed Extension
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      - uses: ./.github/actions/setup-bun
+
+      - name: Get version tag
+        id: get_tag
+        run: |
+          if [ "${{ github.event_name }}" = "release" ]; then
+            TAG="${{ github.event.release.tag_name }}"
+          else
+            TAG=$(git tag --list 'v[0-9]*.*' --sort=-version:refname | head -n 1)
+          fi
+          echo "tag=${TAG}" >> $GITHUB_OUTPUT
+          echo "Using tag: ${TAG}"
+
+      - name: Sync Zed extension
+        run: |
+          ./script/sync-zed.ts ${{ steps.get_tag.outputs.tag }}
+        env:
+          GITHUB_TOKEN: ${{ secrets.SST_GITHUB_TOKEN }}

+ 0 - 0
opencode.json → .opencode/opencode.json


+ 23 - 23
AGENTS.md

@@ -21,27 +21,27 @@
 
 json
 {
-    "recipient_name": "multi_tool_use.parallel",
-    "parameters": {
-        "tool_uses": [
-            {
-                "recipient_name": "functions.read",
-                "parameters": {
-                    "filePath": "path/to/file.tsx"
-                }
-            },
-            {
-                "recipient_name": "functions.read",
-                "parameters": {
-                    "filePath": "path/to/file.ts"
-                }
-            },
-            {
-                "recipient_name": "functions.read",
-                "parameters": {
-                    "filePath": "path/to/file.md"
-                }
-            }
-        ]
-    }
+"recipient_name": "multi_tool_use.parallel",
+"parameters": {
+"tool_uses": [
+{
+"recipient_name": "functions.read",
+"parameters": {
+"filePath": "path/to/file.tsx"
+}
+},
+{
+"recipient_name": "functions.read",
+"parameters": {
+"filePath": "path/to/file.ts"
+}
+},
+{
+"recipient_name": "functions.read",
+"parameters": {
+"filePath": "path/to/file.md"
+}
+}
+]
+}
 }

+ 6 - 0
STATS.md

@@ -130,3 +130,9 @@
 | 2025-11-02 | 644,067 (+7,967)  | 590,004 (+8,198)  | 1,234,071 (+16,165) |
 | 2025-11-03 | 653,130 (+9,063)  | 597,139 (+7,135)  | 1,250,269 (+16,198) |
 | 2025-11-04 | 663,912 (+10,782) | 608,056 (+10,917) | 1,271,968 (+21,699) |
+| 2025-11-05 | 675,074 (+11,162) | 619,690 (+11,634) | 1,294,764 (+22,796) |
+| 2025-11-06 | 686,252 (+11,178) | 630,885 (+11,195) | 1,317,137 (+22,373) |
+| 2025-11-07 | 696,646 (+10,394) | 642,146 (+11,261) | 1,338,792 (+21,655) |
+| 2025-11-08 | 706,035 (+9,389)  | 653,489 (+11,343) | 1,359,524 (+20,732) |
+| 2025-11-09 | 713,462 (+7,427)  | 660,459 (+6,970)  | 1,373,921 (+14,397) |
+| 2025-11-10 | 722,288 (+8,826)  | 668,225 (+7,766)  | 1,390,513 (+16,592) |

+ 178 - 136
bun.lock

@@ -11,7 +11,7 @@
         "@tsconfig/bun": "catalog:",
         "husky": "9.1.7",
         "prettier": "3.6.2",
-        "sst": "3.17.19",
+        "sst": "3.17.23",
         "turbo": "2.5.6",
       },
     },
@@ -39,7 +39,7 @@
     },
     "packages/console/core": {
       "name": "@opencode-ai/console-core",
-      "version": "1.0.23",
+      "version": "1.0.55",
       "dependencies": {
         "@aws-sdk/client-sts": "3.782.0",
         "@jsx-email/render": "1.1.1",
@@ -66,7 +66,7 @@
     },
     "packages/console/function": {
       "name": "@opencode-ai/console-function",
-      "version": "1.0.23",
+      "version": "1.0.55",
       "dependencies": {
         "@ai-sdk/anthropic": "2.0.0",
         "@ai-sdk/openai": "2.0.2",
@@ -90,7 +90,7 @@
     },
     "packages/console/mail": {
       "name": "@opencode-ai/console-mail",
-      "version": "1.0.23",
+      "version": "1.0.55",
       "dependencies": {
         "@jsx-email/all": "2.2.3",
         "@jsx-email/cli": "1.4.3",
@@ -106,12 +106,15 @@
         "@cloudflare/workers-types": "catalog:",
       },
       "devDependencies": {
+        "@cloudflare/workers-types": "catalog:",
         "@tsconfig/node22": "22.0.2",
+        "@types/node": "catalog:",
+        "cloudflare": "5.2.0",
       },
     },
     "packages/desktop": {
       "name": "@opencode-ai/desktop",
-      "version": "1.0.23",
+      "version": "1.0.55",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
@@ -121,6 +124,7 @@
         "@solid-primitives/event-bus": "1.1.2",
         "@solid-primitives/resize-observer": "2.1.3",
         "@solid-primitives/scroll": "2.1.3",
+        "@solid-primitives/storage": "4.3.3",
         "@solidjs/meta": "catalog:",
         "@solidjs/router": "0.15.3",
         "@thisbeyond/solid-dnd": "0.7.5",
@@ -150,7 +154,7 @@
     },
     "packages/function": {
       "name": "@opencode-ai/function",
-      "version": "1.0.23",
+      "version": "1.0.55",
       "dependencies": {
         "@octokit/auth-app": "8.0.1",
         "@octokit/rest": "22.0.0",
@@ -166,14 +170,14 @@
     },
     "packages/opencode": {
       "name": "opencode",
-      "version": "1.0.23",
+      "version": "1.0.55",
       "bin": {
         "opencode": "./bin/opencode",
       },
       "dependencies": {
         "@actions/core": "1.11.1",
         "@actions/github": "6.0.1",
-        "@agentclientprotocol/sdk": "0.4.9",
+        "@agentclientprotocol/sdk": "0.5.1",
         "@clack/prompts": "1.0.0-alpha.1",
         "@hono/standard-validator": "0.1.5",
         "@hono/zod-validator": "catalog:",
@@ -184,8 +188,8 @@
         "@opencode-ai/plugin": "workspace:*",
         "@opencode-ai/script": "workspace:*",
         "@opencode-ai/sdk": "workspace:*",
-        "@opentui/core": "0.1.33",
-        "@opentui/solid": "0.1.33",
+        "@opentui/core": "0.0.0-20251108-0c7899b1",
+        "@opentui/solid": "0.0.0-20251108-0c7899b1",
         "@parcel/watcher": "2.5.1",
         "@pierre/precision-diffs": "catalog:",
         "@solid-primitives/event-bus": "1.1.2",
@@ -207,6 +211,7 @@
         "partial-json": "0.1.7",
         "remeda": "catalog:",
         "solid-js": "catalog:",
+        "strip-ansi": "7.1.2",
         "tree-sitter-bash": "0.25.0",
         "turndown": "7.2.0",
         "ulid": "catalog:",
@@ -243,7 +248,7 @@
     },
     "packages/plugin": {
       "name": "@opencode-ai/plugin",
-      "version": "1.0.23",
+      "version": "1.0.55",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "zod": "catalog:",
@@ -263,7 +268,7 @@
     },
     "packages/sdk/js": {
       "name": "@opencode-ai/sdk",
-      "version": "1.0.23",
+      "version": "1.0.55",
       "devDependencies": {
         "@hey-api/openapi-ts": "0.81.0",
         "@tsconfig/node22": "catalog:",
@@ -274,7 +279,7 @@
     },
     "packages/slack": {
       "name": "@opencode-ai/slack",
-      "version": "1.0.23",
+      "version": "1.0.55",
       "dependencies": {
         "@opencode-ai/sdk": "workspace:*",
         "@slack/bolt": "^3.17.1",
@@ -287,7 +292,7 @@
     },
     "packages/ui": {
       "name": "@opencode-ai/ui",
-      "version": "1.0.23",
+      "version": "1.0.55",
       "dependencies": {
         "@kobalte/core": "catalog:",
         "@opencode-ai/sdk": "workspace:*",
@@ -317,7 +322,7 @@
     },
     "packages/web": {
       "name": "@opencode-ai/web",
-      "version": "1.0.23",
+      "version": "1.0.55",
       "dependencies": {
         "@astrojs/cloudflare": "12.6.3",
         "@astrojs/markdown-remark": "6.3.1",
@@ -351,7 +356,6 @@
   "trustedDependencies": [
     "sharp",
     "esbuild",
-    "tree-sitter",
     "web-tree-sitter",
     "tree-sitter-bash",
   ],
@@ -364,7 +368,7 @@
     "@hono/zod-validator": "0.4.2",
     "@kobalte/core": "0.13.11",
     "@openauthjs/openauth": "0.0.0-20250322224806",
-    "@pierre/precision-diffs": "0.4.1",
+    "@pierre/precision-diffs": "0.4.4",
     "@solidjs/meta": "0.29.4",
     "@tailwindcss/vite": "4.1.11",
     "@tsconfig/bun": "1.0.9",
@@ -401,7 +405,7 @@
 
     "@adobe/css-tools": ["@adobe/[email protected]", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
 
-    "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.4.9", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-ExwH828LaTGoTTjxuw49l+fwOLA+Yx0+qkWn1TcHMOsY5mVI9CUfkj7ZDhv2klgZ7mJeT+lxX/Dn/KINv1AkNQ=="],
+    "@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.5.1", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg=="],
 
     "@ai-sdk/amazon-bedrock": ["@ai-sdk/[email protected]", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
 
@@ -571,7 +575,7 @@
 
     "@cloudflare/kv-asset-handler": ["@cloudflare/[email protected]", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="],
 
-    "@cloudflare/unenv-preset": ["@cloudflare/[email protected].8", "", { "peerDependencies": { "unenv": "2.0.0-rc.21", "workerd": "^1.20250927.0" }, "optionalPeers": ["workerd"] }, "sha512-Ky929MfHh+qPhwCapYrRPwPVHtA2Ioex/DbGZyskGyNRDe9Ru3WThYZivyNVaPy5ergQSgMs9OKrM9Ajtz9F6w=="],
+    "@cloudflare/unenv-preset": ["@cloudflare/[email protected].9", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "^1.20250927.0" }, "optionalPeers": ["workerd"] }, "sha512-Drm7qlTKnvncEv+DANiQNEonq0H0LyIsoFZYJ6tJ8OhAoy5udIE8yp6BsVDYcIjcYLIybp4M7c/P7ly/56SoHg=="],
 
     "@cloudflare/workerd-darwin-64": ["@cloudflare/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-0DirVP+Z82RtZLlK2B+VhLOkk+ShBqDYO/jhcRw4oVlp0TOvk3cOVZChrt3+y3NV8Y/PYgTEywzLKFSziK4wCg=="],
 
@@ -601,7 +605,7 @@
 
     "@drizzle-team/brocli": ["@drizzle-team/[email protected]", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
 
-    "@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="],
+    "@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="],
 
     "@emotion/is-prop-valid": ["@emotion/[email protected]", "", { "dependencies": { "@emotion/memoize": "0.7.4" } }, "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA=="],
 
@@ -611,57 +615,57 @@
 
     "@esbuild-kit/esm-loader": ["@esbuild-kit/[email protected]", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
 
-    "@esbuild/aix-ppc64": ["@esbuild/[email protected]1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="],
+    "@esbuild/aix-ppc64": ["@esbuild/[email protected]2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
 
-    "@esbuild/android-arm": ["@esbuild/[email protected]1", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="],
+    "@esbuild/android-arm": ["@esbuild/[email protected]2", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
 
-    "@esbuild/android-arm64": ["@esbuild/[email protected]1", "", { "os": "android", "cpu": "arm64" }, "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ=="],
+    "@esbuild/android-arm64": ["@esbuild/[email protected]2", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
 
-    "@esbuild/android-x64": ["@esbuild/[email protected]1", "", { "os": "android", "cpu": "x64" }, "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g=="],
+    "@esbuild/android-x64": ["@esbuild/[email protected]2", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
 
-    "@esbuild/darwin-arm64": ["@esbuild/[email protected]1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w=="],
+    "@esbuild/darwin-arm64": ["@esbuild/[email protected]2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
 
-    "@esbuild/darwin-x64": ["@esbuild/[email protected]1", "", { "os": "darwin", "cpu": "x64" }, "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ=="],
+    "@esbuild/darwin-x64": ["@esbuild/[email protected]2", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
 
-    "@esbuild/freebsd-arm64": ["@esbuild/[email protected]1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA=="],
+    "@esbuild/freebsd-arm64": ["@esbuild/[email protected]2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
 
-    "@esbuild/freebsd-x64": ["@esbuild/[email protected]1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw=="],
+    "@esbuild/freebsd-x64": ["@esbuild/[email protected]2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
 
-    "@esbuild/linux-arm": ["@esbuild/[email protected]1", "", { "os": "linux", "cpu": "arm" }, "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw=="],
+    "@esbuild/linux-arm": ["@esbuild/[email protected]2", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
 
-    "@esbuild/linux-arm64": ["@esbuild/[email protected]1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA=="],
+    "@esbuild/linux-arm64": ["@esbuild/[email protected]2", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
 
-    "@esbuild/linux-ia32": ["@esbuild/[email protected]1", "", { "os": "linux", "cpu": "ia32" }, "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw=="],
+    "@esbuild/linux-ia32": ["@esbuild/[email protected]2", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
 
-    "@esbuild/linux-loong64": ["@esbuild/[email protected]1", "", { "os": "linux", "cpu": "none" }, "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw=="],
+    "@esbuild/linux-loong64": ["@esbuild/[email protected]2", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
 
-    "@esbuild/linux-mips64el": ["@esbuild/[email protected]1", "", { "os": "linux", "cpu": "none" }, "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ=="],
+    "@esbuild/linux-mips64el": ["@esbuild/[email protected]2", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
 
-    "@esbuild/linux-ppc64": ["@esbuild/[email protected]1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw=="],
+    "@esbuild/linux-ppc64": ["@esbuild/[email protected]2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
 
-    "@esbuild/linux-riscv64": ["@esbuild/[email protected]1", "", { "os": "linux", "cpu": "none" }, "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww=="],
+    "@esbuild/linux-riscv64": ["@esbuild/[email protected]2", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
 
-    "@esbuild/linux-s390x": ["@esbuild/[email protected]1", "", { "os": "linux", "cpu": "s390x" }, "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw=="],
+    "@esbuild/linux-s390x": ["@esbuild/[email protected]2", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
 
-    "@esbuild/linux-x64": ["@esbuild/[email protected]1", "", { "os": "linux", "cpu": "x64" }, "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ=="],
+    "@esbuild/linux-x64": ["@esbuild/[email protected]2", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
 
-    "@esbuild/netbsd-arm64": ["@esbuild/[email protected]1", "", { "os": "none", "cpu": "arm64" }, "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg=="],
+    "@esbuild/netbsd-arm64": ["@esbuild/[email protected]2", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
 
-    "@esbuild/netbsd-x64": ["@esbuild/[email protected]1", "", { "os": "none", "cpu": "x64" }, "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A=="],
+    "@esbuild/netbsd-x64": ["@esbuild/[email protected]2", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
 
-    "@esbuild/openbsd-arm64": ["@esbuild/[email protected]1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg=="],
+    "@esbuild/openbsd-arm64": ["@esbuild/[email protected]2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
 
-    "@esbuild/openbsd-x64": ["@esbuild/[email protected]1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw=="],
+    "@esbuild/openbsd-x64": ["@esbuild/[email protected]2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
 
-    "@esbuild/openharmony-arm64": ["@esbuild/[email protected]1", "", { "os": "none", "cpu": "arm64" }, "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ=="],
+    "@esbuild/openharmony-arm64": ["@esbuild/[email protected]2", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
 
-    "@esbuild/sunos-x64": ["@esbuild/[email protected]1", "", { "os": "sunos", "cpu": "x64" }, "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA=="],
+    "@esbuild/sunos-x64": ["@esbuild/[email protected]2", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
 
-    "@esbuild/win32-arm64": ["@esbuild/[email protected]1", "", { "os": "win32", "cpu": "arm64" }, "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q=="],
+    "@esbuild/win32-arm64": ["@esbuild/[email protected]2", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
 
-    "@esbuild/win32-ia32": ["@esbuild/[email protected]1", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA=="],
+    "@esbuild/win32-ia32": ["@esbuild/[email protected]2", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
 
-    "@esbuild/win32-x64": ["@esbuild/[email protected]1", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="],
+    "@esbuild/win32-x64": ["@esbuild/[email protected]2", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
 
     "@expressive-code/core": ["@expressive-code/[email protected]", "", { "dependencies": { "@ctrl/tinycolor": "^4.0.4", "hast-util-select": "^6.0.2", "hast-util-to-html": "^9.0.1", "hast-util-to-text": "^4.0.1", "hastscript": "^9.0.0", "postcss": "^8.4.38", "postcss-nested": "^6.0.1", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1" } }, "sha512-9qzohqU7O0+JwMEEgQhnBPOw5DtsQRBXhW++5fvEywsuX44vCGGof1SL5OvPElvNgaWZ4pFZAFSlkNOkGyLwSQ=="],
 
@@ -961,21 +965,21 @@
 
     "@opentelemetry/api": ["@opentelemetry/[email protected]", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
 
-    "@opentui/core": ["@opentui/core@0.1.33", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.33", "@opentui/core-darwin-x64": "0.1.33", "@opentui/core-linux-arm64": "0.1.33", "@opentui/core-linux-x64": "0.1.33", "@opentui/core-win32-arm64": "0.1.33", "@opentui/core-win32-x64": "0.1.33", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-vwHdrPIqnsY6YnG2JTNhenHSsx+HUPYrQTBZdmEfCj9ROGVzKgUKbSDH1xGK2OtSNRb2KVBg4XaMpq0bie6afQ=="],
+    "@opentui/core": ["@opentui/core@0.0.0-20251108-0c7899b1", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.0.0-20251108-0c7899b1", "@opentui/core-darwin-x64": "0.0.0-20251108-0c7899b1", "@opentui/core-linux-arm64": "0.0.0-20251108-0c7899b1", "@opentui/core-linux-x64": "0.0.0-20251108-0c7899b1", "@opentui/core-win32-arm64": "0.0.0-20251108-0c7899b1", "@opentui/core-win32-x64": "0.0.0-20251108-0c7899b1", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-uJ7wbVw2v5NnL6g3v72SjPLUwMl2wqOejUEo8t4NeBA8nsboSxggqkrqOYf6OOmCADoAqyFDY7akZMsz6HMZtg=="],
 
-    "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.33", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JBvzcP2V7fT9KxFAMenHRd/t72qPP5IL5kzge2uok1T7t2nw3Wa+CWI5s6FYP42p2b1W9qZkv5Fno5gA7OAYuQ=="],
+    "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.0.0-20251108-0c7899b1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DS9CmFmZZjwe6PIhz6zhZAsDx11DtyMFDxn8V3On2b8G892aBG6rHYtBBnsM28/1GGEJBTeDQ/jUXPVd6FNJ/g=="],
 
-    "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.33", "", { "os": "darwin", "cpu": "x64" }, "sha512-x7DY6VCkAky10z/2o4UkkuNW/nIvoX7uAh3dJOHWZCLbiKywSFvFk3QZVVcH5BMk4tOOophYTzika4s4HpaeMg=="],
+    "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.0.0-20251108-0c7899b1", "", { "os": "darwin", "cpu": "x64" }, "sha512-K4XwdmT6FTShn7EG8AKliPzO5H59R0XUlZi9+kfRVW59IIJtna5wxbu69SkA28dFoWj5i4yDumwoBI+tI7T6vg=="],
 
-    "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.33", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBc1EdkVxsLBtqGjXM2BYpBJLa57ogcrSADSZbc5cQkPu0muSGzUwBbVnVZJUjWEfk6n5jcd4dDmLezVoQga0A=="],
+    "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.0.0-20251108-0c7899b1", "", { "os": "linux", "cpu": "arm64" }, "sha512-3JUmxZeSvxV5yU7NEXSecy5Z1/LcVUMy1oWyusZgp96X0CTYAXMrolZt9IJDGO5raeO7JId1UaJmWW0r4DR8TA=="],
 
-    "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.33", "", { "os": "linux", "cpu": "x64" }, "sha512-3oVL5mrLlKLUc1lc4v7xS3BJ9N7PnnimbGwAvlnVpfaAygotAs1XkPcjsUe6ItMnSJyi0FWiDHUE2+GiDtM5Nw=="],
+    "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.0.0-20251108-0c7899b1", "", { "os": "linux", "cpu": "x64" }, "sha512-i/AQWGyanpPRpk9NK7Ze1tn+d5bqzM9wZFKNB3rd9d2Vbt/ROgBJItG6igz8vzKPKgnlHK4Gw9b5iG5sbjpd+Q=="],
 
-    "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.33", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q68v7wssE+r0OG1KIGfi7m3fnu8KOK4ZNg9ML6EwE47VF9/bqgUe+6fPiXh5mmHzTwof7nAOdXCf052av5/upQ=="],
+    "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.0.0-20251108-0c7899b1", "", { "os": "win32", "cpu": "arm64" }, "sha512-C7JLWuNN3w2txiVx3demwNwogVi4DQB5ZNHy2b09++kd2m449/RwGPyLcKpuoTzU4s/usYOeY4TxKIAd8cKedQ=="],
 
-    "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.33", "", { "os": "win32", "cpu": "x64" }, "sha512-PvuchmUnbMCUXXMzfle/WTzhNGIdJ6RGCCoclx3YVUyNUVuUicPf42OEV+td2m81/Hr3CgcLn98HYX1TLIzPrw=="],
+    "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.0.0-20251108-0c7899b1", "", { "os": "win32", "cpu": "x64" }, "sha512-mpOryp37YaHlTsN70LhiSn9hJJBktbyhlH/eB3N2K7H1ANYQVrekgBJ3rDxlH1GDVtRz6vLS3IDlyK75qNX4pg=="],
 
-    "@opentui/solid": ["@opentui/solid@0.1.33", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.33", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-bWSALdGJ2j51zwZ2gK1ZIBxFgauHq+V1ejEnyd4XamYMdWfpAKU+AUWDVLbpx1T9XG1oAnycJZfYX7BsZdVOOg=="],
+    "@opentui/solid": ["@opentui/solid@0.0.0-20251108-0c7899b1", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.0.0-20251108-0c7899b1", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-tcsYnFGH/KBlQNG0IyZE2bisnm5NwN/w7theuWga3L1zoXqZqA5dQHutAVg4zkq5l/YKULeDI4jBlvz0lzH88A=="],
 
     "@oslojs/asn1": ["@oslojs/[email protected]", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
 
@@ -1033,7 +1037,7 @@
 
     "@petamoriken/float16": ["@petamoriken/[email protected]", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
 
-    "@pierre/precision-diffs": ["@pierre/[email protected].1", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/transformers": "3.13.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.13.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-AoozHakINGyNJFgbYc/1PlDK0yunrAxbtXEMBe9fdu8RLkNjVtYRTLw7EF2mM/YuVoVRjj2HT/2VJ4a2rMyDOA=="],
+    "@pierre/precision-diffs": ["@pierre/[email protected].4", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/transformers": "3.14.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.14.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-9bhWs+hsz1i0/SMIrzce+fFrSec8aLIFrJYTGHATlynmQovngIWz1Gc+XwGigvY4+zSMksrGPzO5HiaNlvRqtQ=="],
 
     "@pkgjs/parseargs": ["@pkgjs/[email protected]", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
 
@@ -1041,7 +1045,7 @@
 
     "@poppinss/colors": ["@poppinss/[email protected]", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw=="],
 
-    "@poppinss/dumper": ["@poppinss/[email protected].4", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ=="],
+    "@poppinss/dumper": ["@poppinss/[email protected].5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="],
 
     "@poppinss/exception": ["@poppinss/[email protected]", "", {}, "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg=="],
 
@@ -1187,7 +1191,7 @@
 
     "@shikijs/vscode-textmate": ["@shikijs/[email protected]", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
 
-    "@sindresorhus/is": ["@sindresorhus/[email protected].0", "", {}, "sha512-7F/yz2IphV39hiS2zB4QYVkivrptHHh0K8qJJd9HhuWSdvf8AN7NpebW3CcDZDBQsUPMoDKWsY2WWgW7bqOcfA=="],
+    "@sindresorhus/is": ["@sindresorhus/[email protected].1", "", {}, "sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ=="],
 
     "@sindresorhus/merge-streams": ["@sindresorhus/[email protected]", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
 
@@ -1205,7 +1209,7 @@
 
     "@smithy/abort-controller": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ=="],
 
-    "@smithy/config-resolver": ["@smithy/[email protected].1", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-BciDJ5hkyYEGBBKMbjGB1A/Zq8bYZ41Zo9BMnGdKF6QD1fY4zIkYx6zui/0CHaVGnv6h0iy8y4rnPX9CPCAPyQ=="],
+    "@smithy/config-resolver": ["@smithy/[email protected].2", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-4Jys0ni2tB2VZzgslbEgszZyMdTkPOFGA8g+So/NjR8oy6Qwaq4eSwsrRI+NMtb0Dq4kqCzGUu/nGUx7OM/xfw=="],
 
     "@smithy/core": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-stream": "^4.5.5", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ=="],
 
@@ -1267,7 +1271,7 @@
 
     "@smithy/util-defaults-mode-browser": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-GwaGjv/QLuL/QHQaqhf/maM7+MnRFQQs7Bsl6FlaeK6lm6U7mV5AAnVabw68cIoMl5FQFyKK62u7RWRzWL25OQ=="],
 
-    "@smithy/util-defaults-mode-node": ["@smithy/[email protected].7", "", { "dependencies": { "@smithy/config-resolver": "^4.4.1", "@smithy/credential-provider-imds": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-6hinjVqec0WYGsqN7h9hL/ywfULmJJNXGXnNZW7jrIn/cFuC/aVlVaiDfBIJEvKcOrmN8/EgsW69eY0gXABeHw=="],
+    "@smithy/util-defaults-mode-node": ["@smithy/[email protected].8", "", { "dependencies": { "@smithy/config-resolver": "^4.4.2", "@smithy/credential-provider-imds": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-gIoTf9V/nFSIZ0TtgDNLd+Ws59AJvijmMDYrOozoMHPJaG9cMRdqNO50jZTlbM6ydzQYY8L/mQ4tKSw/TB+s6g=="],
 
     "@smithy/util-endpoints": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg=="],
 
@@ -1309,6 +1313,8 @@
 
     "@solid-primitives/static-store": ["@solid-primitives/[email protected]", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw=="],
 
+    "@solid-primitives/storage": ["@solid-primitives/[email protected]", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "@tauri-apps/plugin-store": "*", "solid-js": "^1.6.12" }, "optionalPeers": ["@tauri-apps/plugin-store"] }, "sha512-ACbNwMZ1s8VAvld6EUXkDkX/US3IhtlPLxg6+B2s9MwNUugwdd51I98LPEaHrdLpqPmyzqgoJe0TxEFlf3Dqrw=="],
+
     "@solid-primitives/trigger": ["@solid-primitives/[email protected]", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-IWoptVc0SWYgmpBPpCMehS5b07+tpFcvw15tOQ3QbXedSYn6KP8zCjPkHNzMxcOvOicTneleeZDP7lqmz+PQ6g=="],
 
     "@solid-primitives/utils": ["@solid-primitives/[email protected]", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="],
@@ -1319,7 +1325,7 @@
 
     "@solidjs/start": ["@solidjs/[email protected]", "", { "dependencies": { "@tanstack/server-functions-plugin": "1.121.21", "@vinxi/plugin-directives": "^0.5.0", "@vinxi/server-components": "^0.5.0", "cookie-es": "^2.0.0", "defu": "^6.1.2", "error-stack-parser": "^2.1.4", "html-to-image": "^1.11.11", "radix3": "^1.1.0", "seroval": "^1.0.2", "seroval-plugins": "^1.0.2", "shiki": "^1.26.1", "source-map-js": "^1.0.2", "terracotta": "^1.0.4", "tinyglobby": "^0.2.2", "vite-plugin-solid": "^2.11.1" }, "peerDependencies": { "vinxi": "^0.5.7" } }, "sha512-SRv1g3R+4sxZnxCBPK1IedtLKsPhPJ7W/Yv4xEHjM4jJGPWi3ed35/yd0D5zhRK0C7zJIkZKbhnR/S3g8JUD5w=="],
 
-    "@speed-highlight/core": ["@speed-highlight/[email protected].8", "", {}, "sha512-IGytNtnUnPIobIbOq5Y6LIlqiHNX+vnToQIS7lj6L5819C+rA8TXRDkkG8vePsiBOGcoW9R6i+dp2YBUKdB09Q=="],
+    "@speed-highlight/core": ["@speed-highlight/[email protected].12", "", {}, "sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA=="],
 
     "@standard-community/standard-json": ["@standard-community/[email protected]", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "@valibot/to-json-schema": "^1.3.0", "arktype": "^2.1.20", "effect": "^3.16.8", "quansync": "^0.2.11", "sury": "^10.0.0", "typebox": "^1.0.17", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-to-json-schema"] }, "sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA=="],
 
@@ -1429,6 +1435,8 @@
 
     "@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
 
+    "@types/node-fetch": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
+
     "@types/promise.allsettled": ["@types/[email protected]", "", {}, "sha512-wA0UT0HeT2fGHzIFV9kWpYz5mdoyLxKrTgMdZQM++5h6pYAFH73HXcQhefg24nD1yivUFEn5KU+EF4b+CXJ4Wg=="],
 
     "@types/prop-types": ["@types/[email protected]", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
@@ -1447,7 +1455,7 @@
 
     "@types/scheduler": ["@types/[email protected]", "", {}, "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA=="],
 
-    "@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
+    "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
 
     "@types/serve-static": ["@types/[email protected]", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
 
@@ -1515,6 +1523,8 @@
 
     "agent-base": ["[email protected]", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
 
+    "agentkeepalive": ["[email protected]", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
+
     "ai": ["[email protected]", "", { "dependencies": { "@ai-sdk/gateway": "1.0.4", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.1", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-qbnhj046UvG30V1S5WhjBn+RBGEAmi8PSZWqMhRsE3EPxvO5BcePXTZFA23e9MYyWS9zr4Vm8Mv3wQXwLmtIBw=="],
 
     "ajv": ["[email protected]", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
@@ -1589,7 +1599,7 @@
 
     "aws4fetch": ["[email protected]", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
 
-    "axios": ["[email protected].1", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw=="],
+    "axios": ["[email protected].2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="],
 
     "axobject-query": ["[email protected]", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
 
@@ -1617,13 +1627,13 @@
 
     "bare-stream": ["[email protected]", "", { "dependencies": { "streamx": "^2.21.0" }, "peerDependencies": { "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-buffer", "bare-events"] }, "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A=="],
 
-    "bare-url": ["[email protected].1", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-v2yl0TnaZTdEnelkKtXZGnotiV6qATBlnNuUMrHl6v9Lmmrh9mw9RYyImPU7/4RahumSwQS1k2oKXcRfXcbjJw=="],
+    "bare-url": ["[email protected].2", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw=="],
 
     "base-64": ["[email protected]", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="],
 
     "base64-js": ["[email protected]", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
 
-    "baseline-browser-mapping": ["[email protected]1", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q=="],
+    "baseline-browser-mapping": ["[email protected]5", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA=="],
 
     "bcp-47": ["[email protected]", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="],
 
@@ -1701,7 +1711,7 @@
 
     "camelcase-css": ["[email protected]", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
 
-    "caniuse-lite": ["[email protected]2", "", {}, "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g=="],
+    "caniuse-lite": ["[email protected]3", "", {}, "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw=="],
 
     "ccount": ["[email protected]", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
 
@@ -1739,6 +1749,8 @@
 
     "clone": ["[email protected]", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="],
 
+    "cloudflare": ["[email protected]", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-dVzqDpPFYR9ApEC9e+JJshFJZXcw4HzM8W+3DHzO5oy9+8rLC53G7x6fEf9A7/gSuSCxuvndzui5qJKftfIM9A=="],
+
     "clsx": ["[email protected]", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
 
     "cluster-key-slot": ["[email protected]", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
@@ -1919,7 +1931,7 @@
 
     "ee-first": ["[email protected]", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
 
-    "electron-to-chromium": ["[email protected]4", "", {}, "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw=="],
+    "electron-to-chromium": ["[email protected]5", "", {}, "sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ=="],
 
     "emoji-regex": ["[email protected]", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
 
@@ -1961,7 +1973,7 @@
 
     "esast-util-from-js": ["[email protected]", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="],
 
-    "esbuild": ["[email protected]1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="],
+    "esbuild": ["[email protected]2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
 
     "esbuild-plugin-copy": ["[email protected]", "", { "dependencies": { "chalk": "^4.1.2", "chokidar": "^3.5.3", "fs-extra": "^10.0.1", "globby": "^11.0.3" }, "peerDependencies": { "esbuild": ">= 0.14.0" } }, "sha512-Bk66jpevTcV8KMFzZI1P7MZKZ+uDcrZm2G2egZ2jNIvVnivDpodZI+/KnpL3Jnap0PBdIHU7HwFGB8r+vV5CVw=="],
 
@@ -2065,7 +2077,11 @@
 
     "foreground-child": ["[email protected]", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
 
-    "form-data": ["[email protected]", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="],
+    "form-data": ["[email protected]", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
+
+    "form-data-encoder": ["[email protected]", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
+
+    "formdata-node": ["[email protected]", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
 
     "forwarded": ["[email protected]", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
 
@@ -2255,6 +2271,8 @@
 
     "human-signals": ["[email protected]", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],
 
+    "humanize-ms": ["[email protected]", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
+
     "husky": ["[email protected]", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
 
     "i18next": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="],
@@ -2665,7 +2683,7 @@
 
     "mimic-response": ["[email protected]", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
 
-    "miniflare": ["[email protected].1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251011.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-Qbw1Z8HTYM1adWl6FAtzhrj34/6dPRDPwdYOx21dkae8a/EaxbMzRIPbb4HKVGMVvtqbK1FaRCgDLVLolNzGHg=="],
+    "miniflare": ["[email protected].2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251011.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-5oAaz6lqZus4QFwzEJiNtgpjZR2TBVwBeIhOW33V4gu+l23EukpKja831tFIX2o6sOD/hqZmKZHplOrWl3YGtQ=="],
 
     "minimatch": ["[email protected]", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
 
@@ -2713,6 +2731,8 @@
 
     "node-addon-api": ["[email protected]", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="],
 
+    "node-domexception": ["[email protected]", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
+
     "node-fetch": ["[email protected]", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
 
     "node-fetch-native": ["[email protected]", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
@@ -2749,11 +2769,11 @@
 
     "object.assign": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
 
-    "ofetch": ["[email protected].0", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-A7llJ7eZyziA5xq9//3ZurA8OhFqtS99K5/V1sLBJ5j137CM/OAjlbA/TEJXBuOWwOfLqih+oH5U3ran4za1FQ=="],
+    "ofetch": ["[email protected].1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
 
     "ohash": ["[email protected]", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
 
-    "oidc-token-hash": ["oidc-token-hash@5.1.1", "", {}, "sha512-D7EmwxJV6DsEB6vOFLrBM2OzsVgQzgPWyHlV2OOAVj772n+WTXpudC9e9u5BVKQnYwaD30Ivhi9b+4UeBcGu9g=="],
+    "oidc-token-hash": ["oidc-token-hash@5.2.0", "", {}, "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw=="],
 
     "omggif": ["[email protected]", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
 
@@ -3175,23 +3195,23 @@
 
     "sqlstring": ["[email protected]", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="],
 
-    "sst": ["[email protected].19", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.19", "sst-darwin-x64": "3.17.19", "sst-linux-arm64": "3.17.19", "sst-linux-x64": "3.17.19", "sst-linux-x86": "3.17.19", "sst-win32-arm64": "3.17.19", "sst-win32-x64": "3.17.19", "sst-win32-x86": "3.17.19" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-j0FlQhFZW+QWCczzqfPr6fZAF0Um7lP1tbGdd7zkbjFlxdk9BUBI4CYXUnopC6KaTMtjvpfg3XRF7v0bDc9g+A=="],
+    "sst": ["[email protected].23", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.23", "sst-darwin-x64": "3.17.23", "sst-linux-arm64": "3.17.23", "sst-linux-x64": "3.17.23", "sst-linux-x86": "3.17.23", "sst-win32-arm64": "3.17.23", "sst-win32-x64": "3.17.23", "sst-win32-x86": "3.17.23" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-TwKgUgDnZdc1Swe+bvCNeyO4dQnYz5cTodMpYj3jlXZdK9/KNz0PVxT1f0u5E76i1pmilXrUBL/f7iiMPw4RDg=="],
 
-    "sst-darwin-arm64": ["[email protected].19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6FeEgPqXkRT3o5qV0xktJ1eUiscJiPLBcGaxOxIEClpkVggZM83hO7Nizx/cAaAMhr1XQhbOZcKYueDHPdUY+Q=="],
+    "sst-darwin-arm64": ["[email protected].23", "", { "os": "darwin", "cpu": "arm64" }, "sha512-R6kvmF+rUideOoU7KBs2SdvrIupoE+b+Dor/eq9Uo4Dojj7KvYDZI/EDm8sSCbbcx/opiWeyNqKtlnLEdCxE6g=="],
 
-    "sst-darwin-x64": ["[email protected].19", "", { "os": "darwin", "cpu": "x64" }, "sha512-/z78dxfLHG8FtOhpjMnYSpKSdQjfdyKbq+cL3eud2+g2BQr7IyQ8BWNGimk2oadh38V3r6dO1/5aVJh3x3l1rg=="],
+    "sst-darwin-x64": ["[email protected].23", "", { "os": "darwin", "cpu": "x64" }, "sha512-WW4P1S35iYCifQXxD+sE3wuzcN+LHLpuKMaNoaBqEcWGZnH3IPaDJ7rpLF0arkDAo/z3jZmWWzOCkr0JuqJ8vQ=="],
 
-    "sst-linux-arm64": ["[email protected].19", "", { "os": "linux", "cpu": "arm64" }, "sha512-vbcMjiuLVxZ7352ajGlMqsS4J5AkAYvjLmsEALySUBVQhJUO9U7pk2P+Orfn702ZcO+6+NkGG9AL/g3K9EM1Tg=="],
+    "sst-linux-arm64": ["[email protected].23", "", { "os": "linux", "cpu": "arm64" }, "sha512-TjtNqgIh7RlAWgPLFCAt0mXvIB+J7WjmRvIRrAdX0mXsndOiBJ/DMOgXSLVsIWHCfPj8MIEot/hWpnJgXgIeag=="],
 
-    "sst-linux-x64": ["[email protected].19", "", { "os": "linux", "cpu": "x64" }, "sha512-gkNNmuHyvKjcb7RwMyoUH4wtgd7/bH7vUlMbcVsDzwt38y7+iTxyPMbcihucw42wDQRaDJtkDneSqj08U+MTFQ=="],
+    "sst-linux-x64": ["[email protected].23", "", { "os": "linux", "cpu": "x64" }, "sha512-qdqJiEbYfCjZlI3F/TA6eoIU7JXVkEEI/UMILNf2JWhky0KQdCW2Xyz+wb6c0msVJCWdUM/uj+1DaiP2eXvghw=="],
 
-    "sst-linux-x86": ["[email protected].19", "", { "os": "linux", "cpu": "none" }, "sha512-Bsvunkh4onZRVv4Rxq7bT/63qQOg2KJoQKhAQtFkJdbri/cOA2QWkzqH8+pC5Sv9rSvbcIJAEIhMXILC0pqCJw=="],
+    "sst-linux-x86": ["[email protected].23", "", { "os": "linux", "cpu": "none" }, "sha512-aGmUujIvoNlmAABEGsOgfY1rxD9koC6hN8bnTLbDI+oI/u/zjHYh50jsbL0p3TlaHpwF/lxP3xFSuT6IKp+KgA=="],
 
-    "sst-win32-arm64": ["[email protected].19", "", { "os": "win32", "cpu": "arm64" }, "sha512-dKxR4v24AODJLHiT9yNena0JUgyz3cHyCi6HZyxyG3dXyWncMe1ZXMXIgs1ZEUcU4XeYM2HVy+Nnz4KB1US1Kg=="],
+    "sst-win32-arm64": ["[email protected].23", "", { "os": "win32", "cpu": "arm64" }, "sha512-ZxdkGqYDrrZGz98rijDCN+m5yuCcwD6Bc9/6hubLsvdpNlVorUqzpg801Ec97xSK0nIC9g6pNiRyxAcsQQstUg=="],
 
-    "sst-win32-x64": ["[email protected].19", "", { "os": "win32", "cpu": "x64" }, "sha512-zgxSkGWZ1dewAr4R3slN/d3X9yumQDvAUOlJiX/6QE9Z67t/XNlow4+5i3L2oz4WHAFi59Un12YxbfM+RsBDmA=="],
+    "sst-win32-x64": ["[email protected].23", "", { "os": "win32", "cpu": "x64" }, "sha512-yc9cor4MS49Ccy2tQCF1tf6M81yLeSGzGL+gjhUxpVKo2pN3bxl3w70eyU/mTXSEeyAmG9zEfbt6FNu4sy5cUA=="],
 
-    "sst-win32-x86": ["[email protected].19", "", { "os": "win32", "cpu": "none" }, "sha512-z8S0kyb0ibz9Q3cNYDpcKYX47jys7j/mdebC8HUhtED1qKEAfqQ1vsR+zvWyN64Z9Ijj7aPi1KwNV6Et3d7F8g=="],
+    "sst-win32-x86": ["[email protected].23", "", { "os": "win32", "cpu": "none" }, "sha512-DIp3s54IpNAfdYjSRt6McvkbEPQDMxUu6RUeRAd2C+FcTJgTloon/ghAPQBaDgu2VoVgymjcJARO/XyfKcCLOQ=="],
 
     "stackframe": ["[email protected]", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
 
@@ -3243,7 +3263,7 @@
 
     "strtok3": ["[email protected]", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
 
-    "style-to-js": ["[email protected]8", "", { "dependencies": { "style-to-object": "1.0.11" } }, "sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg=="],
+    "style-to-js": ["[email protected]9", "", { "dependencies": { "style-to-object": "1.0.12" } }, "sha512-Ev+SgeqiNGT1ufsXyVC5RrJRXdrkRJ1Gol9Qw7Pb72YCKJXrBvP0ckZhBeVSrw2m06DJpei2528uIpjMb4TsoQ=="],
 
     "style-to-object": ["[email protected]", "", { "dependencies": { "inline-style-parser": "0.2.6" } }, "sha512-ddJqYnoT4t97QvN2C95bCgt+m7AAgXjVnkk/jxAfmp7EAB8nnqqZYEbMd3em7/vEomDb2LAQKAy1RFfv41mdNw=="],
 
@@ -3271,7 +3291,7 @@
 
     "terracotta": ["[email protected]", "", { "dependencies": { "solid-use": "^0.9.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-yVrmT/Lg6a3tEbeYEJH8ksb1PYkR5FA9k5gr1TchaSNIiA2ZWs5a+koEbePXwlBP0poaV7xViZ/v50bQFcMgqw=="],
 
-    "terser": ["[email protected].0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w=="],
+    "terser": ["[email protected].1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw=="],
 
     "text-decoder": ["[email protected]", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="],
 
@@ -3305,8 +3325,6 @@
 
     "tr46": ["[email protected]", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
 
-    "tree-sitter": ["[email protected]", "", { "dependencies": { "node-addon-api": "^8.3.0", "node-gyp-build": "^4.8.4" } }, "sha512-PGZZzFW63eElZJDe/b/R/LbsjDDYJa5UEjLZJB59RQsMX+fo0j54fqBPn1MGKav/QNa0JR0zBiVaikYDWCj5KQ=="],
-
     "tree-sitter-bash": ["[email protected]", "", { "dependencies": { "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.25.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-gZtlj9+qFS81qKxpLfD6H0UssQ3QBc/F0nKkPsiFDyfQF2YBqYvglFJUzchrPpVhZe9kLZTrJ9n2J6lmka69Vg=="],
 
     "trim-lines": ["[email protected]", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
@@ -3421,7 +3439,7 @@
 
     "unplugin-utils": ["[email protected]", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="],
 
-    "unstorage": ["[email protected].1", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.4.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ=="],
+    "unstorage": ["[email protected].2", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.0", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-cKEsD6iBWJgOMJ6vW1ID/SYuqNf8oN4yqRk8OYqaVQ3nnkJXOT1PSpaMh2QfzLs78UN5kSNRD2c/mgjT8tX7+w=="],
 
     "untun": ["[email protected]", "", { "dependencies": { "citty": "^0.1.5", "consola": "^3.2.3", "pathe": "^1.1.1" }, "bin": { "untun": "bin/untun.mjs" } }, "sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ=="],
 
@@ -3479,6 +3497,8 @@
 
     "web-namespaces": ["[email protected]", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
 
+    "web-streams-polyfill": ["[email protected]", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
+
     "web-tree-sitter": ["[email protected]", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="],
 
     "webidl-conversions": ["[email protected]", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
@@ -3507,7 +3527,7 @@
 
     "workerd": ["[email protected]", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251011.0", "@cloudflare/workerd-darwin-arm64": "1.20251011.0", "@cloudflare/workerd-linux-64": "1.20251011.0", "@cloudflare/workerd-linux-arm64": "1.20251011.0", "@cloudflare/workerd-windows-64": "1.20251011.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Dq35TLPEJAw7BuYQMkN3p9rge34zWMU2Gnd4DSJFeVqld4+DAO2aPG7+We2dNIAyM97S8Y9BmHulbQ00E0HC7Q=="],
 
-    "wrangler": ["[email protected].3", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.8", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251011.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.21", "workerd": "1.20251011.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251011.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-0ddEA9t4HeBgSVTVTcqtBHl7Z5CorWZ8tGgTQCP5XuL+9E1TJRwS6t/zzG51Ruwjb17SZYCaLchoM8V629S8cw=="],
+    "wrangler": ["[email protected].4", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.9", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251011.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20251011.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251011.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-niXT7B463wQi7WXIHjYK8txgWhuKQLrGmhjoR58SnPhlkq4wGjd3rFrkVyRc/O58clGTfs672BSGOph4XMoQKw=="],
 
     "wrap-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
 
@@ -3545,7 +3565,7 @@
 
     "yoga-layout": ["[email protected]", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
 
-    "youch": ["[email protected]1", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-sQi6PERyO/mT8w564ojOVeAlYTtVQmC2GaktQAf+IdI75/GKIggosBuvyVXvEV+FATAT6RbLdIjFoiIId4ozoQ=="],
+    "youch": ["[email protected]2", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.5", "@speed-highlight/core": "^1.2.9", "cookie-es": "^2.0.0", "youch-core": "^0.3.3" } }, "sha512-X+AQ2EdigcZb2h1XQmBMm19TrrfKXxEXWpnf8ThbARwiiSf/pA7MvRTCj5VHCI9z3vjJBsDeqWWyvaI9Bfp9Pg=="],
 
     "youch-core": ["[email protected]", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
 
@@ -3589,6 +3609,8 @@
 
     "@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/[email protected]", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="],
 
+    "@astrojs/markdown-remark/shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="],
+
     "@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/[email protected]", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.4", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.13.0", "smol-toml": "^1.4.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-uFNyFWadnULWK2cOw4n0hLKeu+xaVWeuECdP10cQ3K2fkybtTlhb7J7TcScdjmS8Yps7oje9S/ehYMfZrhrgCg=="],
 
     "@astrojs/sitemap/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
@@ -3609,13 +3631,13 @@
 
     "@cloudflare/kv-asset-handler/mime": ["[email protected]", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
 
-    "@cloudflare/unenv-preset/unenv": ["[email protected]1", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.7", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A=="],
+    "@cloudflare/unenv-preset/unenv": ["[email protected]4", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
 
     "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
 
     "@esbuild-kit/core-utils/esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
 
-    "@expressive-code/plugin-shiki/shiki": ["[email protected]3.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/engine-javascript": "3.13.0", "@shikijs/engine-oniguruma": "3.13.0", "@shikijs/langs": "3.13.0", "@shikijs/themes": "3.13.0", "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g=="],
+    "@expressive-code/plugin-shiki/shiki": ["[email protected]4.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="],
 
     "@hono/zod-validator/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
 
@@ -3725,11 +3747,11 @@
 
     "@parcel/watcher-wasm/napi-wasm": ["[email protected]", "", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="],
 
-    "@pierre/precision-diffs/@shikijs/core": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="],
+    "@pierre/precision-diffs/@shikijs/core": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="],
 
-    "@pierre/precision-diffs/@shikijs/transformers": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/types": "3.13.0" } }, "sha512-833lcuVzcRiG+fXvgslWsM2f4gHpjEgui1ipIknSizRuTgMkNZupiXE5/TVJ6eSYfhNBFhBZKkReKWO2GgYmqA=="],
+    "@pierre/precision-diffs/@shikijs/transformers": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/types": "3.14.0" } }, "sha512-i67zQnY9wLMMnKasonVW1L9fKneSLZDj1ePsA4o0AZWU4uUobmJY9baRDa36z+a9/g0aG76/2tybQvm4hrwxIQ=="],
 
-    "@pierre/precision-diffs/shiki": ["[email protected]3.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/engine-javascript": "3.13.0", "@shikijs/engine-oniguruma": "3.13.0", "@shikijs/langs": "3.13.0", "@shikijs/themes": "3.13.0", "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g=="],
+    "@pierre/precision-diffs/shiki": ["[email protected]4.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="],
 
     "@poppinss/dumper/supports-color": ["[email protected]", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
 
@@ -3751,15 +3773,17 @@
 
     "@slack/web-api/eventemitter3": ["[email protected]", "", {}, "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="],
 
+    "@slack/web-api/form-data": ["[email protected]", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" } }, "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A=="],
+
     "@slack/web-api/p-queue": ["[email protected]", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
 
     "@solidjs/start/shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="],
 
     "@tailwindcss/node/jiti": ["[email protected]", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
 
-    "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.6.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg=="],
+    "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="],
 
-    "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="],
+    "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="],
 
     "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
 
@@ -3775,6 +3799,8 @@
 
     "@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/[email protected]", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
 
+    "@types/serve-static/@types/send": ["@types/[email protected]", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
+
     "@vercel/nft/estree-walker": ["[email protected]", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
 
     "@vercel/nft/glob": ["[email protected]", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
@@ -3801,16 +3827,14 @@
 
     "astro/diff": ["[email protected]", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="],
 
-    "astro/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
-
     "astro/sharp": ["[email protected]", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
 
+    "astro/shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="],
+
     "astro/vite": ["[email protected]", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
 
     "astro/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
 
-    "axios/form-data": ["[email protected]", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
-
     "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/[email protected]", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
 
     "babel-plugin-module-resolver/glob": ["[email protected]", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
@@ -3849,7 +3873,7 @@
 
     "dir-glob/path-type": ["[email protected]", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
 
-    "dot-prop/type-fest": ["type-fest@5.1.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-wQ531tuWvB6oK+pchHIu5lHe5f5wpSCqB8Kf4dWQRbOYc9HTge7JL0G4Qd44bh6QuJCccIzL3bugb8GI0MwHrg=="],
+    "dot-prop/type-fest": ["type-fest@5.2.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA=="],
 
     "drizzle-kit/esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="],
 
@@ -3885,8 +3909,6 @@
 
     "gaxios/uuid": ["[email protected]", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
 
-    "gel/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
-
     "giget/pathe": ["[email protected]", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
 
     "giget/tar": ["[email protected]", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
@@ -4029,9 +4051,7 @@
 
     "send/mime": ["[email protected]", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
 
-    "sharp/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
-
-    "sitemap/sax": ["[email protected]", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="],
+    "sitemap/sax": ["[email protected]", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
 
     "source-map-support/source-map": ["[email protected]", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
 
@@ -4047,8 +4067,6 @@
 
     "strip-literal/js-tokens": ["[email protected]", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
 
-    "style-to-js/style-to-object": ["[email protected]", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow=="],
-
     "sucrase/commander": ["[email protected]", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
 
     "sucrase/glob": ["[email protected]", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
@@ -4059,8 +4077,6 @@
 
     "token-types/ieee754": ["[email protected]", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
 
-    "tree-sitter/node-addon-api": ["[email protected]", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="],
-
     "tree-sitter-bash/node-addon-api": ["[email protected]", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="],
 
     "tw-to-css/postcss": ["[email protected]", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
@@ -4097,7 +4113,7 @@
 
     "wrangler/esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
 
-    "wrangler/unenv": ["[email protected]1", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.7", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A=="],
+    "wrangler/unenv": ["[email protected]4", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
 
     "wrap-ansi/ansi-styles": ["[email protected]", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
 
@@ -4133,11 +4149,23 @@
 
     "@actions/github/@octokit/request-error/@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
 
+    "@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="],
+
+    "@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="],
+
+    "@astrojs/markdown-remark/shiki/@shikijs/engine-oniguruma": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="],
+
+    "@astrojs/markdown-remark/shiki/@shikijs/langs": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="],
+
+    "@astrojs/markdown-remark/shiki/@shikijs/themes": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="],
+
+    "@astrojs/markdown-remark/shiki/@shikijs/types": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
+
     "@astrojs/mdx/@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/[email protected]", "", {}, "sha512-lDA9MqE8WGi7T/t2BMi+EAXhs4Vcvr94Gqx3q15cFEz8oFZMO4/SFBqYr/UcmNlvW+35alowkVj+w9VhLvs5Cw=="],
 
     "@astrojs/mdx/@astrojs/markdown-remark/@astrojs/prism": ["@astrojs/[email protected]", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="],
 
-    "@astrojs/mdx/@astrojs/markdown-remark/shiki": ["[email protected]", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/engine-javascript": "3.13.0", "@shikijs/engine-oniguruma": "3.13.0", "@shikijs/langs": "3.13.0", "@shikijs/themes": "3.13.0", "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g=="],
+    "@astrojs/mdx/@astrojs/markdown-remark/shiki": ["[email protected]4.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="],
 
     "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/[email protected]", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
 
@@ -4191,17 +4219,17 @@
 
     "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
 
-    "@expressive-code/plugin-shiki/shiki/@shikijs/core": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="],
+    "@expressive-code/plugin-shiki/shiki/@shikijs/core": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="],
 
-    "@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="],
+    "@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="],
 
-    "@expressive-code/plugin-shiki/shiki/@shikijs/engine-oniguruma": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="],
+    "@expressive-code/plugin-shiki/shiki/@shikijs/engine-oniguruma": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="],
 
-    "@expressive-code/plugin-shiki/shiki/@shikijs/langs": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ=="],
+    "@expressive-code/plugin-shiki/shiki/@shikijs/langs": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="],
 
-    "@expressive-code/plugin-shiki/shiki/@shikijs/themes": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg=="],
+    "@expressive-code/plugin-shiki/shiki/@shikijs/themes": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="],
 
-    "@expressive-code/plugin-shiki/shiki/@shikijs/types": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
+    "@expressive-code/plugin-shiki/shiki/@shikijs/types": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
 
     "@isaacs/cliui/string-width/emoji-regex": ["[email protected]", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
 
@@ -4327,19 +4355,21 @@
 
     "@opentui/solid/@babel/core/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
 
-    "@pierre/precision-diffs/@shikijs/core/@shikijs/types": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
+    "@pierre/precision-diffs/@shikijs/core/@shikijs/types": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
 
-    "@pierre/precision-diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
+    "@pierre/precision-diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
 
-    "@pierre/precision-diffs/shiki/@shikijs/engine-javascript": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="],
+    "@pierre/precision-diffs/shiki/@shikijs/engine-javascript": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="],
 
-    "@pierre/precision-diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="],
+    "@pierre/precision-diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="],
 
-    "@pierre/precision-diffs/shiki/@shikijs/langs": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ=="],
+    "@pierre/precision-diffs/shiki/@shikijs/langs": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="],
 
-    "@pierre/precision-diffs/shiki/@shikijs/themes": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg=="],
+    "@pierre/precision-diffs/shiki/@shikijs/themes": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="],
 
-    "@pierre/precision-diffs/shiki/@shikijs/types": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
+    "@pierre/precision-diffs/shiki/@shikijs/types": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
+
+    "@slack/web-api/form-data/mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
 
     "@slack/web-api/p-queue/eventemitter3": ["[email protected]", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
 
@@ -4379,7 +4409,19 @@
 
     "archiver-utils/glob/path-scurry": ["[email protected]", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
 
-    "axios/form-data/mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+    "astro/sharp/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
+
+    "astro/shiki/@shikijs/core": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="],
+
+    "astro/shiki/@shikijs/engine-javascript": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="],
+
+    "astro/shiki/@shikijs/engine-oniguruma": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="],
+
+    "astro/shiki/@shikijs/langs": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="],
+
+    "astro/shiki/@shikijs/themes": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="],
+
+    "astro/shiki/@shikijs/types": ["@shikijs/[email protected]", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
 
     "babel-plugin-module-resolver/glob/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA=="],
 
@@ -4387,6 +4429,8 @@
 
     "babel-plugin-module-resolver/glob/path-scurry": ["[email protected]", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
 
+    "bl/buffer/ieee754": ["[email protected]", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
     "body-parser/debug/ms": ["[email protected]", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
 
     "c12/pkg-types/pathe": ["[email protected]", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
@@ -4499,7 +4543,7 @@
 
     "opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["[email protected]", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
 
-    "parse-bmfont-xml/xml2js/sax": ["[email protected].1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="],
+    "parse-bmfont-xml/xml2js/sax": ["[email protected].3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
 
     "pkg-up/find-up/locate-path": ["[email protected]", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
 
@@ -4523,8 +4567,6 @@
 
     "string-width-cjs/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
 
-    "style-to-js/style-to-object/inline-style-parser": ["[email protected]", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="],
-
     "sucrase/glob/jackspeak": ["[email protected]", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
 
     "sucrase/glob/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
@@ -4607,17 +4649,17 @@
 
     "@actions/github/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
 
-    "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="],
+    "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="],
 
-    "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="],
+    "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="],
 
-    "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-oniguruma": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="],
+    "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-oniguruma": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="],
 
-    "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/langs": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ=="],
+    "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/langs": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="],
 
-    "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/themes": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg=="],
+    "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/themes": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="],
 
-    "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/types": ["@shikijs/[email protected]3.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
+    "@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/types": ["@shikijs/[email protected]4.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
 
     "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/[email protected]", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
 
@@ -4677,6 +4719,8 @@
 
     "@modelcontextprotocol/sdk/express/type-is/media-typer": ["[email protected]", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
 
+    "@slack/web-api/form-data/mime-types/mime-db": ["[email protected]", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+
     "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["[email protected]", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
 
     "@vercel/nft/glob/path-scurry/lru-cache": ["[email protected]", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
@@ -4685,8 +4729,6 @@
 
     "archiver-utils/glob/path-scurry/lru-cache": ["[email protected]", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
 
-    "axios/form-data/mime-types/mime-db": ["[email protected]", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
-
     "babel-plugin-module-resolver/glob/path-scurry/lru-cache": ["[email protected]", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
 
     "babel-plugin-module-resolver/glob/path-scurry/minipass": ["[email protected]", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
@@ -4749,7 +4791,7 @@
 
     "@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["[email protected]", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="],
 
-    "nitropack/c12/giget/nypm/tinyexec": ["[email protected].1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
+    "nitropack/c12/giget/nypm/tinyexec": ["[email protected].2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
 
     "opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["[email protected]", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
 

+ 14 - 43
github/index.ts

@@ -171,9 +171,7 @@ try {
         const summary = await summarize(response)
         await pushToLocalBranch(summary)
       }
-      const hasShared = prData.comments.nodes.some((c) =>
-        c.body.includes(`${useShareUrl()}/s/${shareId}`),
-      )
+      const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${useShareUrl()}/s/${shareId}`))
       await updateComment(`${response}${footer({ image: !hasShared })}`)
     }
     // Fork PR
@@ -185,9 +183,7 @@ try {
         const summary = await summarize(response)
         await pushToForkBranch(summary, prData)
       }
-      const hasShared = prData.comments.nodes.some((c) =>
-        c.body.includes(`${useShareUrl()}/s/${shareId}`),
-      )
+      const hasShared = prData.comments.nodes.some((c) => c.body.includes(`${useShareUrl()}/s/${shareId}`))
       await updateComment(`${response}${footer({ image: !hasShared })}`)
     }
   }
@@ -368,9 +364,7 @@ async function getAccessToken() {
 
   if (!response.ok) {
     const responseJson = (await response.json()) as { error?: string }
-    throw new Error(
-      `App token exchange failed: ${response.status} ${response.statusText} - ${responseJson.error}`,
-    )
+    throw new Error(`App token exchange failed: ${response.status} ${response.statusText} - ${responseJson.error}`)
   }
 
   const responseJson = (await response.json()) as { token: string }
@@ -411,12 +405,8 @@ async function getUserPrompt() {
   // ie. <img alt="Image" src="https://github.com/user-attachments/assets/xxxx" />
   // ie. [api.json](https://github.com/user-attachments/files/21433810/api.json)
   // ie. ![Image](https://github.com/user-attachments/assets/xxxx)
-  const mdMatches = prompt.matchAll(
-    /!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi,
-  )
-  const tagMatches = prompt.matchAll(
-    /<img .*?src="(https:\/\/github\.com\/user-attachments\/[^"]+)" \/>/gi,
-  )
+  const mdMatches = prompt.matchAll(/!?\[.*?\]\((https:\/\/github\.com\/user-attachments\/[^)]+)\)/gi)
+  const tagMatches = prompt.matchAll(/<img .*?src="(https:\/\/github\.com\/user-attachments\/[^"]+)" \/>/gi)
   const matches = [...mdMatches, ...tagMatches].sort((a, b) => a.index - b.index)
   console.log("Images", JSON.stringify(matches, null, 2))
 
@@ -443,8 +433,7 @@ async function getUserPrompt() {
 
     // Replace img tag with file path, ie. @image.png
     const replacement = `@${filename}`
-    prompt =
-      prompt.slice(0, start + offset) + replacement + prompt.slice(start + offset + tag.length)
+    prompt = prompt.slice(0, start + offset) + replacement + prompt.slice(start + offset + tag.length)
     offset += replacement.length - tag.length
 
     const contentType = res.headers.get("content-type")
@@ -512,12 +501,7 @@ async function subscribeSessionEvents() {
                     ? JSON.stringify(part.state.input)
                     : "Unknown"
                 console.log()
-                console.log(
-                  color + `|`,
-                  "\x1b[0m\x1b[2m" + ` ${tool.padEnd(7, " ")}`,
-                  "",
-                  "\x1b[0m" + title,
-                )
+                console.log(color + `|`, "\x1b[0m\x1b[2m" + ` ${tool.padEnd(7, " ")}`, "", "\x1b[0m" + title)
               }
 
               if (part.type === "text") {
@@ -729,8 +713,7 @@ async function assertPermissions() {
     throw new Error(`Failed to check permissions for user ${actor}: ${error}`)
   }
 
-  if (!["admin", "write"].includes(permission))
-    throw new Error(`User ${actor} does not have write permissions`)
+  if (!["admin", "write"].includes(permission)) throw new Error(`User ${actor} does not have write permissions`)
 }
 
 async function updateComment(body: string) {
@@ -774,9 +757,7 @@ function footer(opts?: { image?: boolean }) {
 
     return `<a href="${useShareUrl()}/s/${shareId}"><img width="200" alt="${titleAlt}" src="https://social-cards.sst.dev/opencode-share/${title64}.png?model=${providerID}/${modelID}&version=${session.version}&id=${shareId}" /></a>\n`
   })()
-  const shareUrl = shareId
-    ? `[opencode session](${useShareUrl()}/s/${shareId})&nbsp;&nbsp;|&nbsp;&nbsp;`
-    : ""
+  const shareUrl = shareId ? `[opencode session](${useShareUrl()}/s/${shareId})&nbsp;&nbsp;|&nbsp;&nbsp;` : ""
   return `\n\n${image}${shareUrl}[github run](${useEnvRunUrl()})`
 }
 
@@ -959,13 +940,9 @@ function buildPromptDataForPR(pr: GitHubPullRequest) {
     })
     .map((c) => `- ${c.author.login} at ${c.createdAt}: ${c.body}`)
 
-  const files = (pr.files.nodes || []).map(
-    (f) => `- ${f.path} (${f.changeType}) +${f.additions}/-${f.deletions}`,
-  )
+  const files = (pr.files.nodes || []).map((f) => `- ${f.path} (${f.changeType}) +${f.additions}/-${f.deletions}`)
   const reviewData = (pr.reviews.nodes || []).map((r) => {
-    const comments = (r.comments.nodes || []).map(
-      (c) => `    - ${c.path}:${c.line ?? "?"}: ${c.body}`,
-    )
+    const comments = (r.comments.nodes || []).map((c) => `    - ${c.path}:${c.line ?? "?"}: ${c.body}`)
     return [
       `- ${r.author.login} at ${r.submittedAt}:`,
       `  - Review body: ${r.body}`,
@@ -987,15 +964,9 @@ function buildPromptDataForPR(pr: GitHubPullRequest) {
     `Deletions: ${pr.deletions}`,
     `Total Commits: ${pr.commits.totalCount}`,
     `Changed Files: ${pr.files.nodes.length} files`,
-    ...(comments.length > 0
-      ? ["<pull_request_comments>", ...comments, "</pull_request_comments>"]
-      : []),
-    ...(files.length > 0
-      ? ["<pull_request_changed_files>", ...files, "</pull_request_changed_files>"]
-      : []),
-    ...(reviewData.length > 0
-      ? ["<pull_request_reviews>", ...reviewData, "</pull_request_reviews>"]
-      : []),
+    ...(comments.length > 0 ? ["<pull_request_comments>", ...comments, "</pull_request_comments>"] : []),
+    ...(files.length > 0 ? ["<pull_request_changed_files>", ...files, "</pull_request_changed_files>"] : []),
+    ...(reviewData.length > 0 ? ["<pull_request_reviews>", ...reviewData, "</pull_request_reviews>"] : []),
     "</pull_request>",
   ].join("\n")
 }

+ 1 - 1
github/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 9 - 7
infra/console.ts

@@ -61,13 +61,7 @@ export const auth = new sst.cloudflare.Worker("AuthApi", {
   domain: `auth.${domain}`,
   handler: "packages/console/function/src/auth.ts",
   url: true,
-  link: [
-    database,
-    authStorage,
-    GITHUB_CLIENT_ID_CONSOLE,
-    GITHUB_CLIENT_SECRET_CONSOLE,
-    GOOGLE_CLIENT_ID,
-  ],
+  link: [database, authStorage, GITHUB_CLIENT_ID_CONSOLE, GITHUB_CLIENT_SECRET_CONSOLE, GOOGLE_CLIENT_ID],
 })
 
 ////////////////
@@ -112,6 +106,7 @@ const AUTH_API_URL = new sst.Linkable("AUTH_API_URL", {
 const STRIPE_WEBHOOK_SECRET = new sst.Linkable("STRIPE_WEBHOOK_SECRET", {
   properties: { value: stripeWebhook.secret },
 })
+const gatewayKv = new sst.cloudflare.Kv("GatewayKv")
 
 ////////////////
 // CONSOLE
@@ -142,6 +137,13 @@ new sst.cloudflare.x.SolidStart("Console", {
     EMAILOCTOPUS_API_KEY,
     AWS_SES_ACCESS_KEY_ID,
     AWS_SES_SECRET_ACCESS_KEY,
+    ...($dev
+      ? [
+          new sst.Secret("CLOUDFLARE_DEFAULT_ACCOUNT_ID", process.env.CLOUDFLARE_DEFAULT_ACCOUNT_ID!),
+          new sst.Secret("CLOUDFLARE_API_TOKEN", process.env.CLOUDFLARE_API_TOKEN!),
+        ]
+      : []),
+    gatewayKv,
   ],
   environment: {
     //VITE_DOCS_URL: web.url.apply((url) => url!),

+ 14 - 14
logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json

@@ -1,15 +1,15 @@
 {
-    "keep": {
-        "days": true,
-        "amount": 14
-    },
-    "auditLog": "/home/thdxr/dev/projects/sst/opencode/logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json",
-    "files": [
-        {
-            "date": 1759827172859,
-            "name": "/home/thdxr/dev/projects/sst/opencode/logs/mcp-puppeteer-2025-10-07.log",
-            "hash": "a3d98b26edd793411b968a0d24cfeee8332138e282023c3b83ec169d55c67f16"
-        }
-    ],
-    "hashType": "sha256"
-}
+  "keep": {
+    "days": true,
+    "amount": 14
+  },
+  "auditLog": "/home/thdxr/dev/projects/sst/opencode/logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json",
+  "files": [
+    {
+      "date": 1759827172859,
+      "name": "/home/thdxr/dev/projects/sst/opencode/logs/mcp-puppeteer-2025-10-07.log",
+      "hash": "a3d98b26edd793411b968a0d24cfeee8332138e282023c3b83ec169d55c67f16"
+    }
+  ],
+  "hashType": "sha256"
+}

+ 3 - 3
package.json

@@ -28,7 +28,7 @@
       "@tsconfig/bun": "1.0.9",
       "@cloudflare/workers-types": "4.20251008.0",
       "@openauthjs/openauth": "0.0.0-20250322224806",
-      "@pierre/precision-diffs": "0.4.1",
+      "@pierre/precision-diffs": "0.4.4",
       "@solidjs/meta": "0.29.4",
       "@tailwindcss/vite": "4.1.11",
       "diff": "8.0.2",
@@ -52,7 +52,7 @@
     "@tsconfig/bun": "catalog:",
     "husky": "9.1.7",
     "prettier": "3.6.2",
-    "sst": "3.17.19",
+    "sst": "3.17.23",
     "turbo": "2.5.6"
   },
   "dependencies": {
@@ -66,7 +66,7 @@
   "license": "MIT",
   "prettier": {
     "semi": false,
-    "printWidth": 100
+    "printWidth": 120
   },
   "trustedDependencies": [
     "esbuild",

+ 3 - 0
packages/console/app/.gitignore

@@ -23,6 +23,9 @@ app.config.timestamp_*.js
 # Temp
 gitignore
 
+# Generated files
+public/sitemap.xml
+
 # System Files
 .DS_Store
 Thumbs.db

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

@@ -5,9 +5,9 @@
     "typecheck": "tsgo --noEmit",
     "dev": "vinxi dev --host 0.0.0.0",
     "dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
-    "build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
+    "build": "./script/generate-sitemap.ts && vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
     "start": "vinxi start",
-    "version": "1.0.23"
+    "version": "1.0.55"
   },
   "dependencies": {
     "@ibm/plex": "6.4.1",

+ 103 - 0
packages/console/app/script/generate-sitemap.ts

@@ -0,0 +1,103 @@
+#!/usr/bin/env bun
+import { readdir, writeFile } from "fs/promises"
+import { join, dirname } from "path"
+import { fileURLToPath } from "url"
+import { config } from "../src/config.js"
+
+const __dirname = dirname(fileURLToPath(import.meta.url))
+const BASE_URL = config.baseUrl
+const PUBLIC_DIR = join(__dirname, "../public")
+const ROUTES_DIR = join(__dirname, "../src/routes")
+const DOCS_DIR = join(__dirname, "../../../web/src/content/docs")
+
+interface SitemapEntry {
+  url: string
+  priority: number
+  changefreq: string
+}
+
+async function getMainRoutes(): Promise<SitemapEntry[]> {
+  const routes: SitemapEntry[] = []
+
+  // Add main static routes
+  const staticRoutes = [
+    { path: "/", priority: 1.0, changefreq: "daily" },
+    { path: "/enterprise", priority: 0.8, changefreq: "weekly" },
+    { path: "/brand", priority: 0.6, changefreq: "monthly" },
+    { path: "/zen", priority: 0.8, changefreq: "weekly" },
+  ]
+
+  for (const route of staticRoutes) {
+    routes.push({
+      url: `${BASE_URL}${route.path}`,
+      priority: route.priority,
+      changefreq: route.changefreq,
+    })
+  }
+
+  return routes
+}
+
+async function getDocsRoutes(): Promise<SitemapEntry[]> {
+  const routes: SitemapEntry[] = []
+
+  try {
+    const files = await readdir(DOCS_DIR)
+
+    for (const file of files) {
+      if (!file.endsWith(".mdx")) continue
+
+      const slug = file.replace(".mdx", "")
+      const path = slug === "index" ? "/docs/" : `/docs/${slug}`
+
+      routes.push({
+        url: `${BASE_URL}${path}`,
+        priority: slug === "index" ? 0.9 : 0.7,
+        changefreq: "weekly",
+      })
+    }
+  } catch (error) {
+    console.error("Error reading docs directory:", error)
+  }
+
+  return routes
+}
+
+function generateSitemapXML(entries: SitemapEntry[]): string {
+  const urls = entries
+    .map(
+      (entry) => `  <url>
+    <loc>${entry.url}</loc>
+    <changefreq>${entry.changefreq}</changefreq>
+    <priority>${entry.priority}</priority>
+  </url>`,
+    )
+    .join("\n")
+
+  return `<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+${urls}
+</urlset>`
+}
+
+async function main() {
+  console.log("Generating sitemap...")
+
+  const mainRoutes = await getMainRoutes()
+  const docsRoutes = await getDocsRoutes()
+
+  const allRoutes = [...mainRoutes, ...docsRoutes]
+
+  console.log(`Found ${mainRoutes.length} main routes`)
+  console.log(`Found ${docsRoutes.length} docs routes`)
+  console.log(`Total: ${allRoutes.length} routes`)
+
+  const xml = generateSitemapXML(allRoutes)
+
+  const outputPath = join(PUBLIC_DIR, "sitemap.xml")
+  await writeFile(outputPath, xml, "utf-8")
+
+  console.log(`✓ Sitemap generated at ${outputPath}`)
+}
+
+main()

+ 1 - 1
packages/console/app/src/app.tsx

@@ -12,7 +12,7 @@ export default function App() {
       root={(props) => (
         <MetaProvider>
           <Title>opencode</Title>
-          <Meta name="description" content="opencode - The AI coding agent built for the terminal." />
+          <Meta name="description" content="OpenCode - The AI coding agent built for the terminal." />
           <Suspense>{props.children}</Suspense>
         </MetaProvider>
       )}

+ 1 - 1
packages/console/app/src/component/dropdown.css

@@ -77,4 +77,4 @@
       background-color: var(--color-accent-alpha);
     }
   }
-}
+}

+ 1 - 1
packages/console/app/src/component/modal.css

@@ -63,4 +63,4 @@
     font-weight: 600;
     color: var(--color-text);
   }
-}
+}

+ 3 - 0
packages/console/app/src/config.ts

@@ -2,6 +2,9 @@
  * Application-wide constants and configuration
  */
 export const config = {
+  // Base URL
+  baseUrl: "https://opencode.ai",
+
   // GitHub
   github: {
     repoUrl: "https://github.com/sst/opencode",

+ 1 - 4
packages/console/app/src/lib/github.ts

@@ -7,10 +7,7 @@ export const github = query(async () => {
     "User-Agent":
       "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
   }
-  const apiBaseUrl = config.github.repoUrl.replace(
-    "https://github.com/",
-    "https://api.github.com/repos/",
-  )
+  const apiBaseUrl = config.github.repoUrl.replace("https://github.com/", "https://api.github.com/repos/")
   try {
     const [meta, releases, contributors] = await Promise.all([
       fetch(apiBaseUrl, { headers }).then((res) => res.json()),

+ 5 - 8
packages/console/app/src/routes/brand/index.css

@@ -264,7 +264,7 @@
   [data-component="brand-content"] {
     padding: 4rem 5rem;
 
-    h2 {
+    h1 {
       font-size: 1.5rem;
       font-weight: 500;
       color: var(--color-text-strong);
@@ -299,7 +299,6 @@
       transition: all 0.2s ease;
       text-decoration: none;
 
-
       &:hover:not(:disabled) {
         background: var(--color-background-strong-hover);
       }
@@ -385,23 +384,21 @@
         0 1px 2px -1px rgba(19, 16, 16, 0.12);
 
       @media (max-width: 40rem) {
-        box-shadow:
-          0 0 0 1px rgba(19, 16, 16, 0.16)
+        box-shadow: 0 0 0 1px rgba(19, 16, 16, 0.16);
       }
 
-
       &:hover {
         background: var(--color-background);
       }
 
       &:active {
         transform: scale(0.98);
-        box-shadow: 0 0 0 1px rgba(19, 16, 16, 0.08), 0 6px 8px -8px rgba(19, 16, 16, 0.50);
+        box-shadow:
+          0 0 0 1px rgba(19, 16, 16, 0.08),
+          0 6px 8px -8px rgba(19, 16, 16, 0.5);
       }
     }
 
-
-
     @media (max-width: 60rem) {
       padding: 2rem 1.5rem;
     }

+ 25 - 125
packages/console/app/src/routes/brand/index.tsx

@@ -1,6 +1,7 @@
 import "./index.css"
-import { Title, Meta } from "@solidjs/meta"
+import { Title, Meta, Link } from "@solidjs/meta"
 import { Header } from "~/component/header"
+import { config } from "~/config"
 import { Footer } from "~/component/footer"
 import { Legal } from "~/component/legal"
 import previewLogoLight from "../../asset/brand/preview-opencode-logo-light.png"
@@ -53,26 +54,21 @@ export default function Brand() {
   return (
     <main data-page="enterprise">
       <Title>OpenCode | Brand</Title>
+      <Link rel="canonical" href={`${config.baseUrl}/brand`} />
       <Meta name="description" content="OpenCode brand guidelines" />
       <div data-component="container">
         <Header />
 
         <div data-component="content">
           <section data-component="brand-content">
-            <h2>Brand guidelines</h2>
+            <h1>Brand guidelines</h1>
             <p>Resources and assets to help you work with the OpenCode brand.</p>
             <button
               data-component="download-button"
               onClick={() => downloadFile(brandAssets, "opencode-brand-assets.zip")}
             >
               Download all assets
-              <svg
-                width="20"
-                height="20"
-                viewBox="0 0 20 20"
-                fill="none"
-                xmlns="http://www.w3.org/2000/svg"
-              >
+              <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                 <path
                   d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                   stroke="currentColor"
@@ -88,13 +84,7 @@ export default function Brand() {
                 <div data-component="actions">
                   <button onClick={() => downloadFile(logoLightPng, "opencode-logo-light.png")}>
                     PNG
-                    <svg
-                      width="20"
-                      height="20"
-                      viewBox="0 0 20 20"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
+                    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                       <path
                         d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                         stroke="currentColor"
@@ -105,13 +95,7 @@ export default function Brand() {
                   </button>
                   <button onClick={() => downloadFile(logoLightSvg, "opencode-logo-light.svg")}>
                     SVG
-                    <svg
-                      width="20"
-                      height="20"
-                      viewBox="0 0 20 20"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
+                    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                       <path
                         d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                         stroke="currentColor"
@@ -127,13 +111,7 @@ export default function Brand() {
                 <div data-component="actions">
                   <button onClick={() => downloadFile(logoDarkPng, "opencode-logo-dark.png")}>
                     PNG
-                    <svg
-                      width="20"
-                      height="20"
-                      viewBox="0 0 20 20"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
+                    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                       <path
                         d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                         stroke="currentColor"
@@ -144,13 +122,7 @@ export default function Brand() {
                   </button>
                   <button onClick={() => downloadFile(logoDarkSvg, "opencode-logo-dark.svg")}>
                     SVG
-                    <svg
-                      width="20"
-                      height="20"
-                      viewBox="0 0 20 20"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
+                    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                       <path
                         d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                         stroke="currentColor"
@@ -164,17 +136,9 @@ export default function Brand() {
               <div>
                 <img src={previewWordmarkLight} alt="OpenCode brand guidelines" />
                 <div data-component="actions">
-                  <button
-                    onClick={() => downloadFile(wordmarkLightPng, "opencode-wordmark-light.png")}
-                  >
+                  <button onClick={() => downloadFile(wordmarkLightPng, "opencode-wordmark-light.png")}>
                     PNG
-                    <svg
-                      width="20"
-                      height="20"
-                      viewBox="0 0 20 20"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
+                    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                       <path
                         d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                         stroke="currentColor"
@@ -183,17 +147,9 @@ export default function Brand() {
                       />
                     </svg>
                   </button>
-                  <button
-                    onClick={() => downloadFile(wordmarkLightSvg, "opencode-wordmark-light.svg")}
-                  >
+                  <button onClick={() => downloadFile(wordmarkLightSvg, "opencode-wordmark-light.svg")}>
                     SVG
-                    <svg
-                      width="20"
-                      height="20"
-                      viewBox="0 0 20 20"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
+                    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                       <path
                         d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                         stroke="currentColor"
@@ -207,17 +163,9 @@ export default function Brand() {
               <div>
                 <img src={previewWordmarkDark} alt="OpenCode brand guidelines" />
                 <div data-component="actions">
-                  <button
-                    onClick={() => downloadFile(wordmarkDarkPng, "opencode-wordmark-dark.png")}
-                  >
+                  <button onClick={() => downloadFile(wordmarkDarkPng, "opencode-wordmark-dark.png")}>
                     PNG
-                    <svg
-                      width="20"
-                      height="20"
-                      viewBox="0 0 20 20"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
+                    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                       <path
                         d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                         stroke="currentColor"
@@ -226,17 +174,9 @@ export default function Brand() {
                       />
                     </svg>
                   </button>
-                  <button
-                    onClick={() => downloadFile(wordmarkDarkSvg, "opencode-wordmark-dark.svg")}
-                  >
+                  <button onClick={() => downloadFile(wordmarkDarkSvg, "opencode-wordmark-dark.svg")}>
                     SVG
-                    <svg
-                      width="20"
-                      height="20"
-                      viewBox="0 0 20 20"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
+                    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                       <path
                         d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                         stroke="currentColor"
@@ -250,19 +190,9 @@ export default function Brand() {
               <div>
                 <img src={previewWordmarkSimpleLight} alt="OpenCode brand guidelines" />
                 <div data-component="actions">
-                  <button
-                    onClick={() =>
-                      downloadFile(wordmarkSimpleLightPng, "opencode-wordmark-simple-light.png")
-                    }
-                  >
+                  <button onClick={() => downloadFile(wordmarkSimpleLightPng, "opencode-wordmark-simple-light.png")}>
                     PNG
-                    <svg
-                      width="20"
-                      height="20"
-                      viewBox="0 0 20 20"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
+                    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                       <path
                         d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                         stroke="currentColor"
@@ -271,19 +201,9 @@ export default function Brand() {
                       />
                     </svg>
                   </button>
-                  <button
-                    onClick={() =>
-                      downloadFile(wordmarkSimpleLightSvg, "opencode-wordmark-simple-light.svg")
-                    }
-                  >
+                  <button onClick={() => downloadFile(wordmarkSimpleLightSvg, "opencode-wordmark-simple-light.svg")}>
                     SVG
-                    <svg
-                      width="20"
-                      height="20"
-                      viewBox="0 0 20 20"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
+                    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                       <path
                         d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                         stroke="currentColor"
@@ -297,19 +217,9 @@ export default function Brand() {
               <div>
                 <img src={previewWordmarkSimpleDark} alt="OpenCode brand guidelines" />
                 <div data-component="actions">
-                  <button
-                    onClick={() =>
-                      downloadFile(wordmarkSimpleDarkPng, "opencode-wordmark-simple-dark.png")
-                    }
-                  >
+                  <button onClick={() => downloadFile(wordmarkSimpleDarkPng, "opencode-wordmark-simple-dark.png")}>
                     PNG
-                    <svg
-                      width="20"
-                      height="20"
-                      viewBox="0 0 20 20"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
+                    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                       <path
                         d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                         stroke="currentColor"
@@ -318,19 +228,9 @@ export default function Brand() {
                       />
                     </svg>
                   </button>
-                  <button
-                    onClick={() =>
-                      downloadFile(wordmarkSimpleDarkSvg, "opencode-wordmark-simple-dark.svg")
-                    }
-                  >
+                  <button onClick={() => downloadFile(wordmarkSimpleDarkSvg, "opencode-wordmark-simple-dark.svg")}>
                     SVG
-                    <svg
-                      width="20"
-                      height="20"
-                      viewBox="0 0 20 20"
-                      fill="none"
-                      xmlns="http://www.w3.org/2000/svg"
-                    >
+                    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                       <path
                         d="M13.9583 10.6247L10 14.583L6.04167 10.6247M10 2.08301V13.958M16.25 17.9163H3.75"
                         stroke="currentColor"

+ 5 - 0
packages/console/app/src/routes/desktop-feedback.ts

@@ -0,0 +1,5 @@
+import { redirect } from "@solidjs/router"
+
+export async function GET() {
+  return redirect("https://discord.gg/h5TNnkFVNy")
+}

+ 1 - 1
packages/console/app/src/routes/enterprise/index.css

@@ -287,7 +287,7 @@
   }
 
   [data-component="enterprise-column-1"] {
-    h2 {
+    h1 {
       font-size: 1.5rem;
       font-weight: 500;
       color: var(--color-text-strong);

+ 23 - 40
packages/console/app/src/routes/enterprise/index.tsx

@@ -1,6 +1,7 @@
 import "./index.css"
-import { Title, Meta } from "@solidjs/meta"
+import { Title, Meta, Link } from "@solidjs/meta"
 import { createSignal, Show } from "solid-js"
+import { config } from "~/config"
 import { Header } from "~/component/header"
 import { Footer } from "~/component/footer"
 import { Legal } from "~/component/legal"
@@ -54,6 +55,7 @@ export default function Enterprise() {
   return (
     <main data-page="enterprise">
       <Title>OpenCode | Enterprise solutions for your organisation</Title>
+      <Link rel="canonical" href={`${config.baseUrl}/enterprise`} />
       <Meta name="description" content="Contact OpenCode for enterprise solutions" />
       <div data-component="container">
         <Header />
@@ -62,41 +64,28 @@ export default function Enterprise() {
           <section data-component="enterprise-content">
             <div data-component="enterprise-columns">
               <div data-component="enterprise-column-1">
-                <h2>Your code is yours</h2>
+                <h1>Your code is yours</h1>
                 <p>
-                  OpenCode operates securely inside your organization with no data or context stored
-                  and no licensing restrictions or ownership claims. Start a trial with your team,
-                  then deploy it across your organization by integrating it with your SSO and
-                  internal AI gateway.
+                  OpenCode operates securely inside your organization with no data or context stored and no licensing
+                  restrictions or ownership claims. Start a trial with your team, then deploy it across your
+                  organization by integrating it with your SSO and internal AI gateway.
                 </p>
                 <p>Let us know and how we can help.</p>
 
                 <Show when={false}>
                   <div data-component="testimonial">
                     <div data-component="quotation">
-                      <svg
-                        width="20"
-                        height="17"
-                        viewBox="0 0 20 17"
-                        fill="none"
-                        xmlns="http://www.w3.org/2000/svg"
-                      >
+                      <svg width="20" height="17" viewBox="0 0 20 17" fill="none" xmlns="http://www.w3.org/2000/svg">
                         <path
                           d="M19.4118 0L16.5882 9.20833H20V17H12.2353V10.0938L16 0H19.4118ZM7.17647 0L4.35294 9.20833H7.76471V17H0V10.0938L3.76471 0H7.17647Z"
                           fill="currentColor"
                         />
                       </svg>
                     </div>
-                    Thanks to OpenCode, we found a way to create software to track all our assets —
-                    even the imaginary ones.
+                    Thanks to OpenCode, we found a way to create software to track all our assets — even the imaginary
+                    ones.
                     <div data-component="testimonial-logo">
-                      <svg
-                        width="80"
-                        height="79"
-                        viewBox="0 0 80 79"
-                        fill="none"
-                        xmlns="http://www.w3.org/2000/svg"
-                      >
+                      <svg width="80" height="79" viewBox="0 0 80 79" fill="none" xmlns="http://www.w3.org/2000/svg">
                         <path
                           fill-rule="evenodd"
                           clip-rule="evenodd"
@@ -213,11 +202,7 @@ export default function Enterprise() {
                     </button>
                   </form>
 
-                  {showSuccess() && (
-                    <div data-component="success-message">
-                      Message sent, we'll be in touch soon.
-                    </div>
-                  )}
+                  {showSuccess() && <div data-component="success-message">Message sent, we'll be in touch soon.</div>}
                 </div>
               </div>
             </div>
@@ -230,31 +215,29 @@ export default function Enterprise() {
             <ul>
               <li>
                 <Faq question="What is OpenCode Enterprise?">
-                  OpenCode Enterprise is for organizations that want to ensure that their code and
-                  data never leaves their infrastructure. It can do this by using a centralized
-                  config that integrates with your SSO and internal AI gateway.
+                  OpenCode Enterprise is for organizations that want to ensure that their code and data never leaves
+                  their infrastructure. It can do this by using a centralized config that integrates with your SSO and
+                  internal AI gateway.
                 </Faq>
               </li>
               <li>
                 <Faq question="How do I get started with OpenCode Enterprise?">
-                  Simply start with an internal trial with your team. OpenCode by default does not
-                  store your code or context data, making it easy to get started. Then contact us to
-                  discuss pricing and implementation options.
+                  Simply start with an internal trial with your team. OpenCode by default does not store your code or
+                  context data, making it easy to get started. Then contact us to discuss pricing and implementation
+                  options.
                 </Faq>
               </li>
               <li>
                 <Faq question="How does enterprise pricing work?">
-                  We offer per-seat enterprise pricing. If you have your own LLM gateway, we do not
-                  charge for tokens used. For further details, contact us for a custom quote based
-                  on your organization's needs.
+                  We offer per-seat enterprise pricing. If you have your own LLM gateway, we do not charge for tokens
+                  used. For further details, contact us for a custom quote based on your organization's needs.
                 </Faq>
               </li>
               <li>
                 <Faq question="Is my data secure with OpenCode Enterprise?">
-                  Yes. OpenCode does not store your code or context data. All processing happens
-                  locally or through direct API calls to your AI provider. With central config and
-                  SSO integration, your data remains secure within your organization's
-                  infrastructure.
+                  Yes. OpenCode does not store your code or context data. All processing happens locally or through
+                  direct API calls to your AI provider. With central config and SSO integration, your data remains
+                  secure within your organization's infrastructure.
                 </Faq>
               </li>
             </ul>

+ 1 - 1
packages/console/app/src/routes/index.css

@@ -479,7 +479,7 @@ body {
       border-bottom: 1px solid var(--color-border-weak);
     }
 
-    strong {
+    h1 {
       font-size: 28px;
       color: var(--color-text-strong);
       font-weight: 500;

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 110 - 647
packages/console/app/src/routes/index.tsx


+ 116 - 118
packages/console/app/src/routes/stripe/webhook.ts

@@ -13,146 +13,144 @@ export async function POST(input: APIEvent) {
     input.request.headers.get("stripe-signature")!,
     Resource.STRIPE_WEBHOOK_SECRET.value,
   )
-
   console.log(body.type, JSON.stringify(body, null, 2))
-  if (body.type === "customer.updated") {
-    // check default payment method changed
-    const prevInvoiceSettings = body.data.previous_attributes?.invoice_settings ?? {}
-    if (!("default_payment_method" in prevInvoiceSettings)) return
 
-    const customerID = body.data.object.id
-    const paymentMethodID = body.data.object.invoice_settings.default_payment_method as string
+  return (async () => {
+    if (body.type === "customer.updated") {
+      // check default payment method changed
+      const prevInvoiceSettings = body.data.previous_attributes?.invoice_settings ?? {}
+      if (!("default_payment_method" in prevInvoiceSettings)) return "ignored"
 
-    if (!customerID) throw new Error("Customer ID not found")
-    if (!paymentMethodID) throw new Error("Payment method ID not found")
+      const customerID = body.data.object.id
+      const paymentMethodID = body.data.object.invoice_settings.default_payment_method as string
 
-    const paymentMethod = await Billing.stripe().paymentMethods.retrieve(paymentMethodID)
-    await Database.use(async (tx) => {
-      await tx
-        .update(BillingTable)
-        .set({
-          paymentMethodID,
-          paymentMethodLast4: paymentMethod.card?.last4 ?? null,
-          paymentMethodType: paymentMethod.type,
-        })
-        .where(eq(BillingTable.customerID, customerID))
-    })
-  }
-  if (body.type === "checkout.session.completed") {
-    const workspaceID = body.data.object.metadata?.workspaceID
-    const customerID = body.data.object.customer as string
-    const paymentID = body.data.object.payment_intent as string
-    const invoiceID = body.data.object.invoice as string
-    const amount = body.data.object.amount_total
+      if (!customerID) throw new Error("Customer ID not found")
+      if (!paymentMethodID) throw new Error("Payment method ID not found")
 
-    if (!workspaceID) throw new Error("Workspace ID not found")
-    if (!customerID) throw new Error("Customer ID not found")
-    if (!amount) throw new Error("Amount not found")
-    if (!paymentID) throw new Error("Payment ID not found")
-    if (!invoiceID) throw new Error("Invoice ID not found")
-
-    await Actor.provide("system", { workspaceID }, async () => {
-      const customer = await Billing.get()
-      if (customer?.customerID && customer.customerID !== customerID)
-        throw new Error("Customer ID mismatch")
+      const paymentMethod = await Billing.stripe().paymentMethods.retrieve(paymentMethodID)
+      await Database.use(async (tx) => {
+        await tx
+          .update(BillingTable)
+          .set({
+            paymentMethodID,
+            paymentMethodLast4: paymentMethod.card?.last4 ?? null,
+            paymentMethodType: paymentMethod.type,
+          })
+          .where(eq(BillingTable.customerID, customerID))
+      })
+    }
+    if (body.type === "checkout.session.completed") {
+      const workspaceID = body.data.object.metadata?.workspaceID
+      const amountInCents = body.data.object.metadata?.amount && parseInt(body.data.object.metadata?.amount)
+      const customerID = body.data.object.customer as string
+      const paymentID = body.data.object.payment_intent as string
+      const invoiceID = body.data.object.invoice as string
+
+      if (!workspaceID) throw new Error("Workspace ID not found")
+      if (!customerID) throw new Error("Customer ID not found")
+      if (!amountInCents) throw new Error("Amount not found")
+      if (!paymentID) throw new Error("Payment ID not found")
+      if (!invoiceID) throw new Error("Invoice ID not found")
+
+      await Actor.provide("system", { workspaceID }, async () => {
+        const customer = await Billing.get()
+        if (customer?.customerID && customer.customerID !== customerID) throw new Error("Customer ID mismatch")
+
+        // set customer metadata
+        if (!customer?.customerID) {
+          await Billing.stripe().customers.update(customerID, {
+            metadata: {
+              workspaceID,
+            },
+          })
+        }
 
-      // set customer metadata
-      if (!customer?.customerID) {
-        await Billing.stripe().customers.update(customerID, {
-          metadata: {
+        // get payment method for the payment intent
+        const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
+          expand: ["payment_method"],
+        })
+        const paymentMethod = paymentIntent.payment_method
+        if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
+
+        await Database.transaction(async (tx) => {
+          await tx
+            .update(BillingTable)
+            .set({
+              balance: sql`${BillingTable.balance} + ${centsToMicroCents(amountInCents)}`,
+              customerID,
+              paymentMethodID: paymentMethod.id,
+              paymentMethodLast4: paymentMethod.card?.last4 ?? null,
+              paymentMethodType: paymentMethod.type,
+              // enable reload if first time enabling billing
+              ...(customer?.customerID
+                ? {}
+                : {
+                    reload: true,
+                    reloadError: null,
+                    timeReloadError: null,
+                  }),
+            })
+            .where(eq(BillingTable.workspaceID, workspaceID))
+          await tx.insert(PaymentTable).values({
             workspaceID,
-          },
+            id: Identifier.create("payment"),
+            amount: centsToMicroCents(amountInCents),
+            paymentID,
+            invoiceID,
+            customerID,
+          })
         })
-      }
-
-      // get payment method for the payment intent
-      const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
-        expand: ["payment_method"],
       })
-      const paymentMethod = paymentIntent.payment_method
-      if (!paymentMethod || typeof paymentMethod === "string")
-        throw new Error("Payment method not expanded")
-
-      const oldBillingInfo = await Database.use((tx) =>
+    }
+    if (body.type === "charge.refunded") {
+      const customerID = body.data.object.customer as string
+      const paymentIntentID = body.data.object.payment_intent as string
+      if (!customerID) throw new Error("Customer ID not found")
+      if (!paymentIntentID) throw new Error("Payment ID not found")
+
+      const workspaceID = await Database.use((tx) =>
         tx
           .select({
-            customerID: BillingTable.customerID,
+            workspaceID: BillingTable.workspaceID,
           })
           .from(BillingTable)
-          .where(eq(BillingTable.workspaceID, workspaceID))
-          .then((rows) => rows[0]),
+          .where(eq(BillingTable.customerID, customerID))
+          .then((rows) => rows[0]?.workspaceID),
       )
+      if (!workspaceID) throw new Error("Workspace ID not found")
+
+      const amount = await Database.use((tx) =>
+        tx
+          .select({
+            amount: PaymentTable.amount,
+          })
+          .from(PaymentTable)
+          .where(and(eq(PaymentTable.paymentID, paymentIntentID), eq(PaymentTable.workspaceID, workspaceID)))
+          .then((rows) => rows[0]?.amount),
+      )
+      if (!amount) throw new Error("Payment not found")
 
       await Database.transaction(async (tx) => {
+        await tx
+          .update(PaymentTable)
+          .set({
+            timeRefunded: new Date(body.created * 1000),
+          })
+          .where(and(eq(PaymentTable.paymentID, paymentIntentID), eq(PaymentTable.workspaceID, workspaceID)))
+
         await tx
           .update(BillingTable)
           .set({
-            balance: sql`${BillingTable.balance} + ${centsToMicroCents(Billing.CHARGE_AMOUNT)}`,
-            customerID,
-            paymentMethodID: paymentMethod.id,
-            paymentMethodLast4: paymentMethod.card?.last4 ?? null,
-            paymentMethodType: paymentMethod.type,
-            // enable reload if first time enabling billing
-            ...(oldBillingInfo?.customerID
-              ? {}
-              : {
-                  reload: true,
-                  reloadError: null,
-                  timeReloadError: null,
-                }),
+            balance: sql`${BillingTable.balance} - ${amount}`,
           })
           .where(eq(BillingTable.workspaceID, workspaceID))
-        await tx.insert(PaymentTable).values({
-          workspaceID,
-          id: Identifier.create("payment"),
-          amount: centsToMicroCents(Billing.CHARGE_AMOUNT),
-          paymentID,
-          invoiceID,
-          customerID,
-        })
       })
+    }
+  })()
+    .then((message) => {
+      return Response.json({ message: message ?? "done" }, { status: 200 })
     })
-  }
-  if (body.type === "charge.refunded") {
-    const customerID = body.data.object.customer as string
-    const paymentIntentID = body.data.object.payment_intent as string
-    if (!customerID) throw new Error("Customer ID not found")
-    if (!paymentIntentID) throw new Error("Payment ID not found")
-
-    const workspaceID = await Database.use((tx) =>
-      tx
-        .select({
-          workspaceID: BillingTable.workspaceID,
-        })
-        .from(BillingTable)
-        .where(eq(BillingTable.customerID, customerID))
-        .then((rows) => rows[0]?.workspaceID),
-    )
-    if (!workspaceID) throw new Error("Workspace ID not found")
-
-    await Database.transaction(async (tx) => {
-      await tx
-        .update(PaymentTable)
-        .set({
-          timeRefunded: new Date(body.created * 1000),
-        })
-        .where(
-          and(
-            eq(PaymentTable.paymentID, paymentIntentID),
-            eq(PaymentTable.workspaceID, workspaceID),
-          ),
-        )
-
-      await tx
-        .update(BillingTable)
-        .set({
-          balance: sql`${BillingTable.balance} - ${centsToMicroCents(Billing.CHARGE_AMOUNT)}`,
-        })
-        .where(eq(BillingTable.workspaceID, workspaceID))
+    .catch((error: any) => {
+      return Response.json({ message: error.message }, { status: 500 })
     })
-  }
-
-  console.log("finished handling")
-
-  return Response.json("ok", { status: 200 })
 }

+ 4 - 6
packages/console/app/src/routes/temp.tsx

@@ -79,19 +79,17 @@ export default function Home() {
               <strong>LSP enabled</strong> Automatically loads the right LSPs for the LLM
             </li>
             <li>
-              <strong>opencode zen</strong> A <a href="/docs/zen">curated list of models</a>{" "}
-              provided by opencode <label>New</label>
+              <strong>opencode zen</strong> A <a href="/docs/zen">curated list of models</a> provided by opencode{" "}
+              <label>New</label>
             </li>
             <li>
               <strong>Multi-session</strong> Start multiple agents in parallel on the same project
             </li>
             <li>
-              <strong>Shareable links</strong> Share a link to any sessions for reference or to
-              debug
+              <strong>Shareable links</strong> Share a link to any sessions for reference or to debug
             </li>
             <li>
-              <strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max
-              account
+              <strong>Claude Pro</strong> Log in with Anthropic to use your Claude Pro or Max account
             </li>
             <li>
               <strong>Use any model</strong> Supports 75+ LLM providers through{" "}

+ 1 - 1
packages/console/app/src/routes/user-menu.css

@@ -14,4 +14,4 @@
       color: var(--color-danger);
     }
   }
-}
+}

+ 1 - 1
packages/console/app/src/routes/workspace-picker.css

@@ -71,4 +71,4 @@
       color: var(--color-text-muted);
     }
   }
-}
+}

+ 1 - 1
packages/console/app/src/routes/workspace.css

@@ -104,4 +104,4 @@
       }
     }
   }
-}
+}

+ 52 - 1
packages/console/app/src/routes/workspace/[id]/billing/billing-section.module.css

@@ -71,6 +71,57 @@
       flex: 1;
     }
 
+    [data-slot="add-balance-form-container"] {
+      display: flex;
+      flex-direction: column;
+      gap: var(--space-2);
+    }
+
+    [data-slot="add-balance-form"] {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      gap: var(--space-3);
+
+      label {
+        font-size: var(--font-size-sm);
+        font-weight: 500;
+        color: var(--color-text-muted);
+        white-space: nowrap;
+      }
+
+      input[data-component="input"] {
+        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);
+        line-height: 1.5;
+
+        &:focus {
+          outline: none;
+          border-color: var(--color-accent);
+          box-shadow: 0 0 0 3px var(--color-accent-alpha);
+        }
+
+        &::placeholder {
+          color: var(--color-text-disabled);
+        }
+      }
+
+      [data-slot="form-actions"] {
+        display: flex;
+        gap: var(--space-2);
+      }
+    }
+
+    [data-slot="form-error"] {
+      color: var(--color-danger);
+      font-size: var(--font-size-sm);
+      line-height: 1.4;
+    }
+
     [data-slot="credit-card"] {
       padding: var(--space-2) var(--space-4);
       background-color: var(--color-bg-surface);
@@ -131,4 +182,4 @@
     padding: var(--space-4);
     min-width: 150px;
   }
-}
+}

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

@@ -1,24 +1,86 @@
-import { action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router"
-import { createMemo, Match, Show, Switch } from "solid-js"
+import { action, useParams, useAction, createAsync, useSubmission, json } from "@solidjs/router"
+import { createMemo, Match, Show, Switch, createEffect } from "solid-js"
+import { createStore } from "solid-js/store"
 import { Billing } from "@opencode-ai/console-core/billing.js"
 import { withActor } from "~/context/auth.withActor"
 import { IconCreditCard, IconStripe } from "~/component/icon"
 import styles from "./billing-section.module.css"
-import { createCheckoutUrl, queryBillingInfo } from "../../common"
+import { createCheckoutUrl, formatBalance, queryBillingInfo } from "../../common"
 
 const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
   "use server"
-  return withActor(() => Billing.generateSessionUrl({ returnUrl }), workspaceID)
+  return json(
+    await withActor(
+      () =>
+        Billing.generateSessionUrl({ returnUrl })
+          .then((data) => ({ error: undefined, data }))
+          .catch((e) => ({
+            error: e.message as string,
+            data: undefined,
+          })),
+      workspaceID,
+    ),
+    { revalidate: queryBillingInfo.key },
+  )
 }, "sessionUrl")
 
 export function BillingSection() {
   const params = useParams()
   // ORIGINAL CODE - COMMENTED OUT FOR TESTING
-  const balanceInfo = createAsync(() => queryBillingInfo(params.id))
-  const createCheckoutUrlAction = useAction(createCheckoutUrl)
-  const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
-  const createSessionUrlAction = useAction(createSessionUrl)
-  const createSessionUrlSubmission = useSubmission(createSessionUrl)
+  const billingInfo = createAsync(() => queryBillingInfo(params.id))
+  const checkoutAction = useAction(createCheckoutUrl)
+  const checkoutSubmission = useSubmission(createCheckoutUrl)
+  const sessionAction = useAction(createSessionUrl)
+  const sessionSubmission = useSubmission(createSessionUrl)
+  const [store, setStore] = createStore({
+    showAddBalanceForm: false,
+    addBalanceAmount: billingInfo()?.reloadAmount.toString() ?? "",
+    checkoutRedirecting: false,
+    sessionRedirecting: false,
+  })
+
+  createEffect(() => {
+    const info = billingInfo()
+    if (info) {
+      setStore("addBalanceAmount", info.reloadAmount.toString())
+    }
+  })
+  const balance = createMemo(() => formatBalance(billingInfo()?.balance ?? 0))
+
+  async function onClickCheckout() {
+    const amount = parseInt(store.addBalanceAmount)
+    const baseUrl = window.location.href
+
+    const checkout = await checkoutAction(params.id, amount, baseUrl, baseUrl)
+    if (checkout && checkout.data) {
+      setStore("checkoutRedirecting", true)
+      window.location.href = checkout.data
+    }
+  }
+
+  async function onClickSession() {
+    const baseUrl = window.location.href
+    const sessionUrl = await sessionAction(params.id, baseUrl)
+    if (sessionUrl && sessionUrl.data) {
+      setStore("sessionRedirecting", true)
+      window.location.href = sessionUrl.data
+    }
+  }
+
+  function showAddBalanceForm() {
+    while (true) {
+      checkoutSubmission.clear()
+      if (!checkoutSubmission.result) break
+    }
+    setStore({
+      showAddBalanceForm: true,
+    })
+  }
+
+  function hideAddBalanceForm() {
+    setStore("showAddBalanceForm", false)
+    checkoutSubmission.clear()
+  }
 
   // DUMMY DATA FOR TESTING - UNCOMMENT ONE OF THE SCENARIOS BELOW
 
@@ -72,97 +134,104 @@ export function BillingSection() {
   //   timeReloadError: null as Date | null
   // })
 
-  const balanceAmount = createMemo(() => {
-    return ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
-  })
-
   return (
     <section class={styles.root}>
       <div data-slot="section-title">
         <h2>Billing</h2>
         <p>
-          Manage payments methods. <a href="mailto:[email protected]">Contact us</a> if you have any
-          questions.
+          Manage payments methods. <a href="mailto:[email protected]">Contact us</a> if you have any questions.
         </p>
       </div>
       <div data-slot="section-content">
         <div data-slot="balance-display">
           <div data-slot="balance-amount">
-            <span data-slot="balance-value">
-              ${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()}
-            </span>
+            <span data-slot="balance-value">${balance()}</span>
             <span data-slot="balance-label">Current Balance</span>
           </div>
-          <Show when={balanceInfo()?.customerID}>
+          <Show when={billingInfo()?.customerID}>
             <div data-slot="balance-right-section">
-              <button
-                data-color="primary"
-                disabled={createCheckoutUrlSubmission.pending}
-                onClick={async () => {
-                  const baseUrl = window.location.href
-                  const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
-                  if (checkoutUrl) {
-                    window.location.href = checkoutUrl
-                  }
-                }}
+              <Show
+                when={!store.showAddBalanceForm}
+                fallback={
+                  <div data-slot="add-balance-form-container">
+                    <div data-slot="add-balance-form">
+                      <label>Add $</label>
+                      <input
+                        data-component="input"
+                        type="number"
+                        min={billingInfo()?.reloadAmountMin.toString()}
+                        step="1"
+                        value={store.addBalanceAmount}
+                        onInput={(e) => {
+                          setStore("addBalanceAmount", e.currentTarget.value)
+                          checkoutSubmission.clear()
+                        }}
+                        placeholder="Enter amount"
+                      />
+                      <div data-slot="form-actions">
+                        <button data-color="ghost" type="button" onClick={() => hideAddBalanceForm()}>
+                          Cancel
+                        </button>
+                        <button
+                          data-color="primary"
+                          type="button"
+                          disabled={!store.addBalanceAmount || checkoutSubmission.pending || store.checkoutRedirecting}
+                          onClick={onClickCheckout}
+                        >
+                          {checkoutSubmission.pending || store.checkoutRedirecting ? "Loading..." : "Add"}
+                        </button>
+                      </div>
+                    </div>
+                    <Show when={checkoutSubmission.result && (checkoutSubmission.result as any).error}>
+                      {(err: any) => <div data-slot="form-error">{err()}</div>}
+                    </Show>
+                  </div>
+                }
               >
-                {createCheckoutUrlSubmission.pending ? "Loading..." : "Add Balance"}
-              </button>
+                <button data-color="primary" onClick={() => showAddBalanceForm()}>
+                  Add Balance
+                </button>
+              </Show>
               <div data-slot="credit-card">
                 <div data-slot="card-icon">
                   <Switch fallback={<IconCreditCard style={{ width: "24px", height: "24px" }} />}>
-                    <Match when={balanceInfo()?.paymentMethodType === "link"}>
+                    <Match when={billingInfo()?.paymentMethodType === "link"}>
                       <IconStripe style={{ width: "24px", height: "24px" }} />
                     </Match>
                   </Switch>
                 </div>
                 <div data-slot="card-details">
                   <Switch>
-                    <Match when={balanceInfo()?.paymentMethodType === "card"}>
-                      <Show
-                        when={balanceInfo()?.paymentMethodLast4}
-                        fallback={<span data-slot="number">----</span>}
-                      >
+                    <Match when={billingInfo()?.paymentMethodType === "card"}>
+                      <Show when={billingInfo()?.paymentMethodLast4} fallback={<span data-slot="number">----</span>}>
                         <span data-slot="secret">••••</span>
-                        <span data-slot="number">{balanceInfo()?.paymentMethodLast4}</span>
+                        <span data-slot="number">{billingInfo()?.paymentMethodLast4}</span>
                       </Show>
                     </Match>
-                    <Match when={balanceInfo()?.paymentMethodType === "link"}>
+                    <Match when={billingInfo()?.paymentMethodType === "link"}>
                       <span data-slot="type">Linked to Stripe</span>
                     </Match>
                   </Switch>
                 </div>
                 <button
                   data-color="ghost"
-                  disabled={createSessionUrlSubmission.pending}
-                  onClick={async () => {
-                    const baseUrl = window.location.href
-                    const sessionUrl = await createSessionUrlAction(params.id, baseUrl)
-                    if (sessionUrl) {
-                      window.location.href = sessionUrl
-                    }
-                  }}
+                  disabled={sessionSubmission.pending || store.sessionRedirecting}
+                  onClick={onClickSession}
                 >
-                  {createSessionUrlSubmission.pending ? "Loading..." : "Manage"}
+                  {sessionSubmission.pending || store.sessionRedirecting ? "Loading..." : "Manage"}
                 </button>
               </div>
             </div>
           </Show>
         </div>
-        <Show when={!balanceInfo()?.customerID}>
+        <Show when={!billingInfo()?.customerID}>
           <button
             data-slot="enable-billing-button"
             data-color="primary"
-            disabled={createCheckoutUrlSubmission.pending}
-            onClick={async () => {
-              const baseUrl = window.location.href
-              const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
-              if (checkoutUrl) {
-                window.location.href = checkoutUrl
-              }
-            }}
+            disabled={checkoutSubmission.pending || store.checkoutRedirecting}
+            onClick={onClickCheckout}
           >
-            {createCheckoutUrlSubmission.pending ? "Loading..." : "Enable Billing"}
+            {checkoutSubmission.pending || store.checkoutRedirecting ? "Loading..." : "Enable Billing"}
           </button>
         </Show>
       </div>

+ 1 - 1
packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.module.css

@@ -93,4 +93,4 @@
     margin: 0;
     line-height: 1.4;
   }
-}
+}

+ 11 - 17
packages/console/app/src/routes/workspace/[id]/billing/monthly-limit-section.tsx

@@ -1,16 +1,10 @@
-import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router"
+import { json, action, useParams, createAsync, useSubmission } from "@solidjs/router"
 import { createEffect, Show } from "solid-js"
 import { createStore } from "solid-js/store"
 import { withActor } from "~/context/auth.withActor"
 import { Billing } from "@opencode-ai/console-core/billing.js"
 import styles from "./monthly-limit-section.module.css"
-
-const getBillingInfo = query(async (workspaceID: string) => {
-  "use server"
-  return withActor(async () => {
-    return await Billing.get()
-  }, workspaceID)
-}, "billing.get")
+import { queryBillingInfo } from "../../common"
 
 const setMonthlyLimit = action(async (form: FormData) => {
   "use server"
@@ -28,7 +22,7 @@ const setMonthlyLimit = action(async (form: FormData) => {
           .catch((e) => ({ error: e.message as string })),
       workspaceID,
     ),
-    { revalidate: getBillingInfo.key },
+    { revalidate: queryBillingInfo.key },
   )
 }, "billing.setMonthlyLimit")
 
@@ -36,7 +30,7 @@ export function MonthlyLimitSection() {
   const params = useParams()
   const submission = useSubmission(setMonthlyLimit)
   const [store, setStore] = createStore({ show: false })
-  const balanceInfo = createAsync(() => getBillingInfo(params.id))
+  const billingInfo = createAsync(() => queryBillingInfo(params.id))
 
   let input: HTMLInputElement
 
@@ -68,13 +62,13 @@ export function MonthlyLimitSection() {
     <section class={styles.root}>
       <div data-slot="section-title">
         <h2>Monthly Limit</h2>
-        <p>Set a monthly spending limit for your account.</p>
+        <p>Set a monthly usage limit for your account.</p>
       </div>
       <div data-slot="section-content">
         <div data-slot="balance">
           <div data-slot="amount">
-            {balanceInfo()?.monthlyLimit ? <span data-slot="currency">$</span> : null}
-            <span data-slot="value">{balanceInfo()?.monthlyLimit ?? "-"}</span>
+            {billingInfo()?.monthlyLimit ? <span data-slot="currency">$</span> : null}
+            <span data-slot="value">{billingInfo()?.monthlyLimit ?? "-"}</span>
           </div>
           <Show
             when={!store.show}
@@ -106,15 +100,15 @@ export function MonthlyLimitSection() {
             }
           >
             <button data-color="primary" onClick={() => show()}>
-              {balanceInfo()?.monthlyLimit ? "Edit Limit" : "Set Limit"}
+              {billingInfo()?.monthlyLimit ? "Edit Limit" : "Set Limit"}
             </button>
           </Show>
         </div>
-        <Show when={balanceInfo()?.monthlyLimit} fallback={<p data-slot="usage-status">No spending limit set.</p>}>
+        <Show when={billingInfo()?.monthlyLimit} fallback={<p data-slot="usage-status">No usage limit set.</p>}>
           <p data-slot="usage-status">
             Current usage for {new Date().toLocaleDateString("en-US", { month: "long", timeZone: "UTC" })} is $
             {(() => {
-              const dateLastUsed = balanceInfo()?.timeMonthlyUsageUpdated
+              const dateLastUsed = billingInfo()?.timeMonthlyUsageUpdated
               if (!dateLastUsed) return "0"
 
               const current = new Date().toLocaleDateString("en-US", {
@@ -128,7 +122,7 @@ export function MonthlyLimitSection() {
                 timeZone: "UTC",
               })
               if (current !== lastUsed) return "0"
-              return ((balanceInfo()?.monthlyUsage ?? 0) / 100000000).toFixed(2)
+              return ((billingInfo()?.monthlyUsage ?? 0) / 100000000).toFixed(2)
             })()}
             .
           </p>

+ 202 - 1
packages/console/app/src/routes/workspace/[id]/billing/reload-section.module.css

@@ -34,6 +34,206 @@
     }
   }
 
+  [data-slot="create-form"] {
+    display: flex;
+    flex-direction: column;
+    gap: var(--space-3);
+    padding: var(--space-4);
+    border: 1px solid var(--color-border);
+    border-radius: var(--border-radius-sm);
+    margin-top: var(--space-4);
+
+    [data-slot="form-field"] {
+      display: flex;
+      flex-direction: column;
+      gap: var(--space-2);
+
+      label {
+        display: flex;
+        flex-direction: column;
+        gap: var(--space-2);
+      }
+
+      [data-slot="field-label"] {
+        font-size: var(--font-size-sm);
+        font-weight: 500;
+        color: var(--color-text-muted);
+      }
+
+      [data-slot="toggle-container"] {
+        display: flex;
+        align-items: center;
+      }
+
+      input[data-component="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="input-row"] {
+      display: flex;
+      flex-direction: row;
+      gap: var(--space-3);
+
+      @media (max-width: 40rem) {
+        flex-direction: column;
+        gap: var(--space-2);
+      }
+    }
+
+    [data-slot="input-field"] {
+      display: flex;
+      flex-direction: column;
+      gap: var(--space-1);
+      flex: 1;
+
+      p {
+        line-height: 1.2;
+        margin: 0;
+        color: var(--color-text-muted);
+        font-size: var(--font-size-sm);
+      }
+
+      input[data-component="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);
+        line-height: 1.5;
+        min-width: 0;
+
+        &:focus {
+          outline: none;
+          border-color: var(--color-accent);
+          box-shadow: 0 0 0 3px var(--color-accent-alpha);
+        }
+
+        &::placeholder {
+          color: var(--color-text-disabled);
+        }
+
+        &:disabled {
+          opacity: 0.5;
+          cursor: not-allowed;
+          background-color: var(--color-bg-surface);
+        }
+      }
+
+      [data-slot="field-with-connector"] {
+        display: flex;
+        align-items: center;
+        gap: var(--space-2);
+
+        [data-slot="field-connector"] {
+          font-size: var(--font-size-sm);
+          color: var(--color-text-muted);
+          white-space: nowrap;
+        }
+
+        input[data-component="input"] {
+          flex: 1;
+          min-width: 80px;
+        }
+      }
+    }
+
+    [data-slot="form-actions"] {
+      display: flex;
+      gap: var(--space-2);
+      margin-top: var(--space-1);
+    }
+
+    [data-slot="form-error"] {
+      color: var(--color-danger);
+      font-size: var(--font-size-sm);
+      line-height: 1.4;
+      margin-top: calc(var(--space-1) * -1);
+    }
+
+    [data-slot="model-toggle-label"] {
+      position: relative;
+      display: inline-block;
+      width: 2.5rem;
+      height: 1.5rem;
+      cursor: pointer;
+
+      input {
+        opacity: 0;
+        width: 0;
+        height: 0;
+      }
+
+      span {
+        position: absolute;
+        inset: 0;
+        background-color: #ccc;
+        border: 1px solid #bbb;
+        border-radius: 1.5rem;
+        transition: all 0.3s ease;
+        cursor: pointer;
+
+        &::before {
+          content: "";
+          position: absolute;
+          top: 50%;
+          left: 0.125rem;
+          width: 1.25rem;
+          height: 1.25rem;
+          background-color: white;
+          border: 1px solid #ddd;
+          border-radius: 50%;
+          transform: translateY(-50%);
+          transition: all 0.3s ease;
+        }
+      }
+
+      input:checked + span {
+        background-color: #21ad0e;
+        border-color: #148605;
+
+        &::before {
+          transform: translateX(1rem) translateY(-50%);
+        }
+      }
+
+      &:hover span {
+        box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
+      }
+
+      input:checked:hover + span {
+        box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.3);
+      }
+
+      &:has(input:disabled) {
+        cursor: not-allowed;
+      }
+
+      input:disabled + span {
+        opacity: 0.5;
+        cursor: not-allowed;
+      }
+    }
+  }
+
   [data-slot="reload-error"] {
     display: flex;
     align-items: center;
@@ -54,7 +254,8 @@
       gap: var(--space-2);
       margin: 0;
       flex-shrink: 0;
+      padding: 0;
+      border: none;
     }
   }
 }
-

+ 131 - 35
packages/console/app/src/routes/workspace/[id]/billing/reload-section.tsx

@@ -1,17 +1,19 @@
-import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router"
-import { Show } from "solid-js"
+import { json, action, useParams, createAsync, useSubmission } from "@solidjs/router"
+import { createEffect, Show } from "solid-js"
+import { createStore } from "solid-js/store"
 import { withActor } from "~/context/auth.withActor"
 import { Billing } from "@opencode-ai/console-core/billing.js"
 import { Database, eq } from "@opencode-ai/console-core/drizzle/index.js"
 import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js"
 import styles from "./reload-section.module.css"
+import { queryBillingInfo } from "../../common"
 
 const reload = action(async (form: FormData) => {
   "use server"
   const workspaceID = form.get("workspaceID")?.toString()
   if (!workspaceID) return { error: "Workspace ID is required" }
   return json(await withActor(() => Billing.reload(), workspaceID), {
-    revalidate: getBillingInfo.key,
+    revalidate: queryBillingInfo.key,
   })
 }, "billing.reload")
 
@@ -20,12 +22,27 @@ const setReload = action(async (form: FormData) => {
   const workspaceID = form.get("workspaceID")?.toString()
   if (!workspaceID) return { error: "Workspace ID is required" }
   const reloadValue = form.get("reload")?.toString() === "true"
+  const amountStr = form.get("reloadAmount")?.toString()
+  const triggerStr = form.get("reloadTrigger")?.toString()
+
+  const reloadAmount = amountStr && amountStr.trim() !== "" ? parseInt(amountStr) : null
+  const reloadTrigger = triggerStr && triggerStr.trim() !== "" ? parseInt(triggerStr) : null
+
+  if (reloadValue) {
+    if (reloadAmount === null || reloadAmount < Billing.RELOAD_AMOUNT_MIN)
+      return { error: `Reload amount must be at least $${Billing.RELOAD_AMOUNT_MIN}` }
+    if (reloadTrigger === null || reloadTrigger < Billing.RELOAD_TRIGGER_MIN)
+      return { error: `Balance trigger must be at least $${Billing.RELOAD_TRIGGER_MIN}` }
+  }
+
   return json(
     await Database.use((tx) =>
       tx
         .update(BillingTable)
         .set({
           reload: reloadValue,
+          ...(reloadAmount !== null ? { reloadAmount } : {}),
+          ...(reloadTrigger !== null ? { reloadTrigger } : {}),
           ...(reloadValue
             ? {
                 reloadError: null,
@@ -35,22 +52,43 @@ const setReload = action(async (form: FormData) => {
         })
         .where(eq(BillingTable.workspaceID, workspaceID)),
     ),
-    { revalidate: getBillingInfo.key },
+    { revalidate: queryBillingInfo.key },
   )
 }, "billing.setReload")
 
-const getBillingInfo = query(async (workspaceID: string) => {
-  "use server"
-  return withActor(async () => {
-    return await Billing.get()
-  }, workspaceID)
-}, "billing.get")
-
 export function ReloadSection() {
   const params = useParams()
-  const balanceInfo = createAsync(() => getBillingInfo(params.id))
+  const billingInfo = createAsync(() => queryBillingInfo(params.id))
   const setReloadSubmission = useSubmission(setReload)
   const reloadSubmission = useSubmission(reload)
+  const [store, setStore] = createStore({
+    show: false,
+    reload: false,
+    reloadAmount: "",
+    reloadTrigger: "",
+  })
+
+  createEffect(() => {
+    if (!setReloadSubmission.pending && setReloadSubmission.result && !(setReloadSubmission.result as any).error) {
+      setStore("show", false)
+    }
+  })
+
+  function show() {
+    while (true) {
+      setReloadSubmission.clear()
+      if (!setReloadSubmission.result) break
+    }
+    const info = billingInfo()!
+    setStore("show", true)
+    setStore("reload", info.reload ? true : true)
+    setStore("reloadAmount", info.reloadAmount.toString())
+    setStore("reloadTrigger", info.reloadTrigger.toString())
+  }
+
+  function hide() {
+    setStore("show", false)
+  }
 
   return (
     <section class={styles.root}>
@@ -58,44 +96,102 @@ export function ReloadSection() {
         <h2>Auto Reload</h2>
         <div data-slot="title-row">
           <Show
-            when={balanceInfo()?.reload}
+            when={billingInfo()?.reload}
             fallback={
-              <p>Auto reload is disabled. Enable to automatically reload when balance is low.</p>
+              <p>
+                Auto reload is <b>disabled</b>. Enable to automatically reload when balance is low.
+              </p>
             }
           >
             <p>
-              We'll automatically reload <b>$20</b> (+$1.23 processing fee) when it reaches{" "}
-              <b>$5</b>.
+              Auto reload is <b>enabled</b>. We'll reload <b>${billingInfo()?.reloadAmount}</b> (+$1.23 processing fee)
+              when balance reaches <b>${billingInfo()?.reloadTrigger}</b>.
             </p>
           </Show>
-          <form action={setReload} method="post" data-slot="create-form">
-            <input type="hidden" name="workspaceID" value={params.id} />
-            <input type="hidden" name="reload" value={balanceInfo()?.reload ? "false" : "true"} />
-            <button data-color="primary" type="submit" disabled={setReloadSubmission.pending}>
-              <Show
-                when={balanceInfo()?.reload}
-                fallback={setReloadSubmission.pending ? "Enabling..." : "Enable"}
-              >
-                {setReloadSubmission.pending ? "Disabling..." : "Disable"}
-              </Show>
-            </button>
-          </form>
+          <button data-color="primary" type="button" onClick={() => show()}>
+            {billingInfo()?.reload ? "Edit" : "Enable"}
+          </button>
         </div>
       </div>
-      <div data-slot="section-content">
-        <Show when={balanceInfo()?.reload && balanceInfo()?.reloadError}>
+      <Show when={store.show}>
+        <form action={setReload} method="post" data-slot="create-form">
+          <div data-slot="form-field">
+            <label>
+              <span data-slot="field-label">Enable Auto Reload</span>
+              <div data-slot="toggle-container">
+                <label data-slot="model-toggle-label">
+                  <input
+                    type="checkbox"
+                    name="reload"
+                    value="true"
+                    checked={store.reload}
+                    onChange={(e) => setStore("reload", e.currentTarget.checked)}
+                  />
+                  <span></span>
+                </label>
+              </div>
+            </label>
+          </div>
+
+          <div data-slot="input-row">
+            <div data-slot="input-field">
+              <p>Reload $</p>
+              <input
+                data-component="input"
+                name="reloadAmount"
+                type="number"
+                min={billingInfo()?.reloadAmountMin.toString()}
+                step="1"
+                value={store.reloadAmount}
+                onInput={(e) => setStore("reloadAmount", e.currentTarget.value)}
+                placeholder={billingInfo()?.reloadAmount.toString()}
+                disabled={!store.reload}
+              />
+            </div>
+            <div data-slot="input-field">
+              <p>When balance reaches $</p>
+              <input
+                data-component="input"
+                name="reloadTrigger"
+                type="number"
+                min={billingInfo()?.reloadTriggerMin.toString()}
+                step="1"
+                value={store.reloadTrigger}
+                onInput={(e) => setStore("reloadTrigger", e.currentTarget.value)}
+                placeholder={billingInfo()?.reloadTrigger.toString()}
+                disabled={!store.reload}
+              />
+            </div>
+          </div>
+
+          <Show when={setReloadSubmission.result && (setReloadSubmission.result as any).error}>
+            {(err: any) => <div data-slot="form-error">{err()}</div>}
+          </Show>
+          <input type="hidden" name="workspaceID" value={params.id} />
+          <div data-slot="form-actions">
+            <button type="button" data-color="ghost" onClick={() => hide()}>
+              Cancel
+            </button>
+            <button type="submit" data-color="primary" disabled={setReloadSubmission.pending}>
+              {setReloadSubmission.pending ? "Saving..." : "Save"}
+            </button>
+          </div>
+        </form>
+      </Show>
+      <Show when={billingInfo()?.reload && billingInfo()?.reloadError}>
+        <div data-slot="section-content">
           <div data-slot="reload-error">
             <p>
               Reload failed at{" "}
-              {balanceInfo()?.timeReloadError!.toLocaleString("en-US", {
+              {billingInfo()?.timeReloadError!.toLocaleString("en-US", {
                 month: "short",
                 day: "numeric",
                 hour: "numeric",
                 minute: "2-digit",
                 second: "2-digit",
               })}
-              . Reason: {balanceInfo()?.reloadError?.replace(/\.$/, "")}. Please update your payment
-              method and try again.
+              . Reason: {billingInfo()?.reloadError?.replace(/\.$/, "")}. Please update your payment method and try
+              again.
             </p>
             <form action={reload} method="post" data-slot="create-form">
               <input type="hidden" name="workspaceID" value={params.id} />
@@ -104,8 +200,8 @@ export function ReloadSection() {
               </button>
             </form>
           </div>
-        </Show>
-      </div>
+        </div>
+      </Show>
     </section>
   )
 }

+ 22 - 18
packages/console/app/src/routes/workspace/[id]/index.tsx

@@ -1,22 +1,32 @@
+import { Show, createMemo } from "solid-js"
+import { createStore } from "solid-js/store"
+import { createAsync, useParams, useAction, useSubmission } from "@solidjs/router"
 import { NewUserSection } from "./new-user-section"
 import { UsageSection } from "./usage-section"
 import { ModelSection } from "./model-section"
 import { ProviderSection } from "./provider-section"
 import { IconLogo } from "~/component/icon"
-import { createAsync, useParams, useAction, useSubmission } from "@solidjs/router"
-import { querySessionInfo, queryBillingInfo, createCheckoutUrl } from "../common"
-import { Show, createMemo } from "solid-js"
+import { querySessionInfo, queryBillingInfo, createCheckoutUrl, formatBalance } from "../common"
 
 export default function () {
   const params = useParams()
   const userInfo = createAsync(() => querySessionInfo(params.id))
   const billingInfo = createAsync(() => queryBillingInfo(params.id))
-  const createCheckoutUrlAction = useAction(createCheckoutUrl)
-  const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
-
-  const balanceAmount = createMemo(() => {
-    return ((billingInfo()?.balance ?? 0) / 100000000).toFixed(2)
+  const checkoutAction = useAction(createCheckoutUrl)
+  const checkoutSubmission = useSubmission(createCheckoutUrl)
+  const [store, setStore] = createStore({
+    checkoutRedirecting: false,
   })
+  const balance = createMemo(() => formatBalance(billingInfo()?.balance ?? 0))
+
+  async function onClickCheckout() {
+    const baseUrl = window.location.href
+    const checkout = await checkoutAction(params.id, billingInfo()!.reloadAmount, baseUrl, baseUrl)
+    if (checkout && checkout.data) {
+      setStore("checkoutRedirecting", true)
+      window.location.href = checkout.data
+    }
+  }
 
   return (
     <div data-page="workspace-[id]">
@@ -38,21 +48,15 @@ export default function () {
                   <button
                     data-color="primary"
                     data-size="sm"
-                    disabled={createCheckoutUrlSubmission.pending}
-                    onClick={async () => {
-                      const baseUrl = window.location.href
-                      const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
-                      if (checkoutUrl) {
-                        window.location.href = checkoutUrl
-                      }
-                    }}
+                    disabled={checkoutSubmission.pending || store.checkoutRedirecting}
+                    onClick={onClickCheckout}
                   >
-                    {createCheckoutUrlSubmission.pending ? "Loading..." : "Enable billing"}
+                    {checkoutSubmission.pending || store.checkoutRedirecting ? "Loading..." : "Enable billing"}
                   </button>
                 }
               >
                 <span data-slot="balance">
-                  Current balance <b>${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()}</b>
+                  Current balance <b>${balance()}</b>
                 </span>
               </Show>
             </span>

+ 3 - 6
packages/console/app/src/routes/workspace/[id]/keys/key-section.module.css

@@ -171,7 +171,6 @@
     }
 
     @media (max-width: 40rem) {
-
       th,
       td {
         padding: var(--space-2) var(--space-3);
@@ -181,8 +180,7 @@
       th {
         &:nth-child(3)
 
-        /* Date */
-          {
+        /* Date */ {
           display: none;
         }
       }
@@ -190,11 +188,10 @@
       td {
         &:nth-child(3)
 
-        /* Date */
-          {
+        /* Date */ {
           display: none;
         }
       }
     }
   }
-}
+}

+ 1 - 1
packages/console/app/src/routes/workspace/[id]/members/role-dropdown.css

@@ -69,4 +69,4 @@
       }
     }
   }
-}
+}

+ 3 - 11
packages/console/app/src/routes/workspace/[id]/model-section.tsx

@@ -5,15 +5,7 @@ import { withActor } from "~/context/auth.withActor"
 import { ZenData } from "@opencode-ai/console-core/model.js"
 import styles from "./model-section.module.css"
 import { querySessionInfo } from "../common"
-import {
-  IconAlibaba,
-  IconAnthropic,
-  IconMoonshotAI,
-  IconOpenAI,
-  IconStealth,
-  IconXai,
-  IconZai,
-} from "~/component/icon"
+import { IconAlibaba, IconAnthropic, IconMoonshotAI, IconOpenAI, IconStealth, IconXai, IconZai } from "~/component/icon"
 
 const getModelLab = (modelId: string) => {
   if (modelId.startsWith("claude")) return "Anthropic"
@@ -31,6 +23,7 @@ const getModelsInfo = query(async (workspaceID: string) => {
     return {
       all: Object.entries(ZenData.list().models)
         .filter(([id, _model]) => !["claude-3-5-haiku", "minimax-m2"].includes(id))
+        .filter(([id, _model]) => !id.startsWith("an-"))
         .sort(([_idA, modelA], [_idB, modelB]) => modelA.name.localeCompare(modelB.name))
         .map(([id, model]) => ({ id, name: model.name })),
       disabled: await Model.listDisabled(),
@@ -75,8 +68,7 @@ export function ModelSection() {
       <div data-slot="section-title">
         <h2>Models</h2>
         <p>
-          Manage which models workspace members can access.{" "}
-          <a href="/docs/zen#pricing ">Learn more</a>.
+          Manage which models workspace members can access. <a href="/docs/zen#pricing ">Learn more</a>.
         </p>
       </div>
       <div data-slot="models-list">

+ 1 - 1
packages/console/app/src/routes/workspace/[id]/new-user-section.module.css

@@ -140,4 +140,4 @@
       }
     }
   }
-}
+}

+ 1 - 2
packages/console/app/src/routes/workspace/[id]/provider-section.module.css

@@ -128,7 +128,6 @@
     }
 
     @media (max-width: 40rem) {
-
       th,
       td {
         padding: var(--space-2) var(--space-3);
@@ -136,4 +135,4 @@
       }
     }
   }
-}
+}

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

@@ -22,7 +22,9 @@ const removeProvider = action(async (form: FormData) => {
   if (!provider) return { error: "Provider is required" }
   const workspaceID = form.get("workspaceID")?.toString()
   if (!workspaceID) return { error: "Workspace ID is required" }
-  return json(await withActor(() => Provider.remove({ provider }), workspaceID), { revalidate: listProviders.key })
+  return json(await withActor(() => Provider.remove({ provider }), workspaceID), {
+    revalidate: listProviders.key,
+  })
 }, "provider.remove")
 
 const saveProvider = action(async (form: FormData) => {

+ 3 - 3
packages/console/app/src/routes/workspace/[id]/settings/settings-section.module.css

@@ -31,7 +31,7 @@
       margin: 0;
     }
 
-    >button {
+    > button {
       align-self: flex-start;
     }
   }
@@ -80,7 +80,7 @@
       }
     }
 
-    >button[type="reset"] {
+    > button[type="reset"] {
       align-self: flex-start;
     }
 
@@ -91,4 +91,4 @@
       margin-top: calc(var(--space-1) * -1);
     }
   }
-}
+}

+ 30 - 8
packages/console/app/src/routes/workspace/common.tsx

@@ -1,6 +1,6 @@
 import { Resource } from "@opencode-ai/console-resource"
 import { Actor } from "@opencode-ai/console-core/actor.js"
-import { action, query } from "@solidjs/router"
+import { action, json, query } from "@solidjs/router"
 import { withActor } from "~/context/auth.withActor"
 import { Billing } from "@opencode-ai/console-core/billing.js"
 import { User } from "@opencode-ai/console-core/user.js"
@@ -34,6 +34,11 @@ export function formatDateUTC(date: Date) {
   return date.toLocaleDateString("en-US", options)
 }
 
+export function formatBalance(amount: number) {
+  const balance = ((amount ?? 0) / 100000000).toFixed(2)
+  return balance === "-0.00" ? "0.00" : balance
+}
+
 export async function getLastSeenWorkspaceID() {
   "use server"
   return withActor(async () => {
@@ -62,23 +67,40 @@ export const querySessionInfo = query(async (workspaceID: string) => {
   return withActor(() => {
     return {
       isAdmin: Actor.userRole() === "admin",
-      isBeta:
-        Resource.App.stage === "production"
-          ? workspaceID === "wrk_01K46JDFR0E75SG2Q8K172KF3Y"
-          : true,
+      isBeta: Resource.App.stage === "production" ? workspaceID === "wrk_01K46JDFR0E75SG2Q8K172KF3Y" : true,
     }
   }, workspaceID)
 }, "session.get")
 
 export const createCheckoutUrl = action(
-  async (workspaceID: string, successUrl: string, cancelUrl: string) => {
+  async (workspaceID: string, amount: number, successUrl: string, cancelUrl: string) => {
     "use server"
-    return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID)
+    return json(
+      await withActor(
+        () =>
+          Billing.generateCheckoutUrl({ amount, successUrl, cancelUrl })
+            .then((data) => ({ error: undefined, data }))
+            .catch((e) => ({
+              error: e.message as string,
+              data: undefined,
+            })),
+        workspaceID,
+      ),
+    )
   },
   "checkoutUrl",
 )
 
 export const queryBillingInfo = query(async (workspaceID: string) => {
   "use server"
-  return withActor(() => Billing.get(), workspaceID)
+  return withActor(async () => {
+    const billing = await Billing.get()
+    return {
+      ...billing,
+      reloadAmount: billing.reloadAmount ?? Billing.RELOAD_AMOUNT,
+      reloadAmountMin: Billing.RELOAD_AMOUNT_MIN,
+      reloadTrigger: billing.reloadTrigger ?? Billing.RELOAD_TRIGGER,
+      reloadTriggerMin: Billing.RELOAD_TRIGGER_MIN,
+    }
+  }, workspaceID)
 }, "billing.get")

+ 1 - 1
packages/console/app/src/routes/zen/index.css

@@ -277,7 +277,7 @@ body {
       margin-bottom: 24px;
     }
 
-    strong {
+    h1 {
       font-size: 28px;
       color: var(--color-text-strong);
       font-weight: 500;

+ 3 - 1
packages/console/app/src/routes/zen/index.tsx

@@ -3,6 +3,7 @@ import { createAsync, query, redirect } from "@solidjs/router"
 import { Title, Meta, Link } from "@solidjs/meta"
 import { HttpHeader } from "@solidjs/start"
 import zenLogoLight from "../../asset/zen-ornate-light.svg"
+import { config } from "~/config"
 import zenLogoDark from "../../asset/zen-ornate-dark.svg"
 import compareVideo from "../../asset/lander/opencode-comparison-min.mp4"
 import compareVideoPoster from "../../asset/lander/opencode-comparison-poster.png"
@@ -30,6 +31,7 @@ export default function Home() {
     <main data-page="zen">
       <HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />
       <Title>OpenCode Zen | A curated set of reliable optimized models for coding agents</Title>
+      <Link rel="canonical" href={`${config.baseUrl}/zen`} />
       <Link rel="icon" type="image/svg+xml" href="/favicon-zen.svg" />
       <Meta property="og:image" content="/social-share-zen.png" />
       <Meta name="twitter:image" content="/social-share-zen.png" />
@@ -42,7 +44,7 @@ export default function Home() {
             <div data-slot="hero-copy">
               <img data-slot="zen logo light" src={zenLogoLight} alt="zen logo light" />
               <img data-slot="zen logo dark" src={zenLogoDark} alt="zen logo dark" />
-              <strong>Reliable optimized models for coding agents</strong>
+              <h1>Reliable optimized models for coding agents</h1>
               <p>
                 Zen gives you access to a curated set of AI models that OpenCode has tested and benchmarked specifically
                 for coding agents. No need to worry about inconsistent performance and quality, use validated models

+ 1 - 0
packages/console/app/src/routes/zen/util/error.ts

@@ -3,3 +3,4 @@ export class CreditsError extends Error {}
 export class MonthlyLimitError extends Error {}
 export class UserLimitError extends Error {}
 export class ModelError extends Error {}
+export class RateLimitError extends Error {}

+ 53 - 80
packages/console/app/src/routes/zen/util/handler.ts

@@ -12,18 +12,14 @@ import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js"
 import { ModelTable } from "@opencode-ai/console-core/schema/model.sql.js"
 import { ProviderTable } from "@opencode-ai/console-core/schema/provider.sql.js"
 import { logger } from "./logger"
-import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError } from "./error"
-import {
-  createBodyConverter,
-  createStreamPartConverter,
-  createResponseConverter,
-} from "./provider/provider"
+import { AuthError, CreditsError, MonthlyLimitError, UserLimitError, ModelError, RateLimitError } from "./error"
+import { createBodyConverter, createStreamPartConverter, createResponseConverter } from "./provider/provider"
 import { anthropicHelper } from "./provider/anthropic"
 import { openaiHelper } from "./provider/openai"
 import { oaCompatHelper } from "./provider/openai-compatible"
+import { createRateLimiter } from "./rateLimiter"
 
 type ZenData = Awaited<ReturnType<typeof ZenData.list>>
-type Model = ZenData["models"][string]
 
 export async function handler(
   input: APIEvent,
@@ -32,6 +28,10 @@ export async function handler(
     parseApiKey: (headers: Headers) => string | undefined
   },
 ) {
+  type AuthInfo = Awaited<ReturnType<typeof authenticate>>
+  type ModelInfo = Awaited<ReturnType<typeof validateModel>>
+  type ProviderInfo = Awaited<ReturnType<typeof selectProvider>>
+
   const FREE_WORKSPACES = [
     "wrk_01K46JDFR0E75SG2Q8K172KF3Y", // frank
     "wrk_01K6W1A3VE0KMNVSCQT43BG2SX", // opencode bench
@@ -39,6 +39,7 @@ export async function handler(
 
   try {
     const body = await input.request.json()
+    const ip = input.request.headers.get("x-real-ip") ?? ""
     logger.metric({
       is_tream: !!body.stream,
       session: input.request.headers.get("x-opencode-session"),
@@ -46,13 +47,11 @@ export async function handler(
     })
     const zenData = ZenData.list()
     const modelInfo = validateModel(zenData, body.model)
-    const providerInfo = selectProvider(
-      zenData,
-      modelInfo,
-      input.request.headers.get("x-real-ip") ?? "",
-    )
+    const providerInfo = selectProvider(zenData, modelInfo, ip)
     const authInfo = await authenticate(modelInfo, providerInfo)
-    validateBilling(modelInfo, authInfo)
+    const rateLimiter = createRateLimiter(modelInfo.id, modelInfo.rateLimit, ip)
+    await rateLimiter?.check()
+    validateBilling(authInfo, modelInfo)
     validateModelSettings(authInfo)
     updateProviderKey(authInfo, providerInfo)
     logger.metric({ provider: providerInfo.id })
@@ -67,7 +66,7 @@ export async function handler(
       }),
     )
     logger.debug("REQUEST URL: " + reqUrl)
-    logger.debug("REQUEST: " + reqBody)
+    logger.debug("REQUEST: " + reqBody.substring(0, 300) + "...")
     const res = await fetch(reqUrl, {
       method: "POST",
       headers: (() => {
@@ -92,9 +91,6 @@ export async function handler(
       }
     }
     logger.debug("STATUS: " + res.status + " " + res.statusText)
-    if (res.status === 400 || res.status === 503) {
-      logger.debug("RESPONSE: " + (await res.text()))
-    }
 
     // Handle non-streaming response
     if (!body.stream) {
@@ -103,6 +99,7 @@ export async function handler(
       const body = JSON.stringify(responseConverter(json))
       logger.metric({ response_length: body.length })
       logger.debug("RESPONSE: " + body)
+      await rateLimiter?.track()
       await trackUsage(authInfo, modelInfo, providerInfo, json.usage)
       await reload(authInfo)
       return new Response(body, {
@@ -131,6 +128,7 @@ export async function handler(
                   response_length: responseLength,
                   "timestamp.last_byte": Date.now(),
                 })
+                await rateLimiter?.track()
                 const usage = usageParser.retrieve()
                 if (usage) {
                   await trackUsage(authInfo, modelInfo, providerInfo, usage)
@@ -205,6 +203,15 @@ export async function handler(
         { status: 401 },
       )
 
+    if (error instanceof RateLimitError)
+      return new Response(
+        JSON.stringify({
+          type: "error",
+          error: { type: error.constructor.name, message: error.message },
+        }),
+        { status: 429 },
+      )
+
     return new Response(
       JSON.stringify({
         type: "error",
@@ -229,12 +236,8 @@ export async function handler(
     return { id: modelId, ...modelData }
   }
 
-  function selectProvider(
-    zenData: ZenData,
-    model: Awaited<ReturnType<typeof validateModel>>,
-    ip: string,
-  ) {
-    const providers = model.providers
+  function selectProvider(zenData: ZenData, modelInfo: ModelInfo, ip: string) {
+    const providers = modelInfo.providers
       .filter((provider) => !provider.disabled)
       .flatMap((provider) => Array<typeof provider>(provider.weight ?? 1).fill(provider))
 
@@ -247,26 +250,22 @@ export async function handler(
       throw new ModelError(`Provider ${provider.id} not supported`)
     }
 
-    const format = zenData.providers[provider.id].format
-
     return {
       ...provider,
       ...zenData.providers[provider.id],
-      ...(format === "anthropic"
-        ? anthropicHelper
-        : format === "openai"
-          ? openaiHelper
-          : oaCompatHelper),
+      ...(() => {
+        const format = zenData.providers[provider.id].format
+        if (format === "anthropic") return anthropicHelper
+        if (format === "openai") return openaiHelper
+        return oaCompatHelper
+      })(),
     }
   }
 
-  async function authenticate(
-    model: Awaited<ReturnType<typeof validateModel>>,
-    providerInfo: Awaited<ReturnType<typeof selectProvider>>,
-  ) {
+  async function authenticate(modelInfo: ModelInfo, providerInfo: ProviderInfo) {
     const apiKey = opts.parseApiKey(input.request.headers)
     if (!apiKey) {
-      if (model.allowAnonymous) return
+      if (modelInfo.allowAnonymous) return
       throw new AuthError("Missing API key.")
     }
 
@@ -281,6 +280,7 @@ export async function handler(
             monthlyLimit: BillingTable.monthlyLimit,
             monthlyUsage: BillingTable.monthlyUsage,
             timeMonthlyUsageUpdated: BillingTable.timeMonthlyUsageUpdated,
+            reloadTrigger: BillingTable.reloadTrigger,
           },
           user: {
             id: UserTable.id,
@@ -296,20 +296,11 @@ export async function handler(
         .from(KeyTable)
         .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID))
         .innerJoin(BillingTable, eq(BillingTable.workspaceID, KeyTable.workspaceID))
-        .innerJoin(
-          UserTable,
-          and(eq(UserTable.workspaceID, KeyTable.workspaceID), eq(UserTable.id, KeyTable.userID)),
-        )
-        .leftJoin(
-          ModelTable,
-          and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, model.id)),
-        )
+        .innerJoin(UserTable, and(eq(UserTable.workspaceID, KeyTable.workspaceID), eq(UserTable.id, KeyTable.userID)))
+        .leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), eq(ModelTable.model, modelInfo.id)))
         .leftJoin(
           ProviderTable,
-          and(
-            eq(ProviderTable.workspaceID, KeyTable.workspaceID),
-            eq(ProviderTable.provider, providerInfo.id),
-          ),
+          and(eq(ProviderTable.workspaceID, KeyTable.workspaceID), eq(ProviderTable.provider, providerInfo.id)),
         )
         .where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
         .then((rows) => rows[0]),
@@ -332,11 +323,11 @@ export async function handler(
     }
   }
 
-  function validateBilling(model: Model, authInfo: Awaited<ReturnType<typeof authenticate>>) {
+  function validateBilling(authInfo: AuthInfo, modelInfo: ModelInfo) {
     if (!authInfo) return
     if (authInfo.provider?.credentials) return
     if (authInfo.isFree) return
-    if (model.allowAnonymous) return
+    if (modelInfo.allowAnonymous) return
 
     const billing = authInfo.billing
     if (!billing.paymentMethodID)
@@ -380,39 +371,24 @@ export async function handler(
     }
   }
 
-  function validateModelSettings(authInfo: Awaited<ReturnType<typeof authenticate>>) {
+  function validateModelSettings(authInfo: AuthInfo) {
     if (!authInfo) return
     if (authInfo.isDisabled) throw new ModelError("Model is disabled")
   }
 
-  function updateProviderKey(
-    authInfo: Awaited<ReturnType<typeof authenticate>>,
-    providerInfo: Awaited<ReturnType<typeof selectProvider>>,
-  ) {
+  function updateProviderKey(authInfo: AuthInfo, providerInfo: ProviderInfo) {
     if (!authInfo) return
     if (!authInfo.provider?.credentials) return
     providerInfo.apiKey = authInfo.provider.credentials
   }
 
-  async function trackUsage(
-    authInfo: Awaited<ReturnType<typeof authenticate>>,
-    modelInfo: ReturnType<typeof validateModel>,
-    providerInfo: Awaited<ReturnType<typeof selectProvider>>,
-    usage: any,
-  ) {
-    const {
-      inputTokens,
-      outputTokens,
-      reasoningTokens,
-      cacheReadTokens,
-      cacheWrite5mTokens,
-      cacheWrite1hTokens,
-    } = providerInfo.normalizeUsage(usage)
+  async function trackUsage(authInfo: AuthInfo, modelInfo: ModelInfo, providerInfo: ProviderInfo, usage: any) {
+    const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWrite5mTokens, cacheWrite1hTokens } =
+      providerInfo.normalizeUsage(usage)
 
     const modelCost =
       modelInfo.cost200K &&
-      inputTokens + (cacheReadTokens ?? 0) + (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0) >
-        200_000
+      inputTokens + (cacheReadTokens ?? 0) + (cacheWrite5mTokens ?? 0) + (cacheWrite1hTokens ?? 0) > 200_000
         ? modelInfo.cost200K
         : modelInfo.cost
 
@@ -463,8 +439,7 @@ export async function handler(
 
     if (!authInfo) return
 
-    const cost =
-      authInfo.isFree || authInfo.provider?.credentials ? 0 : centsToMicroCents(totalCostInCent)
+    const cost = authInfo.isFree || authInfo.provider?.credentials ? 0 : centsToMicroCents(totalCostInCent)
     await Database.transaction(async (tx) => {
       await tx.insert(UsageTable).values({
         workspaceID: authInfo.workspaceID,
@@ -504,9 +479,7 @@ export async function handler(
             `,
           timeMonthlyUsageUpdated: sql`now()`,
         })
-        .where(
-          and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id)),
-        )
+        .where(and(eq(UserTable.workspaceID, authInfo.workspaceID), eq(UserTable.id, authInfo.user.id)))
     })
 
     await Database.use((tx) =>
@@ -517,7 +490,7 @@ export async function handler(
     )
   }
 
-  async function reload(authInfo: Awaited<ReturnType<typeof authenticate>>) {
+  async function reload(authInfo: AuthInfo) {
     if (!authInfo) return
     if (authInfo.isFree) return
     if (authInfo.provider?.credentials) return
@@ -532,11 +505,11 @@ export async function handler(
           and(
             eq(BillingTable.workspaceID, authInfo.workspaceID),
             eq(BillingTable.reload, true),
-            lt(BillingTable.balance, centsToMicroCents(Billing.CHARGE_THRESHOLD)),
-            or(
-              isNull(BillingTable.timeReloadLockedTill),
-              lt(BillingTable.timeReloadLockedTill, sql`now()`),
+            lt(
+              BillingTable.balance,
+              centsToMicroCents((authInfo.billing.reloadTrigger ?? Billing.RELOAD_TRIGGER) * 100),
             ),
+            or(isNull(BillingTable.timeReloadLockedTill), lt(BillingTable.timeReloadLockedTill, sql`now()`)),
           ),
         ),
     )

+ 29 - 6
packages/console/app/src/routes/zen/util/provider/anthropic.ts

@@ -98,7 +98,10 @@ export function fromAnthropicRequest(body: any): CommonRequest {
       typeof (src as any).media_type === "string" &&
       typeof (src as any).data === "string"
     )
-      return { type: "image_url", image_url: { url: `data:${(src as any).media_type};base64,${(src as any).data}` } }
+      return {
+        type: "image_url",
+        image_url: { url: `data:${(src as any).media_type};base64,${(src as any).data}` },
+      }
     return undefined
   }
 
@@ -165,7 +168,11 @@ export function fromAnthropicRequest(body: any): CommonRequest {
         .filter((t: any) => t && typeof t === "object" && "input_schema" in t)
         .map((t: any) => ({
           type: "function",
-          function: { name: (t as any).name, description: (t as any).description, parameters: (t as any).input_schema },
+          function: {
+            name: (t as any).name,
+            description: (t as any).description,
+            parameters: (t as any).input_schema,
+          },
         }))
     : undefined
 
@@ -452,7 +459,12 @@ export function toAnthropicResponse(resp: CommonResponse) {
         } catch {
           input = (tc as any).function.arguments
         }
-        content.push({ type: "tool_use", id: (tc as any).id, name: (tc as any).function.name, input })
+        content.push({
+          type: "tool_use",
+          id: (tc as any).id,
+          name: (tc as any).function.name,
+          input,
+        })
       }
     }
   }
@@ -511,13 +523,22 @@ export function fromAnthropicChunk(chunk: string): CommonChunk | string {
   if (json.type === "content_block_start") {
     const cb = json.content_block
     if (cb?.type === "text") {
-      out.choices.push({ index: json.index ?? 0, delta: { role: "assistant", content: "" }, finish_reason: null })
+      out.choices.push({
+        index: json.index ?? 0,
+        delta: { role: "assistant", content: "" },
+        finish_reason: null,
+      })
     } else if (cb?.type === "tool_use") {
       out.choices.push({
         index: json.index ?? 0,
         delta: {
           tool_calls: [
-            { index: json.index ?? 0, id: cb.id, type: "function", function: { name: cb.name, arguments: "" } },
+            {
+              index: json.index ?? 0,
+              id: cb.id,
+              type: "function",
+              function: { name: cb.name, arguments: "" },
+            },
           ],
         },
         finish_reason: null,
@@ -532,7 +553,9 @@ export function fromAnthropicChunk(chunk: string): CommonChunk | string {
     } else if (d?.type === "input_json_delta") {
       out.choices.push({
         index: json.index ?? 0,
-        delta: { tool_calls: [{ index: json.index ?? 0, function: { arguments: d.partial_json } }] },
+        delta: {
+          tool_calls: [{ index: json.index ?? 0, function: { arguments: d.partial_json } }],
+        },
         finish_reason: null,
       })
     }

+ 3 - 1
packages/console/app/src/routes/zen/util/provider/openai-compatible.ts

@@ -532,7 +532,9 @@ export function toOaCompatibleChunk(chunk: CommonChunk): string {
       total_tokens: chunk.usage.total_tokens,
       ...(chunk.usage.prompt_tokens_details?.cached_tokens
         ? {
-            prompt_tokens_details: { cached_tokens: chunk.usage.prompt_tokens_details.cached_tokens },
+            prompt_tokens_details: {
+              cached_tokens: chunk.usage.prompt_tokens_details.cached_tokens,
+            },
           }
         : {}),
     }

+ 34 - 7
packages/console/app/src/routes/zen/util/provider/openai.ts

@@ -77,7 +77,10 @@ export function fromOpenaiRequest(body: any): CommonRequest {
       typeof (s as any).media_type === "string" &&
       typeof (s as any).data === "string"
     )
-      return { type: "image_url", image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` } }
+      return {
+        type: "image_url",
+        image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` },
+      }
     return undefined
   }
 
@@ -153,7 +156,11 @@ export function fromOpenaiRequest(body: any): CommonRequest {
     }
 
     if ((m as any).role === "tool") {
-      msgs.push({ role: "tool", tool_call_id: (m as any).tool_call_id, content: (m as any).content })
+      msgs.push({
+        role: "tool",
+        tool_call_id: (m as any).tool_call_id,
+        content: (m as any).content,
+      })
       continue
     }
   }
@@ -210,7 +217,10 @@ export function toOpenaiRequest(body: CommonRequest) {
       typeof (s as any).media_type === "string" &&
       typeof (s as any).data === "string"
     )
-      return { type: "input_image", image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` } }
+      return {
+        type: "input_image",
+        image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` },
+      }
     return undefined
   }
 
@@ -498,7 +508,9 @@ export function fromOpenaiChunk(chunk: string): CommonChunk | string {
     if (typeof name === "string" && name.length > 0) {
       out.choices.push({
         index: 0,
-        delta: { tool_calls: [{ index: 0, id, type: "function", function: { name, arguments: "" } }] },
+        delta: {
+          tool_calls: [{ index: 0, id, type: "function", function: { name, arguments: "" } }],
+        },
         finish_reason: null,
       })
     }
@@ -555,7 +567,12 @@ export function toOpenaiChunk(chunk: CommonChunk): string {
   const model = chunk.model
 
   if (d.content) {
-    const data = { id, type: "response.output_text.delta", delta: d.content, response: { id, model } }
+    const data = {
+      id,
+      type: "response.output_text.delta",
+      delta: d.content,
+      response: { id, model },
+    }
     return `event: response.output_text.delta\ndata: ${JSON.stringify(data)}`
   }
 
@@ -565,7 +582,13 @@ export function toOpenaiChunk(chunk: CommonChunk): string {
         const data = {
           type: "response.output_item.added",
           output_index: 0,
-          item: { id: tc.id, type: "function_call", name: tc.function.name, call_id: tc.id, arguments: "" },
+          item: {
+            id: tc.id,
+            type: "function_call",
+            name: tc.function.name,
+            call_id: tc.id,
+            arguments: "",
+          },
         }
         return `event: response.output_item.added\ndata: ${JSON.stringify(data)}`
       }
@@ -593,7 +616,11 @@ export function toOpenaiChunk(chunk: CommonChunk): string {
         }
       : undefined
 
-    const data: any = { id, type: "response.completed", response: { id, model, ...(usage ? { usage } : {}) } }
+    const data: any = {
+      id,
+      type: "response.completed",
+      response: { id, model, ...(usage ? { usage } : {}) },
+    }
     return `event: response.completed\ndata: ${JSON.stringify(data)}`
   }
 

+ 35 - 0
packages/console/app/src/routes/zen/util/rateLimiter.ts

@@ -0,0 +1,35 @@
+import { Resource } from "@opencode-ai/console-resource"
+import { RateLimitError } from "./error"
+import { logger } from "./logger"
+
+export function createRateLimiter(model: string, limit: number | undefined, ip: string) {
+  if (!limit) return
+
+  const now = Date.now()
+  const currKey = `usage:${ip}:${model}:${buildYYYYMMDDHH(now)}`
+  const prevKey = `usage:${ip}:${model}:${buildYYYYMMDDHH(now - 3_600_000)}`
+  let currRate: number
+  let prevRate: number
+
+  return {
+    track: async () => {
+      await Resource.GatewayKv.put(currKey, currRate + 1, { expirationTtl: 3600 })
+    },
+    check: async () => {
+      const values = await Resource.GatewayKv.get([currKey, prevKey])
+      const prevValue = values?.get(prevKey)
+      const currValue = values?.get(currKey)
+      prevRate = prevValue ? parseInt(prevValue) : 0
+      currRate = currValue ? parseInt(currValue) : 0
+      logger.debug(`rate limit ${model} prev/curr: ${prevRate}/${currRate}`)
+      if (prevRate + currRate >= limit) throw new RateLimitError(`Rate limit exceeded. Please try again later.`)
+    },
+  }
+}
+
+function buildYYYYMMDDHH(timestamp: number) {
+  return new Date(timestamp)
+    .toISOString()
+    .replace(/[^0-9]/g, "")
+    .substring(0, 10)
+}

+ 1 - 4
packages/console/app/src/routes/zen/v1/models.ts

@@ -50,10 +50,7 @@ export async function GET(input: APIEvent) {
         })
         .from(KeyTable)
         .innerJoin(WorkspaceTable, eq(WorkspaceTable.id, KeyTable.workspaceID))
-        .leftJoin(
-          ModelTable,
-          and(eq(ModelTable.workspaceID, KeyTable.workspaceID), isNull(ModelTable.timeDeleted)),
-        )
+        .leftJoin(ModelTable, and(eq(ModelTable.workspaceID, KeyTable.workspaceID), isNull(ModelTable.timeDeleted)))
         .where(and(eq(KeyTable.key, apiKey), isNull(KeyTable.timeDeleted)))
         .then((rows) => rows.map((row) => row.model)),
     )

+ 1 - 1
packages/console/app/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../../../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 13 - 44
packages/console/core/migrations/meta/0018_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -486,17 +473,12 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         },
         "name": {
           "name": "name",
-          "columns": [
-            "workspace_id",
-            "name"
-          ],
+          "columns": ["workspace_id", "name"],
           "isUnique": true
         }
       },
@@ -504,10 +486,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -599,10 +578,7 @@
       "indexes": {
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         }
       },
@@ -610,10 +586,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -670,9 +643,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -680,9 +651,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -699,4 +668,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 13 - 44
packages/console/core/migrations/meta/0019_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -486,17 +473,12 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         },
         "name": {
           "name": "name",
-          "columns": [
-            "workspace_id",
-            "name"
-          ],
+          "columns": ["workspace_id", "name"],
           "isUnique": true
         }
       },
@@ -504,10 +486,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -599,10 +578,7 @@
       "indexes": {
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         }
       },
@@ -610,10 +586,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -670,9 +643,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -680,9 +651,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -699,4 +668,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 13 - 44
packages/console/core/migrations/meta/0020_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -486,17 +473,12 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         },
         "name": {
           "name": "name",
-          "columns": [
-            "workspace_id",
-            "name"
-          ],
+          "columns": ["workspace_id", "name"],
           "isUnique": true
         }
       },
@@ -504,10 +486,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -592,10 +571,7 @@
       "indexes": {
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         }
       },
@@ -603,10 +579,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -663,9 +636,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -673,9 +644,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -692,4 +661,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 13 - 44
packages/console/core/migrations/meta/0021_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -486,17 +473,12 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         },
         "name": {
           "name": "name",
-          "columns": [
-            "workspace_id",
-            "name"
-          ],
+          "columns": ["workspace_id", "name"],
           "isUnique": true
         }
       },
@@ -504,10 +486,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -599,10 +578,7 @@
       "indexes": {
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         }
       },
@@ -610,10 +586,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -670,9 +643,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -680,9 +651,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -699,4 +668,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 14 - 48
packages/console/core/migrations/meta/0022_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -486,17 +473,12 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         },
         "name": {
           "name": "name",
-          "columns": [
-            "workspace_id",
-            "name"
-          ],
+          "columns": ["workspace_id", "name"],
           "isUnique": true
         }
       },
@@ -504,10 +486,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -613,18 +592,12 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         }
       },
@@ -632,10 +605,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -692,9 +662,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -702,9 +670,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -721,4 +687,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 16 - 54
packages/console/core/migrations/meta/0023_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -486,17 +473,12 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         },
         "name": {
           "name": "name",
-          "columns": [
-            "workspace_id",
-            "name"
-          ],
+          "columns": ["workspace_id", "name"],
           "isUnique": true
         }
       },
@@ -504,10 +486,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -613,32 +592,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -646,10 +615,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -706,9 +672,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -716,9 +680,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -735,4 +697,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 16 - 54
packages/console/core/migrations/meta/0024_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -486,17 +473,12 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         },
         "name": {
           "name": "name",
-          "columns": [
-            "workspace_id",
-            "name"
-          ],
+          "columns": ["workspace_id", "name"],
           "isUnique": true
         }
       },
@@ -504,10 +486,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -599,32 +578,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -632,10 +601,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -692,9 +658,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -702,9 +666,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -721,4 +683,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 16 - 54
packages/console/core/migrations/meta/0025_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -493,17 +480,12 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         },
         "name": {
           "name": "name",
-          "columns": [
-            "workspace_id",
-            "name"
-          ],
+          "columns": ["workspace_id", "name"],
           "isUnique": true
         }
       },
@@ -511,10 +493,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -606,32 +585,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -639,10 +608,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -699,9 +665,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -709,9 +673,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -728,4 +690,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 15 - 50
packages/console/core/migrations/meta/0026_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -493,9 +480,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -503,10 +488,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -598,32 +580,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -631,10 +603,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -691,9 +660,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -701,9 +668,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -720,4 +685,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 15 - 50
packages/console/core/migrations/meta/0027_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -479,9 +466,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -489,10 +474,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -584,32 +566,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -617,10 +589,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -677,9 +646,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -687,9 +654,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -706,4 +671,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 15 - 50
packages/console/core/migrations/meta/0028_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -479,9 +466,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -489,10 +474,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -584,32 +566,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -617,10 +589,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -677,9 +646,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -687,9 +654,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -706,4 +671,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 15 - 50
packages/console/core/migrations/meta/0029_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -479,9 +466,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -489,10 +474,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -605,32 +587,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -638,10 +610,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -698,9 +667,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -708,9 +675,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -727,4 +692,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 17 - 58
packages/console/core/migrations/meta/0030_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -479,9 +466,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -489,10 +474,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -549,10 +531,7 @@
       "indexes": {
         "model_workspace_model": {
           "name": "model_workspace_model",
-          "columns": [
-            "workspace_id",
-            "model"
-          ],
+          "columns": ["workspace_id", "model"],
           "isUnique": true
         }
       },
@@ -560,10 +539,7 @@
       "compositePrimaryKeys": {
         "model_workspace_id_id_pk": {
           "name": "model_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -676,32 +652,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -709,10 +675,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -769,9 +732,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -779,9 +740,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -798,4 +757,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 19 - 66
packages/console/core/migrations/meta/0031_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -398,10 +388,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -479,9 +466,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -489,10 +474,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -549,10 +531,7 @@
       "indexes": {
         "model_workspace_model": {
           "name": "model_workspace_model",
-          "columns": [
-            "workspace_id",
-            "model"
-          ],
+          "columns": ["workspace_id", "model"],
           "isUnique": true
         }
       },
@@ -560,10 +539,7 @@
       "compositePrimaryKeys": {
         "model_workspace_id_id_pk": {
           "name": "model_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -627,10 +603,7 @@
       "indexes": {
         "workspace_provider": {
           "name": "workspace_provider",
-          "columns": [
-            "workspace_id",
-            "provider"
-          ],
+          "columns": ["workspace_id", "provider"],
           "isUnique": true
         }
       },
@@ -638,10 +611,7 @@
       "compositePrimaryKeys": {
         "provider_workspace_id_id_pk": {
           "name": "provider_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -754,32 +724,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -787,10 +747,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -847,9 +804,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -857,9 +812,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -876,4 +829,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 19 - 66
packages/console/core/migrations/meta/0032_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -180,9 +178,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -190,10 +186,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -280,10 +273,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -405,10 +395,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -486,9 +473,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -496,10 +481,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -556,10 +538,7 @@
       "indexes": {
         "model_workspace_model": {
           "name": "model_workspace_model",
-          "columns": [
-            "workspace_id",
-            "model"
-          ],
+          "columns": ["workspace_id", "model"],
           "isUnique": true
         }
       },
@@ -567,10 +546,7 @@
       "compositePrimaryKeys": {
         "model_workspace_id_id_pk": {
           "name": "model_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -634,10 +610,7 @@
       "indexes": {
         "workspace_provider": {
           "name": "workspace_provider",
-          "columns": [
-            "workspace_id",
-            "provider"
-          ],
+          "columns": ["workspace_id", "provider"],
           "isUnique": true
         }
       },
@@ -645,10 +618,7 @@
       "compositePrimaryKeys": {
         "provider_workspace_id_id_pk": {
           "name": "provider_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -761,32 +731,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -794,10 +754,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -854,9 +811,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -864,9 +819,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -883,4 +836,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 19 - 66
packages/console/core/migrations/meta/0033_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -187,9 +185,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -197,10 +193,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -287,10 +280,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -412,10 +402,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -493,9 +480,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -503,10 +488,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -563,10 +545,7 @@
       "indexes": {
         "model_workspace_model": {
           "name": "model_workspace_model",
-          "columns": [
-            "workspace_id",
-            "model"
-          ],
+          "columns": ["workspace_id", "model"],
           "isUnique": true
         }
       },
@@ -574,10 +553,7 @@
       "compositePrimaryKeys": {
         "model_workspace_id_id_pk": {
           "name": "model_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -641,10 +617,7 @@
       "indexes": {
         "workspace_provider": {
           "name": "workspace_provider",
-          "columns": [
-            "workspace_id",
-            "provider"
-          ],
+          "columns": ["workspace_id", "provider"],
           "isUnique": true
         }
       },
@@ -652,10 +625,7 @@
       "compositePrimaryKeys": {
         "provider_workspace_id_id_pk": {
           "name": "provider_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -768,32 +738,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -801,10 +761,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -861,9 +818,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -871,9 +826,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -890,4 +843,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 20 - 70
packages/console/core/migrations/meta/0034_snapshot.json

@@ -48,9 +48,7 @@
       "indexes": {
         "email": {
           "name": "email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": true
         }
       },
@@ -117,10 +115,7 @@
       "indexes": {
         "provider": {
           "name": "provider",
-          "columns": [
-            "provider",
-            "subject"
-          ],
+          "columns": ["provider", "subject"],
           "isUnique": true
         }
       },
@@ -257,9 +252,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -267,10 +260,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -357,10 +347,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -482,10 +469,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -563,9 +547,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -573,10 +555,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -633,10 +612,7 @@
       "indexes": {
         "model_workspace_model": {
           "name": "model_workspace_model",
-          "columns": [
-            "workspace_id",
-            "model"
-          ],
+          "columns": ["workspace_id", "model"],
           "isUnique": true
         }
       },
@@ -644,10 +620,7 @@
       "compositePrimaryKeys": {
         "model_workspace_id_id_pk": {
           "name": "model_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -711,10 +684,7 @@
       "indexes": {
         "workspace_provider": {
           "name": "workspace_provider",
-          "columns": [
-            "workspace_id",
-            "provider"
-          ],
+          "columns": ["workspace_id", "provider"],
           "isUnique": true
         }
       },
@@ -722,10 +692,7 @@
       "compositePrimaryKeys": {
         "provider_workspace_id_id_pk": {
           "name": "provider_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -838,32 +805,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -871,10 +828,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -931,9 +885,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -941,9 +893,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -960,4 +910,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 20 - 70
packages/console/core/migrations/meta/0035_snapshot.json

@@ -102,17 +102,12 @@
       "indexes": {
         "provider": {
           "name": "provider",
-          "columns": [
-            "provider",
-            "subject"
-          ],
+          "columns": ["provider", "subject"],
           "isUnique": true
         },
         "account_id": {
           "name": "account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         }
       },
@@ -249,9 +244,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -259,10 +252,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -349,10 +339,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -474,10 +461,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -555,9 +539,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -565,10 +547,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -625,10 +604,7 @@
       "indexes": {
         "model_workspace_model": {
           "name": "model_workspace_model",
-          "columns": [
-            "workspace_id",
-            "model"
-          ],
+          "columns": ["workspace_id", "model"],
           "isUnique": true
         }
       },
@@ -636,10 +612,7 @@
       "compositePrimaryKeys": {
         "model_workspace_id_id_pk": {
           "name": "model_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -703,10 +676,7 @@
       "indexes": {
         "workspace_provider": {
           "name": "workspace_provider",
-          "columns": [
-            "workspace_id",
-            "provider"
-          ],
+          "columns": ["workspace_id", "provider"],
           "isUnique": true
         }
       },
@@ -714,10 +684,7 @@
       "compositePrimaryKeys": {
         "provider_workspace_id_id_pk": {
           "name": "provider_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -830,32 +797,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -863,10 +820,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -923,9 +877,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -933,9 +885,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -952,4 +902,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 22 - 76
packages/console/core/migrations/meta/0036_snapshot.json

@@ -43,9 +43,7 @@
       "compositePrimaryKeys": {
         "account_id_pk": {
           "name": "account_id_pk",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -109,17 +107,12 @@
       "indexes": {
         "provider": {
           "name": "provider",
-          "columns": [
-            "provider",
-            "subject"
-          ],
+          "columns": ["provider", "subject"],
           "isUnique": true
         },
         "account_id": {
           "name": "account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         }
       },
@@ -127,9 +120,7 @@
       "compositePrimaryKeys": {
         "auth_id_pk": {
           "name": "auth_id_pk",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -263,9 +254,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -273,10 +262,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -363,10 +349,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -488,10 +471,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -569,9 +549,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -579,10 +557,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -639,10 +614,7 @@
       "indexes": {
         "model_workspace_model": {
           "name": "model_workspace_model",
-          "columns": [
-            "workspace_id",
-            "model"
-          ],
+          "columns": ["workspace_id", "model"],
           "isUnique": true
         }
       },
@@ -650,10 +622,7 @@
       "compositePrimaryKeys": {
         "model_workspace_id_id_pk": {
           "name": "model_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -717,10 +686,7 @@
       "indexes": {
         "workspace_provider": {
           "name": "workspace_provider",
-          "columns": [
-            "workspace_id",
-            "provider"
-          ],
+          "columns": ["workspace_id", "provider"],
           "isUnique": true
         }
       },
@@ -728,10 +694,7 @@
       "compositePrimaryKeys": {
         "provider_workspace_id_id_pk": {
           "name": "provider_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -844,32 +807,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -877,10 +830,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -937,9 +887,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -947,9 +895,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -966,4 +912,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 22 - 76
packages/console/core/migrations/meta/0037_snapshot.json

@@ -43,9 +43,7 @@
       "compositePrimaryKeys": {
         "account_id_pk": {
           "name": "account_id_pk",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -109,17 +107,12 @@
       "indexes": {
         "provider": {
           "name": "provider",
-          "columns": [
-            "provider",
-            "subject"
-          ],
+          "columns": ["provider", "subject"],
           "isUnique": true
         },
         "account_id": {
           "name": "account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         }
       },
@@ -127,9 +120,7 @@
       "compositePrimaryKeys": {
         "auth_id_pk": {
           "name": "auth_id_pk",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -277,9 +268,7 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         }
       },
@@ -287,10 +276,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -377,10 +363,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -502,10 +485,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -583,9 +563,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -593,10 +571,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -653,10 +628,7 @@
       "indexes": {
         "model_workspace_model": {
           "name": "model_workspace_model",
-          "columns": [
-            "workspace_id",
-            "model"
-          ],
+          "columns": ["workspace_id", "model"],
           "isUnique": true
         }
       },
@@ -664,10 +636,7 @@
       "compositePrimaryKeys": {
         "model_workspace_id_id_pk": {
           "name": "model_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -731,10 +700,7 @@
       "indexes": {
         "workspace_provider": {
           "name": "workspace_provider",
-          "columns": [
-            "workspace_id",
-            "provider"
-          ],
+          "columns": ["workspace_id", "provider"],
           "isUnique": true
         }
       },
@@ -742,10 +708,7 @@
       "compositePrimaryKeys": {
         "provider_workspace_id_id_pk": {
           "name": "provider_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -858,32 +821,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -891,10 +844,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -951,9 +901,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -961,9 +909,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -980,4 +926,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 1 - 1
packages/console/core/migrations/meta/_journal.json

@@ -269,4 +269,4 @@
       "breakpoints": true
     }
   ]
-}
+}

+ 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.0.23",
+  "version": "1.0.55",
   "private": true,
   "type": "module",
   "dependencies": {

+ 3 - 10
packages/console/core/script/lookup-user.ts

@@ -8,22 +8,15 @@ if (!email) {
   process.exit(1)
 }
 
-const authData = await printTable("Auth", (tx) =>
-  tx.select().from(AuthTable).where(eq(AuthTable.subject, email)),
-)
+const authData = await printTable("Auth", (tx) => tx.select().from(AuthTable).where(eq(AuthTable.subject, email)))
 if (authData.length === 0) {
   console.error("User not found")
   process.exit(1)
 }
 
-await printTable("Auth", (tx) =>
-  tx.select().from(AuthTable).where(eq(AuthTable.accountID, authData[0].accountID)),
-)
+await printTable("Auth", (tx) => tx.select().from(AuthTable).where(eq(AuthTable.accountID, authData[0].accountID)))
 
-function printTable(
-  title: string,
-  callback: (tx: Database.TxOrDb) => Promise<any[]>,
-): Promise<any[]> {
+function printTable(title: string, callback: (tx: Database.TxOrDb) => Promise<any[]>): Promise<any[]> {
   return Database.use(async (tx) => {
     const data = await callback(tx)
     console.log(`== ${title} ==`)

+ 1 - 9
packages/console/core/script/reset-db.ts

@@ -8,14 +8,6 @@ import { KeyTable } from "../src/schema/key.sql.js"
 
 if (Resource.App.stage !== "frank") throw new Error("This script is only for frank")
 
-for (const table of [
-  AccountTable,
-  BillingTable,
-  KeyTable,
-  PaymentTable,
-  UsageTable,
-  UserTable,
-  WorkspaceTable,
-]) {
+for (const table of [AccountTable, BillingTable, KeyTable, PaymentTable, UsageTable, UserTable, WorkspaceTable]) {
   await Database.use((tx) => tx.delete(table))
 }

+ 0 - 1
packages/console/core/script/update-models.ts

@@ -7,7 +7,6 @@ import { ZenData } from "../src/model"
 
 const root = path.resolve(process.cwd(), "..", "..", "..")
 const models = await $`bun sst secret list`.cwd(root).text()
-console.log("models", models)
 
 // read the line starting with "ZEN_MODELS"
 const oldValue1 = models

+ 38 - 23
packages/console/core/src/billing.ts

@@ -10,13 +10,12 @@ import { centsToMicroCents } from "./util/price"
 import { User } from "./user"
 
 export namespace Billing {
-  export const CHARGE_NAME = "opencode credits"
-  export const CHARGE_FEE_NAME = "processing fee"
-  export const CHARGE_AMOUNT = 2000 // $20
-  export const CHARGE_AMOUNT_DOLLAR = 20
-  export const CHARGE_FEE = 123 // Stripe fee 4.4% + $0.30
-  export const CHARGE_THRESHOLD_DOLLAR = 5
-  export const CHARGE_THRESHOLD = 500 // $5
+  export const ITEM_CREDIT_NAME = "opencode credits"
+  export const ITEM_FEE_NAME = "processing fee"
+  export const RELOAD_AMOUNT = 20
+  export const RELOAD_AMOUNT_MIN = 10
+  export const RELOAD_TRIGGER = 5
+  export const RELOAD_TRIGGER_MIN = 5
   export const stripe = () =>
     new Stripe(Resource.STRIPE_SECRET_KEY.value, {
       apiVersion: "2025-03-31.basil",
@@ -33,6 +32,8 @@ export namespace Billing {
           paymentMethodLast4: BillingTable.paymentMethodLast4,
           balance: BillingTable.balance,
           reload: BillingTable.reload,
+          reloadAmount: BillingTable.reloadAmount,
+          reloadTrigger: BillingTable.reloadTrigger,
           monthlyLimit: BillingTable.monthlyLimit,
           monthlyUsage: BillingTable.monthlyUsage,
           timeMonthlyUsageUpdated: BillingTable.timeMonthlyUsageUpdated,
@@ -67,17 +68,28 @@ export namespace Billing {
     )
   }
 
+  export const calculateFeeInCents = (x: number) => {
+    // math: x = total - (total * 0.044 + 0.30)
+    // math: x = total * (1-0.044) - 0.30
+    // math: (x + 0.30) / 0.956 = total
+    return Math.round(((x + 30) / 0.956) * 0.044 + 30)
+  }
+
   export const reload = async () => {
-    const { customerID, paymentMethodID } = await Database.use((tx) =>
+    const billing = await Database.use((tx) =>
       tx
         .select({
           customerID: BillingTable.customerID,
           paymentMethodID: BillingTable.paymentMethodID,
+          reloadAmount: BillingTable.reloadAmount,
         })
         .from(BillingTable)
         .where(eq(BillingTable.workspaceID, Actor.workspace()))
         .then((rows) => rows[0]),
     )
+    const customerID = billing.customerID
+    const paymentMethodID = billing.paymentMethodID
+    const amountInCents = (billing.reloadAmount ?? Billing.RELOAD_AMOUNT) * 100
     const paymentID = Identifier.create("payment")
     let invoice
     try {
@@ -89,18 +101,18 @@ export namespace Billing {
         currency: "usd",
       })
       await Billing.stripe().invoiceItems.create({
-        amount: Billing.CHARGE_AMOUNT,
+        amount: amountInCents,
         currency: "usd",
         customer: customerID!,
-        description: CHARGE_NAME,
         invoice: draft.id!,
+        description: ITEM_CREDIT_NAME,
       })
       await Billing.stripe().invoiceItems.create({
-        amount: Billing.CHARGE_FEE,
+        amount: calculateFeeInCents(amountInCents),
         currency: "usd",
         customer: customerID!,
-        description: CHARGE_FEE_NAME,
         invoice: draft.id!,
+        description: ITEM_FEE_NAME,
       })
       await Billing.stripe().invoices.finalizeInvoice(draft.id!)
       invoice = await Billing.stripe().invoices.pay(draft.id!, {
@@ -128,7 +140,7 @@ export namespace Billing {
       await tx
         .update(BillingTable)
         .set({
-          balance: sql`${BillingTable.balance} + ${centsToMicroCents(CHARGE_AMOUNT)}`,
+          balance: sql`${BillingTable.balance} + ${centsToMicroCents(amountInCents)}`,
           reloadError: null,
           timeReloadError: null,
         })
@@ -136,7 +148,7 @@ export namespace Billing {
       await tx.insert(PaymentTable).values({
         workspaceID: Actor.workspace(),
         id: paymentID,
-        amount: centsToMicroCents(CHARGE_AMOUNT),
+        amount: centsToMicroCents(amountInCents),
         invoiceID: invoice.id!,
         paymentID: invoice.payments?.data[0].payment.payment_intent as string,
         customerID,
@@ -159,13 +171,19 @@ export namespace Billing {
     z.object({
       successUrl: z.string(),
       cancelUrl: z.string(),
+      amount: z.number().optional(),
     }),
     async (input) => {
       const user = Actor.assert("user")
-      const { successUrl, cancelUrl } = input
+      const { successUrl, cancelUrl, amount } = input
+
+      if (amount !== undefined && amount < Billing.RELOAD_AMOUNT_MIN) {
+        throw new Error(`Amount must be at least $${Billing.RELOAD_AMOUNT_MIN}`)
+      }
 
       const email = await User.getAuthEmail(user.properties.userID)
       const customer = await Billing.get()
+      const amountInCents = (amount ?? customer.reloadAmount ?? Billing.RELOAD_AMOUNT) * 100
       const session = await Billing.stripe().checkout.sessions.create({
         mode: "payment",
         billing_address_collection: "required",
@@ -173,20 +191,16 @@ export namespace Billing {
           {
             price_data: {
               currency: "usd",
-              product_data: {
-                name: CHARGE_NAME,
-              },
-              unit_amount: CHARGE_AMOUNT,
+              product_data: { name: ITEM_CREDIT_NAME },
+              unit_amount: amountInCents,
             },
             quantity: 1,
           },
           {
             price_data: {
               currency: "usd",
-              product_data: {
-                name: CHARGE_FEE_NAME,
-              },
-              unit_amount: CHARGE_FEE,
+              product_data: { name: ITEM_FEE_NAME },
+              unit_amount: calculateFeeInCents(amountInCents),
             },
             quantity: 1,
           },
@@ -218,6 +232,7 @@ export namespace Billing {
         },
         metadata: {
           workspaceID: Actor.workspace(),
+          amount: amountInCents.toString(),
         },
         success_url: successUrl,
         cancel_url: cancelUrl,

+ 2 - 3
packages/console/core/src/model.ts

@@ -24,6 +24,7 @@ export namespace ZenData {
     cost: ModelCostSchema,
     cost200K: ModelCostSchema.optional(),
     allowAnonymous: z.boolean().optional(),
+    rateLimit: z.number().optional(),
     providers: z.array(
       z.object({
         id: z.string(),
@@ -60,9 +61,7 @@ export namespace Model {
   export const enable = fn(z.object({ model: z.string() }), ({ model }) => {
     Actor.assertAdmin()
     return Database.use((db) =>
-      db
-        .delete(ModelTable)
-        .where(and(eq(ModelTable.workspaceID, Actor.workspace()), eq(ModelTable.model, model))),
+      db.delete(ModelTable).where(and(eq(ModelTable.workspaceID, Actor.workspace()), eq(ModelTable.model, model))),
     )
   })
 

+ 96 - 87
packages/console/core/sst-env.d.ts

@@ -6,99 +6,108 @@
 import "sst"
 declare module "sst" {
   export interface Resource {
-    "ADMIN_SECRET": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "AUTH_API_URL": {
-      "type": "sst.sst.Linkable"
-      "value": string
-    }
-    "AWS_SES_ACCESS_KEY_ID": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "AWS_SES_SECRET_ACCESS_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "Console": {
-      "type": "sst.cloudflare.SolidStart"
-      "url": string
-    }
-    "Database": {
-      "database": string
-      "host": string
-      "password": string
-      "port": number
-      "type": "sst.sst.Linkable"
-      "username": string
-    }
-    "Desktop": {
-      "type": "sst.cloudflare.StaticSite"
-      "url": string
-    }
-    "EMAILOCTOPUS_API_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GITHUB_APP_ID": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GITHUB_APP_PRIVATE_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GITHUB_CLIENT_ID_CONSOLE": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GITHUB_CLIENT_SECRET_CONSOLE": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GOOGLE_CLIENT_ID": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "HONEYCOMB_API_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "STRIPE_SECRET_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "STRIPE_WEBHOOK_SECRET": {
-      "type": "sst.sst.Linkable"
-      "value": string
-    }
-    "Web": {
-      "type": "sst.cloudflare.Astro"
-      "url": string
-    }
-    "ZEN_MODELS1": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "ZEN_MODELS2": {
-      "type": "sst.sst.Secret"
-      "value": string
+    ADMIN_SECRET: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    AUTH_API_URL: {
+      type: "sst.sst.Linkable"
+      value: string
+    }
+    AWS_SES_ACCESS_KEY_ID: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    AWS_SES_SECRET_ACCESS_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    CLOUDFLARE_API_TOKEN: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    Console: {
+      type: "sst.cloudflare.SolidStart"
+      url: string
+    }
+    Database: {
+      database: string
+      host: string
+      password: string
+      port: number
+      type: "sst.sst.Linkable"
+      username: string
+    }
+    Desktop: {
+      type: "sst.cloudflare.StaticSite"
+      url: string
+    }
+    EMAILOCTOPUS_API_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GITHUB_APP_ID: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GITHUB_APP_PRIVATE_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GITHUB_CLIENT_ID_CONSOLE: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GITHUB_CLIENT_SECRET_CONSOLE: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GOOGLE_CLIENT_ID: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    HONEYCOMB_API_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    STRIPE_SECRET_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    STRIPE_WEBHOOK_SECRET: {
+      type: "sst.sst.Linkable"
+      value: string
+    }
+    Web: {
+      type: "sst.cloudflare.Astro"
+      url: string
+    }
+    ZEN_MODELS1: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    ZEN_MODELS2: {
+      type: "sst.sst.Secret"
+      value: string
     }
   }
 }
-// cloudflare 
-import * as cloudflare from "@cloudflare/workers-types";
+// cloudflare
+import * as cloudflare from "@cloudflare/workers-types"
 declare module "sst" {
   export interface Resource {
-    "Api": cloudflare.Service
-    "AuthApi": cloudflare.Service
-    "AuthStorage": cloudflare.KVNamespace
-    "Bucket": cloudflare.R2Bucket
-    "LogProcessor": cloudflare.Service
+    Api: cloudflare.Service
+    AuthApi: cloudflare.Service
+    AuthStorage: cloudflare.KVNamespace
+    Bucket: cloudflare.R2Bucket
+    GatewayKv: cloudflare.KVNamespace
+    LogProcessor: cloudflare.Service
   }
 }
 
 import "sst"
-export {}
+export {}

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

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

+ 96 - 87
packages/console/function/sst-env.d.ts

@@ -6,99 +6,108 @@
 import "sst"
 declare module "sst" {
   export interface Resource {
-    "ADMIN_SECRET": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "AUTH_API_URL": {
-      "type": "sst.sst.Linkable"
-      "value": string
-    }
-    "AWS_SES_ACCESS_KEY_ID": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "AWS_SES_SECRET_ACCESS_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "Console": {
-      "type": "sst.cloudflare.SolidStart"
-      "url": string
-    }
-    "Database": {
-      "database": string
-      "host": string
-      "password": string
-      "port": number
-      "type": "sst.sst.Linkable"
-      "username": string
-    }
-    "Desktop": {
-      "type": "sst.cloudflare.StaticSite"
-      "url": string
-    }
-    "EMAILOCTOPUS_API_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GITHUB_APP_ID": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GITHUB_APP_PRIVATE_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GITHUB_CLIENT_ID_CONSOLE": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GITHUB_CLIENT_SECRET_CONSOLE": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GOOGLE_CLIENT_ID": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "HONEYCOMB_API_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "STRIPE_SECRET_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "STRIPE_WEBHOOK_SECRET": {
-      "type": "sst.sst.Linkable"
-      "value": string
-    }
-    "Web": {
-      "type": "sst.cloudflare.Astro"
-      "url": string
-    }
-    "ZEN_MODELS1": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "ZEN_MODELS2": {
-      "type": "sst.sst.Secret"
-      "value": string
+    ADMIN_SECRET: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    AUTH_API_URL: {
+      type: "sst.sst.Linkable"
+      value: string
+    }
+    AWS_SES_ACCESS_KEY_ID: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    AWS_SES_SECRET_ACCESS_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    CLOUDFLARE_API_TOKEN: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    Console: {
+      type: "sst.cloudflare.SolidStart"
+      url: string
+    }
+    Database: {
+      database: string
+      host: string
+      password: string
+      port: number
+      type: "sst.sst.Linkable"
+      username: string
+    }
+    Desktop: {
+      type: "sst.cloudflare.StaticSite"
+      url: string
+    }
+    EMAILOCTOPUS_API_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GITHUB_APP_ID: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GITHUB_APP_PRIVATE_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GITHUB_CLIENT_ID_CONSOLE: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GITHUB_CLIENT_SECRET_CONSOLE: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GOOGLE_CLIENT_ID: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    HONEYCOMB_API_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    STRIPE_SECRET_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    STRIPE_WEBHOOK_SECRET: {
+      type: "sst.sst.Linkable"
+      value: string
+    }
+    Web: {
+      type: "sst.cloudflare.Astro"
+      url: string
+    }
+    ZEN_MODELS1: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    ZEN_MODELS2: {
+      type: "sst.sst.Secret"
+      value: string
     }
   }
 }
-// cloudflare 
-import * as cloudflare from "@cloudflare/workers-types";
+// cloudflare
+import * as cloudflare from "@cloudflare/workers-types"
 declare module "sst" {
   export interface Resource {
-    "Api": cloudflare.Service
-    "AuthApi": cloudflare.Service
-    "AuthStorage": cloudflare.KVNamespace
-    "Bucket": cloudflare.R2Bucket
-    "LogProcessor": cloudflare.Service
+    Api: cloudflare.Service
+    AuthApi: cloudflare.Service
+    AuthStorage: cloudflare.KVNamespace
+    Bucket: cloudflare.R2Bucket
+    GatewayKv: cloudflare.KVNamespace
+    LogProcessor: cloudflare.Service
   }
 }
 
 import "sst"
-export {}
+export {}

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

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

+ 1 - 1
packages/console/mail/sst-env.d.ts

@@ -6,4 +6,4 @@
 /// <reference path="../../../sst-env.d.ts" />
 
 import "sst"
-export {}
+export {}

+ 4 - 1
packages/console/resource/package.json

@@ -13,6 +13,9 @@
     }
   },
   "devDependencies": {
-    "@tsconfig/node22": "22.0.2"
+    "@cloudflare/workers-types": "catalog:",
+    "@tsconfig/node22": "22.0.2",
+    "@types/node": "catalog:",
+    "cloudflare": "5.2.0"
   }
 }

+ 58 - 1
packages/console/resource/resource.node.ts

@@ -1 +1,58 @@
-export { Resource } from "sst"
+import type { KVNamespaceListOptions, KVNamespaceListResult, KVNamespacePutOptions } from "@cloudflare/workers-types"
+import { Resource as ResourceBase } from "sst"
+import Cloudflare from "cloudflare"
+
+export const Resource = new Proxy(
+  {},
+  {
+    get(_target, prop: keyof typeof ResourceBase) {
+      const value = ResourceBase[prop]
+      // @ts-ignore
+      if ("type" in value && value.type === "sst.cloudflare.Kv") {
+        const client = new Cloudflare({
+          apiToken: ResourceBase.CLOUDFLARE_API_TOKEN.value,
+        })
+        // @ts-ignore
+        const namespaceId = value.namespaceId
+        const accountId = ResourceBase.CLOUDFLARE_DEFAULT_ACCOUNT_ID.value
+        return {
+          get: (k: string | string[]) => {
+            const isMulti = Array.isArray(k)
+            return client.kv.namespaces
+              .bulkGet(namespaceId, {
+                keys: Array.isArray(k) ? k : [k],
+                account_id: accountId,
+              })
+              .then((result) => (isMulti ? new Map(Object.entries(result?.values ?? {})) : result?.values?.[k]))
+          },
+          put: (k: string, v: string, opts?: KVNamespacePutOptions) =>
+            client.kv.namespaces.values.update(namespaceId, k, {
+              account_id: accountId,
+              value: v,
+              expiration: opts?.expiration,
+              expiration_ttl: opts?.expirationTtl,
+              metadata: opts?.metadata,
+            }),
+          delete: (k: string) =>
+            client.kv.namespaces.values.delete(namespaceId, k, {
+              account_id: accountId,
+            }),
+          list: (opts?: KVNamespaceListOptions): Promise<KVNamespaceListResult<unknown, string>> =>
+            client.kv.namespaces.keys
+              .list(namespaceId, {
+                account_id: accountId,
+                prefix: opts?.prefix ?? undefined,
+              })
+              .then((result) => {
+                return {
+                  keys: result.result,
+                  list_complete: true,
+                  cacheStatus: null,
+                }
+              }),
+        }
+      }
+      return value
+    },
+  },
+) as Record<string, any>

+ 96 - 87
packages/console/resource/sst-env.d.ts

@@ -6,99 +6,108 @@
 import "sst"
 declare module "sst" {
   export interface Resource {
-    "ADMIN_SECRET": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "AUTH_API_URL": {
-      "type": "sst.sst.Linkable"
-      "value": string
-    }
-    "AWS_SES_ACCESS_KEY_ID": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "AWS_SES_SECRET_ACCESS_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "Console": {
-      "type": "sst.cloudflare.SolidStart"
-      "url": string
-    }
-    "Database": {
-      "database": string
-      "host": string
-      "password": string
-      "port": number
-      "type": "sst.sst.Linkable"
-      "username": string
-    }
-    "Desktop": {
-      "type": "sst.cloudflare.StaticSite"
-      "url": string
-    }
-    "EMAILOCTOPUS_API_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GITHUB_APP_ID": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GITHUB_APP_PRIVATE_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GITHUB_CLIENT_ID_CONSOLE": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GITHUB_CLIENT_SECRET_CONSOLE": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "GOOGLE_CLIENT_ID": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "HONEYCOMB_API_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "STRIPE_SECRET_KEY": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "STRIPE_WEBHOOK_SECRET": {
-      "type": "sst.sst.Linkable"
-      "value": string
-    }
-    "Web": {
-      "type": "sst.cloudflare.Astro"
-      "url": string
-    }
-    "ZEN_MODELS1": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
-    "ZEN_MODELS2": {
-      "type": "sst.sst.Secret"
-      "value": string
+    ADMIN_SECRET: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    AUTH_API_URL: {
+      type: "sst.sst.Linkable"
+      value: string
+    }
+    AWS_SES_ACCESS_KEY_ID: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    AWS_SES_SECRET_ACCESS_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    CLOUDFLARE_API_TOKEN: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    CLOUDFLARE_DEFAULT_ACCOUNT_ID: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    Console: {
+      type: "sst.cloudflare.SolidStart"
+      url: string
+    }
+    Database: {
+      database: string
+      host: string
+      password: string
+      port: number
+      type: "sst.sst.Linkable"
+      username: string
+    }
+    Desktop: {
+      type: "sst.cloudflare.StaticSite"
+      url: string
+    }
+    EMAILOCTOPUS_API_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GITHUB_APP_ID: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GITHUB_APP_PRIVATE_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GITHUB_CLIENT_ID_CONSOLE: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GITHUB_CLIENT_SECRET_CONSOLE: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    GOOGLE_CLIENT_ID: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    HONEYCOMB_API_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    STRIPE_SECRET_KEY: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    STRIPE_WEBHOOK_SECRET: {
+      type: "sst.sst.Linkable"
+      value: string
+    }
+    Web: {
+      type: "sst.cloudflare.Astro"
+      url: string
+    }
+    ZEN_MODELS1: {
+      type: "sst.sst.Secret"
+      value: string
+    }
+    ZEN_MODELS2: {
+      type: "sst.sst.Secret"
+      value: string
     }
   }
 }
-// cloudflare 
-import * as cloudflare from "@cloudflare/workers-types";
+// cloudflare
+import * as cloudflare from "@cloudflare/workers-types"
 declare module "sst" {
   export interface Resource {
-    "Api": cloudflare.Service
-    "AuthApi": cloudflare.Service
-    "AuthStorage": cloudflare.KVNamespace
-    "Bucket": cloudflare.R2Bucket
-    "LogProcessor": cloudflare.Service
+    Api: cloudflare.Service
+    AuthApi: cloudflare.Service
+    AuthStorage: cloudflare.KVNamespace
+    Bucket: cloudflare.R2Bucket
+    GatewayKv: cloudflare.KVNamespace
+    LogProcessor: cloudflare.Service
   }
 }
 
 import "sst"
-export {}
+export {}

+ 2 - 5
packages/desktop/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@opencode-ai/desktop",
-  "version": "1.0.23",
+  "version": "1.0.55",
   "description": "",
   "type": "module",
   "scripts": {
@@ -31,6 +31,7 @@
     "@solid-primitives/event-bus": "1.1.2",
     "@solid-primitives/resize-observer": "2.1.3",
     "@solid-primitives/scroll": "2.1.3",
+    "@solid-primitives/storage": "4.3.3",
     "@solidjs/meta": "catalog:",
     "@solidjs/router": "0.15.3",
     "@thisbeyond/solid-dnd": "0.7.5",
@@ -45,9 +46,5 @@
     "solid-list": "catalog:",
     "tailwindcss": "catalog:",
     "virtua": "catalog:"
-  },
-  "prettier": {
-    "semi": false,
-    "printWidth": 120
   }
 }

+ 0 - 846
packages/desktop/src/components/code.tsx

@@ -1,846 +0,0 @@
-import { bundledLanguages, type BundledLanguage, type ShikiTransformer } from "shiki"
-import { splitProps, type ComponentProps, createEffect, onMount, onCleanup, createMemo, createResource } from "solid-js"
-import { useLocal, type TextSelection } from "@/context/local"
-import { getFileExtension, getNodeOffsetInLine, getSelectionInContainer } from "@/utils"
-import { useShiki } from "@opencode-ai/ui"
-
-type DefinedSelection = Exclude<TextSelection, undefined>
-
-interface Props extends ComponentProps<"div"> {
-  code: string
-  path: string
-}
-
-export function Code(props: Props) {
-  const ctx = useLocal()
-  const highlighter = useShiki()
-  const [local, others] = splitProps(props, ["class", "classList", "code", "path"])
-  const lang = createMemo(() => {
-    const ext = getFileExtension(local.path)
-    if (ext in bundledLanguages) return ext
-    return "text"
-  })
-
-  let container: HTMLDivElement | undefined
-  let isProgrammaticSelection = false
-
-  const ranges = createMemo<DefinedSelection[]>(() => {
-    const items = ctx.context.all() as Array<{ type: "file"; path: string; selection?: DefinedSelection }>
-    const result: DefinedSelection[] = []
-    for (const item of items) {
-      if (item.path !== local.path) continue
-      const selection = item.selection
-      if (!selection) continue
-      result.push(selection)
-    }
-    return result
-  })
-
-  const createLineNumberTransformer = (selections: DefinedSelection[]): ShikiTransformer => {
-    const highlighted = new Set<number>()
-    for (const selection of selections) {
-      const startLine = selection.startLine
-      const endLine = selection.endLine
-      const start = Math.max(1, Math.min(startLine, endLine))
-      const end = Math.max(start, Math.max(startLine, endLine))
-      const count = end - start + 1
-      if (count <= 0) continue
-      const values = Array.from({ length: count }, (_, index) => start + index)
-      for (const value of values) highlighted.add(value)
-    }
-    return {
-      name: "line-number-highlight",
-      line(node, index) {
-        if (!highlighted.has(index)) return
-        this.addClassToHast(node, "line-number-highlight")
-        const children = node.children
-        if (!Array.isArray(children)) return
-        for (const child of children) {
-          if (!child || typeof child !== "object") continue
-          const element = child as { type?: string; properties?: { className?: string[] } }
-          if (element.type !== "element") continue
-          const className = element.properties?.className
-          if (!Array.isArray(className)) continue
-          const matches = className.includes("diff-oldln") || className.includes("diff-newln")
-          if (!matches) continue
-          if (className.includes("line-number-highlight")) continue
-          className.push("line-number-highlight")
-        }
-      },
-    }
-  }
-
-  const [html] = createResource(
-    () => ranges(),
-    async (activeRanges) => {
-      if (!highlighter.getLoadedLanguages().includes(lang())) {
-        await highlighter.loadLanguage(lang() as BundledLanguage)
-      }
-      return highlighter.codeToHtml(local.code || "", {
-        lang: lang() && lang() in bundledLanguages ? lang() : "text",
-        theme: "opencode",
-        transformers: [transformerUnifiedDiff(), transformerDiffGroups(), createLineNumberTransformer(activeRanges)],
-      }) as string
-    },
-  )
-
-  onMount(() => {
-    if (!container) return
-
-    let ticking = false
-    const onScroll = () => {
-      if (!container) return
-      // if (ctx.file.active()?.path !== local.path) return
-      if (ticking) return
-      ticking = true
-      requestAnimationFrame(() => {
-        ticking = false
-        ctx.file.scroll(local.path, container!.scrollTop)
-      })
-    }
-
-    const onSelectionChange = () => {
-      if (!container) return
-      if (isProgrammaticSelection) return
-      // if (ctx.file.active()?.path !== local.path) return
-      const d = getSelectionInContainer(container)
-      if (!d) return
-      const p = ctx.file.node(local.path)?.selection
-      if (p && p.startLine === d.sl && p.endLine === d.el && p.startChar === d.sch && p.endChar === d.ech) return
-      ctx.file.select(local.path, { startLine: d.sl, startChar: d.sch, endLine: d.el, endChar: d.ech })
-    }
-
-    const MOD = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) ? "Meta" : "Control"
-    const onKeyDown = (e: KeyboardEvent) => {
-      // if (ctx.file.active()?.path !== local.path) return
-      const ae = document.activeElement as HTMLElement | undefined
-      const tag = (ae?.tagName || "").toLowerCase()
-      const inputFocused = !!ae && (tag === "input" || tag === "textarea" || ae.isContentEditable)
-      if (inputFocused) return
-      if (e.getModifierState(MOD) && e.key.toLowerCase() === "a") {
-        e.preventDefault()
-        if (!container) return
-        const element = container.querySelector("code") as HTMLElement | undefined
-        if (!element) return
-        const lines = Array.from(element.querySelectorAll(".line"))
-        if (!lines.length) return
-        const r = document.createRange()
-        const last = lines[lines.length - 1]
-        r.selectNodeContents(last)
-        const lastLen = r.toString().length
-        ctx.file.select(local.path, { startLine: 1, startChar: 0, endLine: lines.length, endChar: lastLen })
-      }
-    }
-
-    container.addEventListener("scroll", onScroll)
-    document.addEventListener("selectionchange", onSelectionChange)
-    document.addEventListener("keydown", onKeyDown)
-
-    onCleanup(() => {
-      container?.removeEventListener("scroll", onScroll)
-      document.removeEventListener("selectionchange", onSelectionChange)
-      document.removeEventListener("keydown", onKeyDown)
-    })
-  })
-
-  // Restore scroll position from store when content is ready
-  createEffect(() => {
-    const content = html()
-    if (!container || !content) return
-    const top = ctx.file.node(local.path)?.scrollTop
-    if (top !== undefined && container.scrollTop !== top) container.scrollTop = top
-  })
-
-  // Sync selection from store -> DOM
-  createEffect(() => {
-    const content = html()
-    if (!container || !content) return
-    // if (ctx.file.active()?.path !== local.path) return
-    const codeEl = container.querySelector("code") as HTMLElement | undefined
-    if (!codeEl) return
-    const target = ctx.file.node(local.path)?.selection
-    const current = getSelectionInContainer(container)
-    const sel = window.getSelection()
-    if (!sel) return
-    if (!target) {
-      if (current) {
-        isProgrammaticSelection = true
-        sel.removeAllRanges()
-        queueMicrotask(() => {
-          isProgrammaticSelection = false
-        })
-      }
-      return
-    }
-    const matches = !!(
-      current &&
-      current.sl === target.startLine &&
-      current.sch === target.startChar &&
-      current.el === target.endLine &&
-      current.ech === target.endChar
-    )
-    if (matches) return
-    const lines = Array.from(codeEl.querySelectorAll(".line"))
-    if (lines.length === 0) return
-    let sIdx = Math.max(0, target.startLine - 1)
-    let eIdx = Math.max(0, target.endLine - 1)
-    let sChar = Math.max(0, target.startChar || 0)
-    let eChar = Math.max(0, target.endChar || 0)
-    if (sIdx > eIdx || (sIdx === eIdx && sChar > eChar)) {
-      const ti = sIdx
-      sIdx = eIdx
-      eIdx = ti
-      const tc = sChar
-      sChar = eChar
-      eChar = tc
-    }
-    if (eChar === 0 && eIdx > sIdx) {
-      eIdx = eIdx - 1
-      eChar = Number.POSITIVE_INFINITY
-    }
-    if (sIdx >= lines.length) return
-    if (eIdx >= lines.length) eIdx = lines.length - 1
-    const s = getNodeOffsetInLine(lines[sIdx], sChar) ?? { node: lines[sIdx], offset: 0 }
-    const e = getNodeOffsetInLine(lines[eIdx], eChar) ?? { node: lines[eIdx], offset: lines[eIdx].childNodes.length }
-    const range = document.createRange()
-    range.setStart(s.node, s.offset)
-    range.setEnd(e.node, e.offset)
-    isProgrammaticSelection = true
-    sel.removeAllRanges()
-    sel.addRange(range)
-    queueMicrotask(() => {
-      isProgrammaticSelection = false
-    })
-  })
-
-  // Build/toggle split layout and apply folding (both unified and split)
-  createEffect(() => {
-    const content = html()
-    if (!container || !content) return
-    const view = ctx.file.view(local.path)
-
-    const pres = Array.from(container.querySelectorAll<HTMLPreElement>("pre"))
-    if (pres.length === 0) return
-    const originalPre = pres[0]
-
-    const split = container.querySelector<HTMLElement>(".diff-split")
-    if (view === "diff-split") {
-      applySplitDiff(container)
-      const next = container.querySelector<HTMLElement>(".diff-split")
-      if (next) next.style.display = ""
-      originalPre.style.display = "none"
-    } else {
-      if (split) split.style.display = "none"
-      originalPre.style.display = ""
-    }
-
-    const expanded = ctx.file.folded(local.path)
-    if (view === "diff-split") {
-      const left = container.querySelector<HTMLElement>(".diff-split pre:nth-child(1) code")
-      const right = container.querySelector<HTMLElement>(".diff-split pre:nth-child(2) code")
-      if (left)
-        applyDiffFolding(left, 3, { expanded, onExpand: (key) => ctx.file.unfold(local.path, key), side: "left" })
-      if (right)
-        applyDiffFolding(right, 3, { expanded, onExpand: (key) => ctx.file.unfold(local.path, key), side: "right" })
-    } else {
-      const code = container.querySelector<HTMLElement>("pre code")
-      if (code)
-        applyDiffFolding(code, 3, {
-          expanded,
-          onExpand: (key) => ctx.file.unfold(local.path, key),
-        })
-    }
-  })
-
-  // Highlight groups + scroll coupling
-  const clearHighlights = () => {
-    if (!container) return
-    container.querySelectorAll<HTMLElement>(".diff-selected").forEach((el) => el.classList.remove("diff-selected"))
-  }
-
-  const applyHighlight = (idx: number, scroll?: boolean) => {
-    if (!container) return
-    const view = ctx.file.view(local.path)
-    if (view === "raw") return
-
-    clearHighlights()
-
-    const nodes: HTMLElement[] = []
-    if (view === "diff-split") {
-      const left = container.querySelector<HTMLElement>(".diff-split pre:nth-child(1) code")
-      const right = container.querySelector<HTMLElement>(".diff-split pre:nth-child(2) code")
-      if (left)
-        nodes.push(...Array.from(left.querySelectorAll<HTMLElement>(`[data-chgrp="${idx}"][data-diff="remove"]`)))
-      if (right)
-        nodes.push(...Array.from(right.querySelectorAll<HTMLElement>(`[data-chgrp="${idx}"][data-diff="add"]`)))
-    } else {
-      const code = container.querySelector<HTMLElement>("pre code")
-      if (code) nodes.push(...Array.from(code.querySelectorAll<HTMLElement>(`[data-chgrp="${idx}"]`)))
-    }
-
-    for (const n of nodes) n.classList.add("diff-selected")
-    if (scroll && nodes.length) nodes[0].scrollIntoView({ block: "center", behavior: "smooth" })
-  }
-
-  const countGroups = () => {
-    if (!container) return 0
-    const code = container.querySelector<HTMLElement>("pre code")
-    if (!code) return 0
-    const set = new Set<string>()
-    for (const el of Array.from(code.querySelectorAll<HTMLElement>(".diff-line[data-chgrp]"))) {
-      const v = el.getAttribute("data-chgrp")
-      if (v != undefined) set.add(v)
-    }
-    return set.size
-  }
-
-  let lastIdx: number | undefined = undefined
-  let lastView: string | undefined
-  let lastContent: string | undefined
-  let lastRawIdx: number | undefined = undefined
-  createEffect(() => {
-    const content = html()
-    if (!container || !content) return
-    const view = ctx.file.view(local.path)
-    const raw = ctx.file.changeIndex(local.path)
-    if (raw === undefined) return
-    const total = countGroups()
-    if (total <= 0) return
-    const next = ((raw % total) + total) % total
-
-    const navigated = lastRawIdx !== undefined && lastRawIdx !== raw
-
-    if (next !== raw) {
-      ctx.file.setChangeIndex(local.path, next)
-      applyHighlight(next, true)
-    } else {
-      if (lastView !== view || lastContent !== content) applyHighlight(next)
-      if ((lastIdx !== undefined && lastIdx !== next) || navigated) applyHighlight(next, true)
-    }
-
-    lastRawIdx = raw
-    lastIdx = next
-    lastView = view
-    lastContent = content
-  })
-
-  return (
-    <div
-      ref={(el) => {
-        container = el
-      }}
-      innerHTML={html()}
-      class="
-          font-mono text-xs tracking-wide overflow-y-auto h-full
-          [&]:[counter-reset:line]
-          [&_pre]:focus-visible:outline-none
-          [&_pre]:overflow-x-auto [&_pre]:no-scrollbar
-          [&_code]:min-w-full [&_code]:inline-block 
-          [&_.tab]:relative
-          [&_.tab::before]:content['⇥']
-          [&_.tab::before]:absolute
-          [&_.tab::before]:opacity-0
-          [&_.space]:relative
-          [&_.space::before]:content-['·']
-          [&_.space::before]:absolute
-          [&_.space::before]:opacity-0
-          [&_.line]:inline-block [&_.line]:w-full
-          [&_.line]:hover:bg-background-element
-          [&_.line::before]:sticky [&_.line::before]:left-0
-          [&_.line::before]:w-12 [&_.line::before]:pr-4
-          [&_.line::before]:z-10
-          [&_.line::before]:bg-background-panel
-          [&_.line::before]:text-text-muted/60
-          [&_.line::before]:text-right [&_.line::before]:inline-block
-          [&_.line::before]:select-none
-          [&_.line::before]:[counter-increment:line]
-          [&_.line::before]:content-[counter(line)]
-          [&_.line-number-highlight]:bg-accent/20
-          [&_.line-number-highlight::before]:bg-accent/40!
-          [&_.line-number-highlight::before]:text-background-panel!
-          [&_code.code-diff_.line::before]:content-['']
-          [&_code.code-diff_.line::before]:w-0
-          [&_code.code-diff_.line::before]:pr-0
-          [&_.diff-split_code.code-diff::before]:w-10
-          [&_.diff-split_.diff-newln]:left-0
-          [&_.diff-oldln]:sticky [&_.diff-oldln]:left-0
-          [&_.diff-oldln]:w-10 [&_.diff-oldln]:pr-2
-          [&_.diff-oldln]:z-40
-          [&_.diff-oldln]:text-text-muted/60
-          [&_.diff-oldln]:text-right [&_.diff-oldln]:inline-block
-          [&_.diff-oldln]:select-none
-          [&_.diff-oldln]:bg-background-panel
-          [&_.diff-newln]:sticky [&_.diff-newln]:left-10
-          [&_.diff-newln]:w-10 [&_.diff-newln]:pr-2
-          [&_.diff-newln]:z-40
-          [&_.diff-newln]:text-text-muted/60
-          [&_.diff-newln]:text-right [&_.diff-newln]:inline-block
-          [&_.diff-newln]:select-none
-          [&_.diff-newln]:bg-background-panel
-          [&_.diff-add]:bg-success/20!
-          [&_.diff-add.diff-selected]:bg-success/50!
-          [&_.diff-add_.diff-oldln]:bg-success!
-          [&_.diff-add_.diff-oldln]:text-background-panel!
-          [&_.diff-add_.diff-newln]:bg-success!
-          [&_.diff-add_.diff-newln]:text-background-panel!
-          [&_.diff-remove]:bg-error/20!
-          [&_.diff-remove.diff-selected]:bg-error/50!
-          [&_.diff-remove_.diff-newln]:bg-error!
-          [&_.diff-remove_.diff-newln]:text-background-panel!
-          [&_.diff-remove_.diff-oldln]:bg-error!
-          [&_.diff-remove_.diff-oldln]:text-background-panel!
-          [&_.diff-sign]:inline-block [&_.diff-sign]:px-2 [&_.diff-sign]:select-none
-          [&_.diff-blank]:bg-background-element
-          [&_.diff-blank_.diff-oldln]:bg-background-element
-          [&_.diff-blank_.diff-newln]:bg-background-element
-          [&_.diff-collapsed]:block! [&_.diff-collapsed]:w-full [&_.diff-collapsed]:relative
-          [&_.diff-collapsed]:select-none
-          [&_.diff-collapsed]:bg-info/20 [&_.diff-collapsed]:hover:bg-info/40!
-          [&_.diff-collapsed]:text-info/80 [&_.diff-collapsed]:hover:text-info
-          [&_.diff-collapsed]:text-xs
-          [&_.diff-collapsed_.diff-oldln]:bg-info!
-          [&_.diff-collapsed_.diff-newln]:bg-info!
-        "
-      classList={{
-        ...(local.classList || {}),
-        [local.class ?? ""]: !!local.class,
-      }}
-      {...others}
-    ></div>
-  )
-}
-
-function transformerUnifiedDiff(): ShikiTransformer {
-  const kinds = new Map<number, string>()
-  const meta = new Map<number, { old?: number; new?: number; sign?: string }>()
-  let isDiff = false
-
-  return {
-    name: "unified-diff",
-    preprocess(input) {
-      kinds.clear()
-      meta.clear()
-      isDiff = false
-
-      const ls = input.split(/\r?\n/)
-      const out: Array<string> = []
-      let oldNo = 0
-      let newNo = 0
-      let inHunk = false
-
-      for (let i = 0; i < ls.length; i++) {
-        const s = ls[i]
-
-        const m = s.match(/^@@\s*-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s*@@/)
-        if (m) {
-          isDiff = true
-          inHunk = true
-          oldNo = parseInt(m[1], 10)
-          newNo = parseInt(m[3], 10)
-          continue
-        }
-
-        if (
-          /^diff --git /.test(s) ||
-          /^Index: /.test(s) ||
-          /^--- /.test(s) ||
-          /^\+\+\+ /.test(s) ||
-          /^[=]{3,}$/.test(s) ||
-          /^\*{3,}$/.test(s) ||
-          /^\\ No newline at end of file$/.test(s)
-        ) {
-          isDiff = true
-          continue
-        }
-
-        if (!inHunk) {
-          out.push(s)
-          continue
-        }
-
-        if (/^\+/.test(s)) {
-          out.push(s)
-          const ln = out.length
-          kinds.set(ln, "add")
-          meta.set(ln, { new: newNo, sign: "+" })
-          newNo++
-          continue
-        }
-
-        if (/^-/.test(s)) {
-          out.push(s)
-          const ln = out.length
-          kinds.set(ln, "remove")
-          meta.set(ln, { old: oldNo, sign: "-" })
-          oldNo++
-          continue
-        }
-
-        if (/^ /.test(s)) {
-          out.push(s)
-          const ln = out.length
-          kinds.set(ln, "context")
-          meta.set(ln, { old: oldNo, new: newNo })
-          oldNo++
-          newNo++
-          continue
-        }
-
-        // fallback in hunks
-        out.push(s)
-      }
-
-      return out.join("\n").trimEnd()
-    },
-    code(node) {
-      if (isDiff) this.addClassToHast(node, "code-diff")
-    },
-    pre(node) {
-      if (isDiff) this.addClassToHast(node, "code-diff")
-    },
-    line(node, line) {
-      if (!isDiff) return
-      const kind = kinds.get(line)
-      if (!kind) return
-
-      const m = meta.get(line) || {}
-
-      this.addClassToHast(node, "diff-line")
-      this.addClassToHast(node, `diff-${kind}`)
-      node.properties = node.properties || {}
-      ;(node.properties as any)["data-diff"] = kind
-      if (m.old != undefined) (node.properties as any)["data-old"] = String(m.old)
-      if (m.new != undefined) (node.properties as any)["data-new"] = String(m.new)
-
-      const oldSpan = {
-        type: "element",
-        tagName: "span",
-        properties: { className: ["diff-oldln"] },
-        children: [{ type: "text", value: m.old != undefined ? String(m.old) : " " }],
-      }
-      const newSpan = {
-        type: "element",
-        tagName: "span",
-        properties: { className: ["diff-newln"] },
-        children: [{ type: "text", value: m.new != undefined ? String(m.new) : " " }],
-      }
-
-      if (kind === "add" || kind === "remove" || kind === "context") {
-        const first = (node.children && (node.children as any[])[0]) as any
-        if (first && first.type === "element" && first.children && first.children.length > 0) {
-          const t = first.children[0]
-          if (t && t.type === "text" && typeof t.value === "string" && t.value.length > 0) {
-            const ch = t.value[0]
-            if (ch === "+" || ch === "-" || ch === " ") t.value = t.value.slice(1)
-          }
-        }
-      }
-
-      const signSpan = {
-        type: "element",
-        tagName: "span",
-        properties: { className: ["diff-sign"] },
-        children: [{ type: "text", value: (m as any).sign || " " }],
-      }
-
-      // @ts-expect-error hast typing across versions
-      node.children = [oldSpan, newSpan, signSpan, ...(node.children || [])]
-    },
-  }
-}
-
-function transformerDiffGroups(): ShikiTransformer {
-  let group = -1
-  let inGroup = false
-  return {
-    name: "diff-groups",
-    pre() {
-      group = -1
-      inGroup = false
-    },
-    line(node) {
-      const props = (node.properties || {}) as any
-      const kind = props["data-diff"] as string | undefined
-      if (kind === "add" || kind === "remove") {
-        if (!inGroup) {
-          group += 1
-          inGroup = true
-        }
-        ;(node.properties as any)["data-chgrp"] = String(group)
-      } else {
-        inGroup = false
-      }
-    },
-  }
-}
-
-function applyDiffFolding(
-  root: HTMLElement,
-  context = 3,
-  options?: { expanded?: string[]; onExpand?: (key: string) => void; side?: "left" | "right" },
-) {
-  if (!root.classList.contains("code-diff")) return
-
-  // Cleanup: unwrap previous collapsed blocks and remove toggles
-  const blocks = Array.from(root.querySelectorAll<HTMLElement>(".diff-collapsed-block"))
-  for (const block of blocks) {
-    const p = block.parentNode
-    if (!p) {
-      block.remove()
-      continue
-    }
-    while (block.firstChild) p.insertBefore(block.firstChild, block)
-    block.remove()
-  }
-  const toggles = Array.from(root.querySelectorAll<HTMLElement>(".diff-collapsed"))
-  for (const t of toggles) t.remove()
-
-  const lines = Array.from(root.querySelectorAll<HTMLElement>(".diff-line"))
-  if (lines.length === 0) return
-
-  const n = lines.length
-  const isChange = lines.map((l) => l.dataset["diff"] === "add" || l.dataset["diff"] === "remove")
-  const isContext = lines.map((l) => l.dataset["diff"] === "context")
-  if (!isChange.some(Boolean)) return
-
-  const visible = new Array(n).fill(false) as boolean[]
-  for (let i = 0; i < n; i++) if (isChange[i]) visible[i] = true
-  for (let i = 0; i < n; i++) {
-    if (isChange[i]) {
-      const s = Math.max(0, i - context)
-      const e = Math.min(n - 1, i + context)
-      for (let j = s; j <= e; j++) if (isContext[j]) visible[j] = true
-    }
-  }
-
-  type Range = { start: number; end: number }
-  const ranges: Range[] = []
-  let i = 0
-  while (i < n) {
-    if (!visible[i] && isContext[i]) {
-      let j = i
-      while (j + 1 < n && !visible[j + 1] && isContext[j + 1]) j++
-      ranges.push({ start: i, end: j })
-      i = j + 1
-    } else {
-      i++
-    }
-  }
-
-  for (const r of ranges) {
-    const start = lines[r.start]
-    const end = lines[r.end]
-    const count = r.end - r.start + 1
-    const minCollapse = 20
-    if (count < minCollapse) {
-      continue
-    }
-
-    // Wrap the entire collapsed chunk (including trailing newline) so it takes no space
-    const block = document.createElement("span")
-    block.className = "diff-collapsed-block"
-    start.parentElement?.insertBefore(block, start)
-
-    let cur: Node | undefined = start
-    while (cur) {
-      const next: Node | undefined = cur.nextSibling || undefined
-      block.appendChild(cur)
-      if (cur === end) {
-        // Also move the newline after the last line into the block
-        if (next && next.nodeType === Node.TEXT_NODE && (next.textContent || "").startsWith("\n")) {
-          block.appendChild(next)
-        }
-        break
-      }
-      cur = next
-    }
-
-    block.style.display = "none"
-    const row = document.createElement("span")
-    row.className = "line diff-collapsed"
-    row.setAttribute("data-kind", "collapsed")
-    row.setAttribute("data-count", String(count))
-    row.setAttribute("tabindex", "0")
-    row.setAttribute("role", "button")
-
-    const oldln = document.createElement("span")
-    oldln.className = "diff-oldln"
-    oldln.textContent = " "
-
-    const newln = document.createElement("span")
-    newln.className = "diff-newln"
-    newln.textContent = " "
-
-    const sign = document.createElement("span")
-    sign.className = "diff-sign"
-    sign.textContent = "…"
-
-    const label = document.createElement("span")
-    label.textContent = `show ${count} unchanged line${count > 1 ? "s" : ""}`
-
-    const key = `o${start.dataset["old"] || ""}-${end.dataset["old"] || ""}:n${start.dataset["new"] || ""}-${end.dataset["new"] || ""}`
-
-    const show = (record = true) => {
-      if (record) options?.onExpand?.(key)
-      const p = block.parentNode
-      if (p) {
-        while (block.firstChild) p.insertBefore(block.firstChild, block)
-        block.remove()
-      }
-      row.remove()
-    }
-
-    row.addEventListener("click", () => show(true))
-    row.addEventListener("keydown", (ev) => {
-      if (ev.key === "Enter" || ev.key === " ") {
-        ev.preventDefault()
-        show(true)
-      }
-    })
-
-    block.parentElement?.insertBefore(row, block)
-    if (!options?.side || options.side === "left") row.appendChild(oldln)
-    if (!options?.side || options.side === "right") row.appendChild(newln)
-    row.appendChild(sign)
-    row.appendChild(label)
-
-    if (options?.expanded && options.expanded.includes(key)) {
-      show(false)
-    }
-  }
-}
-
-function applySplitDiff(container: HTMLElement) {
-  const pres = Array.from(container.querySelectorAll<HTMLPreElement>("pre"))
-  if (pres.length === 0) return
-  const originalPre = pres[0]
-  const originalCode = originalPre.querySelector("code") as HTMLElement | undefined
-  if (!originalCode || !originalCode.classList.contains("code-diff")) return
-
-  // Rebuild split each time to match current content
-  const existing = container.querySelector<HTMLElement>(".diff-split")
-  if (existing) existing.remove()
-
-  const grid = document.createElement("div")
-  grid.className = "diff-split grid grid-cols-2 gap-x-6"
-
-  const makeColumn = () => {
-    const pre = document.createElement("pre")
-    pre.className = originalPre.className
-    const code = document.createElement("code")
-    code.className = originalCode.className
-    pre.appendChild(code)
-    return { pre, code }
-  }
-
-  const left = makeColumn()
-  const right = makeColumn()
-
-  // Helpers
-  const cloneSide = (line: HTMLElement, side: "old" | "new"): HTMLElement => {
-    const clone = line.cloneNode(true) as HTMLElement
-    const oldln = clone.querySelector(".diff-oldln")
-    const newln = clone.querySelector(".diff-newln")
-    if (side === "old") {
-      if (newln) newln.remove()
-    } else {
-      if (oldln) oldln.remove()
-    }
-    return clone
-  }
-
-  const blankLine = (side: "old" | "new", kind: "add" | "remove"): HTMLElement => {
-    const span = document.createElement("span")
-    span.className = "line diff-line diff-blank"
-    span.setAttribute("data-diff", kind)
-    const ln = document.createElement("span")
-    ln.className = side === "old" ? "diff-oldln" : "diff-newln"
-    ln.textContent = " "
-    span.appendChild(ln)
-    return span
-  }
-
-  const lines = Array.from(originalCode.querySelectorAll<HTMLElement>(".diff-line"))
-  let i = 0
-  while (i < lines.length) {
-    const cur = lines[i]
-    const kind = cur.dataset["diff"]
-
-    if (kind === "context") {
-      left.code.appendChild(cloneSide(cur, "old"))
-      left.code.appendChild(document.createTextNode("\n"))
-      right.code.appendChild(cloneSide(cur, "new"))
-      right.code.appendChild(document.createTextNode("\n"))
-      i++
-      continue
-    }
-
-    if (kind === "remove") {
-      // Batch consecutive removes and following adds, then pair
-      const removes: HTMLElement[] = []
-      const adds: HTMLElement[] = []
-      let j = i
-      while (j < lines.length && lines[j].dataset["diff"] === "remove") {
-        removes.push(lines[j])
-        j++
-      }
-      let k = j
-      while (k < lines.length && lines[k].dataset["diff"] === "add") {
-        adds.push(lines[k])
-        k++
-      }
-
-      const pairs = Math.min(removes.length, adds.length)
-      for (let p = 0; p < pairs; p++) {
-        left.code.appendChild(cloneSide(removes[p], "old"))
-        left.code.appendChild(document.createTextNode("\n"))
-        right.code.appendChild(cloneSide(adds[p], "new"))
-        right.code.appendChild(document.createTextNode("\n"))
-      }
-      for (let p = pairs; p < removes.length; p++) {
-        left.code.appendChild(cloneSide(removes[p], "old"))
-        left.code.appendChild(document.createTextNode("\n"))
-        right.code.appendChild(blankLine("new", "remove"))
-        right.code.appendChild(document.createTextNode("\n"))
-      }
-      for (let p = pairs; p < adds.length; p++) {
-        left.code.appendChild(blankLine("old", "add"))
-        left.code.appendChild(document.createTextNode("\n"))
-        right.code.appendChild(cloneSide(adds[p], "new"))
-        right.code.appendChild(document.createTextNode("\n"))
-      }
-
-      i = k
-      continue
-    }
-
-    if (kind === "add") {
-      // Run of adds not preceded by removes
-      const adds: HTMLElement[] = []
-      let j = i
-      while (j < lines.length && lines[j].dataset["diff"] === "add") {
-        adds.push(lines[j])
-        j++
-      }
-      for (let p = 0; p < adds.length; p++) {
-        left.code.appendChild(blankLine("old", "add"))
-        left.code.appendChild(document.createTextNode("\n"))
-        right.code.appendChild(cloneSide(adds[p], "new"))
-        right.code.appendChild(document.createTextNode("\n"))
-      }
-      i = j
-      continue
-    }
-
-    // Any other kind: mirror as context
-    left.code.appendChild(cloneSide(cur, "old"))
-    left.code.appendChild(document.createTextNode("\n"))
-    right.code.appendChild(cloneSide(cur, "new"))
-    right.code.appendChild(document.createTextNode("\n"))
-    i++
-  }
-
-  grid.appendChild(left.pre)
-  grid.appendChild(right.pre)
-  container.appendChild(grid)
-}

+ 2 - 2
packages/desktop/src/components/file-tree.tsx

@@ -77,7 +77,7 @@ export default function FileTree(props: {
                 <Collapsible
                   class="w-full"
                   forceMount={false}
-                  open={local.file.node(node.path)?.expanded}
+                  // open={local.file.node(node.path)?.expanded}
                   onOpenChange={(open) => (open ? local.file.expand(node.path) : local.file.collapse(node.path))}
                 >
                   <Collapsible.Trigger>
@@ -85,7 +85,7 @@ export default function FileTree(props: {
                       <Collapsible.Arrow class="text-text-muted/60 ml-1" />
                       <FileIcon
                         node={node}
-                        expanded={local.file.node(node.path).expanded}
+                        // expanded={local.file.node(node.path).expanded}
                         class="text-text-muted/60 -ml-1"
                       />
                     </Node>

+ 5 - 6
packages/desktop/src/components/message-progress.tsx

@@ -70,9 +70,8 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
 
   const lastPart = createMemo(() => resolvedParts().slice(-1)?.at(0))
   const rawStatus = createMemo(() => {
-    const defaultStatus = "Working..."
     const last = lastPart()
-    if (!last) return defaultStatus
+    if (!last) return undefined
 
     if (last.type === "tool") {
       switch (last.tool) {
@@ -102,7 +101,7 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
     } else if (last.type === "text") {
       return "Gathering thoughts..."
     }
-    return defaultStatus
+    return undefined
   })
 
   const [status, setStatus] = createSignal(rawStatus())
@@ -111,11 +110,11 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
 
   createEffect(() => {
     const newStatus = rawStatus()
-    if (newStatus === status()) return
+    if (newStatus === status() || !newStatus) return
 
     const timeSinceLastChange = Date.now() - lastStatusChange
 
-    if (timeSinceLastChange >= 1000) {
+    if (timeSinceLastChange >= 1500) {
       setStatus(newStatus)
       lastStatusChange = Date.now()
       if (statusTimeout) {
@@ -145,7 +144,7 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
       {/*   )} */}
       {/* </Show> */}
       <div class="flex items-center gap-x-5 pl-3 border border-transparent text-text-base">
-        <Spinner /> <span class="text-12-medium">{status()}</span>
+        <Spinner /> <span class="text-12-medium">{status() ?? "Considering next steps..."}</span>
       </div>
       <Show when={eligibleItems().length > 0}>
         <div

+ 175 - 68
packages/desktop/src/components/prompt-input.tsx

@@ -1,51 +1,41 @@
-import { Button, Icon, IconButton, Select, SelectDialog } from "@opencode-ai/ui"
+import { Button, Icon, IconButton, Select, SelectDialog, Tooltip } from "@opencode-ai/ui"
 import { useFilteredList } from "@opencode-ai/ui/hooks"
-import { createEffect, on, Component, createMemo, Show, For, onMount, onCleanup } from "solid-js"
+import { createEffect, on, Component, Show, For, onMount, onCleanup, Switch, Match } from "solid-js"
 import { createStore } from "solid-js/store"
 import { FileIcon } from "@/ui"
 import { getDirectory, getFilename } from "@/utils"
 import { createFocusSignal } from "@solid-primitives/active-element"
-import { TextSelection, useLocal } from "@/context/local"
+import { useLocal } from "@/context/local"
 import { DateTime } from "luxon"
-
-interface PartBase {
-  content: string
-  start: number
-  end: number
-}
-
-export interface TextPart extends PartBase {
-  type: "text"
-}
-
-export interface FileAttachmentPart extends PartBase {
-  type: "file"
-  path: string
-  selection?: TextSelection
-}
-
-export type ContentPart = TextPart | FileAttachmentPart
+import { ContentPart, DEFAULT_PROMPT, isPromptEqual, Prompt, useSession } from "@/context/session"
+import { useSDK } from "@/context/sdk"
+import { useNavigate } from "@solidjs/router"
+import { useSync } from "@/context/sync"
 
 interface PromptInputProps {
-  onSubmit: (parts: ContentPart[]) => void
   class?: string
   ref?: (el: HTMLDivElement) => void
 }
 
 export const PromptInput: Component<PromptInputProps> = (props) => {
+  const navigate = useNavigate()
+  const sdk = useSDK()
+  const sync = useSync()
   const local = useLocal()
+  const session = useSession()
   let editorRef!: HTMLDivElement
 
-  const defaultParts = [{ type: "text", content: "", start: 0, end: 0 } as const]
   const [store, setStore] = createStore<{
-    contentParts: ContentPart[]
     popoverIsOpen: boolean
   }>({
-    contentParts: defaultParts,
     popoverIsOpen: false,
   })
 
-  const isEmpty = createMemo(() => isEqual(store.contentParts, defaultParts))
+  createEffect(() => {
+    session.id
+    editorRef.focus()
+  })
+
   const isFocused = createFocusSignal(() => editorRef)
 
   const handlePaste = (event: ClipboardEvent) => {
@@ -71,14 +61,16 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     }
   })
 
+  const handleFileSelect = (path: string | undefined) => {
+    if (!path) return
+    addPart({ type: "file", path, content: "@" + getFilename(path), start: 0, end: 0 })
+    setStore("popoverIsOpen", false)
+  }
+
   const { flat, active, onInput, onKeyDown, refetch } = useFilteredList<string>({
     items: local.file.searchFilesAndDirectories,
     key: (x) => x,
-    onSelect: (path) => {
-      if (!path) return
-      addPart({ type: "file", path, content: "@" + getFilename(path), start: 0, end: 0 })
-      setStore("popoverIsOpen", false)
-    },
+    onSelect: handleFileSelect,
   })
 
   createEffect(() => {
@@ -88,10 +80,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
 
   createEffect(
     on(
-      () => store.contentParts,
+      () => session.prompt.current(),
       (currentParts) => {
         const domParts = parseFromDOM()
-        if (isEqual(currentParts, domParts)) return
+        if (isPromptEqual(currentParts, domParts)) return
 
         const selection = window.getSelection()
         let cursorPosition: number | null = null
@@ -122,8 +114,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     ),
   )
 
-  const parseFromDOM = (): ContentPart[] => {
-    const newParts: ContentPart[] = []
+  createEffect(
+    on(
+      () => session.prompt.cursor(),
+      (cursor) => {
+        if (cursor === undefined) return
+        queueMicrotask(() => setCursorPosition(editorRef, cursor))
+      },
+    ),
+  )
+
+  const parseFromDOM = (): Prompt => {
+    const newParts: Prompt = []
     let position = 0
     editorRef.childNodes.forEach((node) => {
       if (node.nodeType === Node.TEXT_NODE) {
@@ -150,7 +152,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
         }
       }
     })
-    if (newParts.length === 0) newParts.push(...defaultParts)
+    if (newParts.length === 0) newParts.push(...DEFAULT_PROMPT)
     return newParts
   }
 
@@ -167,12 +169,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       setStore("popoverIsOpen", false)
     }
 
-    setStore("contentParts", rawParts)
+    session.prompt.set(rawParts, cursorPosition)
   }
 
   const addPart = (part: ContentPart) => {
     const cursorPosition = getCursorPosition(editorRef)
-    const rawText = store.contentParts.map((p) => p.content).join("")
+    const prompt = session.prompt.current()
+    const rawText = prompt.map((p) => p.content).join("")
     const textBeforeCursor = rawText.substring(0, cursorPosition)
     const atMatch = textBeforeCursor.match(/@(\S*)$/)
 
@@ -198,7 +201,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       parts: nextParts,
       inserted,
       cursorPositionAfter,
-    } = store.contentParts.reduce(
+    } = prompt.reduce(
       (acc, item) => {
         if (acc.inserted) {
           acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
@@ -257,7 +260,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     )
 
     if (!inserted) {
-      const baseParts = store.contentParts.filter((item) => !(item.type === "text" && item.content === ""))
+      const baseParts = prompt.filter((item) => !(item.type === "text" && item.content === ""))
       const runningIndex = baseParts.reduce((sum, p) => sum + p.content.length, 0)
       const appendedAcc = { parts: [...baseParts] as ContentPart[], runningIndex }
       if (part.type === "text") {
@@ -270,20 +273,27 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
           end: appendedAcc.runningIndex + part.content.length,
         })
       }
-      const next = appendedAcc.parts.length > 0 ? appendedAcc.parts : defaultParts
-      setStore("contentParts", next)
-      setStore("popoverIsOpen", false)
+      const next = appendedAcc.parts.length > 0 ? appendedAcc.parts : DEFAULT_PROMPT
       const nextCursor = rawText.length + part.content.length
+      session.prompt.set(next, nextCursor)
+      setStore("popoverIsOpen", false)
       queueMicrotask(() => setCursorPosition(editorRef, nextCursor))
       return
     }
 
-    setStore("contentParts", nextParts)
+    session.prompt.set(nextParts, cursorPositionAfter)
     setStore("popoverIsOpen", false)
 
     queueMicrotask(() => setCursorPosition(editorRef, cursorPositionAfter))
   }
 
+  const abort = () =>
+    sdk.client.session.abort({
+      path: {
+        id: session.id!,
+      },
+    })
+
   const handleKeyDown = (event: KeyboardEvent) => {
     if (store.popoverIsOpen && (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter")) {
       onKeyDown(event)
@@ -293,14 +303,100 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     if (event.key === "Enter" && !event.shiftKey) {
       handleSubmit(event)
     }
+    if (event.key === "Escape") {
+      if (store.popoverIsOpen) {
+        setStore("popoverIsOpen", false)
+      } else if (session.working()) {
+        abort()
+      }
+    }
   }
 
-  const handleSubmit = (event: Event) => {
+  const handleSubmit = async (event: Event) => {
     event.preventDefault()
-    if (store.contentParts.length > 0) {
-      props.onSubmit([...store.contentParts])
-      setStore("contentParts", defaultParts)
+    const prompt = session.prompt.current()
+    const text = prompt.map((part) => part.content).join("")
+    if (text.trim().length === 0) {
+      if (session.working()) abort()
+      return
     }
+
+    let existing = session.info()
+    if (!existing) {
+      const created = await sdk.client.session.create()
+      existing = created.data ?? undefined
+      if (existing) navigate(`/session/${existing.id}`)
+    }
+    if (!existing) return
+
+    // if (!session.id) {
+    // session.layout.setOpenedTabs(
+    // session.layout.copyTabs("", session.id)
+    // }
+
+    const toAbsolutePath = (path: string) => (path.startsWith("/") ? path : sync.absolute(path))
+    const attachments = prompt.filter((part) => part.type === "file")
+
+    // const activeFile = local.context.active()
+    // if (activeFile) {
+    //   registerAttachment(
+    //     activeFile.path,
+    //     activeFile.selection,
+    //     activeFile.name ?? formatAttachmentLabel(activeFile.path, activeFile.selection),
+    //   )
+    // }
+
+    // for (const contextFile of local.context.all()) {
+    //   registerAttachment(
+    //     contextFile.path,
+    //     contextFile.selection,
+    //     formatAttachmentLabel(contextFile.path, contextFile.selection),
+    //   )
+    // }
+
+    const attachmentParts = attachments.map((attachment) => {
+      const absolute = toAbsolutePath(attachment.path)
+      const query = attachment.selection
+        ? `?start=${attachment.selection.startLine}&end=${attachment.selection.endLine}`
+        : ""
+      return {
+        type: "file" as const,
+        mime: "text/plain",
+        url: `file://${absolute}${query}`,
+        filename: getFilename(attachment.path),
+        source: {
+          type: "file" as const,
+          text: {
+            value: attachment.content,
+            start: attachment.start,
+            end: attachment.end,
+          },
+          path: absolute,
+        },
+      }
+    })
+
+    session.layout.setActiveTab(undefined)
+    session.messages.setActive(undefined)
+    session.prompt.set(DEFAULT_PROMPT, 0)
+
+    sdk.client.session.prompt({
+      path: { id: existing.id },
+      body: {
+        agent: local.agent.current()!.name,
+        model: {
+          modelID: local.model.current()!.id,
+          providerID: local.model.current()!.provider.id,
+        },
+        parts: [
+          {
+            type: "text",
+            text,
+          },
+          ...attachmentParts,
+        ],
+      },
+    })
   }
 
   return (
@@ -310,11 +406,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
           <Show when={flat().length > 0} fallback={<div class="text-text-weak px-2">No matching files</div>}>
             <For each={flat()}>
               {(i) => (
-                <div
+                <button
                   classList={{
                     "w-full flex items-center justify-between rounded-md": true,
                     "bg-surface-raised-base-hover": active() === i,
                   }}
+                  onClick={() => handleFileSelect(i)}
                 >
                   <div class="flex items-center gap-x-2 grow min-w-0">
                     <FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
@@ -326,7 +423,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                     </div>
                   </div>
                   <div class="flex items-center gap-x-1 text-text-muted/40 shrink-0"></div>
-                </div>
+                </button>
               )}
             </For>
           </Show>
@@ -354,7 +451,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
               "[&>[data-type=file]]:text-icon-info-active": true,
             }}
           />
-          <Show when={isEmpty()}>
+          <Show when={!session.prompt.dirty()}>
             <div class="absolute top-0 left-0 p-3 text-14-regular text-text-weak pointer-events-none">
               Plan and build anything
             </div>
@@ -419,29 +516,39 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
               )}
             </SelectDialog>
           </div>
-          <IconButton type="submit" disabled={isEmpty()} icon="arrow-up" variant="primary" />
+          <Tooltip
+            placement="top"
+            value={
+              <Switch>
+                <Match when={session.working()}>
+                  <div class="flex items-center gap-2">
+                    <span>Stop</span>
+                    <span class="text-icon-base text-12-medium text-[10px]!">ESC</span>
+                  </div>
+                </Match>
+                <Match when={true}>
+                  <div class="flex items-center gap-2">
+                    <span>Send</span>
+                    <Icon name="enter" size="small" class="text-icon-base" />
+                  </div>
+                </Match>
+              </Switch>
+            }
+          >
+            <IconButton
+              type="submit"
+              disabled={!session.prompt.dirty() && !session.working()}
+              icon={session.working() ? "stop" : "arrow-up"}
+              variant="primary"
+              class="rounded-full"
+            />
+          </Tooltip>
         </div>
       </form>
     </div>
   )
 }
 
-function isEqual(arrA: ContentPart[], arrB: ContentPart[]): boolean {
-  if (arrA.length !== arrB.length) return false
-  for (let i = 0; i < arrA.length; i++) {
-    const partA = arrA[i]
-    const partB = arrB[i]
-    if (partA.type !== partB.type) return false
-    if (partA.type === "text" && partA.content !== (partB as TextPart).content) {
-      return false
-    }
-    if (partA.type === "file" && partA.path !== (partB as FileAttachmentPart).path) {
-      return false
-    }
-  }
-  return true
-}
-
 function getCursorPosition(parent: HTMLElement): number {
   const selection = window.getSelection()
   if (!selection || selection.rangeCount === 0) return 0

+ 0 - 558
packages/desktop/src/components/theme.json

@@ -1,558 +0,0 @@
-{
-  "colors": {
-    "actionBar.toggledBackground": "var(--surface-raised-base)",
-    "activityBarBadge.background": "var(--surface-brand-base)",
-    "checkbox.border": "var(--border-base)",
-    "editor.background": "transparent",
-    "editor.foreground": "var(--text-base)",
-    "editor.inactiveSelectionBackground": "var(--surface-raised-base)",
-    "editor.selectionHighlightBackground": "var(--border-active)",
-    "editorIndentGuide.activeBackground1": "var(--border-weak-base)",
-    "editorIndentGuide.background1": "var(--border-weak-base)",
-    "input.placeholderForeground": "var(--text-weak)",
-    "list.activeSelectionIconForeground": "var(--text-base)",
-    "list.dropBackground": "var(--surface-raised-base)",
-    "menu.background": "var(--surface-base)",
-    "menu.border": "var(--border-base)",
-    "menu.foreground": "var(--text-base)",
-    "menu.selectionBackground": "var(--surface-interactive-base)",
-    "menu.separatorBackground": "var(--border-base)",
-    "ports.iconRunningProcessForeground": "var(--icon-success-base)",
-    "sideBarSectionHeader.background": "transparent",
-    "sideBarSectionHeader.border": "var(--border-weak-base)",
-    "sideBarTitle.foreground": "var(--text-weak)",
-    "statusBarItem.remoteBackground": "var(--surface-success-base)",
-    "statusBarItem.remoteForeground": "var(--text-base)",
-    "tab.lastPinnedBorder": "var(--border-weak-base)",
-    "tab.selectedBackground": "var(--surface-raised-base)",
-    "tab.selectedForeground": "var(--text-weak)",
-    "terminal.inactiveSelectionBackground": "var(--surface-raised-base)",
-    "widget.border": "var(--border-base)"
-  },
-  "displayName": "opencode",
-  "name": "opencode",
-  "semanticHighlighting": true,
-  "semanticTokenColors": {
-    "customLiteral": "var(--syntax-function)",
-    "newOperator": "var(--syntax-operator)",
-    "numberLiteral": "var(--syntax-number)",
-    "stringLiteral": "var(--syntax-string)"
-  },
-  "tokenColors": [
-    {
-      "scope": [
-        "meta.embedded",
-        "source.groovy.embedded",
-        "string meta.image.inline.markdown",
-        "variable.legacy.builtin.python"
-      ],
-      "settings": {
-        "foreground": "var(--text-base)"
-      }
-    },
-    {
-      "scope": "emphasis",
-      "settings": {
-        "fontStyle": "italic"
-      }
-    },
-    {
-      "scope": "strong",
-      "settings": {
-        "fontStyle": "bold"
-      }
-    },
-    {
-      "scope": "header",
-      "settings": {
-        "foreground": "var(--markdown-heading)"
-      }
-    },
-    {
-      "scope": "comment",
-      "settings": {
-        "foreground": "var(--syntax-comment)"
-      }
-    },
-    {
-      "scope": "constant.language",
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": [
-        "constant.numeric",
-        "variable.other.enummember",
-        "keyword.operator.plus.exponent",
-        "keyword.operator.minus.exponent"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-number)"
-      }
-    },
-    {
-      "scope": "constant.regexp",
-      "settings": {
-        "foreground": "var(--syntax-operator)"
-      }
-    },
-    {
-      "scope": "entity.name.tag",
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": ["entity.name.tag.css", "entity.name.tag.less"],
-      "settings": {
-        "foreground": "var(--syntax-operator)"
-      }
-    },
-    {
-      "scope": "entity.other.attribute-name",
-      "settings": {
-        "foreground": "var(--syntax-variable)"
-      }
-    },
-    {
-      "scope": [
-        "entity.other.attribute-name.class.css",
-        "source.css entity.other.attribute-name.class",
-        "entity.other.attribute-name.id.css",
-        "entity.other.attribute-name.parent-selector.css",
-        "entity.other.attribute-name.parent.less",
-        "source.css entity.other.attribute-name.pseudo-class",
-        "entity.other.attribute-name.pseudo-element.css",
-        "source.css.less entity.other.attribute-name.id",
-        "entity.other.attribute-name.scss"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-operator)"
-      }
-    },
-    {
-      "scope": "invalid",
-      "settings": {
-        "foreground": "var(--syntax-critical)"
-      }
-    },
-    {
-      "scope": "markup.underline",
-      "settings": {
-        "fontStyle": "underline"
-      }
-    },
-    {
-      "scope": "markup.bold",
-      "settings": {
-        "fontStyle": "bold",
-        "foreground": "var(--markdown-strong)"
-      }
-    },
-    {
-      "scope": "markup.heading",
-      "settings": {
-        "fontStyle": "bold",
-        "foreground": "var(--theme-markdown-heading)"
-      }
-    },
-    {
-      "scope": "markup.italic",
-      "settings": {
-        "fontStyle": "italic"
-      }
-    },
-    {
-      "scope": "markup.strikethrough",
-      "settings": {
-        "fontStyle": "strikethrough"
-      }
-    },
-    {
-      "scope": "markup.inserted",
-      "settings": {
-        "foreground": "var(--text-diff-add-base)"
-      }
-    },
-    {
-      "scope": "markup.deleted",
-      "settings": {
-        "foreground": "var(--text-diff-delete-base)"
-      }
-    },
-    {
-      "scope": "markup.changed",
-      "settings": {
-        "foreground": "var(--text-base)"
-      }
-    },
-    {
-      "scope": "punctuation.definition.quote.begin.markdown",
-      "settings": {
-        "foreground": "var(--markdown-block-quote)"
-      }
-    },
-    {
-      "scope": "punctuation.definition.list.begin.markdown",
-      "settings": {
-        "foreground": "var(--markdown-list-enumeration)"
-      }
-    },
-    {
-      "scope": "markup.inline.raw",
-      "settings": {
-        "foreground": "var(--markdown-code)"
-      }
-    },
-    {
-      "scope": "punctuation.definition.tag",
-      "settings": {
-        "foreground": "var(--syntax-punctuation)"
-      }
-    },
-    {
-      "scope": ["meta.preprocessor", "entity.name.function.preprocessor"],
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": "meta.preprocessor.string",
-      "settings": {
-        "foreground": "var(--syntax-string)"
-      }
-    },
-    {
-      "scope": "meta.preprocessor.numeric",
-      "settings": {
-        "foreground": "var(--syntax-number)"
-      }
-    },
-    {
-      "scope": "meta.structure.dictionary.key.python",
-      "settings": {
-        "foreground": "var(--syntax-variable)"
-      }
-    },
-    {
-      "scope": "meta.diff.header",
-      "settings": {
-        "foreground": "var(--text-weak)"
-      }
-    },
-    {
-      "scope": "storage",
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": "storage.type",
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": ["storage.modifier", "keyword.operator.noexcept"],
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": ["string", "meta.embedded.assembly"],
-      "settings": {
-        "foreground": "var(--syntax-string)"
-      }
-    },
-    {
-      "scope": "string.tag",
-      "settings": {
-        "foreground": "var(--syntax-string)"
-      }
-    },
-    {
-      "scope": "string.value",
-      "settings": {
-        "foreground": "var(--syntax-string)"
-      }
-    },
-    {
-      "scope": "string.regexp",
-      "settings": {
-        "foreground": "var(--syntax-operator)"
-      }
-    },
-    {
-      "scope": [
-        "punctuation.definition.template-expression.begin",
-        "punctuation.definition.template-expression.end",
-        "punctuation.section.embedded"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": ["meta.template.expression"],
-      "settings": {
-        "foreground": "var(--text-base)"
-      }
-    },
-    {
-      "scope": [
-        "support.type.vendored.property-name",
-        "support.type.property-name",
-        "source.css variable",
-        "source.coffee.embedded"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-variable)"
-      }
-    },
-    {
-      "scope": "keyword",
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": "keyword.control",
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": "keyword.operator",
-      "settings": {
-        "foreground": "var(--syntax-operator)"
-      }
-    },
-    {
-      "scope": [
-        "keyword.operator.new",
-        "keyword.operator.expression",
-        "keyword.operator.cast",
-        "keyword.operator.sizeof",
-        "keyword.operator.alignof",
-        "keyword.operator.typeid",
-        "keyword.operator.alignas",
-        "keyword.operator.instanceof",
-        "keyword.operator.logical.python",
-        "keyword.operator.wordlike"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": "keyword.other.unit",
-      "settings": {
-        "foreground": "var(--syntax-number)"
-      }
-    },
-    {
-      "scope": ["punctuation.section.embedded.begin.php", "punctuation.section.embedded.end.php"],
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": "support.function.git-rebase",
-      "settings": {
-        "foreground": "var(--syntax-variable)"
-      }
-    },
-    {
-      "scope": "constant.sha.git-rebase",
-      "settings": {
-        "foreground": "var(--syntax-number)"
-      }
-    },
-    {
-      "scope": ["storage.modifier.import.java", "variable.language.wildcard.java", "storage.modifier.package.java"],
-      "settings": {
-        "foreground": "var(--text-base)"
-      }
-    },
-    {
-      "scope": "variable.language",
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": [
-        "entity.name.function",
-        "support.function",
-        "support.constant.handlebars",
-        "source.powershell variable.other.member",
-        "entity.name.operator.custom-literal"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-function)"
-      }
-    },
-    {
-      "scope": [
-        "support.class",
-        "support.type",
-        "entity.name.type",
-        "entity.name.namespace",
-        "entity.other.attribute",
-        "entity.name.scope-resolution",
-        "entity.name.class",
-        "storage.type.numeric.go",
-        "storage.type.byte.go",
-        "storage.type.boolean.go",
-        "storage.type.string.go",
-        "storage.type.uintptr.go",
-        "storage.type.error.go",
-        "storage.type.rune.go",
-        "storage.type.cs",
-        "storage.type.generic.cs",
-        "storage.type.modifier.cs",
-        "storage.type.variable.cs",
-        "storage.type.annotation.java",
-        "storage.type.generic.java",
-        "storage.type.java",
-        "storage.type.object.array.java",
-        "storage.type.primitive.array.java",
-        "storage.type.primitive.java",
-        "storage.type.token.java",
-        "storage.type.groovy",
-        "storage.type.annotation.groovy",
-        "storage.type.parameters.groovy",
-        "storage.type.generic.groovy",
-        "storage.type.object.array.groovy",
-        "storage.type.primitive.array.groovy",
-        "storage.type.primitive.groovy"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-type)"
-      }
-    },
-    {
-      "scope": [
-        "meta.type.cast.expr",
-        "meta.type.new.expr",
-        "support.constant.math",
-        "support.constant.dom",
-        "support.constant.json",
-        "entity.other.inherited-class",
-        "punctuation.separator.namespace.ruby"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-type)"
-      }
-    },
-    {
-      "scope": [
-        "keyword.control",
-        "source.cpp keyword.operator.new",
-        "keyword.operator.delete",
-        "keyword.other.using",
-        "keyword.other.directive.using",
-        "keyword.other.operator",
-        "entity.name.operator"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-operator)"
-      }
-    },
-    {
-      "scope": [
-        "variable",
-        "meta.definition.variable.name",
-        "support.variable",
-        "entity.name.variable",
-        "constant.other.placeholder"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-variable)"
-      }
-    },
-    {
-      "scope": ["variable.other.constant", "variable.other.enummember"],
-      "settings": {
-        "foreground": "var(--syntax-variable)"
-      }
-    },
-    {
-      "scope": ["meta.object-literal.key"],
-      "settings": {
-        "foreground": "var(--syntax-variable)"
-      }
-    },
-    {
-      "scope": [
-        "support.constant.property-value",
-        "support.constant.font-name",
-        "support.constant.media-type",
-        "support.constant.media",
-        "constant.other.color.rgb-value",
-        "constant.other.rgb-value",
-        "support.constant.color"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-string)"
-      }
-    },
-    {
-      "scope": [
-        "punctuation.definition.group.regexp",
-        "punctuation.definition.group.assertion.regexp",
-        "punctuation.definition.character-class.regexp",
-        "punctuation.character.set.begin.regexp",
-        "punctuation.character.set.end.regexp",
-        "keyword.operator.negation.regexp",
-        "support.other.parenthesis.regexp"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-string)"
-      }
-    },
-    {
-      "scope": [
-        "constant.character.character-class.regexp",
-        "constant.other.character-class.set.regexp",
-        "constant.other.character-class.regexp",
-        "constant.character.set.regexp"
-      ],
-      "settings": {
-        "foreground": "var(--syntax-operator)"
-      }
-    },
-    {
-      "scope": ["keyword.operator.or.regexp", "keyword.control.anchor.regexp"],
-      "settings": {
-        "foreground": "var(--syntax-operator)"
-      }
-    },
-    {
-      "scope": "keyword.operator.quantifier.regexp",
-      "settings": {
-        "foreground": "var(--syntax-operator)"
-      }
-    },
-    {
-      "scope": ["constant.character", "constant.other.option"],
-      "settings": {
-        "foreground": "var(--syntax-keyword)"
-      }
-    },
-    {
-      "scope": "constant.character.escape",
-      "settings": {
-        "foreground": "var(--syntax-operator)"
-      }
-    },
-    {
-      "scope": "entity.name.label",
-      "settings": {
-        "foreground": "var(--text-weak)"
-      }
-    }
-  ],
-  "type": "dark"
-}

+ 71 - 155
packages/desktop/src/context/local.tsx

@@ -5,6 +5,7 @@ import type { FileContent, FileNode, Model, Provider, File as FileStatus } from
 import { createSimpleContext } from "./helper"
 import { useSDK } from "./sdk"
 import { useSync } from "./sync"
+import { makePersisted } from "@solid-primitives/storage"
 
 export type LocalFile = FileNode &
   Partial<{
@@ -195,18 +196,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
     const file = (() => {
       const [store, setStore] = createStore<{
         node: Record<string, LocalFile>
-        // opened: string[]
-        // active?: string
       }>({
         node: Object.fromEntries(sync.data.node.map((x) => [x.path, x])),
-        // opened: [],
       })
 
-      // const active = createMemo(() => {
-      //   if (!store.active) return undefined
-      //   return store.node[store.active]
-      // })
-      // const opened = createMemo(() => store.opened.map((x) => store.node[x]))
       const changeset = createMemo(() => new Set(sync.data.changes.map((f) => f.path)))
       const changes = createMemo(() => Array.from(changeset()).sort((a, b) => a.localeCompare(b)))
 
@@ -247,18 +240,18 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
         return false
       }
 
-      const resetNode = (path: string) => {
-        setStore("node", path, {
-          loaded: undefined,
-          pinned: undefined,
-          content: undefined,
-          selection: undefined,
-          scrollTop: undefined,
-          folded: undefined,
-          view: undefined,
-          selectedChange: undefined,
-        })
-      }
+      // const resetNode = (path: string) => {
+      //   setStore("node", path, {
+      //     loaded: undefined,
+      //     pinned: undefined,
+      //     content: undefined,
+      //     selection: undefined,
+      //     scrollTop: undefined,
+      //     folded: undefined,
+      //     view: undefined,
+      //     selectedChange: undefined,
+      //   })
+      // }
 
       const relative = (path: string) => path.replace(sync.data.path.directory + "/", "")
 
@@ -333,31 +326,21 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
       sdk.event.listen((e) => {
         const event = e.details
         switch (event.type) {
-          case "message.part.updated":
-            const part = event.properties.part
-            if (part.type === "tool" && part.state.status === "completed") {
-              switch (part.tool) {
-                case "read":
-                  break
-                case "edit":
-                  // load(part.state.input["filePath"] as string)
-                  break
-                default:
-                  break
-              }
-            }
-            break
           case "file.watcher.updated":
-            // setTimeout(sync.load.changes, 1000)
-            // const relativePath = relative(event.properties.file)
-            // if (relativePath.startsWith(".git/")) return
-            // load(relativePath)
+            const relativePath = relative(event.properties.file)
+            if (relativePath.startsWith(".git/")) return
+            load(relativePath)
             break
         }
       })
 
       return {
-        node: (path: string) => store.node[path],
+        node: async (path: string) => {
+          if (!store.node[path]) {
+            await init(path)
+          }
+          return store.node[path]
+        },
         update: (path: string, node: LocalFile) => setStore("node", path, reconcile(node)),
         open,
         load,
@@ -417,121 +400,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
         searchFiles,
         searchFilesAndDirectories,
         relative,
-        // active,
-        // opened,
-        // close(path: string) {
-        //   setStore("opened", (opened) => opened.filter((x) => x !== path))
-        //   if (store.active === path) {
-        //     const index = store.opened.findIndex((f) => f === path)
-        //     const previous = store.opened[Math.max(0, index - 1)]
-        //     setStore("active", previous)
-        //   }
-        //   resetNode(path)
-        // },
-        // move(path: string, to: number) {
-        //   const index = store.opened.findIndex((f) => f === path)
-        //   if (index === -1) return
-        //   setStore(
-        //     "opened",
-        //     produce((opened) => {
-        //       opened.splice(to, 0, opened.splice(index, 1)[0])
-        //     }),
-        //   )
-        //   setStore("node", path, "pinned", true)
-        // },
-      }
-    })()
-
-    const session = (() => {
-      const [store, setStore] = createStore<{
-        active?: string
-        tabs: Record<
-          string,
-          {
-            active?: string
-            opened: string[]
-          }
-        >
-      }>({
-        tabs: {
-          "": {
-            opened: [],
-          },
-        },
-      })
-
-      const active = createMemo(() => {
-        if (!store.active) return undefined
-        return sync.session.get(store.active)
-      })
-
-      createEffect(() => {
-        if (!store.active) return
-        sync.session.sync(store.active)
-
-        if (!store.tabs[store.active]) {
-          setStore("tabs", store.active, {
-            opened: [],
-          })
-        }
-      })
-
-      const tabs = createMemo(() => store.tabs[store.active ?? ""])
-
-      return {
-        active,
-        setActive(sessionId: string | undefined) {
-          setStore("active", sessionId)
-        },
-        clearActive() {
-          setStore("active", undefined)
-        },
-        tabs,
-        copyTabs(from: string, to: string) {
-          setStore("tabs", to, {
-            opened: store.tabs[from]?.opened ?? [],
-          })
-        },
-        setActiveTab(tab: string | undefined) {
-          setStore("tabs", store.active ?? "", "active", tab)
-        },
-        async open(tab: string) {
-          if (tab !== "chat") {
-            await file.open(tab)
-          }
-          if (!tabs()?.opened?.includes(tab)) {
-            setStore("tabs", store.active ?? "", "opened", [...(tabs()?.opened ?? []), tab])
-          }
-          setStore("tabs", store.active ?? "", "active", tab)
-        },
-        close(tab: string) {
-          batch(() => {
-            if (!tabs()) return
-            setStore("tabs", store.active ?? "", {
-              active: tabs()!.active,
-              opened: tabs()!.opened.filter((x) => x !== tab),
-            })
-            if (tabs()!.active === tab) {
-              const index = tabs()!.opened.findIndex((f) => f === tab)
-              const previous = tabs()!.opened[Math.max(0, index - 1)]
-              setStore("tabs", store.active ?? "", "active", previous)
-            }
-          })
-        },
-        move(tab: string, to: number) {
-          if (!tabs()) return
-          const index = tabs()!.opened.findIndex((f) => f === tab)
-          if (index === -1) return
-          setStore(
-            "tabs",
-            store.active ?? "",
-            "opened",
-            produce((opened) => {
-              opened.splice(to, 0, opened.splice(index, 1)[0])
-            }),
-          )
-          // setStore("node", path, "pinned", true)
-        },
       }
     })()
 
@@ -589,12 +457,60 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
       }
     })()
 
+    const layout = (() => {
+      const [store, setStore] = makePersisted(
+        createStore({
+          sidebar: {
+            opened: true,
+            width: 240,
+          },
+          review: {
+            state: "closed" as "open" | "closed" | "tab",
+          },
+        }),
+        {
+          name: "default-layout",
+        },
+      )
+
+      return {
+        sidebar: {
+          opened: createMemo(() => store.sidebar.opened),
+          open() {
+            setStore("sidebar", "opened", true)
+          },
+          close() {
+            setStore("sidebar", "opened", false)
+          },
+          toggle() {
+            setStore("sidebar", "opened", (x) => !x)
+          },
+          width: createMemo(() => store.sidebar.width),
+          resize(width: number) {
+            setStore("sidebar", "width", width)
+          },
+        },
+        review: {
+          state: createMemo(() => store.review?.state ?? "closed"),
+          open() {
+            setStore("review", "state", "open")
+          },
+          close() {
+            setStore("review", "state", "closed")
+          },
+          tab() {
+            setStore("review", "state", "tab")
+          },
+        },
+      }
+    })()
+
     const result = {
       model,
       agent,
       file,
-      session,
       context,
+      layout,
     }
     return result
   },

+ 217 - 0
packages/desktop/src/context/session.tsx

@@ -0,0 +1,217 @@
+import { createStore, produce } from "solid-js/store"
+import { createSimpleContext } from "./helper"
+import { batch, createEffect, createMemo } from "solid-js"
+import { useSync } from "./sync"
+import { makePersisted } from "@solid-primitives/storage"
+import { TextSelection, useLocal } from "./local"
+import { pipe, sumBy } from "remeda"
+import { AssistantMessage } from "@opencode-ai/sdk"
+
+export const { use: useSession, provider: SessionProvider } = createSimpleContext({
+  name: "Session",
+  init: (props: { sessionId?: string }) => {
+    const sync = useSync()
+    const local = useLocal()
+
+    const [store, setStore] = makePersisted(
+      createStore<{
+        prompt: Prompt
+        cursorPosition?: number
+        messageId?: string
+        tabs: {
+          active?: string
+          opened: string[]
+        }
+      }>({
+        prompt: [{ type: "text", content: "", start: 0, end: 0 }],
+        tabs: {
+          opened: [],
+        },
+      }),
+      {
+        name: props.sessionId ?? "new-session",
+      },
+    )
+
+    createEffect(() => {
+      if (!props.sessionId) return
+      sync.session.sync(props.sessionId)
+    })
+
+    const info = createMemo(() => (props.sessionId ? sync.session.get(props.sessionId) : undefined))
+    const messages = createMemo(() => (props.sessionId ? (sync.data.message[props.sessionId] ?? []) : []))
+    const userMessages = createMemo(() =>
+      messages()
+        .filter((m) => m.role === "user")
+        .sort((a, b) => b.id.localeCompare(a.id)),
+    )
+    const lastUserMessage = createMemo(() => {
+      return userMessages()?.at(0)
+    })
+    const activeMessage = createMemo(() => {
+      if (!store.messageId) return lastUserMessage()
+      return userMessages()?.find((m) => m.id === store.messageId)
+    })
+    const working = createMemo(() => {
+      if (!props.sessionId) return false
+      const last = lastUserMessage()
+      if (!last) return false
+      const assistantMessages = sync.data.message[props.sessionId]?.filter(
+        (m) => m.role === "assistant" && m.parentID == last?.id,
+      ) as AssistantMessage[]
+      const error = assistantMessages?.find((m) => m?.error)?.error
+      return !last?.summary?.body && !error
+    })
+
+    const cost = createMemo(() => {
+      const total = pipe(
+        messages(),
+        sumBy((x) => (x.role === "assistant" ? x.cost : 0)),
+      )
+      return new Intl.NumberFormat("en-US", {
+        style: "currency",
+        currency: "USD",
+      }).format(total)
+    })
+
+    const last = createMemo(
+      () => messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage,
+    )
+    const model = createMemo(() =>
+      last() ? sync.data.provider.find((x) => x.id === last().providerID)?.models[last().modelID] : undefined,
+    )
+    const diffs = createMemo(() => (props.sessionId ? (sync.data.session_diff[props.sessionId] ?? []) : []))
+
+    const tokens = createMemo(() => {
+      if (!last()) return
+      const tokens = last().tokens
+      return tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write
+    })
+
+    const context = createMemo(() => {
+      const total = tokens()
+      const limit = model()?.limit.context
+      if (!total || !limit) return 0
+      return Math.round((total / limit) * 100)
+    })
+
+    return {
+      id: props.sessionId,
+      info,
+      working,
+      diffs,
+      prompt: {
+        current: createMemo(() => store.prompt),
+        cursor: createMemo(() => store.cursorPosition),
+        dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
+        set(prompt: Prompt, cursorPosition?: number) {
+          batch(() => {
+            setStore("prompt", prompt)
+            if (cursorPosition !== undefined) setStore("cursorPosition", cursorPosition)
+          })
+        },
+      },
+      messages: {
+        all: messages,
+        user: userMessages,
+        last: lastUserMessage,
+        active: activeMessage,
+        setActive(id: string | undefined) {
+          setStore("messageId", id)
+        },
+      },
+      usage: {
+        tokens,
+        cost,
+        context,
+      },
+      layout: {
+        tabs: store.tabs,
+        setActiveTab(tab: string | undefined) {
+          setStore("tabs", "active", tab)
+        },
+        setOpenedTabs(tabs: string[]) {
+          setStore("tabs", "opened", tabs)
+        },
+        async openTab(tab: string) {
+          if (tab === "chat") {
+            setStore("tabs", "active", undefined)
+            return
+          }
+          if (tab.startsWith("file://")) {
+            await local.file.open(tab.replace("file://", ""))
+          }
+          if (tab !== "review") {
+            if (!store.tabs.opened.includes(tab)) {
+              setStore("tabs", "opened", [...store.tabs.opened, tab])
+            }
+          }
+          setStore("tabs", "active", tab)
+        },
+        closeTab(tab: string) {
+          batch(() => {
+            setStore(
+              "tabs",
+              "opened",
+              store.tabs.opened.filter((x) => x !== tab),
+            )
+            if (store.tabs.active === tab) {
+              const index = store.tabs.opened.findIndex((f) => f === tab)
+              const previous = store.tabs.opened[Math.max(0, index - 1)]
+              setStore("tabs", "active", previous)
+            }
+          })
+        },
+        moveTab(tab: string, to: number) {
+          const index = store.tabs.opened.findIndex((f) => f === tab)
+          if (index === -1) return
+          setStore(
+            "tabs",
+            "opened",
+            produce((opened) => {
+              opened.splice(to, 0, opened.splice(index, 1)[0])
+            }),
+          )
+          // setStore("node", path, "pinned", true)
+        },
+      },
+    }
+  },
+})
+
+interface PartBase {
+  content: string
+  start: number
+  end: number
+}
+
+export interface TextPart extends PartBase {
+  type: "text"
+}
+
+export interface FileAttachmentPart extends PartBase {
+  type: "file"
+  path: string
+  selection?: TextSelection
+}
+
+export type ContentPart = TextPart | FileAttachmentPart
+export type Prompt = ContentPart[]
+
+export const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
+
+export function isPromptEqual(promptA: Prompt, promptB: Prompt): boolean {
+  if (promptA.length !== promptB.length) return false
+  for (let i = 0; i < promptA.length; i++) {
+    const partA = promptA[i]
+    const partB = promptB[i]
+    if (partA.type !== partB.type) return false
+    if (partA.type === "text" && partA.content !== (partB as TextPart).content) {
+      return false
+    }
+    if (partA.type === "file" && partA.path !== (partB as FileAttachmentPart).path) {
+      return false
+    }
+  }
+  return true
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio